diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 0762ac80..bfa65e32 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -19,7 +19,10 @@ This document tracks compliance gaps and non-standard behaviors. - `if`/`else` statements - `goto` and label statements - `while`, `do-while`, `for` loops -- `switch`/`case`/`default` statements +- `switch`/`case`/`default` statements, including labels after ordinary + statements and within nested compound blocks; strict C99 rejects duplicate + case values and declarations immediately following an ordinary, case, or + default label - `break`, `continue`, `return` statements ### Operators @@ -48,73 +51,69 @@ This document tracks compliance gaps and non-standard behaviors. | Feature | Status | Impact | |---------|--------|--------| -| `static` | Not implemented | No internal linkage or persistent local variables | -| `extern` | Not implemented | No external linkage declarations | -| `register` | Not implemented | No register hint optimization | -| `auto` | Not implemented | Default storage class (implicit) | -| `const` | Parsed but ignored | No read-only enforcement | -| `volatile` | Not implemented | No volatile semantics | -| `restrict` | Not implemented | No pointer aliasing optimization | -| `inline` | Not implemented | No function inlining | +| `static` | Partial | File-scope internal linkage and persistent block-scope objects work; cross-translation-unit linkage remains incomplete. A `static` object in a `for` initializer is an extension: C99 6.8.5p3 allows only `auto` and `register` there, so `--std=c99` rejects it. | +| `extern` | Partial | File- and block-scope object declarations plus function prototypes bind to global declarations; remaining C99 forms need coverage. | +| `register` | Partial | Block-scope declarations and parameters lower as automatic objects and reject address-taking; no allocation hint is implemented. | +| `auto` | Supported | Block-scope declarations and C99 `for` initializers use ordinary automatic storage. | +| `const` | Supported | Enforced for direct and indirect lvalues; pointer-level conversions are checked. | +| `volatile` | Partial | Preserved through declarations and prevents key optimizations; exhaustive optimizer audit remains. | +| `restrict` | Partial | Accepted wherever C99 allows it and then ignored: the qualifier is not retained, so it drives no aliasing optimization. | +| `inline` | Partial | File-scope declarations/definitions are accepted; C99 linkage constraints remain incomplete. | ### Type System | Feature | Status | Notes | |---------|--------|-------| -| `long` | Missing | Only 4-byte integers | -| `long long` | Missing | No 64-bit integers | -| `unsigned` | Missing | All integers are signed | -| `signed` | Missing | Implicit for integers | +| `long` | Partial | Distinct rank with the current 32-bit representation. | +| `long long` | Partial | Eight-byte values work on every target: 32-bit Arm and RISC-V keep them in register pairs and pass them as AAPCS32 and the RV32 calling convention require. Randomized testing still finds wide expressions the x64 and AArch64 backends miscompile. | +| `unsigned` | Supported | Unsigned char/short/int/long families, arithmetic, conversions, and ABI paths are implemented. | +| `signed` | Supported | Signed scalar spellings, including signed char, are distinct and parsed. | | `float` | Missing | No floating-point support | | `double` | Missing | No floating-point support | | `long double` | Missing | No floating-point support | -| Bit-fields | Missing | Cannot pack struct members | +| Bit-fields | Supported | `_Bool`, `int`, and `unsigned int` fields pack least-significant-bit first in their conventional allocation units; narrow unsigned fields receive C99 integer promotion. | ### Literals & Constants | Feature | Status | Current Behavior | |---------|--------|-----------------| -| Integer suffixes (`u`, `l`, `ll`) | Not parsed | All literals are `int` | -| Wide characters (`L'c'`) | Not supported | Single-byte only | -| Wide strings (`L"..."`) | Not supported | Single-byte only | -| Multi-character constants | Not supported | Single character only | -| Universal characters (`\u`, `\U`) | Not supported | ASCII only | -| Hex escapes (`\x...`) | Limited | Max 2 hex digits | +| Integer suffixes (`u`, `l`, `ll`) | Partial | Common suffix spellings and wide literals are parsed on every target; full candidate-type selection remains incomplete. | +| Wide characters (`L'c'`) | Supported | Lowered as the implementation's `int`-sized execution-wide-character representation. | +| Wide strings (`L"..."`) | Supported | Lowered as NUL-terminated `wchar_t` rodata; supported in expressions, `sizeof`, pointers, and compatible array initialization. | +| Multi-character constants | Supported | Implementation-defined left-to-right packing of up to four bytes. | +| Universal characters (`\u`, `\U`) | Partial | Narrow literals, identifiers, and wide character constants use the implementation's UTF-8 decoding; wide string literals decode to execution-wide-character units. | +| Hex escapes (`\x...`) | Supported | The full following hexadecimal run is consumed; a value that does not fit an `unsigned char` is rejected in narrow literals. | ### Preprocessor Gaps | Feature | Status | Description | |---------|--------|-------------| -| `#include` | Partial | Local file inclusion is supported, but lack of capability to include system files | -| Token pasting (`##`) | Missing | Cannot concatenate tokens | -| Stringizing (`#`) | Missing | Cannot convert to string | -| `__DATE__` | Missing | No compile date | -| `__TIME__` | Missing | No compile time | -| `__STDC__` | Missing | No standard compliance indicator | +| `#include` | Partial | Quoted includes and explicit `-I` angle-header search work; hosted C99 headers remain incomplete. | +| Token pasting (`##`) | Supported | Object- and function-like pastes are rescanned and diagnosed when invalid. | +| Stringizing (`#`) | Supported | Function-like macro arguments are stringized with C99 whitespace and escaping behavior. | +| `__DATE__` | Supported | Expands to the C99 date-character array shape. | +| `__TIME__` | Supported | Expands to the C99 time-character array shape. | +| `__STDC__` | Supported | Expands to integer constant `1`; `__STDC_HOSTED__` is also provided. | ### Advanced Features | Feature | Status | Description | |---------|--------|-------------| -| Designated initializers | Missing | No `.field = value` syntax | +| Designated initializers | Partial | Record and bounded-array designators work for local, static, and file-scope objects; higher-rank continuation cases remain incomplete. | | Compound literals | Partial | Limited support | -| Flexible array members | Missing | No `[]` at struct end | +| `sizeof` type names | Partial | Fixed arrays, pointer-to-array, array-of-pointer, and recursive function-pointer declarators are supported, including callback arrays and global constant expressions; arbitrary mixed derived declarators and a shared general type-name parser remain incomplete. | +| Flexible array members | Supported | Final `[]` struct members have zero fixed extent, support pointer-based element access, and enforce C99 placement constraints. | | Variable-length arrays | Missing | No runtime-sized arrays | | `_Complex` | Missing | No complex numbers | | `_Imaginary` | Missing | No imaginary numbers | -| `_Static_assert` | Missing | No compile-time assertions | -| `_Alignof` | Missing | No alignment queries | -| `_Alignas` | Missing | No alignment specification | -| `_Generic` | Missing | No generic selection | ## Non-Standard Behaviors ### GNU Extensions - Binary literals: `0b101010` - Escape sequence: `\e` for ESC character -- `void*` arithmetic (treated as `char*`) -- `sizeof(void)` returns 0 (should be error) - Computed goto +- Comma elision in variadic macros: `, ## __VA_ARGS__` drops the comma when no variadic argument is given ### Implementation-Specific - Array compound literals in scalar context use first element diff --git a/Makefile b/Makefile index 6661e623..434ca363 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,37 @@ LIBDIR := $(shell find lib -type d) BUILTIN_LIBC_SOURCE ?= c.c BUILTIN_LIBC_HEADER := c.h +# The translation timestamp belongs to generated configuration rather than the +# compiler's host clock. A single value is consequently embedded in stages 0, +# 1, and 2, which keeps bootstrap byte-for-byte reproducible. Rebuilders can +# supply SOURCE_DATE_EPOCH for a stable timestamp across separate invocations. +# The fallback is read once: a recursive "?=" would rerun date for the date +# and again for the time, which then disagree across a second boundary. +ifeq ($(origin SOURCE_DATE_EPOCH),undefined) +SOURCE_DATE_EPOCH := $(shell date -u +%s) +endif +# The epoch is spliced into the date command below, so anything but decimal +# digits would be run by the shell there. $(value) keeps make from expanding a +# supplied $(...) before it is checked. +EPOCH_NONDIGITS := $(value SOURCE_DATE_EPOCH) +$(foreach d,0 1 2 3 4 5 6 7 8 9,$(eval EPOCH_NONDIGITS := $$(subst $(d),,$$(EPOCH_NONDIGITS)))) +ifneq ($(if $(strip $(value SOURCE_DATE_EPOCH)),$(EPOCH_NONDIGITS),empty),) +$(error SOURCE_DATE_EPOCH must be a Unix epoch in decimal seconds) +endif +# GNU date converts an epoch given as -d @SECONDS, which BSD and macOS date do +# not accept; they take the seconds as -r SECONDS instead. Ask for the Unix +# epoch itself to learn which spelling this date understands. +ifeq ($(shell date -u -d @0 +%s 2>/dev/null),0) +EPOCH_DATE = LC_ALL=C TZ=UTC date -u -d "@$(SOURCE_DATE_EPOCH)" +else +EPOCH_DATE = LC_ALL=C TZ=UTC date -u -r "$(SOURCE_DATE_EPOCH)" +endif +TRANSLATION_DATE := $(shell $(EPOCH_DATE) '+%b %e %Y') +TRANSLATION_TIME := $(shell $(EPOCH_DATE) '+%H:%M:%S') +ifeq ($(strip $(TRANSLATION_DATE)$(TRANSLATION_TIME)),) +$(error SOURCE_DATE_EPOCH must be a Unix epoch accepted by date) +endif +TRANSLATION_DEFS = "\#define SHECC_TRANSLATION_DATE \"$(TRANSLATION_DATE)\"\n\#define SHECC_TRANSLATION_TIME \"$(TRANSLATION_TIME)\"\n" # --dump-ir is what makes out/shecc-stage1.log the IR of the stage 1 build # rather than an empty file. It is the only thing in the tree that exercises # dump_insn()/dump_ph2_ir(), and it is what a failed CI run uploads. @@ -98,7 +129,8 @@ endif # previous architecture's generated config, so require an explicit reconfigure # instead. # -# Naming "config" or "distclean" anywhere in the goals is that reconfigure: the +# Naming "config", "distclean", or "check-all-targets" anywhere in the goals +# is that reconfigure: the # record is about to be rewritten or removed, so the architecture it still holds # does not apply and the check must not fire. Testing for their presence rather # than filtering them out is what lets a goal list combine them with real work, @@ -106,7 +138,7 @@ endif # generated config, so a mismatch cannot affect it either. CONFIGURED_ARCH := $(shell sed -n 's/^ARCH=//p' $(BUILD_SESSION) 2>/dev/null) ifneq (,$(CONFIGURED_ARCH)) -ifeq (,$(filter config distclean,$(MAKECMDGOALS))) +ifeq (,$(filter config distclean check-all-targets,$(MAKECMDGOALS))) ifneq (,$(filter-out clean,$(or $(MAKECMDGOALS),all))) ifneq ($(CONFIGURED_ARCH),$(ARCH)) $(error Tree is configured for ARCH=$(CONFIGURED_ARCH). Run "make config ARCH=$(ARCH)" to switch) @@ -132,6 +164,7 @@ include mk/common.mk config: $(Q)ln -sf $(PWD)/$(SRCDIR)/$(ARCH)-codegen.c $(SRCDIR)/codegen.c $(Q)$(PRINTF) $(ARCH_DEFS) > $@.tmp + $(Q)$(PRINTF) $(TRANSLATION_DEFS) >> $@.tmp $(Q)if cmp -s $@.tmp $@; then $(RM) $@.tmp; else mv $@.tmp $@; fi $(Q)$(PRINTF) "ARCH=$(ARCH)" > $(BUILD_SESSION) $(VECHO) "Target machine code switch to %s\n" $(ARCH) @@ -141,6 +174,26 @@ config: check: check-stage0 check-stage2 check-abi-stage0 check-abi-stage2 +# Run the complete check -- driver and ABI suites at stages 0 and 2 -- on every +# backend, as CI does. Driver cases that need 64-bit values are gated on the +# target's pointer width, so each target runs everything it can represent. +# Configuration is global to this worktree, so keep recursive invocations +# sequential and restore the caller's architecture even when a target fails. +# Each target is rebuilt after its own `config`, preventing an old compiler from +# being paired with a newly selected backend. This make exported the emulator +# for the caller's architecture, so each recursive make has to derive its own: +# an inherited qemu-arm would run the x64 binaries. +.PHONY: check-all-targets +check-all-targets: + $(Q)set -e; \ + unset TARGET_EXEC; \ + active_arch='$(or $(CONFIGURED_ARCH),$(ARCH))'; \ + trap '$(MAKE) config ARCH="$$active_arch"' EXIT; \ + for target_arch in $(ARCHS); do \ + $(MAKE) config ARCH="$$target_arch"; \ + $(MAKE) check ARCH="$$target_arch"; \ + done + # One checker per target: they share nothing, so "make -j check-style" runs them # concurrently and finishes in the time the slowest one takes. check-style: check-newline check-comments check-format check-shell diff --git a/lib/c.c b/lib/c.c index 6b03909d..ce6ac3fc 100644 --- a/lib/c.c +++ b/lib/c.c @@ -7,7 +7,6 @@ /* minimal libc implementation */ #include "c.h" -#define INT_BUF_LEN 16 /* Staging buffer for the printf family that writes straight to a descriptor. * @@ -47,7 +46,7 @@ int isblank(int c) return c == ' ' || c == '\t'; } -int strlen(char *str) +int strlen(const char *str) { /* process the string by checking 4 characters (a 32-bit word) at a time */ int i = 0; @@ -63,7 +62,7 @@ int strlen(char *str) } } -int strcmp(char *s1, char *s2) +int strcmp(const char *s1, const char *s2) { int i = 0; while (s1[i] && s2[i]) { @@ -76,7 +75,7 @@ int strcmp(char *s1, char *s2) return s1[i] - s2[i]; } -int strncmp(char *s1, char *s2, int len) +int strncmp(const char *s1, const char *s2, int len) { int i = 0; while (i < len) { @@ -91,7 +90,7 @@ int strncmp(char *s1, char *s2, int len) return 0; } -char *strcpy(char *dest, char *src) +char *strcpy(char *dest, const char *src) { int i = 0; while (src[i]) { @@ -102,13 +101,13 @@ char *strcpy(char *dest, char *src) return dest; } -char *strcat(char *dest, char *src) +char *strcat(char *dest, const char *src) { strcpy(&dest[strlen(dest)], src); return dest; } -char *strncat(char *dest, char *src, int len) +char *strncat(char *dest, const char *src, int len) { int i = strlen(dest), j = 0; while (j < len && src[j]) { @@ -148,7 +147,7 @@ char *strchr(char *str, int ch) return NULL; } -char *strncpy(char *dest, char *src, int len) +char *strncpy(char *dest, const char *src, int len) { int i = 0; int beyond = 0; @@ -165,30 +164,33 @@ char *strncpy(char *dest, char *src, int len) return dest; } -char *memcpy(char *dest, char *src, int count) +void *memcpy(void *dest, const void *src, int count) { + char *d = dest; + const char *s = src; int i = 0; /* Continues as long as there are at least 4 bytes remaining to copy. */ for (; i + 4 <= count; i += 4) { - dest[i] = src[i]; - dest[i + 1] = src[i + 1]; - dest[i + 2] = src[i + 2]; - dest[i + 3] = src[i + 3]; + d[i] = s[i]; + d[i + 1] = s[i + 1]; + d[i + 2] = s[i + 2]; + d[i + 3] = s[i + 3]; } /* Ensure all @count bytes are copied, even if @count is not a multiple of * 4, or if @count was less than 4 initially. */ for (; i < count; i++) - dest[i] = src[i]; + d[i] = s[i]; return dest; } -int memcmp(void *s1, void *s2, int n) +int memcmp(const void *s1, const void *s2, int n) { - char *p1 = (char *) s1, *p2 = (char *) s2; + /* C99 7.21.4 compares the bytes as unsigned char. */ + const unsigned char *p1 = s1, *p2 = s2; for (int i = 0; i < n; i++) { if (p1[i] < p2[i]) @@ -217,102 +219,12 @@ void *memset(void *s, int c, int n) return s; } -/* set 10 digits (32bit) without div - * - * This function converts a given integer value to its string representation in - * base-10 without using division operations. The method involves calculating - * the approximate quotient and remainder using bitwise operations, which are - * then used to derive each digit of the result. - * - * The logic is based on an efficient method of dividing by constants, as - * detailed in the reference link: - * http://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf. - * This approach avoids expensive division instructions by using a series of - * bitwise shifts and additions to calculate the quotient and remainder. Pointer - * width of the target, held in a variable rather than tested with the +/* Pointer width of the target, held in a variable rather than tested with the * preprocessor: shecc must be able to compile this file for either target, and * a constant condition would leave statically dead code behind. */ int __ptr_width = __SIZEOF_POINTER__; -void __str_base10(char *pb, int val) -{ - int neg = 0; - int q, r, t; - int i = INT_BUF_LEN - 1; - - /* val is an int on every target: negating INT_MIN overflows even when - * pointers and registers are 64-bit. Spell it directly so the digit loop - * never walks its stack buffer backwards indefinitely. - */ - if (val == -2147483648) { - strncpy(pb + INT_BUF_LEN - 11, "-2147483648", 11); - return; - } - if (val < 0) { - neg = 1; - val = -val; - } - - while (val) { - q = (val >> 1) + (val >> 2); - q += (q >> 4); - q += (q >> 8); - q += (q >> 16); - q >>= 3; - r = val - (((q << 2) + q) << 1); - t = ((r + 6) >> 4); - q += t; - r -= (((t << 2) + t) << 1); - - pb[i] += r; - val = q; - i--; - } - - if (neg) - pb[i] = '-'; -} - -void __str_base8(char *pb, int val) -{ - int c = INT_BUF_LEN - 1, v; - - /* Because every 3 binary digits can be converted to 1 octal digit, here - * performs the conversion 10 times, derived from 32 divided by 3. - * - * Finally, the remaining 2 bits are processed after the loop. - */ - int times = (sizeof(int) << 3) / 3; - for (int i = 0; i < times; i++) { - v = val & 0x7; - pb[c] = '0' + v; - val >>= 3; - c--; - } - v = val & 0x3; - pb[c] = '0' + v; -} - -void __str_base16(char *pb, int val) -{ - int c = INT_BUF_LEN - 1; - int times = sizeof(int) << 1; - for (int i = 0; i < times; i++) { - int v = val & 0xf; - if (v < 10) - pb[c] = '0' + v; - else if (v < 16) - pb[c] = 'a' + v - 10; - else { - abort(); - break; - } - val >>= 4; - c--; - } -} - /* The specification of snprintf() is defined in C99 7.19.6.5, and its behavior * and return value should comply with the following description: * - If n is zero, nothing is written. @@ -374,90 +286,131 @@ void __fmtbuf_write_str(fmtbuf_t *fmtbuf, char *str, int l) fmtbuf->n -= l; } -void __format(fmtbuf_t *fmtbuf, - int val, - int width, - int zeropad, - int base, - int alternate_form) -{ - char pb[INT_BUF_LEN], ch; - int pbi; - - /* set to zeroes */ - for (pbi = 0; pbi < INT_BUF_LEN; pbi++) - pb[pbi] = '0'; - - pbi = 0; - - switch (base) { - case 8: - __str_base8(pb, val); - break; - case 10: - __str_base10(pb, val); - break; - case 16: - __str_base16(pb, val); - break; - default: - abort(); - break; +void __fmtbuf_pad(fmtbuf_t *fmtbuf, int ch, int count) +{ + for (; count > 0; count--) + __fmtbuf_write_char(fmtbuf, ch); +} + +/* Conversion flags (C99 7.19.6.1p6) */ +#define __FMT_LEFT 1 +#define __FMT_PLUS 2 +#define __FMT_SPACE 4 +#define __FMT_ALT 8 +#define __FMT_ZERO 16 + +/* Convert the 64-bit integer @hi:@lo for conversion @conv, one of d, i, u, o, + * x, X and p, with @flags, @width and @precision, where a negative precision is + * none. This avoids long long arithmetic, so that the library needs no 64-bit + * lowering on a 32-bit target: a digit is the remainder of a bitwise long + * division of the two words, over the low word alone once the high one is zero. + */ +void __format_int(fmtbuf_t *fmtbuf, + unsigned lo, + unsigned hi, + int conv, + int flags, + int width, + int precision) +{ + char digits[24], prefix[2]; + char *symbols = conv == 'X' ? "0123456789ABCDEF" : "0123456789abcdef"; + int di = 24, prefix_len = 0, zeros = 0, count, base = 10; + + if (conv == 'o') + base = 8; + else if (conv == 'x' || conv == 'X' || conv == 'p') + base = 16; + + if (conv == 'd' || conv == 'i') { + if (hi & 0x80000000) { + prefix[prefix_len++] = '-'; + lo = ~lo + 1; + hi = ~hi + (lo == 0); + } else if (flags & __FMT_PLUS) + prefix[prefix_len++] = '+'; + else if (flags & __FMT_SPACE) + prefix[prefix_len++] = ' '; + } else if ((lo || hi) && (conv == 'p' || ((flags & __FMT_ALT) && + (conv == 'x' || conv == 'X')))) { + prefix[prefix_len++] = '0'; + prefix[prefix_len++] = conv == 'X' ? 'X' : 'x'; } - while (pb[pbi] == '0' && pbi < INT_BUF_LEN - 1) - pbi++; - - switch (base) { - case 8: - if (alternate_form) { - if (width && zeropad && pb[pbi] != '0') { - __fmtbuf_write_char(fmtbuf, '0'); - width -= 1; - } else if (pb[pbi] != '0') - pb[--pbi] = '0'; - } - break; - case 10: - if (width && zeropad && pb[pbi] == '-') { - __fmtbuf_write_char(fmtbuf, '-'); - pbi++; - width--; - } - break; - case 16: - if (alternate_form) { - if (width && zeropad && pb[pbi] != '0') { - __fmtbuf_write_char(fmtbuf, '0'); - __fmtbuf_write_char(fmtbuf, 'x'); - width -= 2; - } else if (pb[pbi] != '0') { - pb[--pbi] = 'x'; - pb[--pbi] = '0'; + /* A zero precision converts a zero value to no characters. */ + if (precision || lo || hi) { + do { + unsigned rem = 0; + + for (int bit = hi ? 63 : 31; bit >= 0; bit--) { + unsigned *word = bit >= 32 ? &hi : &lo; + unsigned mask = 1U << (bit % 32); + + rem = (rem << 1) | ((*word & mask) != 0); + *word &= ~mask; + if (rem >= (unsigned) base) { + rem -= base; + *word |= mask; + } } - } - break; + digits[--di] = symbols[rem]; + } while (lo || hi); } + count = 24 - di; - width -= (INT_BUF_LEN - pbi); - if (width < 0) - width = 0; + if (precision > count) + zeros = precision - count; - ch = zeropad ? '0' : ' '; - while (width) { - __fmtbuf_write_char(fmtbuf, ch); - width--; - } + /* The alternate form of o makes the first digit a zero. */ + if ((flags & __FMT_ALT) && conv == 'o' && !zeros && + (!count || digits[di] != '0')) + zeros = 1; - __fmtbuf_write_str(fmtbuf, pb + pbi, INT_BUF_LEN - pbi); + /* The 0 flag pads with zeros after the sign or prefix, unless a precision + * is given or the field is left-justified. + */ + if ((flags & __FMT_ZERO) && !(flags & __FMT_LEFT) && precision < 0 && + width > prefix_len + zeros + count) + zeros = width - prefix_len - count; + + width -= prefix_len + zeros + count; + if (!(flags & __FMT_LEFT)) + __fmtbuf_pad(fmtbuf, ' ', width); + __fmtbuf_write_str(fmtbuf, prefix, prefix_len); + __fmtbuf_pad(fmtbuf, '0', zeros); + __fmtbuf_write_str(fmtbuf, digits + di, count); + if (flags & __FMT_LEFT) + __fmtbuf_pad(fmtbuf, ' ', width); } -void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) +/* Write @length characters of @str in a field of @width, as %s and %c do. */ +void __format_str(fmtbuf_t *fmtbuf, char *str, int length, int flags, int width) { - int si = 0, pi = 0; + width -= length; + if (!(flags & __FMT_LEFT)) + __fmtbuf_pad(fmtbuf, ' ', width); + __fmtbuf_write_str(fmtbuf, str, length); + if (flags & __FMT_LEFT) + __fmtbuf_pad(fmtbuf, ' ', width); +} - /* A pointer-width view of the same argument area, for %s. Reading a pointer - * argument through an int would truncate it on LP64. +/* @var_args follows @named named arguments. Each variadic argument takes + * VA_INT_STEP int-sized slots, and a 64-bit one takes two. On a 32-bit target + * the pair starts at an even slot counted from the first named argument, so one + * after an odd count of slots is preceded by an unused one. + * + * long has the width of int in shecc, so l changes no argument's width; ll and + * j read 64 bits everywhere, and z and t read a pointer's width. + */ +void __format_to_buf(fmtbuf_t *fmtbuf, + const char *format, + int *var_args, + int named) +{ + int si = 0, slot = 0; + + /* A pointer-width view of the same argument area, for %s, %p and %n. + * Reading a pointer argument through an int would truncate it on LP64. */ char **var_args_p = (char **) var_args; @@ -465,89 +418,170 @@ void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) if (format[si] != '%') { __fmtbuf_write_char(fmtbuf, format[si]); si++; + continue; + } + + int flags = 0, width = 0, precision = -1, size = 0, conv; + unsigned lo, hi; + + si++; + for (;; si++) { + if (format[si] == '-') + flags |= __FMT_LEFT; + else if (format[si] == '+') + flags |= __FMT_PLUS; + else if (format[si] == ' ') + flags |= __FMT_SPACE; + else if (format[si] == '#') + flags |= __FMT_ALT; + else if (format[si] == '0') + flags |= __FMT_ZERO; + else + break; + } + + /* A negative width from '*' is a '-' flag and a positive width. */ + if (format[si] == '*') { + width = var_args[slot]; + slot += VA_INT_STEP; + si++; + if (width < 0) { + flags |= __FMT_LEFT; + width = -width; + } } else { - int w = 0, zp = 0, pp = 0, v = var_args[pi * VA_INT_STEP], l; + while (format[si] >= '0' && format[si] <= '9') + width = width * 10 + format[si++] - '0'; + } + /* A negative precision from '*' is taken as none. */ + if (format[si] == '.') { si++; - if (format[si] == '#') { - pp = 1; + precision = 0; + if (format[si] == '*') { + precision = var_args[slot]; + slot += VA_INT_STEP; si++; + if (precision < 0) + precision = -1; + } else { + while (format[si] >= '0' && format[si] <= '9') + precision = precision * 10 + format[si++] - '0'; } - if (format[si] == '0') { - zp = 1; + } + + /* @size: -2 hh, -1 h, 0 int or long, 8 a 64-bit argument. */ + if (format[si] == 'h') { + size = -1; + if (format[++si] == 'h') { + size = -2; si++; } - if (format[si] >= '1' && format[si] <= '9') { - w = format[si] - '0'; + } else if (format[si] == 'l') { + if (format[++si] == 'l') { + size = 8; si++; - while (format[si] >= '0' && format[si] <= '9') { - w *= 10; - w += format[si] - '0'; - si++; - } } - switch (format[si]) { - case 's': - /* append param pi as string; read it at pointer width */ - l = strlen(var_args_p[pi]); - __fmtbuf_write_str(fmtbuf, var_args_p[pi], l); - break; - case 'c': - /* append param pi as char */ - __fmtbuf_write_char(fmtbuf, (char) v); - break; - case 'o': - /* append param as octal */ - __format(fmtbuf, v, w, zp, 8, pp); - break; - case 'd': - /* append param as decimal */ - __format(fmtbuf, v, w, zp, 10, 0); - break; - case 'x': - /* append param as hex */ - __format(fmtbuf, v, w, zp, 16, pp); - break; - case 'p': { - /* Append param as a pointer. - * - * A pointer occupies VA_INT_STEP int-sized slots, so on an LP64 - * target the second one carries the high word. Printing only @v - * would drop it, and the graph writer in ssa.c names its nodes - * after these values, so two objects sharing a low word would - * collapse into one node. - * - * A pointer has one spelling here, "0x" and its significant - * digits, so any width or zero-pad in the format is ignored. - * Honoring them on one branch and not the other would render - * the same conversion two ways depending on the value. - */ - int hi = 0; - - if (VA_INT_STEP > 1) - hi = var_args[pi * VA_INT_STEP + 1]; - - __fmtbuf_write_char(fmtbuf, '0'); - __fmtbuf_write_char(fmtbuf, 'x'); - if (hi) { - __format(fmtbuf, hi, 0, 0, 16, 0); - - /* The low word keeps its leading zeros, or the two halves - * would run together into a different number. - */ - __format(fmtbuf, v, 8, 1, 16, 0); - } else - __format(fmtbuf, v, 0, 0, 16, 0); - break; + } else if (format[si] == 'j') { + size = 8; + si++; + } else if (format[si] == 'z' || format[si] == 't') { + if (__ptr_width == 8) + size = 8; + si++; + } + + conv = format[si]; + if (!conv) + break; + si++; + + switch (conv) { + case 'd': + case 'i': + case 'u': + case 'o': + case 'x': + case 'X': + if (size == 8) { + if (__ptr_width == 4 && ((named + slot) & 1)) + slot++; + lo = var_args[slot]; + hi = var_args[slot + 1]; + slot += 2; + } else { + int v = var_args[slot]; + + slot += VA_INT_STEP; + if (size == -2) + v = conv == 'd' || conv == 'i' ? (signed char) v : v & 0xff; + else if (size == -1) + v = conv == 'd' || conv == 'i' ? (short) v : v & 0xffff; + lo = v; + hi = (conv == 'd' || conv == 'i') && v < 0 ? 0xffffffff : 0; } - case '%': - /* append literal '%' character */ - __fmtbuf_write_char(fmtbuf, '%'); - si++; - continue; + __format_int(fmtbuf, lo, hi, conv, flags, width, precision); + break; + case 'p': + /* A pointer occupies VA_INT_STEP int-sized slots, so on an LP64 + * target the second one carries the high word. A null pointer is + * spelled "(nil)", as glibc does. + */ + lo = var_args[slot]; + hi = VA_INT_STEP > 1 ? var_args[slot + 1] : 0; + slot += VA_INT_STEP; + if (lo || hi) + __format_int(fmtbuf, lo, hi, 'p', flags & __FMT_LEFT, width, + -1); + else + __format_str(fmtbuf, "(nil)", 5, flags, width); + break; + case 'c': { + char ch = (char) var_args[slot]; + + slot += VA_INT_STEP; + __format_str(fmtbuf, &ch, 1, flags, width); + break; + } + case 's': { + /* A precision bounds the characters read, so the array need not be + * null-terminated (C99 7.19.6.1p8). + */ + char *str = var_args_p[slot / VA_INT_STEP]; + int length = 0; + + slot += VA_INT_STEP; + if (!str) + str = "(null)"; + while ((precision < 0 || length < precision) && str[length]) + length++; + __format_str(fmtbuf, str, length, flags, width); + break; + } + case 'n': { + /* Store the count of characters written so far. */ + int *count = (int *) var_args_p[slot / VA_INT_STEP]; + + slot += VA_INT_STEP; + if (size == -2) + *(char *) count = fmtbuf->len; + else if (size == -1) + *(short *) count = fmtbuf->len; + else { + count[0] = fmtbuf->len; + if (size == 8) + count[1] = 0; } - pi++; - si++; + break; + } + case '%': + __fmtbuf_write_char(fmtbuf, '%'); + break; + default: + /* An unknown conversion is written as it stands. */ + __fmtbuf_write_char(fmtbuf, '%'); + __fmtbuf_write_char(fmtbuf, conv); + break; } } @@ -556,7 +590,7 @@ void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) fmtbuf->buf[0] = 0; } -int __write_fmt(int fd, char *str, int *var_args) +int __write_fmt(int fd, const char *str, int *var_args, int named) { char buffer[FMT_BUF_LEN]; fmtbuf_t fmtbuf; @@ -564,7 +598,7 @@ int __write_fmt(int fd, char *str, int *var_args) fmtbuf.buf = buffer; fmtbuf.n = FMT_BUF_LEN; fmtbuf.len = 0; - __format_to_buf(&fmtbuf, str, var_args); + __format_to_buf(&fmtbuf, str, var_args, named); /* len counts what the conversion would have produced, not what fit. */ int len = fmtbuf.len; @@ -583,7 +617,7 @@ int __write_fmt(int fd, char *str, int *var_args) fmtbuf.buf = wide; fmtbuf.n = len + 1; fmtbuf.len = 0; - __format_to_buf(&fmtbuf, str, var_args); + __format_to_buf(&fmtbuf, str, var_args, named); int written = __syscall(__syscall_write, fd, wide, fmtbuf.len); @@ -591,38 +625,38 @@ int __write_fmt(int fd, char *str, int *var_args) return written; } -int printf(char *str, ...) +int printf(const char *str, ...) { - return __write_fmt(1, str, &str + 1); + return __write_fmt(1, str, &str + 1, 1); } -int sprintf(char *buffer, char *str, ...) +int sprintf(char *buffer, const char *str, ...) { fmtbuf_t fmtbuf; fmtbuf.buf = buffer; fmtbuf.n = INT_MAX; fmtbuf.len = 0; - __format_to_buf(&fmtbuf, str, &str + 1); + __format_to_buf(&fmtbuf, str, &str + 1, 2); return fmtbuf.len; } -int snprintf(char *buffer, int n, char *str, ...) +int snprintf(char *buffer, int n, const char *str, ...) { fmtbuf_t fmtbuf; fmtbuf.buf = buffer; fmtbuf.n = n; fmtbuf.len = 0; - __format_to_buf(&fmtbuf, str, &str + 1); + __format_to_buf(&fmtbuf, str, &str + 1, 3); return fmtbuf.len; } int __free_all(void); -int fprintf(FILE *stream, char *str, ...) +int fprintf(FILE *stream, const char *str, ...) { - return __write_fmt(stream, str, &str + 1); + return __write_fmt(stream, str, &str + 1, 2); } int fflush(FILE *stream) @@ -643,7 +677,20 @@ void abort(void) exit(-1); } -FILE *fopen(char *filename, char *mode) +/* C99 7.2.1.1 has the message name the expression, the source file, the line + * and the enclosing function. + */ +void __assert_fail(const char *expr, + const char *file, + unsigned int line, + const char *function) +{ + fprintf(stderr, "Assertion failed: %s, function %s, file %s, line %d\n", + expr, function, file, line); + abort(); +} + +FILE *fopen(const char *filename, const char *mode) { int fd; @@ -681,7 +728,7 @@ FILE *fopen(char *filename, char *mode) */ if (fd < 0) return NULL; - return fd; + return (FILE *) fd; } int fclose(FILE *stream) @@ -690,7 +737,7 @@ int fclose(FILE *stream) return 0; } -int chmod(char *filename, int mode) +int chmod(const char *filename, int mode) { #if defined(__riscv) || defined(__aarch64__) /* sys_fchmodat takes (dirfd, filename, mode); AT_FDCWD is -100. */ @@ -776,8 +823,8 @@ int ftell(FILE *stream) #define CHUNK_GET_SIZE(size) (size & CHUNK_SIZE_SZ_MASK) #define IS_CHUNK_GET_FREED(size) (size & CHUNK_SIZE_FREED_MASK) -typedef struct chunk { - struct chunk *next, *prev; +typedef struct __chunk { + struct __chunk *next, *prev; int size; } chunk_t; @@ -812,9 +859,9 @@ void *malloc(int size) size = ALIGN_UP(size, MIN_ALIGNMENT); if (!__alloc_head) { - chunk_t *tmp = - __syscall(__syscall_mmap2, NULL, __align_up(sizeof(chunk_t)), prot, - flags, -1, 0); + chunk_t *tmp = (chunk_t *) __syscall(__syscall_mmap2, NULL, + __align_up(sizeof(chunk_t)), prot, + flags, -1, 0); if (tmp == (void *) -1) return NULL; __alloc_head = tmp; @@ -825,9 +872,9 @@ void *malloc(int size) } if (!__freelist_head) { - chunk_t *tmp = - __syscall(__syscall_mmap2, NULL, __align_up(sizeof(chunk_t)), prot, - flags, -1, 0); + chunk_t *tmp = (chunk_t *) __syscall(__syscall_mmap2, NULL, + __align_up(sizeof(chunk_t)), prot, + flags, -1, 0); if (tmp == (void *) -1) return NULL; __freelist_head = tmp; @@ -867,9 +914,9 @@ void *malloc(int size) } if (!allocated) { - allocated = - __syscall(__syscall_mmap2, NULL, __align_up(sizeof(chunk_t) + size), - prot, flags, -1, 0); + allocated = (chunk_t *) __syscall(__syscall_mmap2, NULL, + __align_up(sizeof(chunk_t) + size), + prot, flags, -1, 0); if (allocated == (void *) -1) return NULL; allocated->size = __align_up(sizeof(chunk_t) + size); diff --git a/lib/c.h b/lib/c.h index d097edcf..cb335794 100644 --- a/lib/c.h +++ b/lib/c.h @@ -109,14 +109,28 @@ int isblank(int c); /* File I/O */ typedef int FILE; +#ifdef __SHECC_DYNLINK__ +/* The host libc's own stream objects. Its stdio functions dereference the + * stream pointer they are given, so a descriptor number would fault there. The + * output cannot import a data symbol, only functions through the PLT, so each + * use asks the dynamic linker for the address of the host's variable. A null + * handle is RTLD_DEFAULT. Before glibc 2.34 dlsym() lived in libdl.so.2, so a + * program that calls it names that library as well. + */ +void *dlsym(void *handle, const char *name); +#define stdin (*(FILE **) dlsym((void *) 0, "stdin")) +#define stdout (*(FILE **) dlsym((void *) 0, "stdout")) +#define stderr (*(FILE **) dlsym((void *) 0, "stderr")) +#else /* Standard streams, as raw file descriptors */ -#define stdin 0 -#define stdout 1 -#define stderr 2 +#define stdin ((FILE *) 0) +#define stdout ((FILE *) 1) +#define stderr ((FILE *) 2) +#endif -FILE *fopen(char *filename, char *mode); +FILE *fopen(const char *filename, const char *mode); int fclose(FILE *stream); -int chmod(char *filename, int mode); +int chmod(const char *filename, int mode); int fgetc(FILE *stream); char *fgets(char *str, int n, FILE *stream); int fputc(int c, FILE *stream); @@ -126,34 +140,42 @@ int fputc(int c, FILE *stream); * through '__syscall' instead. */ int fread(char *ptr, int size, int nmemb, FILE *stream); -int fwrite(char *ptr, int size, int nmemb, FILE *stream); +int fwrite(const void *ptr, int size, int nmemb, FILE *stream); int fseek(FILE *stream, int offset, int whence); int ftell(FILE *stream); /* string-related functions */ -int strlen(char *str); -int strcmp(char *s1, char *s2); -int strncmp(char *s1, char *s2, int len); -char *strcpy(char *dest, char *src); -char *strncpy(char *dest, char *src, int len); -char *strcat(char *dest, char *src); -char *strncat(char *dest, char *src, int len); +int strlen(const char *str); +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, int len); +char *strcpy(char *dest, const char *src); +char *strncpy(char *dest, const char *src, int len); +char *strcat(char *dest, const char *src); +char *strncat(char *dest, const char *src, int len); char *strchr(char *str, int ch); -char *memcpy(char *dest, char *src, int count); -int memcmp(void *s1, void *s2, int n); +void *memcpy(void *dest, const void *src, int count); +int memcmp(const void *s1, const void *s2, int n); void *memset(void *s, int c, int n); /* formatted output string */ -int printf(char *str, ...); -int sprintf(char *buffer, char *str, ...); -int snprintf(char *buffer, int n, char *str, ...); -int fprintf(FILE *stream, char *str, ...); +int printf(const char *str, ...); +int sprintf(char *buffer, const char *str, ...); +int snprintf(char *buffer, int n, const char *str, ...); +int fprintf(FILE *stream, const char *str, ...); int fflush(FILE *stream); /* Terminating program */ void exit(int exit_code); void abort(void); +/* glibc's signature, so a dynamically linked program reaches the host's handler + * with every argument it reads. + */ +void __assert_fail(const char *expr, + const char *file, + unsigned int line, + const char *function); + /* Dynamic memory allocation/deallocation functions */ void *malloc(int size); void *calloc(int n, int size); diff --git a/mk/arm.mk b/mk/arm.mk index 7ef7d7e4..f1f33711 100644 --- a/mk/arm.mk +++ b/mk/arm.mk @@ -14,6 +14,7 @@ ARCH_DEFS = \ \#define ELF_FLAGS 0x5000400\n$\ \#define DYN_LINKER \"/lib/ld-linux-armhf.so.3\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ + \#define LIBDL_SO \"libdl.so.2\"\n$\ \#define PLT_FIXUP_SIZE 20\n$\ \#define PLT_ENT_SIZE 12\n$\ \#define RESERVED_GOT_NUM 3\n$\ diff --git a/mk/arm64.mk b/mk/arm64.mk index c6b402b2..a3a4151a 100644 --- a/mk/arm64.mk +++ b/mk/arm64.mk @@ -21,6 +21,7 @@ ARCH_DEFS = \ \#define CALLEE_SAVED_REGS 3\n$\ \#define DYN_LINKER \"/lib/ld-linux-aarch64.so.1\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ + \#define LIBDL_SO \"libdl.so.2\"\n$\ \#define PLT_FIXUP_SIZE 0\n$\ \#define PLT_ENT_SIZE 16\n$\ \#define RESERVED_GOT_NUM 3\n$\ diff --git a/mk/riscv.mk b/mk/riscv.mk index ee87ce1e..53402b5b 100644 --- a/mk/riscv.mk +++ b/mk/riscv.mk @@ -12,6 +12,7 @@ ARCH_DEFS = \ \#define ELF_FLAGS 0x4\n$\ \#define DYN_LINKER \"/lib/ld-linux-riscv32-ilp32d.so.1\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ + \#define LIBDL_SO \"libdl.so.2\"\n$\ \#define PLT_FIXUP_SIZE 32\n$\ \#define PLT_ENT_SIZE 16\n$\ \#define RESERVED_GOT_NUM 2\n$\ diff --git a/mk/x64.mk b/mk/x64.mk index 85ed95a8..98c73210 100644 --- a/mk/x64.mk +++ b/mk/x64.mk @@ -11,6 +11,7 @@ ARCH_DEFS = \ \#define MAX_ARGS_IN_REG 6 /* System V: rdi rsi rdx rcx r8 r9 */\n$\ \#define DYN_LINKER \"/lib64/ld-linux-x86-64.so.2\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ + \#define LIBDL_SO \"libdl.so.2\"\n$\ \#define PLT_FIXUP_SIZE 16\n$\ \#define PLT_ENT_SIZE 16\n$\ \#define RESERVED_GOT_NUM 3\n$\ diff --git a/src/arm-codegen.c b/src/arm-codegen.c index bf7615f0..359c4ab5 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -11,6 +11,24 @@ #include "defs.h" #include "globals.c" +/* Whether a load or store materializes its frame or data offset with + * MOVW/MOVT/ADD because no immediate form reaches it. LDR, LDRB, STR and STRB + * take 12 bits, LDRSB, LDRH, LDRSH and STRH eight, and a register pair also + * addresses its high word four bytes further up. The size estimator and the + * emitter both ask this, so the two cannot disagree. + */ +bool arm_offset_needs_movw(const ph2_ir_t *ph2_ir) +{ + bool load = ph2_ir->op == OP_load || ph2_ir->op == OP_global_load; + int offset = load ? ph2_ir->src0 : ph2_ir->src1; + bool eight_bits = ph2_ir->size_bytes == 2 || + (load && ph2_ir->size_bytes == 1 && !ph2_ir->is_unsigned); + + if ((load ? ph2_ir->dest_hi : ph2_ir->src0_hi) >= 0) + return offset > 4091; + return offset > (eight_bits ? 255 : 4095); +} + void update_elf_offset(ph2_ir_t *ph2_ir) { func_t *func; @@ -26,6 +44,14 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 8; else elf_offset += 4; + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src1 < 0) + elf_offset += 12; + else if (ph2_ir->src1 > 255) + elf_offset += 8; + else + elf_offset += 4; + } return; case OP_address_of: case OP_global_address_of: @@ -43,47 +69,100 @@ void update_elf_offset(ph2_ir_t *ph2_ir) case OP_assign: if (ph2_ir->dest != ph2_ir->src0) elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->dest_hi != ph2_ir->src0_hi) + elf_offset += 4; + /* Exchanging the two registers of a pair takes a third move. */ + if (ph2_ir->dest_hi >= 0 && ph2_ir->dest == ph2_ir->src0_hi && + ph2_ir->dest_hi == ph2_ir->src0) + elf_offset += 4; return; case OP_load: case OP_global_load: - /* ARMv7 straight uses 12 bits to encode the offset of load instruction - * (no rotation). + /* LDR/LDRB use a 12-bit offset, but LDRSB and LDRH/LDRSH use eight + * bits. A larger signed-byte or halfword offset is materialized with + * MOVW/MOVT/ADD before the load, exactly as an offset beyond the + * general load range is. A wide pair also loads its high word four + * bytes further up. */ - if (ph2_ir->src0 > 4095) - elf_offset += 16; + if (arm_offset_needs_movw(ph2_ir)) + if (ph2_ir->dest_hi >= 0) + elf_offset += 20; + else + elf_offset += 16; else if (ph2_ir->src0 >= 0) - elf_offset += 4; + if (ph2_ir->dest_hi >= 0) + elf_offset += 8; + else + elf_offset += 4; else abort(); return; case OP_store: case OP_global_store: /* ARMv7 straight uses 12 bits to encode the offset of store instruction - * (no rotation). + * (no rotation), but STRH only eight. A wide pair also stores its high + * word four bytes further up. */ - if (ph2_ir->src1 > 4095) - elf_offset += 16; + if (arm_offset_needs_movw(ph2_ir)) + if (ph2_ir->src0_hi >= 0) + elf_offset += 20; + else + elf_offset += 16; else if (ph2_ir->src1 >= 0) - elf_offset += 4; + if (ph2_ir->src0_hi >= 0) + elf_offset += 8; + else + elf_offset += 4; else abort(); return; case OP_read: + elf_offset += ph2_ir->dest_hi >= 0 ? 8 : 4; + return; case OP_write: + elf_offset += ph2_ir->src1_hi >= 0 ? 8 : 4; + return; + case OP_indirect: + /* blx, then under dynamic linking the r12 reload that OP_call adds. */ + elf_offset += dynlink ? 16 : 4; + return; case OP_jump: case OP_load_func: - case OP_indirect: - case OP_add: - case OP_sub: - case OP_mul: case OP_lshift: case OP_rshift: + elf_offset += ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 ? 48 : 4; + return; + case OP_mul: + elf_offset += + ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 + ? 20 + : 4; + return; + case OP_negate: + elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) + elf_offset += 4; + return; + case OP_add: + case OP_sub: + elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + elf_offset += 4; + return; case OP_bit_and: case OP_bit_or: case OP_bit_xor: - case OP_negate: + elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + elf_offset += 4; + return; case OP_bit_not: elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_call: func = find_func(ph2_ir->func_name); @@ -107,6 +186,18 @@ void update_elf_offset(ph2_ir_t *ph2_ir) return; case OP_div: case OP_mod: + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + /* The stack-backed restoring loop below has a fixed 64-round body. + * It keeps all pair state out of operand registers: an SSA result + * may coalesce with either dying input pair. + */ + if (ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + elf_offset += 284; + else + elf_offset += ph2_ir->op == OP_div ? 424 : 416; + return; + } if (hard_mul_div) { if (ph2_ir->op == OP_div) elf_offset += 4; @@ -115,40 +206,69 @@ void update_elf_offset(ph2_ir_t *ph2_ir) return; } /* div/mod emulation's offset */ - elf_offset += 116; + elf_offset += 124; return; case OP_load_data_address: case OP_load_rodata_address: elf_offset += 8; return; case OP_address_of_func: - case OP_eq: - case OP_neq: + elf_offset += 12; + return; + case OP_log_not: + elf_offset += ph2_ir->src0_hi >= 0 ? 16 : 12; + return; case OP_gt: case OP_lt: case OP_geq: case OP_leq: - case OP_log_not: - elf_offset += 12; + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 16 : 12; + return; + case OP_eq: + case OP_neq: + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 16 : 12; return; case OP_branch: if (ph2_ir->is_branch_detached) elf_offset += 12; else elf_offset += 8; + if (ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_return: elf_offset += 24; + if (ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_trunc: - /* SXTB, SXTH and MOV are each one instruction. */ - elf_offset += 4; + /* Unsigned byte/half truncation uses a logical shift pair; signed + * narrowing uses the single-instruction SXTB/SXTH forms. + */ + if (ph2_ir->is_unsigned && (ph2_ir->src1 == 1 || ph2_ir->src1 == 2)) + elf_offset += 8; + else + elf_offset += 4; return; case OP_sign_ext: - elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi < 0) { + int source_size = (ph2_ir->src1 >> 16) & 0xFFFF; + elf_offset += + (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) && + (source_size == 1 || source_size == 2) + ? 12 + : 8; + } else if (ph2_ir->src0_is_unsigned) + elf_offset += 8; + else + elf_offset += 4; return; case OP_cast: - elf_offset += 4; + elf_offset += + ph2_ir->dest_hi >= 0 && + (ph2_ir->src0_hi < 0 || ph2_ir->dest_hi != ph2_ir->src0_hi) + ? 8 + : 4; return; default: fatal("Unknown opcode"); @@ -160,8 +280,9 @@ void cfg_flatten(void) func_t *func; if (dynlink) - elf_offset = - 100; /* offset of __libc_start_main + main_wrapper in codegen */ + elf_offset = 132; /* __libc_start_main call, main_wrapper, and the + * global stack clearing loop in codegen + */ else { func = find_func("__syscall"); func->bbs->elf_offset = 32; /* offset of start + branch in codegen */ @@ -259,6 +380,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) const int rn = ph2_ir->src0; int rm = ph2_ir->src1; /* Not const because OP_trunc modifies it */ int ofs; + int store_offset; bool is_external_call = false; /* Prepare this variable to reuse code for: @@ -270,6 +392,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) switch (ph2_ir->op) { case OP_define: + fatal_function_context = ph2_ir->func_name; + /* We should handle the function entry point carefully due to the * following constraints: * - according to AAPCS, the callee must preserve r4-r11 for the caller, @@ -307,6 +431,17 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__movt(__AL, rd, ph2_ir->src0)); } else emit(__mov_i(__AL, rd, ph2_ir->src0)); + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src1 < 0) { + emit(__movw(__AL, __r8, -ph2_ir->src1)); + emit(__movt(__AL, __r8, -ph2_ir->src1)); + emit(__rsb_i(__AL, ph2_ir->dest_hi, 0, __r8)); + } else if (ph2_ir->src1 > 255) { + emit(__movw(__AL, ph2_ir->dest_hi, ph2_ir->src1)); + emit(__movt(__AL, ph2_ir->dest_hi, ph2_ir->src1)); + } else + emit(__mov_i(__AL, ph2_ir->dest_hi, ph2_ir->src1)); + } return; case OP_address_of: case OP_global_address_of: @@ -323,43 +458,147 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) * emitting one here would push every later address out by four bytes * and leave the data segment's p_offset and p_vaddr incongruent. */ - if (rd == rn) + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + rd == ph2_ir->src0_hi) { + /* A pair moved one register over, as a slot round trip collapsed + * into a move can leave it: the low destination is the high source, + * so move the high word first, through r8 when the two registers + * trade places. + */ + if (ph2_ir->dest_hi == rn) { + emit(__mov_r(__AL, __r8, rn)); + emit(__mov_r(__AL, rd, ph2_ir->src0_hi)); + emit(__mov_r(__AL, ph2_ir->dest_hi, __r8)); + } else { + emit(__mov_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi)); + emit(__mov_r(__AL, rd, rn)); + } return; - emit(__mov_r(__AL, rd, rn)); + } + if (rd != rn) + emit(__mov_r(__AL, rd, rn)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->dest_hi != ph2_ir->src0_hi) + emit(__mov_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi)); return; case OP_load: case OP_global_load: interm = ph2_ir->op == OP_load ? __sp : __r12; - if (ph2_ir->src0 > 4095) { + if (ph2_ir->dest_hi >= 0) { + if (arm_offset_needs_movw(ph2_ir)) { + emit(__movw(__AL, __r8, ph2_ir->src0)); + emit(__movt(__AL, __r8, ph2_ir->src0)); + emit(__add_r(__AL, __r8, interm, __r8)); + emit(__lw(__AL, rd, __r8, 0)); + emit(__lw(__AL, ph2_ir->dest_hi, __r8, 4)); + } else { + emit(__lw(__AL, rd, interm, ph2_ir->src0)); + emit(__lw(__AL, ph2_ir->dest_hi, interm, ph2_ir->src0 + 4)); + } + return; + } + + /* LDRSB, LDRH and LDRSH have only eight immediate bits, unlike LDRB's + * 12-bit field. Materialize a larger offset before loading. + */ + if (arm_offset_needs_movw(ph2_ir)) { emit(__movw(__AL, __r8, ph2_ir->src0)); emit(__movt(__AL, __r8, ph2_ir->src0)); emit(__add_r(__AL, __r8, interm, __r8)); - emit(__lw(__AL, rd, __r8, 0)); - } else + if (ph2_ir->size_bytes == 1) + emit(ph2_ir->is_unsigned ? __lb(__AL, rd, __r8, 0) + : __lsb(__AL, rd, __r8, 0)); + else if (ph2_ir->size_bytes == 2) + emit(ph2_ir->is_unsigned ? __lhu(__AL, rd, __r8, 0) + : __lh(__AL, rd, __r8, 0)); + else + emit(__lw(__AL, rd, __r8, 0)); + } else if (ph2_ir->size_bytes == 1) + emit(ph2_ir->is_unsigned ? __lb(__AL, rd, interm, ph2_ir->src0) + : __lsb(__AL, rd, interm, ph2_ir->src0)); + else if (ph2_ir->size_bytes == 2) + emit(ph2_ir->is_unsigned ? __lhu(__AL, rd, interm, ph2_ir->src0) + : __lh(__AL, rd, interm, ph2_ir->src0)); + else emit(__lw(__AL, rd, interm, ph2_ir->src0)); return; case OP_store: case OP_global_store: interm = ph2_ir->op == OP_store ? __sp : __r12; - if (ph2_ir->src1 > 4095) { - emit(__movw(__AL, __r8, ph2_ir->src1)); - emit(__movt(__AL, __r8, ph2_ir->src1)); + store_offset = ph2_ir->src1; + if (ph2_ir->src0_hi >= 0) { + if (arm_offset_needs_movw(ph2_ir)) { + emit(__movw(__AL, __r8, store_offset)); + emit(__movt(__AL, __r8, store_offset)); + emit(__add_r(__AL, __r8, interm, __r8)); + emit(__sw(__AL, rn, __r8, 0)); + emit(__sw(__AL, ph2_ir->src0_hi, __r8, 4)); + } else { + emit(__sw(__AL, rn, interm, store_offset)); + emit(__sw(__AL, ph2_ir->src0_hi, interm, store_offset + 4)); + } + return; + } + + /* STRH has an 8-bit split immediate while STR/STRB accept 12 bits. + * Materialize larger halfword offsets before selecting the width. + */ + if (arm_offset_needs_movw(ph2_ir)) { + emit(__movw(__AL, __r8, store_offset)); + emit(__movt(__AL, __r8, store_offset)); emit(__add_r(__AL, __r8, interm, __r8)); - emit(__sw(__AL, rn, __r8, 0)); - } else - emit(__sw(__AL, rn, interm, ph2_ir->src1)); + interm = __r8; + store_offset = 0; + } + + /* Global aggregate initialization can address a byte or halfword field + * directly. Treating every OP_global_store as a word store clobbers + * neighbouring designated members on ARM. Ordinary stack slots remain + * word-sized unless their IR says otherwise. + */ + if (ph2_ir->size_bytes == 1) + emit(__sb(__AL, rn, interm, store_offset)); + else if (ph2_ir->size_bytes == 2) + emit(__sh(__AL, rn, interm, store_offset)); + else + emit(__sw(__AL, rn, interm, store_offset)); return; case OP_read: + if (ph2_ir->dest_hi >= 0) { + /* A pair read through a pointer. When the low destination is the + * address register itself, fetch the high word first so the address + * survives until both words are loaded. + */ + if (ph2_ir->src1 != 8) + fatal("unsupported Arm pair load width"); + if (rd == rn) { + emit(__lw(__AL, ph2_ir->dest_hi, rn, 4)); + emit(__lw(__AL, rd, rn, 0)); + } else { + emit(__lw(__AL, rd, rn, 0)); + emit(__lw(__AL, ph2_ir->dest_hi, rn, 4)); + } + return; + } if (ph2_ir->src1 == 1) - emit(__lb(__AL, rd, rn, 0)); + emit(ph2_ir->is_unsigned ? __lb(__AL, rd, rn, 0) + : __lsb(__AL, rd, rn, 0)); else if (ph2_ir->src1 == 2) - emit(__lh(__AL, rd, rn, 0)); + emit(ph2_ir->is_unsigned ? __lhu(__AL, rd, rn, 0) + : __lh(__AL, rd, rn, 0)); else if (ph2_ir->src1 == 4) emit(__lw(__AL, rd, rn, 0)); else - abort(); + fatal("unsupported Arm load width"); return; case OP_write: + if (ph2_ir->src1_hi >= 0) { + if (ph2_ir->dest != 8) + fatal("unsupported Arm pair store width"); + emit(__sw(__AL, rm, rn, 0)); + emit(__sw(__AL, ph2_ir->src1_hi, rn, 4)); + return; + } if (ph2_ir->dest == 1) emit(__sb(__AL, rm, rn, 0)); else if (ph2_ir->dest == 2) @@ -367,12 +606,23 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) else if (ph2_ir->dest == 4) emit(__sw(__AL, rm, rn, 0)); else - abort(); + fatal("unsupported Arm store width"); return; case OP_branch: - emit(__teq(rn)); + /* A pair is true when either word is nonzero. */ + if (ph2_ir->src0_hi >= 0) { + emit(__or_r(__AL, __r8, rn, ph2_ir->src0_hi)); + emit(__teq(__r8)); + } else + emit(__teq(rn)); if (ph2_ir->is_branch_detached) { - emit(__b(__NE, 8)); + /* The else block does not follow, and nothing says the then block + * does either: a loop's back edge lands behind this one. Jump to + * both explicitly rather than skipping over the else jump into + * whatever was laid out next. The estimator charges 12 bytes for + * this form either way. + */ + emit(__b(__NE, ph2_ir->then_bb->elf_offset - elf_code->size)); emit(__b(__AL, ph2_ir->else_bb->elf_offset - elf_code->size)); } else emit(__b(__NE, ph2_ir->then_bb->elf_offset - elf_code->size)); @@ -444,12 +694,24 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_indirect: emit(__blx(__AL, __r8)); + + /* The pointer may name a libc function, reached through its PLT entry, + * which loads the GOT slot through r12. Restore the global stack + * pointer as OP_call does after an external call. + */ + if (dynlink) { + emit(__movw(__AL, __r8, elf_data_start)); + emit(__movt(__AL, __r8, elf_data_start)); + emit(__lw(__AL, __r12, __r8, 0)); + } return; case OP_return: if (ph2_ir->src0 == -1) emit(__mov_r(__AL, __r0, __r0)); else emit(__mov_r(__AL, __r0, rn)); + if (ph2_ir->src0_hi >= 0) + emit(__mov_r(__AL, __r1, ph2_ir->src0_hi)); /* When calling a function, the following operations are performed: * 1. push r4-r11 and lr onto the stack. @@ -468,21 +730,181 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__bx(__AL, __lr)); return; case OP_add: - emit(__add_r(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + emit(__adds_r(__AL, rd, rn, rm)); + emit(__adc_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, + ph2_ir->src1_hi)); + } else + emit(__add_r(__AL, rd, rn, rm)); return; case OP_sub: - emit(__sub_r(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + emit(__subs_r(__AL, rd, rn, rm)); + emit(__sbc_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, + ph2_ir->src1_hi)); + } else + emit(__sub_r(__AL, rd, rn, rm)); return; case OP_mul: - emit(__mul(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + /* Sum the cross terms before UMULL writes the result pair, so the + * product stays right even if that pair shares a register with an + * operand. The allocator does not hand out such a pair today, and + * this order costs nothing over the other. + */ + emit(__mul(__AL, __r8, rn, ph2_ir->src1_hi)); + emit(__mul(__AL, __r9, ph2_ir->src0_hi, rm)); + emit(__add_r(__AL, __r8, __r8, __r9)); + emit(__umull(__AL, rd, ph2_ir->dest_hi, rn, rm)); + emit(__add_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r8)); + } else + emit(__mul(__AL, rd, rn, rm)); return; case OP_div: case OP_mod: + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + bool is_unsigned = + ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned; + + /* Stack words: dividend (0,4), divisor (8,12), remainder (16,20), + * quotient (24,28), counter (32), and the incoming dividend bit + * (36). Signed pairs use (40,44) for sign masks. r8-r10 are codegen + * scratch registers. + */ + emit(__add_i(__AL, __sp, __sp, is_unsigned ? -40 : -48)); + emit(__sw(__AL, rn, __sp, 0)); + emit(__sw(__AL, ph2_ir->src0_hi, __sp, 4)); + emit(__sw(__AL, rm, __sp, 8)); + emit(__sw(__AL, ph2_ir->src1_hi, __sp, 12)); + if (!is_unsigned) { + /* x ^ sign + -sign produces its unsigned magnitude. */ + emit(__lw(__AL, __r8, __sp, 4)); + emit(__srl_amt(__AL, 0, arith_rs, __r10, __r8, 31)); + emit(__sw(__AL, __r10, __sp, 40)); + emit(__lw(__AL, __r8, __sp, 0)); + emit(__eor_r(__AL, __r8, __r8, __r10)); + emit(__rsbs_i(__AL, __r9, 0, __r10)); + emit(__adds_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 0)); + emit(__lw(__AL, __r8, __sp, 4)); + emit(__eor_r(__AL, __r8, __r8, __r10)); + emit(__zero(__r9)); + emit(__adc_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 4)); + emit(__lw(__AL, __r8, __sp, 12)); + emit(__srl_amt(__AL, 0, arith_rs, __r10, __r8, 31)); + emit(__sw(__AL, __r10, __sp, 44)); + emit(__lw(__AL, __r8, __sp, 8)); + emit(__eor_r(__AL, __r8, __r8, __r10)); + emit(__rsbs_i(__AL, __r9, 0, __r10)); + emit(__adds_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 8)); + emit(__lw(__AL, __r8, __sp, 12)); + emit(__eor_r(__AL, __r8, __r8, __r10)); + emit(__zero(__r9)); + emit(__adc_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 12)); + } + emit(__zero(__r8)); + emit(__sw(__AL, __r8, __sp, 16)); + emit(__sw(__AL, __r8, __sp, 20)); + emit(__sw(__AL, __r8, __sp, 24)); + emit(__sw(__AL, __r8, __sp, 28)); + emit(__mov_i(__AL, __r8, 64)); + emit(__sw(__AL, __r8, __sp, 32)); + + /* Shift the remainder, dividend, and quotient left by one. */ + emit(__lw(__AL, __r8, __sp, 4)); + emit(__srl_amt(__AL, 0, logic_rs, __r10, __r8, 31)); + emit(__sw(__AL, __r10, __sp, 36)); + emit(__lw(__AL, __r8, __sp, 16)); + emit(__srl_amt(__AL, 0, logic_rs, __r9, __r8, 31)); + emit(__sll_amt(__AL, 0, logic_ls, __r8, __r8, 1)); + emit(__sw(__AL, __r8, __sp, 16)); + emit(__lw(__AL, __r8, __sp, 20)); + emit(__sll_amt(__AL, 0, logic_ls, __r8, __r8, 1)); + emit(__or_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 20)); + emit(__lw(__AL, __r10, __sp, 36)); + emit(__lw(__AL, __r8, __sp, 16)); + emit(__or_r(__AL, __r8, __r8, __r10)); + emit(__sw(__AL, __r8, __sp, 16)); + emit(__lw(__AL, __r8, __sp, 0)); + emit(__srl_amt(__AL, 0, logic_rs, __r9, __r8, 31)); + emit(__sll_amt(__AL, 0, logic_ls, __r8, __r8, 1)); + emit(__sw(__AL, __r8, __sp, 0)); + emit(__lw(__AL, __r8, __sp, 4)); + emit(__sll_amt(__AL, 0, logic_ls, __r8, __r8, 1)); + emit(__or_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 4)); + emit(__lw(__AL, __r8, __sp, 24)); + emit(__srl_amt(__AL, 0, logic_rs, __r9, __r8, 31)); + emit(__sll_amt(__AL, 0, logic_ls, __r8, __r8, 1)); + emit(__sw(__AL, __r8, __sp, 24)); + emit(__lw(__AL, __r8, __sp, 28)); + emit(__sll_amt(__AL, 0, logic_ls, __r8, __r8, 1)); + emit(__or_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 28)); + + /* Subtract the divisor when the two-word remainder permits it. */ + emit(__lw(__AL, __r8, __sp, 20)); + emit(__lw(__AL, __r9, __sp, 12)); + emit(__cmp_r(__AL, __r8, __r9)); + emit(__b(__CC, 68)); + emit(__b(__HI, 20)); + emit(__lw(__AL, __r8, __sp, 16)); + emit(__lw(__AL, __r9, __sp, 8)); + emit(__cmp_r(__AL, __r8, __r9)); + emit(__b(__CC, 48)); + emit(__lw(__AL, __r8, __sp, 16)); + emit(__lw(__AL, __r9, __sp, 8)); + emit(__subs_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 16)); + emit(__lw(__AL, __r8, __sp, 20)); + emit(__lw(__AL, __r9, __sp, 12)); + emit(__sbc_r(__AL, __r8, __r8, __r9)); + emit(__sw(__AL, __r8, __sp, 20)); + emit(__lw(__AL, __r8, __sp, 24)); + emit(__add_i(__AL, __r8, __r8, 1)); + emit(__sw(__AL, __r8, __sp, 24)); + emit(__lw(__AL, __r8, __sp, 32)); + emit(__add_i(__AL, __r8, __r8, -1)); + emit(__sw(__AL, __r8, __sp, 32)); + emit(__cmp_i(__AL, __r8, 0)); + emit(__b(__NE, -220)); + + emit(__lw(__AL, rd, __sp, ph2_ir->op == OP_mod ? 16 : 24)); + emit(__lw(__AL, ph2_ir->dest_hi, __sp, + ph2_ir->op == OP_mod ? 20 : 28)); + if (!is_unsigned) { + emit(__lw(__AL, __r10, __sp, 40)); + if (ph2_ir->op == OP_div) { + emit(__lw(__AL, __r8, __sp, 44)); + emit(__eor_r(__AL, __r10, __r10, __r8)); + } + emit(__eor_r(__AL, rd, rd, __r10)); + emit(__eor_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r10)); + emit(__rsbs_i(__AL, __r8, 0, __r10)); + emit(__adds_r(__AL, rd, rd, __r8)); + emit(__zero(__r8)); + emit(__adc_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r8)); + } + emit(__add_i(__AL, __sp, __sp, is_unsigned ? 40 : 48)); + return; + } if (hard_mul_div) { if (ph2_ir->op == OP_div) - emit(__div(__AL, rd, rm, rn)); + emit(ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned + ? __udiv(__AL, rd, rm, rn) + : __div(__AL, rd, rm, rn)); else { - emit(__div(__AL, __r8, rm, rn)); + emit(ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned + ? __udiv(__AL, __r8, rm, rn) + : __div(__AL, __r8, rm, rn)); emit(__mul(__AL, __r8, rm, __r8)); emit(__sub_r(__AL, rd, rn, __r8)); } @@ -491,13 +913,25 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) interm = __r8; /* div/mod emulation: preserve the dividend and the divisor */ emit(__stmdb(__AL, 1, __sp, (1 << rn) | (1 << rm))); - /* Obtain absolute values of the dividend and divisor */ - emit(__srl_amt(__AL, 0, arith_rs, __r8, rn, 31)); - emit(__add_r(__AL, rn, rn, __r8)); - emit(__eor_r(__AL, rn, rn, __r8)); - emit(__srl_amt(__AL, 0, arith_rs, __r9, rm, 31)); - emit(__add_r(__AL, rm, rm, __r9)); - emit(__eor_r(__AL, rm, rm, __r9)); + + /* Unsigned operands already are magnitudes. Keep the signed path's + * instruction count so the fixed branch displacements remain valid. + */ + if (ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) { + emit(__zero(__r8)); + emit(__mov_r(__AL, __r8, __r8)); + emit(__mov_r(__AL, __r8, __r8)); + emit(__zero(__r9)); + emit(__mov_r(__AL, __r9, __r9)); + emit(__mov_r(__AL, __r9, __r9)); + } else { + emit(__srl_amt(__AL, 0, arith_rs, __r8, rn, 31)); + emit(__add_r(__AL, rn, rn, __r8)); + emit(__eor_r(__AL, rn, rn, __r8)); + emit(__srl_amt(__AL, 0, arith_rs, __r9, rm, 31)); + emit(__add_r(__AL, rm, rm, __r9)); + emit(__eor_r(__AL, rm, rm, __r9)); + } if (ph2_ir->op == OP_div) emit(__eor_r(__AL, __r10, __r8, __r9)); else { @@ -516,9 +950,17 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__cmp_i(__AL, rn, 0)); emit(__b(__EQ, 44)); emit(__cmp_r(__AL, rm, rn)); - emit(__sll_amt(__CC, 0, logic_ls, rm, rm, 1)); - emit(__sll_amt(__CC, 0, logic_ls, __r9, __r9, 1)); - emit(__b(__CC, -12)); + + /* Scale until the divisor reaches the dividend or the *next* shift + * would overflow. A divisor such as 0xc0000000 is still valid for a + * 0xffffffff dividend; testing carry after shifting would lose it. Test + * bit 31 before the shift instead. + */ + emit(__cmp_i(__CC, rm, 0x80000000)); + emit(__b(__CS, 16)); + emit(__sll_amt(__AL, 0, logic_ls, rm, rm, 1)); + emit(__sll_amt(__AL, 0, logic_ls, __r9, __r9, 1)); + emit(__b(__AL, -20)); emit(__cmp_r(__AL, rn, rm)); emit(__sub_r(__CS, rn, rn, rm)); emit(__add_r(__CS, __r8, __r8, __r9)); @@ -542,10 +984,56 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__rsb_i(__NE, rd, 0, rd)); return; case OP_lshift: + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) { + emit(__cmp_i(__AL, rm, 32)); + emit(__b(__CS, 32)); + emit(__mov_r(__AL, __r8, rn)); + emit(__sll(__AL, rd, rn, rm)); + emit(__sll(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, rm)); + emit(__rsb_i(__AL, __r9, 32, rm)); + emit(__srl(__AL, __r8, __r8, __r9)); + emit(__or_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r8)); + emit(__b(__AL, 16)); + emit(__add_i(__AL, __r9, rm, -32)); + emit(__sll(__AL, ph2_ir->dest_hi, rn, __r9)); + emit(__zero(rd)); + return; + } emit(__sll(__AL, rd, rn, rm)); return; case OP_rshift: - emit(__sra(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) { + int shift_kind = ph2_ir->src0_is_unsigned ? logic_rs : arith_rs; + + emit(__cmp_i(__AL, rm, 32)); + emit(__b(__CS, 32)); + emit(__mov_r(__AL, __r8, ph2_ir->src0_hi)); + + /* The sign lives in the high word. The low word's vacated top bits + * are the high word's low bits, ORed in below, so shifting in the + * low word's own bit 31 would set bits the OR cannot clear. + */ + emit(__srl(__AL, rd, rn, rm)); + emit(__rsb_i(__AL, __r9, 32, rm)); + emit(__sll(__AL, __r8, __r8, __r9)); + emit(__or_r(__AL, rd, rd, __r8)); + emit(shift_kind == logic_rs + ? __srl(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, rm) + : __sra(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, rm)); + emit(__b(__AL, 16)); + emit(__add_i(__AL, __r9, rm, -32)); + emit(shift_kind == logic_rs + ? __srl(__AL, rd, ph2_ir->src0_hi, __r9) + : __sra(__AL, rd, ph2_ir->src0_hi, __r9)); + if (shift_kind == logic_rs) + emit(__zero(ph2_ir->dest_hi)); + else + emit(__srl_amt(__AL, 0, arith_rs, ph2_ir->dest_hi, + ph2_ir->src0_hi, 31)); + return; + } + emit(ph2_ir->src0_is_unsigned ? __srl(__AL, rd, rn, rm) + : __sra(__AL, rd, rn, rm)); return; case OP_eq: case OP_neq: @@ -553,33 +1041,89 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_lt: case OP_geq: case OP_leq: + /* Wide ordering subtracts the pairs, the low words with CMP and the + * high words with SBCS, so the flags describe the whole 64-bit + * difference: N and V order signed operands and C unsigned ones. The + * low words are unsigned whatever the type, which a signed test of them + * got wrong. Swapping the operands turns > and <= into < and >=. The + * result register is written only once the flags are set, so it may be + * any operand register. Equality is left to the word-by-word sequence + * below, since Z is not valid after SBCS. + */ + if (ph2_ir->op != OP_eq && ph2_ir->op != OP_neq && + ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { + bool unsigned_cmp = + ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned; + bool swap = ph2_ir->op == OP_gt || ph2_ir->op == OP_leq; + bool below = ph2_ir->op == OP_lt || ph2_ir->op == OP_gt; + arm_cond_t cond = below ? (unsigned_cmp ? __CC : __LT) + : (unsigned_cmp ? __CS : __GE); + + emit(__cmp_r(__AL, swap ? rm : rn, swap ? rn : rm)); + emit(__sbcs_r(__AL, __r8, swap ? ph2_ir->src1_hi : ph2_ir->src0_hi, + swap ? ph2_ir->src0_hi : ph2_ir->src1_hi)); + emit(__zero(rd)); + emit(__mov_i(cond, rd, 1)); + return; + } emit(__cmp_r(__AL, rn, rm)); + if ((ph2_ir->op == OP_eq || ph2_ir->op == OP_neq) && + ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) + emit(__cmp_r(__EQ, ph2_ir->src0_hi, ph2_ir->src1_hi)); emit(__zero(rd)); - emit(__mov_i(arm_get_cond(ph2_ir->op), rd, 1)); + emit(__mov_i(arm_get_cond(ph2_ir->op, ph2_ir->src0_is_unsigned || + ph2_ir->src1_is_unsigned), + rd, 1)); return; case OP_negate: - emit(__rsb_i(__AL, rd, 0, rn)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) { + emit(__rsbs_i(__AL, rd, 0, rn)); + emit(__rsc_i(__AL, ph2_ir->dest_hi, 0, ph2_ir->src0_hi)); + } else + emit(__rsb_i(__AL, rd, 0, rn)); return; case OP_bit_not: emit(__mvn_r(__AL, rd, rn)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) + emit(__mvn_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi)); return; case OP_bit_and: emit(__and_r(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + emit(__and_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, + ph2_ir->src1_hi)); return; case OP_bit_or: emit(__or_r(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + emit(__or_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, + ph2_ir->src1_hi)); return; case OP_bit_xor: emit(__eor_r(__AL, rd, rn, rm)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + emit(__eor_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi, + ph2_ir->src1_hi)); return; case OP_log_not: - emit(__cmp_i(__AL, rn, 0)); + if (ph2_ir->src0_hi >= 0) { + emit(__or_r(__AL, __r8, rn, ph2_ir->src0_hi)); + emit(__cmp_i(__AL, __r8, 0)); + } else + emit(__cmp_i(__AL, rn, 0)); emit(__mov_i(__NE, rd, 0)); emit(__mov_i(__EQ, rd, 1)); return; case OP_trunc: - /* Narrowing keeps the sign: there are no unsigned types. */ - if (rm == 1) { + if (ph2_ir->is_unsigned && (rm == 1 || rm == 2)) { + int shift = rm == 1 ? 24 : 16; + + emit(__sll_amt(__AL, 0, logic_ls, rd, rn, shift)); + emit(__srl_amt(__AL, 0, logic_rs, rd, rd, shift)); + } else if (rm == 1) { emit(__sxtb(__AL, rd, rn, 0)); } else if (rm == 2) { emit(__sxth(__AL, rd, rn, 0)); @@ -592,6 +1136,33 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_sign_ext: { /* Decode source size from upper 16 bits */ int source_size = (rm >> 16) & 0xFFFF; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi < 0) { + if (source_size == 1) { + if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) { + emit(__sll_amt(__AL, 0, logic_ls, rd, rn, 24)); + emit(__srl_amt(__AL, 0, logic_rs, rd, rd, 24)); + } else + emit(__sxtb(__AL, rd, rn, 0)); + } else if (source_size == 2) { + if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) { + emit(__sll_amt(__AL, 0, logic_ls, rd, rn, 16)); + emit(__srl_amt(__AL, 0, logic_rs, rd, rd, 16)); + } else + emit(__sxth(__AL, rd, rn, 0)); + } else + emit(__mov_r(__AL, rd, rn)); + if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) + emit(__zero(ph2_ir->dest_hi)); + else + emit(__srl_amt(__AL, 0, arith_rs, ph2_ir->dest_hi, rd, 31)); + return; + } + if (ph2_ir->src0_is_unsigned) { + int shift = source_size == 2 ? 16 : 24; + emit(__sll_amt(__AL, 0, logic_ls, rd, rn, shift)); + emit(__srl_amt(__AL, 0, logic_rs, rd, rd, shift)); + return; + } if (source_size == 2) { emit(__sxth(__AL, rd, rn, 0)); } else { @@ -601,8 +1172,20 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; case OP_cast: - /* Generic cast operation - for now, just move the value */ + /* A 32-bit source widened to a direct wide scalar needs an explicit + * high word. Copy an existing pair unchanged, otherwise sign- or + * zero-extend the source according to its original type. + */ emit(__mov_r(__AL, rd, rn)); + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src0_hi >= 0) { + if (ph2_ir->dest_hi != ph2_ir->src0_hi) + emit(__mov_r(__AL, ph2_ir->dest_hi, ph2_ir->src0_hi)); + } else if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) + emit(__zero(ph2_ir->dest_hi)); + else + emit(__srl_amt(__AL, 0, arith_rs, ph2_ir->dest_hi, rn, 31)); + } return; default: fatal("Unknown opcode"); @@ -678,6 +1261,26 @@ void code_generate(void) emit(__movt(__AL, __r8, elf_data_start)); emit(__sw(__AL, __r12, __r8, 0)); + /* Clearing the global stack is the dynamic build's job alone. A static + * image is the first thing to run, so the stack below the entry SP is + * untouched anonymous memory and already reads as zero. A dynamic one has + * had the loader and glibc's startup run over that same memory first, so a + * global with no initializer would otherwise begin life holding their + * leftovers. The loop stores zero from the top word down; 'ofs' is a + * nonzero multiple of four here. No libc call is involved, so this holds + * with --no-libc as well. + */ + if (dynlink) { + emit(__movw(__AL, __r8, ofs)); + emit(__movt(__AL, __r8, ofs)); + emit(__mov_i(__AL, __r0, 0)); + emit(__add_i(__AL, __r8, __r8, -4)); + emit(__add_r(__AL, __r3, __r12, __r8)); + emit(__sw(__AL, __r0, __r3, 0)); + emit(__cmp_i(__AL, __r8, 0)); + emit(__b(__NE, -16)); + } + if (!dynlink) { /* Jump directly to the main preparation and then execute the main * function. diff --git a/src/arm.c b/src/arm.c index 6bc83555..ee101317 100644 --- a/src/arm.c +++ b/src/arm.c @@ -35,6 +35,9 @@ typedef enum { arm_sub = 2, arm_rsb = 3, arm_add = 4, + arm_adc = 5, + arm_sbc = 6, + arm_rsc = 7, arm_ldm = 9, arm_teq = 9, arm_cmp = 10, @@ -52,6 +55,7 @@ typedef enum { __NE = 1, /* Not equal */ __CS = 2, /* Unsigned higher or same */ __CC = 3, /* Unsigned lower */ + __HI = 8, /* Unsigned higher */ __LS = 9, /* Unsigned lower or same */ __GE = 10, /* Signed greater than or equal */ __LT = 11, /* Signed less than */ @@ -87,7 +91,7 @@ typedef enum { rotat_rs = 3 /* Rotate right shift */ } shift_type; -arm_cond_t arm_get_cond(opcode_t op) +arm_cond_t arm_get_cond(opcode_t op, bool is_unsigned) { switch (op) { case OP_eq: @@ -95,13 +99,13 @@ arm_cond_t arm_get_cond(opcode_t op) case OP_neq: return __NE; case OP_lt: - return __LT; + return is_unsigned ? __CC : __LT; case OP_geq: - return __GE; + return is_unsigned ? __CS : __GE; case OP_gt: - return __GT; + return is_unsigned ? __HI : __GT; case OP_leq: - return __LE; + return is_unsigned ? __LS : __LE; default: fatal("Unsupported condition IR opcode"); } @@ -125,6 +129,15 @@ int arm_encode(arm_cond_t cond, int opcode, int rn, int rd, int op2) return (cond << 28) + (opcode << 20) + (rn << 16) + (rd << 12) + op2; } +/* ARM A8.8.247: unsigned 32x32 -> 64 multiply. RdLo/RdHi receive the complete + * product, which is the primitive paired wide multiplication needs. + */ +int __umull(arm_cond_t cond, arm_reg rdlo, arm_reg rdhi, arm_reg rm, arm_reg rs) +{ + return (cond << 28) + 0x00800090 + (rdhi << 16) + (rdlo << 12) + (rs << 8) + + rm; +} + int __svc(void) { return arm_encode(__AL, 240, 0, 0, 0); @@ -133,19 +146,25 @@ int __svc(void) int __mov(arm_cond_t cond, int io, int opcode, int s, int rn, int rd, int op2) { int shift = 0; - if (op2 > 255) { + + /* ARM's immediate is a rotated 32-bit bit pattern. Treat it as unsigned + * while finding that rotation: an `int` carrying 0x80000000 must not look + * like a small negative value and be encoded as zero. + */ + unsigned int encoded_op2 = op2; + if (encoded_op2 > 255) { shift = 16; /* full rotation */ - while ((op2 & 3) == 0) { + while ((encoded_op2 & 3) == 0) { /* we can shift by two bits */ - op2 >>= 2; + encoded_op2 >>= 2; shift -= 1; } - if (op2 > 255) + if (encoded_op2 > 255) /* value spans more than 8 bits */ fatal("Unable to represent value"); } return arm_encode(cond, s + (opcode << 1) + (io << 5), rn, rd, - (shift << 8) + (op2 & 255)); + (shift << 8) + (encoded_op2 & 255)); } int __and_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg rm) @@ -248,11 +267,46 @@ int __add_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) return __mov(cond, 0, arm_add, 0, rs, rd, ro); } +int __adds_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) +{ + return __mov(cond, 0, arm_add, 1, rs, rd, ro); +} + +int __adc_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) +{ + return __mov(cond, 0, arm_adc, 0, rs, rd, ro); +} + int __sub_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) { return __mov(cond, 0, arm_sub, 0, rs, rd, ro); } +int __subs_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) +{ + return __mov(cond, 0, arm_sub, 1, rs, rd, ro); +} + +int __sbc_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) +{ + return __mov(cond, 0, arm_sbc, 0, rs, rd, ro); +} + +int __sbcs_r(arm_cond_t cond, arm_reg rd, arm_reg rs, arm_reg ro) +{ + return __mov(cond, 0, arm_sbc, 1, rs, rd, ro); +} + +int __rsbs_i(arm_cond_t cond, arm_reg rd, int imm, arm_reg rn) +{ + return __mov(cond, 1, arm_rsb, 1, rn, rd, imm); +} + +int __rsc_i(arm_cond_t cond, arm_reg rd, int imm, arm_reg rn) +{ + return __mov(cond, 1, arm_rsc, 0, rn, rd, imm); +} + int __zero(int rd) { return __mov_i(__AL, rd, 0); @@ -317,6 +371,26 @@ int __lb(arm_cond_t cond, arm_reg rd, arm_reg rn, int ofs) return arm_transfer(cond, 1, 1, rn, rd, ofs); } +/* ARM signed byte load (LDRSB). Its immediate-offset encoding shares the + * halfword-transfer layout, but uses 1101 in bits [7:4] rather than LDRH's 1011 + * or LDRSH's 1111. + */ +int __lsb(arm_cond_t cond, arm_reg rd, arm_reg rn, int ofs) +{ + int opcode = 16 + 8 + 4 + 1; + + if (ofs < 0) { + opcode -= 8; + ofs = -ofs; + } + if (ofs > 255) + fatal("Signed byte offset too large"); + + int imm4h = ((ofs >> 4) & 0xF) << 8; + int imm4l = ofs & 0xF; + return arm_encode(cond, opcode, rn, rd, imm4h | 0xD0 | imm4l); +} + int __sw(arm_cond_t cond, arm_reg rd, arm_reg rn, int ofs) { return arm_transfer(cond, 0, 4, rn, rd, ofs); @@ -333,6 +407,12 @@ int __lh(arm_cond_t cond, arm_reg rd, arm_reg rn, int ofs) return arm_halfword_transfer(cond, 1, rn, rd, ofs, 1); } +/* ARM unsigned halfword load (LDRH). */ +int __lhu(arm_cond_t cond, arm_reg rd, arm_reg rn, int ofs) +{ + return arm_halfword_transfer(cond, 1, rn, rd, ofs, 0); +} + /* ARM halfword store (STRH) */ int __sh(arm_cond_t cond, arm_reg rd, arm_reg rn, int ofs) { @@ -392,6 +472,11 @@ int __div(arm_cond_t cond, arm_reg rd, arm_reg r1, arm_reg r2) return arm_encode(cond, 113, rd, 15, (r1 << 8) + 16 + r2); } +int __udiv(arm_cond_t cond, arm_reg rd, arm_reg r1, arm_reg r2) +{ + return arm_encode(cond, 115, rd, 15, (r1 << 8) + 16 + r2); +} + int __rsb_i(arm_cond_t cond, arm_reg rd, int imm, arm_reg rn) { return __mov(cond, 1, arm_rsb, 0, rn, rd, imm); diff --git a/src/arm64-codegen.c b/src/arm64-codegen.c index e1589a54..a66246d7 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -2,17 +2,14 @@ * AArch64 Linux code generator. The allocator's virtual registers map to * x0..x7, x20..x22; x16/x17 are reserved scratch and x19 is the global base. */ +#include "arm64.c" #include "defs.h" #include "globals.c" -#define A64_SP 31 -#define A64_ZR 31 - -/* IP0, the linker's own scratch register: free for a code generator to use - * between instructions, and never allocated to a value. +/* IP0, the linker's own scratch register, is free for a code generator to use + * between instructions and never allocated to a value. x19 is the base of the + * synthetic global frame, held for the life of the program. */ -#define A64_IP0 16 -/* Base of the synthetic global frame, held for the life of the program. */ #define A64_GP 19 /* AAPCS64 keeps SP 16-byte aligned, and the prologue saves five registers in @@ -31,7 +28,7 @@ void emit(int insn) } void a64_mov(int d, int n) { - emit(0xaa0003e0 | (n << 16) | d); + emit(a64_mov_insn(true, d, n)); } /* SP is not a general register in logical instructions: ORR would read ZR. ADD @@ -39,7 +36,7 @@ void a64_mov(int d, int n) */ void a64_mov_sp(int d) { - emit(0x910003e0 | (A64_SP << 5) | d); + emit(a64_add_imm_insn(true, d, A64_SP, 0)); } void a64_mov_imm(int d, int v) { @@ -49,24 +46,78 @@ void a64_mov_imm(int d, int v) * supplies the upper one bits while MOVK fills the second halfword. */ if (v < 0) - emit(0x92800000 | (((~v) & 0xffff) << 5) | d); + emit(a64_movn_insn(true, d, ~v, 0)); else - emit(0xd2800000 | ((v & 0xffff) << 5) | d); - emit(0xf2800000 | (((v >> 16) & 0xffff) << 5) | d | (1 << 21)); + emit(a64_movz_insn(true, d, v, 0)); + emit(a64_movk_insn(true, d, v >> 16, 1)); +} + +/* A 32-bit unsigned constant has zeroes above bit 31 when it later widens to an + * X-register scalar. The signed helper intentionally uses MOVN for a negative + * int, so keep this spelling separate. + */ +void a64_mov_imm_unsigned(int d, int v) +{ + unsigned int value = v; + + emit(a64_movz_insn(true, d, value, 0)); + emit(a64_movk_insn(true, d, value >> 16, 1)); +} + +/* Phase-2 constants retain their upper word in src1. Materialise all four + * halfwords for an eight-byte scalar rather than silently discarding it in the + * ordinary int helper above. + */ +void a64_mov_imm_wide(int d, int lo, int hi) +{ + unsigned int low = lo, high = hi; + + emit(a64_movz_insn(true, d, low, 0)); + emit(a64_movk_insn(true, d, low >> 16, 1)); + emit(a64_movk_insn(true, d, high, 2)); + emit(a64_movk_insn(true, d, high >> 16, 3)); } + +/* Rd = Rn / Rm, unsigned when either operand is. */ +int a64_div_insn(ph2_ir_t *p, int d, int n, int m) +{ + bool sf = p->size_bytes == 8; + + if (p->src0_is_unsigned || p->src1_is_unsigned) + return a64_udiv_insn(sf, d, n, m); + return a64_sdiv_insn(sf, d, n, m); +} + +/* Rd = Rn >> Rm, logical for an unsigned left operand. */ +int a64_rshift_insn(ph2_ir_t *p, int d, int n, int m) +{ + bool sf = p->size_bytes == 8; + + if (p->src0_is_unsigned) + return a64_lsrv_insn(sf, d, n, m); + return a64_asrv_insn(sf, d, n, m); +} + +/* SP cannot be an operand of the shifted-register form, so an addition or + * subtraction involving it takes the extended-register form instead. + */ void a64_add(int d, int n, int m) { - int op = (d == A64_SP || n == A64_SP) ? 0x8b206000 : 0x8b000000; - emit(op | (m << 16) | (n << 5) | d); + if (d == A64_SP || n == A64_SP) + emit(a64_add_ext_insn(d, n, m, A64_EXT_UXTX)); + else + emit(a64_add_reg_insn(true, d, n, m)); } void a64_sub(int d, int n, int m) { - int op = (d == A64_SP || n == A64_SP) ? 0xcb206000 : 0xcb000000; - emit(op | (m << 16) | (n << 5) | d); + if (d == A64_SP || n == A64_SP) + emit(a64_sub_ext_insn(d, n, m, A64_EXT_UXTX)); + else + emit(a64_sub_reg_insn(true, d, n, m)); } void a64_sxtw(int d, int n) { - emit(0x93407c00 | (n << 5) | d); + emit(a64_sext_insn(d, n, 32)); } /* Xd = Xn +/- sign_extend(Wm). The extended-register forms fold the widening of @@ -75,74 +126,52 @@ void a64_sxtw(int d, int n) */ void a64_add_sxtw(int d, int n, int m) { - emit(0x8b20c000 | (m << 16) | (n << 5) | d); + emit(a64_add_ext_insn(d, n, m, A64_EXT_SXTW)); } void a64_sub_sxtw(int d, int n, int m) { - emit(0xcb20c000 | (m << 16) | (n << 5) | d); + emit(a64_sub_ext_insn(d, n, m, A64_EXT_SXTW)); } /* Sign-extend the low @size bytes of Xn into Xd. Always one instruction, so * update_elf_offset()'s default estimate stays correct. */ -void a64_extend(int d, int n, int size) +void a64_extend(int d, int n, int size, bool is_unsigned) { if (size == 1) - emit(0x93401c00 | (n << 5) | d); + emit(is_unsigned ? a64_zext_insn(d, n, 8) : a64_sext_insn(d, n, 8)); else if (size == 2) - emit(0x93403c00 | (n << 5) | d); - else if (size == 4) - a64_sxtw(d, n); - else + emit(is_unsigned ? a64_zext_insn(d, n, 16) : a64_sext_insn(d, n, 16)); + else if (size == 4) { + if (is_unsigned) + emit(a64_zext_insn(d, n, 32)); + else + a64_sxtw(d, n); + } else a64_mov(d, n); } +/* Which load reads @p's value. A narrow unsigned one has to arrive + * zero-extended: its register feeds 64-bit address arithmetic as it stands, and + * there a sign-extended unsigned char of 200 indexes table[-56]. + */ +int a64_load_access(ph2_ir_t *p) +{ + return p->is_unsigned ? A64_LOAD_ZEXT : A64_LOAD; +} + /* AArch64's ordinary LDR/STR immediate is scaled by the access width. The * compiler deliberately supports packed C layouts, so structure members are * often not naturally aligned (func_t.bbs, for example). Such offsets must use * the byte-addressed LDUR/STUR form; rounding them down corrupts adjacent * fields during self-hosting. */ -int a64_mem_count(int size, int ofs) -{ - if (ofs >= 0 && ofs <= 4095 * size && !(ofs % size)) - return 1; - if (ofs >= -256 && ofs <= 255) - return 1; - return 4; /* MOVZ/MOVK + ADD + [base] access */ -} - -/* Base opcode of the unscaled load/store of @size bytes. Size lives in bits - * 31:30 and the operation in 23:22, where a load picks the sign-extending form - * for every width narrower than a doubleword -- values sit sign-extended in - * their X register, so a narrow load must widen the same way whichever - * addressing form carries it. The scaled form is this plus bit 24, which is why - * one table serves both and they can no longer disagree. - */ -int a64_mem_op(int load, int size) +void a64_mem(int access, int size, int rt, int rn, int ofs) { - int sf; - if (size == 8) - sf = 3; - else if (size == 4) - sf = 2; - else if (size == 2) - sf = 1; - else if (size == 1) - sf = 0; - else { + if (size != 1 && size != 2 && size != 4 && size != 8) fatal("unsupported arm64 access width"); - return 0; - } - if (!load) - return 0x38000000 | (sf << 30); - return 0x38000000 | (sf << 30) | ((size == 8 ? 1 : 2) << 22); -} -void a64_mem(int load, int size, int rt, int rn, int ofs) -{ - int op = a64_mem_op(load, size); if (ofs >= -256 && ofs <= 255 && (ofs < 0 || ofs % size)) { - emit(op | ((ofs & 0x1ff) << 12) | (rn << 5) | rt); /* LDUR/STUR */ + emit(a64_mem_unscaled_insn(access, size, rt, rn, ofs)); return; } if (ofs < 0 || ofs > 4095 * size || ofs % size) { @@ -151,7 +180,7 @@ void a64_mem(int load, int size, int rt, int rn, int ofs) rn = A64_IP0; ofs = 0; } - emit(op | (1 << 24) | ((ofs / size) << 10) | (rn << 5) | rt); + emit(a64_mem_scaled_insn(access, size, rt, rn, ofs)); } /* Masking an out-of-range displacement silently branches somewhere else. The @@ -174,26 +203,25 @@ void a64_cbnz(bool wide, int rt, int target) { int disp = (target - elf_code->size) / 4; a64_check_disp(disp, 19, "arm64 conditional branch out of range"); - emit((wide ? 0xb5000000 : 0x35000000) | ((disp & 0x7ffff) << 5) | rt); + emit(a64_cbnz_insn(wide, rt, disp)); } void a64_b(int target) { int d = (target - elf_code->size) / 4; a64_check_disp(d, 26, "arm64 branch out of range"); - emit(0x14000000 | (d & 0x3ffffff)); + emit(a64_b_insn(d)); } void a64_bl_addr(int target) { int d = (target - (elf_code_start + elf_code->size)) / 4; a64_check_disp(d, 26, "arm64 call out of range"); - emit(0x94000000 | (d & 0x3ffffff)); + emit(a64_bl_insn(d)); } int a64_adrp_insn(int d, int pc, int target) { int pages = (target >> 12) - (pc >> 12); a64_check_disp(pages, 21, "arm64 ADRP target out of range"); - return 0x90000000 | ((pages & 3) << 29) | (((pages >> 2) & 0x7ffff) << 5) | - d; + return a64_adrp_pages_insn(d, pages); } /* Which operand of an address expression is the int index that must widen: 0 @@ -214,24 +242,24 @@ int a64_ptr_index(ph2_ir_t *p) */ bool a64_cmp_wide(ph2_ir_t *p) { - return p->src0_is_pointer || p->src1_is_pointer; + return p->size_bytes == 8 || p->src0_is_pointer || p->src1_is_pointer; } -int a64_cond(opcode_t op) +a64_cond_t a64_cond(opcode_t op, bool is_unsigned) { switch (op) { case OP_eq: - return 0; + return A64_EQ; case OP_neq: - return 1; + return A64_NE; case OP_geq: - return 10; + return is_unsigned ? A64_HS : A64_GE; case OP_lt: - return 11; + return is_unsigned ? A64_LO : A64_LT; case OP_gt: - return 12; + return is_unsigned ? A64_HI : A64_GT; default: - return 13; + return is_unsigned ? A64_LS : A64_LE; } } @@ -251,7 +279,7 @@ void update_elf_offset(ph2_ir_t *ir) case OP_load_constant: case OP_load_data_address: case OP_load_rodata_address: - n = 2; + n = ir->op == OP_load_constant && ir->size_bytes == 8 ? 4 : 2; break; case OP_address_of: case OP_global_address_of: @@ -298,9 +326,9 @@ void cfg_flatten(void) /* Entry sequence lengths, which the block offsets below start after. * Static: 12 instructions of setup, then the 9-instruction __syscall - * helper, so 21 in all. Dynamic: 23, having no __syscall helper but saving + * helper, so 21 in all. Dynamic: 25, having no __syscall helper but saving * the original stack pointer for __libc_start_main's stack_end argument, - * spilling argc/argv, and calling memset to clear the frame. + * spilling argc/argv, and clearing the frame with an inline loop. */ f = find_func("__syscall"); if (f && f->bbs) { @@ -310,7 +338,7 @@ void cfg_flatten(void) f->bbs->elf_offset = 12 * 4; } if (dynlink) - elf_offset = 23 * 4; + elf_offset = 25 * 4; else elf_offset = 21 * 4; GLOBAL_FUNC->bbs->elf_offset = elf_offset; @@ -378,32 +406,40 @@ void emit_ph2_ir(ph2_ir_t *p) switch (p->op) { case OP_define: { bool reload_global_base = dynlink && !strcmp(p->func_name, "main"); - emit(0xa9bf7bfd); /* stp x29, x30, [sp, #-16]! */ - emit(0x910003fd); /* mov x29, sp */ - emit(0xa9bf57f4); /* stp x20, x21, [sp, #-16]! */ + + fatal_function_context = p->func_name; + emit(a64_stp_pre_insn(A64_FP, A64_LR, A64_SP, -16)); + emit(a64_add_imm_insn(true, A64_FP, A64_SP, 0)); + emit(a64_stp_pre_insn(20, 21, A64_SP, -16)); + /* x19 holds the synthetic global-frame base, but AAPCS64 makes it * callee-saved and glibc calls into this code at main. Saving it * alongside x22 costs nothing: the slot was half empty anyway. */ - emit(0xa9bf4ff6); /* stp x22, x19, [sp, #-16]! */ + emit(a64_stp_pre_insn(22, A64_GP, A64_SP, -16)); a64_mov_imm(A64_IP0, ALIGN_UP(p->src0, A64_STACK_ALIGN)); a64_sub(A64_SP, A64_SP, A64_IP0); /* Reload x19 rather than trust what called us: glibc enters at main, * and AAPCS64 lets everything in between clobber a callee-saved - * register it has saved. The leading 1 is a64_mem()'s load selector, so - * this reads the base back from the word parked at elf_data_start. The - * frame itself is allocated once in code_generate(), which is also - * where the question of clearing it is settled. + * register it has saved. A64_LOAD is a64_mem()'s load selector, so this + * reads the base back from the word parked at elf_data_start. The frame + * itself is allocated once in code_generate(), which is also where the + * question of clearing it is settled. */ if (reload_global_base) { a64_mov_imm(A64_IP0, elf_data_start); - a64_mem(1, 8, A64_GP, A64_IP0, 0); + a64_mem(A64_LOAD, 8, A64_GP, A64_IP0, 0); } return; } case OP_load_constant: - a64_mov_imm(d, p->src0); + if (p->size_bytes == 8) + a64_mov_imm_wide(d, p->src0, p->src1); + else if (p->is_unsigned) + a64_mov_imm_unsigned(d, p->src0); + else + a64_mov_imm(d, p->src0); return; case OP_assign: if (d != n) @@ -418,22 +454,22 @@ void emit_ph2_ir(ph2_ir_t *p) a64_add(d, A64_GP, A64_IP0); return; case OP_load: - a64_mem(1, p->size_bytes, d, A64_SP, p->src0); + a64_mem(a64_load_access(p), p->size_bytes, d, A64_SP, p->src0); return; case OP_global_load: - a64_mem(1, p->size_bytes, d, A64_GP, p->src0); + a64_mem(a64_load_access(p), p->size_bytes, d, A64_GP, p->src0); return; case OP_store: - a64_mem(0, p->size_bytes, n, A64_SP, p->src1); + a64_mem(A64_STORE, p->size_bytes, n, A64_SP, p->src1); return; case OP_global_store: - a64_mem(0, p->size_bytes, n, A64_GP, p->src1); + a64_mem(A64_STORE, p->size_bytes, n, A64_GP, p->src1); return; case OP_read: - a64_mem(1, p->src1, d, n, 0); + a64_mem(a64_load_access(p), p->src1, d, n, 0); return; case OP_write: - a64_mem(0, p->dest, m, n, 0); + a64_mem(A64_STORE, p->dest, m, n, 0); return; case OP_add: /* Index expressions are int-valued, so the index has to widen before it @@ -463,38 +499,38 @@ void emit_ph2_ir(ph2_ir_t *p) a64_sub(d, n, m); return; case OP_mul: - emit(0x1b007c00 | (m << 16) | (n << 5) | d); + emit(a64_mul_insn(p->size_bytes == 8, d, n, m)); return; case OP_div: - emit(0x1ac00c00 | (m << 16) | (n << 5) | d); + emit(a64_div_insn(p, d, n, m)); return; case OP_mod: /* d = n % m. Do not put the quotient in d: register coalescing may make * d alias n, losing the minuend before MSUB reads it. */ - emit(0x1ac00c00 | (m << 16) | (n << 5) | A64_IP0); - emit(0x1b008000 | (m << 16) | (n << 10) | (A64_IP0 << 5) | d); + emit(a64_div_insn(p, A64_IP0, n, m)); + emit(a64_msub_insn(p->size_bytes == 8, d, A64_IP0, m, n)); return; case OP_lshift: - emit(0x1ac02000 | (m << 16) | (n << 5) | d); + emit(a64_lslv_insn(p->size_bytes == 8, d, n, m)); return; case OP_rshift: - emit(0x1ac02800 | (m << 16) | (n << 5) | d); + emit(a64_rshift_insn(p, d, n, m)); return; case OP_bit_and: - emit(0x0a000000 | (m << 16) | (n << 5) | d); + emit(a64_and_reg_insn(p->size_bytes == 8, d, n, m)); return; case OP_bit_or: - emit(0x2a000000 | (m << 16) | (n << 5) | d); + emit(a64_orr_reg_insn(p->size_bytes == 8, d, n, m)); return; case OP_bit_xor: - emit(0x4a000000 | (m << 16) | (n << 5) | d); + emit(a64_eor_reg_insn(p->size_bytes == 8, d, n, m)); return; case OP_negate: - emit(0x4b0003e0 | (n << 16) | d); + emit(a64_neg_insn(p->size_bytes == 8, d, n)); return; case OP_bit_not: - emit(0x2a2003e0 | (n << 16) | d); + emit(a64_mvn_insn(p->size_bytes == 8, d, n)); return; case OP_eq: case OP_neq: @@ -502,21 +538,21 @@ void emit_ph2_ir(ph2_ir_t *p) case OP_lt: case OP_geq: case OP_leq: - emit((a64_cmp_wide(p) ? 0xeb00001f : 0x6b00001f) | (m << 16) | - (n << 5)); - emit((a64_cmp_wide(p) ? 0x9a9f07e0 : 0x1a9f07e0) | - ((a64_cond(p->op) ^ 1) << 12) | d); + emit(a64_cmp_reg_insn(a64_cmp_wide(p), n, m)); + emit(a64_cset_insn( + a64_cmp_wide(p), d, + a64_cond(p->op, p->src0_is_unsigned || p->src1_is_unsigned))); return; /* Width follows the operand, exactly as the comparisons above do. A pointer - * must be tested whole, or one whose low word happens to be zero reads as - * null. An int must not be: multiply, divide, shift and the bitwise - * operations all use the W forms, which leave the upper half zeroed rather - * than sign-extended, so only the low word is the value. + * or a long long must be tested whole, or one whose low word happens to be + * zero reads as zero. An int must not be: multiply, divide, shift and the + * bitwise operations all use the W forms, which leave the upper half zeroed + * rather than sign-extended, so only the low word is the value. */ case OP_log_not: - emit((p->src0_is_pointer ? 0xf100001f : 0x7100001f) | (n << 5)); - emit(0x1a9f07e0 | (1 << 12) | d); + emit(a64_cmp_imm_insn(p->src0_is_pointer || p->size_bytes == 8, n, 0)); + emit(a64_cset_insn(false, d, A64_EQ)); return; /* OP_trunc's src1 is the target width; OP_sign_ext's packs the source width @@ -524,7 +560,7 @@ void emit_ph2_ir(ph2_ir_t *p) * made every promotion a plain move. */ case OP_trunc: - a64_extend(d, n, p->src1); + a64_extend(d, n, p->src1, p->is_unsigned); return; case OP_sign_ext: { int src_size = (p->src1 >> 16) & 0xffff, dst_size = p->src1 & 0xffff; @@ -532,17 +568,18 @@ void emit_ph2_ir(ph2_ir_t *p) /* Widening to a pointer: the register already holds a full address, so * extending it from 32 bits would discard the upper half. */ - if (dst_size == PTR_SIZE) + if (dst_size == PTR_SIZE && p->is_pointer) a64_mov(d, n); else - a64_extend(d, n, src_size); + a64_extend(d, n, src_size, p->src0_is_unsigned); return; } case OP_cast: a64_mov(d, n); return; case OP_branch: - a64_cbnz(p->src0_is_pointer, n, p->then_bb->elf_offset); + a64_cbnz(p->src0_is_pointer || p->size_bytes == 8, n, + p->then_bb->elf_offset); a64_b(p->else_bb->elf_offset); return; case OP_jump: @@ -557,8 +594,7 @@ void emit_ph2_ir(ph2_ir_t *p) fatal("arm64 external call requires --dynlink"); a64_bl_addr(dynamic_sections.elf_plt_start + f->plt_offset); } else - emit(0x94000000 | - (((f->bbs->elf_offset - elf_code->size) / 4) & 0x3ffffff)); + emit(a64_bl_insn((f->bbs->elf_offset - elf_code->size) / 4)); return; } case OP_load_data_address: @@ -583,24 +619,24 @@ void emit_ph2_ir(ph2_ir_t *p) } else target = elf_code_start + f->bbs->elf_offset; a64_mov_imm(A64_IP0, target); - a64_mem(0, 8, A64_IP0, n, 0); + a64_mem(A64_STORE, 8, A64_IP0, n, 0); return; } case OP_load_func: a64_mov(A64_IP0, n); return; case OP_indirect: - emit(0xd63f0200); + emit(a64_blr_insn(A64_IP0)); return; case OP_return: if (p->src0 >= 0 && n != 0) a64_mov(0, n); a64_mov_imm(A64_IP0, ALIGN_UP(p->src1, A64_STACK_ALIGN)); a64_add(A64_SP, A64_SP, A64_IP0); - emit(0xa8c14ff6); /* ldp x22, x19, [sp], #16 */ - emit(0xa8c157f4); /* ldp x20, x21, [sp], #16 */ - emit(0xa8c17bfd); /* ldp x29, x30, [sp], #16 */ - emit(0xd65f03c0); /* ret */ + emit(a64_ldp_post_insn(22, A64_GP, A64_SP, 16)); + emit(a64_ldp_post_insn(20, 21, A64_SP, 16)); + emit(a64_ldp_post_insn(A64_FP, A64_LR, A64_SP, 16)); + emit(a64_ret_insn()); return; default: fatal("unknown arm64 opcode"); @@ -622,10 +658,10 @@ void plt_generate(void) elf_write_int(dynamic_sections.elf_plt, a64_adrp_insn(A64_IP0, ent, got)); elf_write_int(dynamic_sections.elf_plt, - 0x91000210 | ((got & 0xfff) << 10)); /* add x16, x16, # */ + a64_add_imm_insn(true, A64_IP0, A64_IP0, got & 0xfff)); elf_write_int(dynamic_sections.elf_plt, - 0xf9400211); /* ldr x17, [x16] */ - elf_write_int(dynamic_sections.elf_plt, 0xd61f0220); /* br x17 */ + a64_mem_scaled_insn(A64_LOAD, 8, A64_IP1, A64_IP0, 0)); + elf_write_int(dynamic_sections.elf_plt, a64_br_insn(A64_IP1)); } } @@ -640,15 +676,15 @@ void code_generate(void) */ if (dynlink) a64_mov_sp(25); - a64_mem(1, 8, 20, A64_SP, 0); - emit(0x910023f5); + a64_mem(A64_LOAD, 8, 20, A64_SP, 0); + emit(a64_add_imm_insn(true, 21, A64_SP, 8)); a64_mov(23, 20); a64_mov(24, 21); if (dynlink) { a64_mov_imm(A64_IP0, A64_STACK_ALIGN); a64_sub(A64_SP, A64_SP, A64_IP0); - a64_mem(0, 8, 23, A64_SP, 0); - a64_mem(0, 8, 24, A64_SP, 8); + a64_mem(A64_STORE, 8, 23, A64_SP, 0); + a64_mem(A64_STORE, 8, 24, A64_SP, 8); } /* The synthetic global frame is carved out of the runtime stack, and x19 @@ -665,15 +701,19 @@ void code_generate(void) a64_sub(A64_SP, A64_SP, A64_IP0); a64_mov_sp(A64_GP); a64_mov_imm(A64_IP0, elf_data_start); - a64_mem(0, 8, A64_GP, A64_IP0, 0); + a64_mem(A64_STORE, 8, A64_GP, A64_IP0, 0); if (dynlink) { - func_t *memset_func = find_func("memset"); - if (!memset_func) - fatal("arm64 dynamic startup needs memset"); + /* Clear the frame inline rather than through libc's memset, which a + * --no-libc translation unit never declares. The frame is a multiple of + * sixteen bytes and may be empty: count x2 down to zero while x0 walks + * up through it. + */ a64_mov(0, A64_GP); - a64_mov(1, A64_ZR); a64_mov_imm(2, global_frame); - a64_bl_addr(dynamic_sections.elf_plt_start + memset_func->plt_offset); + emit(a64_cbz_insn(true, 2, 4)); + emit(a64_mem_post_insn(A64_STORE, 8, A64_ZR, 0, 8)); + emit(a64_sub_imm_insn(true, 2, 2, 8)); + emit(a64_cbnz_insn(true, 2, -2)); } a64_b(GLOBAL_FUNC->bbs->elf_offset); /* __syscall(number,arg1,...): AArch64 Linux wants x8,x0..x5. */ @@ -685,15 +725,15 @@ void code_generate(void) a64_mov(3, 4); a64_mov(4, 5); a64_mov(5, 6); - emit(0xd4000001); - emit(0xd65f03c0); + emit(a64_svc_insn(0)); + emit(a64_ret_insn()); } for (ph2_ir_t *p = GLOBAL_FUNC->bbs->ph2_ir_list.head; p; p = p->next) emit_ph2_ir(p); if (MAIN_BB) { if (dynlink) { - a64_mem(1, 8, 23, A64_SP, global_frame); - a64_mem(1, 8, 24, A64_SP, global_frame + 8); + a64_mem(A64_LOAD, 8, 23, A64_SP, global_frame); + a64_mem(A64_LOAD, 8, 24, A64_SP, global_frame + 8); a64_mov_imm(0, elf_code_start + MAIN_BB->elf_offset); a64_mov(1, 23); a64_mov(2, 24); @@ -702,14 +742,13 @@ void code_generate(void) a64_mov(5, A64_ZR); a64_mov(6, 25); a64_bl_addr(dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE); - emit(0xd4200000); /* __libc_start_main does not return */ + emit(a64_brk_insn(0)); /* __libc_start_main does not return */ } else { a64_mov(0, 23); a64_mov(1, 24); - emit(0x94000000 | - (((MAIN_BB->elf_offset - elf_code->size) / 4) & 0x3ffffff)); + emit(a64_bl_insn((MAIN_BB->elf_offset - elf_code->size) / 4)); a64_mov_imm(8, 93); - emit(0xd4000001); + emit(a64_svc_insn(0)); } } for (int i = 0; i < ph2_ir_idx; i++) diff --git a/src/arm64.c b/src/arm64.c new file mode 100644 index 00000000..1fca24b7 --- /dev/null +++ b/src/arm64.c @@ -0,0 +1,339 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* AArch64 instruction encoding */ + +/* Identifier naming conventions + * - suffix _insn : returns one encoded 32-bit instruction word, which the + * code generator hands to emit(). + * - prefix A64_ : architectural register numbers and condition codes. + * + * Every AArch64 instruction is one fixed-width word, so, as in src/arm.c and + * src/riscv.c, each helper here returns that word rather than writing it. The + * @sf argument selects the 64-bit X form when true and the 32-bit W form when + * false; it is bit 31 of every data-processing encoding below. + * + * What belongs here is anything whose arguments are purely architectural -- + * register numbers, immediates, displacements, widths. Anything that reads + * shecc's IR, the register allocator's state or a basic block belongs in + * src/arm64-codegen.c, as do the range checks, which report through fatal(). + */ + +#include "defs.h" + +/* Register 31 is SP or the zero register depending on the instruction. */ +#define A64_SP 31 +#define A64_ZR 31 + +/* IP0 and IP1, the intra-procedure-call scratch registers the linker may use + * between instructions. The frame pointer and link register are x29 and x30. + */ +#define A64_IP0 16 +#define A64_IP1 17 +#define A64_FP 29 +#define A64_LR 30 + +typedef enum { + A64_EQ = 0, + A64_NE = 1, + A64_HS = 2, /* unsigned >= */ + A64_LO = 3, /* unsigned < */ + A64_HI = 8, /* unsigned > */ + A64_LS = 9, /* unsigned <= */ + A64_GE = 10, + A64_LT = 11, + A64_GT = 12, + A64_LE = 13 +} a64_cond_t; + +int a64_sf(bool sf) +{ + return sf ? 0x80000000 : 0; +} + +/* MOVZ/MOVN/MOVK place a 16-bit immediate at bit 16 * @hw. */ +int a64_movz_insn(bool sf, int rd, int imm16, int hw) +{ + return a64_sf(sf) | 0x52800000 | (hw << 21) | ((imm16 & 0xffff) << 5) | rd; +} + +int a64_movn_insn(bool sf, int rd, int imm16, int hw) +{ + return a64_sf(sf) | 0x12800000 | (hw << 21) | ((imm16 & 0xffff) << 5) | rd; +} + +int a64_movk_insn(bool sf, int rd, int imm16, int hw) +{ + return a64_sf(sf) | 0x72800000 | (hw << 21) | ((imm16 & 0xffff) << 5) | rd; +} + +/* MOV between general registers is ORR with the zero register. SP is not a + * general register there, so a move involving SP is ADD #0 instead. + */ +int a64_mov_insn(bool sf, int rd, int rm) +{ + return a64_sf(sf) | 0x2a0003e0 | (rm << 16) | rd; +} + +int a64_add_imm_insn(bool sf, int rd, int rn, int imm12) +{ + return a64_sf(sf) | 0x11000000 | ((imm12 & 0xfff) << 10) | (rn << 5) | rd; +} + +int a64_sub_imm_insn(bool sf, int rd, int rn, int imm12) +{ + return a64_sf(sf) | 0x51000000 | ((imm12 & 0xfff) << 10) | (rn << 5) | rd; +} + +/* CMP #imm is SUBS into the zero register. */ +int a64_cmp_imm_insn(bool sf, int rn, int imm12) +{ + return a64_sf(sf) | 0x7100001f | ((imm12 & 0xfff) << 10) | (rn << 5); +} + +int a64_add_reg_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x0b000000 | (rm << 16) | (rn << 5) | rd; +} + +int a64_sub_reg_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x4b000000 | (rm << 16) | (rn << 5) | rd; +} + +/* CMP Rn, Rm is SUBS into the zero register. */ +int a64_cmp_reg_insn(bool sf, int rn, int rm) +{ + return a64_sf(sf) | 0x6b00001f | (rm << 16) | (rn << 5); +} + +/* The extended-register forms of 64-bit ADD and SUB. UXTX is the plain form + * that may name SP; SXTW widens a W register operand as it is added. + */ +#define A64_EXT_SXTW 6 +#define A64_EXT_UXTX 3 + +int a64_add_ext_insn(int rd, int rn, int rm, int option) +{ + return 0x8b200000 | (rm << 16) | (option << 13) | (rn << 5) | rd; +} + +int a64_sub_ext_insn(int rd, int rn, int rm, int option) +{ + return 0xcb200000 | (rm << 16) | (option << 13) | (rn << 5) | rd; +} + +/* NEG and MVN are SUB and ORN with the zero register as first operand. */ +int a64_neg_insn(bool sf, int rd, int rm) +{ + return a64_sf(sf) | 0x4b0003e0 | (rm << 16) | rd; +} + +int a64_mvn_insn(bool sf, int rd, int rm) +{ + return a64_sf(sf) | 0x2a2003e0 | (rm << 16) | rd; +} + +int a64_and_reg_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x0a000000 | (rm << 16) | (rn << 5) | rd; +} + +int a64_orr_reg_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x2a000000 | (rm << 16) | (rn << 5) | rd; +} + +int a64_eor_reg_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x4a000000 | (rm << 16) | (rn << 5) | rd; +} + +/* MUL is MADD with the zero register as addend; MSUB computes Ra - Rn * Rm. */ +int a64_mul_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x1b007c00 | (rm << 16) | (rn << 5) | rd; +} + +int a64_msub_insn(bool sf, int rd, int rn, int rm, int ra) +{ + return a64_sf(sf) | 0x1b008000 | (rm << 16) | (ra << 10) | (rn << 5) | rd; +} + +int a64_udiv_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x1ac00800 | (rm << 16) | (rn << 5) | rd; +} + +int a64_sdiv_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x1ac00c00 | (rm << 16) | (rn << 5) | rd; +} + +int a64_lslv_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x1ac02000 | (rm << 16) | (rn << 5) | rd; +} + +int a64_lsrv_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x1ac02400 | (rm << 16) | (rn << 5) | rd; +} + +int a64_asrv_insn(bool sf, int rd, int rn, int rm) +{ + return a64_sf(sf) | 0x1ac02800 | (rm << 16) | (rn << 5) | rd; +} + +/* SXTB/SXTH/SXTW and UXTB/UXTH/UXTW into an X register are SBFM and UBFM taking + * the low @bits bits. + */ +int a64_sext_insn(int rd, int rn, int bits) +{ + return 0x93400000 | ((bits - 1) << 10) | (rn << 5) | rd; +} + +int a64_zext_insn(int rd, int rn, int bits) +{ + return 0xd3400000 | ((bits - 1) << 10) | (rn << 5) | rd; +} + +/* CSET Rd, cond is CSINC with the zero register and the inverted condition. */ +int a64_cset_insn(bool sf, int rd, a64_cond_t cond) +{ + return a64_sf(sf) | 0x1a9f07e0 | ((cond ^ 1) << 12) | rd; +} + +/* What a memory access does, the first argument of the helpers below. A load + * narrower than a doubleword widens into its X register: A64_LOAD sign-extends, + * which is where a signed value sits, and A64_LOAD_ZEXT zero-extends, as an + * unsigned one must, or an unsigned char holding 200 reads back as -56. + */ +#define A64_STORE 0 +#define A64_LOAD 1 +#define A64_LOAD_ZEXT 2 + +/* Base opcode of the unscaled @access of @size bytes. Size lives in bits 31:30 + * and the operation in 23:22, where a narrow A64_LOAD picks the sign-extending + * form and every other load the zero-extending one, whichever addressing form + * carries it. The scaled form is this plus bit 24, which is why one table + * serves both and they can no longer disagree. @size must be 1, 2, 4 or 8. + */ +int a64_mem_op(int access, int size) +{ + /* Unsigned, so a doubleword's size field shifted into bits 31:30 does not + * overflow a signed int. + */ + unsigned int sz = size == 8 ? 3 : size == 4 ? 2 : size == 2 ? 1 : 0; + + if (access == A64_STORE) + return 0x38000000 | (sz << 30); + if (access == A64_LOAD && size != 8) + return 0x38000000 | (sz << 30) | (2 << 22); + return 0x38000000 | (sz << 30) | (1 << 22); +} + +/* LDUR/STUR: a signed 9-bit byte offset. */ +int a64_mem_unscaled_insn(int access, int size, int rt, int rn, int ofs) +{ + return a64_mem_op(access, size) | ((ofs & 0x1ff) << 12) | (rn << 5) | rt; +} + +/* LDR/STR with an unsigned 12-bit offset scaled by the access width. */ +int a64_mem_scaled_insn(int access, int size, int rt, int rn, int ofs) +{ + return a64_mem_op(access, size) | (1 << 24) | ((ofs / size) << 10) | + (rn << 5) | rt; +} + +/* LDR/STR post-indexed: access [Rn], then add the signed 9-bit @ofs to Rn. */ +int a64_mem_post_insn(int access, int size, int rt, int rn, int ofs) +{ + return a64_mem_op(access, size) | 0x400 | ((ofs & 0x1ff) << 12) | + (rn << 5) | rt; +} + +/* How many instructions a64_mem() in the code generator spends on an access of + * @size bytes at @ofs: one when either immediate form reaches it, and otherwise + * MOVZ/MOVK, ADD and the access through the scratch register. + */ +int a64_mem_count(int size, int ofs) +{ + if (ofs >= 0 && ofs <= 4095 * size && !(ofs % size)) + return 1; + if (ofs >= -256 && ofs <= 255) + return 1; + return 4; +} + +/* STP pre-indexed and LDP post-indexed on X registers. The 7-bit offset is in + * units of eight bytes. + */ +int a64_stp_pre_insn(int rt, int rt2, int rn, int ofs) +{ + return 0xa9800000 | (((ofs / 8) & 0x7f) << 15) | (rt2 << 10) | (rn << 5) | + rt; +} + +int a64_ldp_post_insn(int rt, int rt2, int rn, int ofs) +{ + return 0xa8c00000 | (((ofs / 8) & 0x7f) << 15) | (rt2 << 10) | (rn << 5) | + rt; +} + +/* Branch displacements are in instructions, relative to the branch itself. */ +int a64_b_insn(int disp) +{ + return 0x14000000 | (disp & 0x3ffffff); +} + +int a64_bl_insn(int disp) +{ + return 0x94000000 | (disp & 0x3ffffff); +} + +int a64_cbz_insn(bool sf, int rt, int disp) +{ + return a64_sf(sf) | 0x34000000 | ((disp & 0x7ffff) << 5) | rt; +} + +int a64_cbnz_insn(bool sf, int rt, int disp) +{ + return a64_sf(sf) | 0x35000000 | ((disp & 0x7ffff) << 5) | rt; +} + +int a64_br_insn(int rn) +{ + return 0xd61f0000 | (rn << 5); +} + +int a64_blr_insn(int rn) +{ + return 0xd63f0000 | (rn << 5); +} + +int a64_ret_insn(void) +{ + return 0xd65f0000 | (A64_LR << 5); +} + +int a64_svc_insn(int imm16) +{ + return 0xd4000001 | ((imm16 & 0xffff) << 5); +} + +int a64_brk_insn(int imm16) +{ + return 0xd4200000 | ((imm16 & 0xffff) << 5); +} + +/* ADRP: the page of the target, @pages 4 KiB pages from the page of the pc. */ +int a64_adrp_pages_insn(int rd, int pages) +{ + return 0x90000000 | ((pages & 3) << 29) | (((pages >> 2) & 0x7ffff) << 5) | + rd; +} diff --git a/src/defs.h b/src/defs.h index 3534f2fe..647b6799 100644 --- a/src/defs.h +++ b/src/defs.h @@ -35,6 +35,13 @@ #define MAX_TOKEN_LEN 256 #define MAX_ID_LEN 64 #define MAX_LINE_LEN 256 + +/* A string literal after its adjacent pieces are decoded and joined. Each piece + * is still one token of at most MAX_TOKEN_LEN, but a long message is routinely + * written as many of them. + */ +#define MAX_STRING_LEN 4096 +#define MAX_INCLUDE_DIRS 16 #define MAX_VAR_LEN 128 /* ".label." plus an int, for basic_block_t's dump name. */ #define MAX_LABEL_LEN 24 @@ -44,7 +51,11 @@ * operator and 41 bytes of markup. */ #define DUMP_INSN_LEN 512 -#define MAX_TYPE_LEN 32 + +/* A type name is a struct, union, enum or typedef identifier, which the lexer + * already bounds by MAX_ID_LEN; a narrower buffer rejects valid tags. + */ +#define MAX_TYPE_LEN MAX_ID_LEN /* Declaration limit, and with MAX_ARGS_IN_REG it also sizes the outgoing * stack-argument area every frame reserves (see add_func() in globals.c). A @@ -57,11 +68,15 @@ #define MAX_PARAMS 8 #endif #define MAX_LOCALS 3200 -#define MAX_FIELDS 64 + +/* var_t itself is parsed as a record by every bootstrap stage. Leave room for + * compiler metadata as well as user records with many declarators; C99 5.2.4.1 + * asks for 127 members in one structure. Field tables are allocated lazily, so + * this does not inflate scalar type storage. + */ +#define MAX_FIELDS 128 #define MAX_TYPES 256 #define MAX_LABELS 256 -/* Pending postfix ++/-- effects in one statement; each one appends 3. */ -#define MAX_SIDE_EFFECT 64 /* Elements captured from an implicitly sized array initializer. */ #define MAX_IMPLICIT_ARRAY 256 /* A self-compile emits ~101k ph2_ir; one pointer per slot in PH2_IR_FLATTEN. */ @@ -282,10 +297,13 @@ typedef enum { T_start, /* FIXME: Unused, intended for lexer state machine init */ T_eof, /* end-of-file (EOF) */ T_numeric, + T_floating, /* C99 floating literal; lowering is staged separately */ T_identifier, - T_comma, /* , */ - T_string, /* null-terminated string */ + T_comma, /* , */ + T_string, /* null-terminated string */ + T_wstring, /* L"..." wide string literal */ T_char, + T_wchar, /* L'...' wide character constant */ T_open_bracket, /* ( */ T_close_bracket, /* ) */ T_open_curly, /* { */ @@ -349,6 +367,20 @@ typedef enum { T_continue, T_goto, T_const, /* const qualifier */ + T_volatile, + T_static, + T_extern, + T_register, + T_auto, + T_restrict, + T_inline, + T_signed, + T_unsigned, + T_long, + T_float, + T_double, + T_complex, + T_imaginary, /* C pre-processor directives */ T_cppd_include, T_cppd_define, @@ -361,6 +393,8 @@ typedef enum { T_cppd_ifdef, T_cppd_ifndef, T_cppd_pragma, + T_cppd_line, + T_cppd_unknown, /* a directive name shecc does not support */ /* C pre-processor specific, these kinds will be removed after * pre-processing is done. @@ -383,6 +417,11 @@ typedef struct { int len; /* length of token */ int line; int column; + + /* Immutable physical path used for quoted-include lookup. #line changes + * filename only, which remains the logical diagnostic/__FILE__ name. + */ + char *physical_filename; char *filename; } source_location_t; @@ -414,6 +453,11 @@ typedef enum { TYPE_int, TYPE_char, TYPE_short, + TYPE_long, + TYPE_long_long, + TYPE_float, + TYPE_double, + TYPE_long_double, TYPE_struct, TYPE_union, TYPE_typedef @@ -541,6 +585,8 @@ typedef struct var_list { struct var { type_t *type; + /* Lexical owner, used while parsing declarator constant expressions. */ + void *scope; /* Interned, not copied. A MAX_VAR_LEN array was 128 of this struct's 312 * bytes on every one of the ~86k variables a self-compile creates, and @@ -552,9 +598,65 @@ struct var { char *var_name; int ptr_level; bool is_func; + + /* A pointer-to-callback object is not callable itself. Its compact callback + * pointee signature is restored only after one dereference. + */ + void *pointee_func_signature; + + /* Set only for the unparenthesized `result name(parameters)` spelling. + * Block typedef support uses it to keep direct function aliases distinct + * from the older parenthesized pointer-to-function declarator path. + */ + bool is_direct_function_declarator; + + /* The parenthesized callback spelling `int (*name)(int)` is distinct from a + * direct function declarator. Block typedef lowering uses this count to + * admit exactly one pointer layer as a callback-pointer alias. + */ + int parenthesized_function_pointer_level; + bool parenthesized_function_pointer_const; + bool parenthesized_function_pointer_restrict; + + /* For a depth-two callback slot, only the second (outer) star denotes the + * pointer object retained after normalization. Keep qualifier placement so + * its mask can be collapsed with that object rather than silently lost. + */ + bool parenthesized_function_pointer_outer_const; + bool parenthesized_function_pointer_outer_volatile; + bool parenthesized_function_pointer_outer_restrict; + bool parenthesized_function_pointer_inner_qualified; + + /* A block-scope `extern int f(void);` hides a local object named f but + * resolves expressions through the translation unit's function table. + */ + bool is_extern_function_alias; + + /* A function declaration originated in a block. Its linkage metadata + * remains global, but its ordinary identifier is lexical until a real + * file-scope declaration appears. + */ + bool is_block_scope_function_declaration; bool is_global; + + /* A compile-time address of static storage, kept distinct from reading a + * global object's value while lowering aggregate initializers. + */ + bool is_global_address; + bool is_static; /* declaration used the static storage class */ + bool is_extern; /* file-scope declaration used extern */ + bool is_register; /* declaration used the register storage class */ + bool is_inline; /* declaration used the inline function specifier */ + bool has_initializer; /* a file-scope definition supplied an initializer */ bool is_const_qualified; /* true if variable has const qualifier */ - bool address_taken; /* true if variable address was taken (&var) */ + bool is_volatile; /* declaration used the volatile qualifier */ + bool is_const_pointer; /* true for the outermost `* const` qualifier */ + /* One bit per pointer level, counted from the base type. The legacy + * is_const_pointer flag describes only the outermost level; this retains + * qualifiers on intermediate pointers such as `int * const *`. + */ + unsigned int pointer_const_mask; + bool address_taken; /* true if variable address was taken (&var) */ /* Working state for strength_reduce(): how many instructions in the * function write the variable, whether it is written inside the loop being * examined, and how much its value moves per iteration when it does. All @@ -586,9 +688,41 @@ struct var { */ bool in_select_arm; int array_size; - int array_dim2; /* second dimension size for 2D arrays */ - int offset; /* offset from stack or frame, index 0 is reserved */ - int init_val; /* for global initialization */ + bool has_direct_array_declarator; + bool has_unsized_array; /* `T name[]`: bound is supplied by initializer */ + /* `T member[]` at the end of a struct has no initializer-supplied bound and + * contributes no bytes to the record's fixed layout. + */ + bool is_flexible_array_member; + int array_dim2; /* second dimension size for multidimensional arrays */ + int array_dim3; /* third dimension size for multidimensional arrays */ + int array_dim4; /* fourth dimension size for multidimensional arrays */ + /* Bounds of the array addressed by a parenthesized pointer declarator, e.g. + * `int (*row)[2]`. They describe the pointee, not this pointer-sized + * object, so they must never participate in size_var(). + */ + int pointee_array_size; + bool has_direct_pointee_array_declarator; + int pointee_array_dim2; + int pointee_array_dim3; + int pointee_array_dim4; + + /* Pointer depth of one element in the array described above. This + * distinguishes `int (*p)[2]` from `int *(*p)[2]`: both address rows, but + * the latter's row elements are pointers. + */ + int pointee_array_element_ptr_level; + int offset; /* offset from stack or frame, index 0 is reserved */ + /* Record bit-field metadata. `offset` is the containing storage unit's byte + * offset. `is_bitfield` distinguishes an ordinary member from the valid + * unnamed zero-width field used as an allocation-unit barrier. + */ + bool is_bitfield; + int bit_width; + int bit_offset; + int bit_storage_size; + int init_val; /* for global initialization */ + int init_val_hi; /* upper word of an 8-byte integer constant */ /* Generation stamps used by compute_live_in() to test set membership in * constant time instead of rescanning live_kill and live_in per element. */ @@ -623,7 +757,18 @@ struct var { bool is_ternary_ret; bool is_logical_ret; bool is_const; /* whether a constant representaion or not */ - int phys_reg; /* Physical register assignment (-1 if unassigned) */ + + /* The value of an assignment expression, read back from the object the + * assignment stored. C11 6.5.16p3 permits that read without requiring it, + * even of a volatile object, so it is not an access that must survive when + * nothing uses the value. + */ + bool is_assignment_reload; + int phys_reg; /* low physical register (-1 if unassigned) */ + /* The high word of a 32-bit-target wide scalar. It remains -1 for the + * ordinary single-register representation and on LP64 targets. + */ + int phys_reg_hi; int first_use; /* First instruction index where variable is used */ int last_use; /* Last instruction index where variable is used */ int use_count; /* Number of times variable is used */ @@ -639,30 +784,128 @@ struct var { * array or struct literal temporaries). */ bool is_compound_literal; + + /* A value loaded from an object that has no declaration of its own: an + * element or member of a compound literal, or an object reached through a + * dereference, subscript or member selection. This points at the object's + * address so a following update, store or member selection reaches the + * object rather than the temporary. + */ + bool is_compound_literal_reference; + struct var *compound_literal_address; + + /* A record value with no storage of its own: its bytes are those of the + * object at compound_literal_address. A member selection or address-of uses + * that object, and a record copy reads the bytes from it. + */ + bool defers_record_copy; + + /* A non-NULL field means the reference is a bit-field and must use the + * mask-and-merge store path rather than a byte/word OP_write. + */ + struct var *compound_literal_bitfield; + + /* String literals have immutable storage duration in C. Preserve that + * provenance separately from the pointer type so the compatibility warning + * can remain opt-in while legacy source still compiles. + */ + bool is_string_literal; + + /* For the address of a static object in a constant initializer, the bytes + * one integer step moves it by, so `1 + array` advances as `array + 1` + * does; 0 where no such step is known. + */ + int address_stride; + + /* The null pointer constant `(void *) 0` (C99 6.3.2.3p3), which unlike any + * other void pointer converts to a function pointer. + */ + bool is_void_null_pointer; + + /* For a callback slot, whether the callback pointer it finally reaches is + * itself const or volatile: `int (*const *slot)(int)`. + */ + bool callback_is_const; + bool callback_is_volatile; + + /* `&__func__` is a pointer to the compiler's static character array. Its + * address has the same machine representation as the decayed char pointer, + * but one unary dereference must restore that pointer without loading the + * first bytes of the string as an address. + */ + bool is_func_name_array_address; + + /* A function-pointer declarator owns a prototype separately from its value + * type. Keeping this syntax-only object lets an indirect call use the same + * argument lowering as a direct call (notably record-by-value arguments) + * without putting function ABI details into type_t. Kept void-typed so the + * self-hosted parser does not need an incomplete `struct func` declaration + * while reading var_t itself. parser.c owns the cast back to func_t. + */ + void *func_signature; + + /* C ABI lowering passes record parameters as pointers to caller-owned + * copies. The source-level declaration remains a record so field access and + * record assignment keep their C semantics; OP_address_of materializes the + * hidden incoming pointer instead of an address of a scalar slot. + */ + bool is_aggregate_param; }; typedef struct func func_t; +typedef struct typedef_binding { + char *name; + type_t *type; + struct typedef_binding *next; +} typedef_binding_t; + /* block definition */ struct block { var_list_t locals; + + /* C tags and enumeration constants have lexical, not translation-unit, + * scope. Variables remain in locals; these lists serve parser lookups. + */ + void *type_tags; + void *constants; + typedef_binding_t *typedefs; struct block *parent; func_t *func; struct block *next; }; typedef struct block block_t; + +int read_const_sizeof_type(block_t *scope); +int read_const_wstring_size(void); +int read_sizeof_constant(block_t *scope); +int read_const_expr_operand(block_t *scope); +void add_block_typedef(block_t *block, char name[], type_t *type); +bool find_block_typedef(block_t *block, const char *name); +type_t *find_visible_type(const char *name, block_t *block); +type_t *find_record_tag(char name[], block_t *block, base_type_t kind); +type_t *reference_record_tag(char name[], block_t *block, base_type_t kind); +type_t *local_record_tag(char name[], block_t *block, base_type_t kind); +void begin_record_definition(type_t *tag); +type_t *find_enum_tag(char name[], block_t *block); +type_t *reference_enum_tag(char name[], block_t *block); +type_t *local_enum_tag(char name[], block_t *block); typedef struct basic_block basic_block_t; /* Definition of a growable buffer for a mutable null-terminated string * @size: Current number of elements in the array * @capacity: Number of elements that can be stored without resizing * @elements: Pointer to the array of characters + * @plain_source: set by the lexer once it has checked that a source buffer has + * no trigraph and no line splice, so phases 1 and 2 are the + * identity on it */ typedef struct { int size; int capacity; char *elements; + bool plain_source; } strbuf_t; /* phase-2 IR definition */ @@ -686,6 +929,13 @@ struct ph2_ir { /* The register OP_cmov keeps when its condition does not hold. */ int src2; int dest; + + /* A 32-bit target represents a wide integer as low/high register pairs. -1 + * means this instruction uses the existing single-register form. + */ + int src0_hi; + int src1_hi; + int dest_hi; /* Type information for LP64 support */ int size_bytes; /* Size in bytes for load/store/read/write operations */ @@ -701,11 +951,26 @@ struct ph2_ir { */ bool ofs_based_on_stack_top; bool is_pointer; /* True if this operation involves a pointer type */ + /* Scalar signedness accompanies register values independently of their + * storage width. Comparisons inspect their sources; arithmetic and loads + * inspect the result. + */ + bool is_unsigned; + /* Operand provenance is required by LP64 backends: pointer arithmetic keeps * the address operand wide but sign-extends an int index. */ bool src0_is_pointer; bool src1_is_pointer; + bool src0_is_unsigned; + bool src1_is_unsigned; + + /* The load, read or store accesses a volatile object, which is a side + * effect whether or not its value is used (C99 6.7.3p6), so no rewrite may + * drop it, replace a load with a copy of a value read earlier, or drop a + * store for writing what the object already holds. + */ + bool is_volatile; }; typedef struct ph2_ir ph2_ir_t; @@ -717,6 +982,12 @@ struct type { struct type *base_struct; int size; + /* Natural ABI alignment of an object of this type. Record definitions + * retain their maximum member alignment so nested records lay out correctly + * too. + */ + int alignment; + /* Member table, allocated when the type is created rather than inlined. A * MAX_FIELDS array of var_t by value made type_t 12 KiB, and TYPES is a * flat MAX_TYPES array that global_init() zeroes up front -- 3 MiB of @@ -725,23 +996,160 @@ struct type { var_t *fields; int num_fields; int ptr_level; /* pointer level for typedef pointer types */ + /* A function-pointer typedef retains its parsed prototype here. Keep it + * opaque because type_t is declared before func_t is complete. + */ + void *func_signature; + + /* A pointer-to-callback typedef is itself non-callable, but its pointee + * callback prototype must survive object declarations and conversions. + */ + void *pointee_func_signature; + + /* Unlike a pointer-to-function typedef, this descriptor denotes the + * function type itself. A single use-site star then forms a callable + * function-pointer object. + */ + bool is_direct_function_type; + + /* Qualifiers on a callback-pointer typedef apply to each pointer object, + * not to its function return type. + */ + bool is_volatile_qualified; + + /* Array bounds carried by an array typedef. Object declarators copy these + * into var_t, where ordinary indexing and initialization already retain + * their row-major representation. + */ + int array_size; + int array_dim2; + int array_dim3; + int array_dim4; + + /* Pointer depth of one element of an array typedef. This is distinct from + * ptr_level when a later typedef adds a pointer to the whole array. + */ + int array_element_ptr_level; + + /* Scalar base descriptor of an array typedef's element. Pointer-element + * arrays need this after a subscript: the outer typedef descriptor still + * carries the array's pointer depth and is not the loaded element type. + */ + struct type *array_element_type; + + /* An array of callback slots is not itself a slot. Preserve the + * non-callable callback prototype on each element for subscript loads. + */ + void *array_element_pointee_func_signature; + + /* Qualifiers on `(**const slots[N])` and `(**volatile slots[N])` apply to + * each selected outer slot pointer, not to the array object or the callback + * pointer reached after one dereference. + */ + bool array_element_is_const_pointer; + bool array_element_is_volatile; + + /* Bounds carried by a pointer-to-array typedef, e.g. `int (*)[2]`. These + * describe the pointed-to array rather than the pointer-sized alias itself + * and are copied to var_t when the typedef names an object. + */ + int pointee_array_size; + int pointee_array_dim2; + int pointee_array_dim3; + int pointee_array_dim4; + int pointee_array_element_ptr_level; + + /* The scalar element descriptor of a pointer-to-array typedef. Unlike an + * ordinary pointer typedef, its outer descriptor is TYPE_typedef and + * pointer-sized, so row indexing cannot recover this from `size`. + */ + struct type *pointee_array_element_type; + + /* Qualifiers written after stars inside a typedef declarator. These bits + * are relative to the typedef's own pointer depth; var_t keeps any stars + * subsequently written at a use site. + */ + unsigned int pointer_const_mask; + bool is_union; /* preserves union semantics for anonymous typedef unions */ + bool + has_flexible_array_member; /* cannot be embedded by value in a record */ + bool is_const_qualified; /* qualifier carried by a scalar typedef */ + /* Set on a struct or union tag once its member list opens. num_fields is + * written only when the list closes, so it cannot tell a definition nested + * in the tag's own member list from the first one. + */ + bool definition_started; + + /* Integer representation is distinct from signedness: unsigned char and + * unsigned int keep the ordinary scalar widths but require zero extension + * and unsigned arithmetic lowering. + */ + bool is_unsigned; + + /* Floating scalars require a distinct IR/register class. This identity is + * intentionally separate from width and signedness so they can never be + * lowered as integer values by accident. + */ + bool is_floating; + + /* Plain char and signed char share this target's representation but are + * distinct C types. Scalar typedefs preserve this fact. + */ + bool is_signed_char; + + /* `_Bool` otherwise shares the byte-sized TYPE_char representation. Keep + * its C type identity through typedefs where pointer equality is lost. + */ + bool is_bool; }; /* lvalue details */ typedef struct { int size; int ptr_level; + + /* Pointer depth of the value designated by this lvalue. A subscript + * computes an address (one level deeper than its selected value), so this + * must not be inferred from the address provenance alone. + */ + int value_ptr_level; bool is_func; bool is_reference; + bool is_const_qualified; + + /* The lvalue designates an array, which C99 6.5.16 does not let an + * assignment, ++ or -- modify. + */ + bool is_array; + + /* Subscripts applied to the declaration in lvalue_t.decl. A designated + * array keeps the bounds after the first this many. + */ + int subscript_depth; + unsigned int pointer_const_mask; type_t *type; + + /* A selected array element can be a non-callable callback slot even when + * the array's scalar base type has no ordinary pointer descriptor. + */ + void *pointee_func_signature; + /* The declaration selected by the lvalue, including a struct member. */ + var_t *decl; } lvalue_t; /* constants for enums */ -typedef struct { +typedef struct constant { char alias[MAX_VAR_LEN]; int value; + struct constant *next; } constant_t; +typedef struct type_tag { + char name[MAX_TYPE_LEN]; + type_t *type; + struct type_tag *next; +} type_tag_t; + struct phi_operand { var_t *var; basic_block_t *from; @@ -943,10 +1351,33 @@ typedef struct { struct func { /* Syntatic info */ var_t return_def; + + /* By-value record returns use a private caller-provided destination pointer + * as ABI argument zero. It is deliberately outside param_defs so C + * prototype arity and compatibility remain source-level facts. + */ + bool returns_aggregate; + var_t sret_def; var_t param_defs[MAX_PARAMS]; int num_params; int va_args; + /* `f()` has no prototype in C99, while `f(void)` and every typed parameter + * list constrain call arity. + */ + bool has_prototype; + + /* A declaration introduced only within a block still has linkage, but its + * ordinary identifier is visible only through that block's lexical alias + * until a file-scope declaration or definition appears. + */ + bool is_block_scope_only_declaration; + bool is_static; /* internal-linkage declaration */ + /* The definition used the inline function specifier. C99 applies extra + * linkage constraints to external-linkage inline definitions. + */ + bool is_inline; + /* inline_calls()'s verdict on this body and the return that ends it, * stamped with the round that reached them: a body is examined once per * round rather than once per call site that names it. @@ -978,6 +1409,8 @@ struct func { /* Information used for dynamic linking */ bool is_used; + /* A direct aggregate-return call requires shecc's private sret ABI. */ + bool aggregate_call_used; int plt_offset; struct func *next; diff --git a/src/elf.c b/src/elf.c index 66ec0e34..c2ff605f 100644 --- a/src/elf.c +++ b/src/elf.c @@ -68,13 +68,12 @@ void elf_write_quad(strbuf_t *elf_array, int val) elf_write_int(elf_array, 0); } -void elf_write_blk(strbuf_t *elf_array, void *blk, int sz) +void elf_write_blk(strbuf_t *elf_array, const void *blk, int sz) { - if (!elf_array || !blk || sz <= 0) + if (!elf_array || !blk || sz <= 0 || !strbuf_extend(elf_array, sz)) return; - const char *ptr = blk; - for (int i = 0; i < sz; i++) - strbuf_putc(elf_array, ptr[i]); + memcpy(elf_array->elements + elf_array->size, blk, sz); + elf_array->size += sz; } /* The dynamic-linking tables differ only in width between the two ELF classes, @@ -294,8 +293,13 @@ void elf_generate_header(void) phnum = 2; shnum = 8; shstrndx = 7; - shoff = elf_header_len + elf_code->size + elf_data->size + - elf_rodata->size + elf_symtab->size + elf_strtab->size + + + /* .data starts the second load segment on a page boundary, and + * elf_generate() pads the file up to it; count that padding too. + */ + shoff = ALIGN_UP(elf_header_len + elf_code->size + elf_rodata->size, + PAGESIZE) + + elf_data->size + elf_symtab->size + elf_strtab->size + elf_shstrtab->size; } @@ -512,7 +516,9 @@ void elf_generate_program_headers(void) /* program header - readable and writable segment */ phdr.p_type = 1; /* PT_LOAD */ phdr.p_offset = elf_header_len + elf_code->size + - elf_rodata->size; /* offset of segment */ + elf_rodata->size; /* offset of segment */ + if (!dynlink) + phdr.p_offset = ALIGN_UP(phdr.p_offset, PAGESIZE); phdr.p_vaddr = elf_data_start; /* virtual address */ phdr.p_paddr = elf_data_start; /* physical address */ phdr.p_filesz = elf_data->size; /* size in file */ @@ -803,6 +809,9 @@ void elf_generate_section_headers(void) sh_name += strlen(".dynamic") + 1; } + if (!dynlink) + ofs = ALIGN_UP(ofs, PAGESIZE); + /* .data */ shdr.sh_name = sh_name; shdr.sh_type = 1; @@ -919,6 +928,7 @@ void elf_generate_dynamic_sections(void) * .plt section is generated at the code generation phase. */ int dymsym_idx = 1, func_plt_ofs, st_name = 0; + int libdl_name = 0; int rel_offset; /* .interp section */ @@ -942,6 +952,20 @@ void elf_generate_dynamic_sections(void) elf_write_byte(dynamic_sections.elf_dynstr, 0); st_name += strlen(LIBC_SO) + 1; + /* lib/c.h reaches the host's stream objects through dlsym(), which glibc + * moved into libc.so.6 only in 2.34. Earlier releases keep it in + * libdl.so.2, and later ones still ship that name as an empty stub, so a + * program that calls it depends on both. + */ + func_t *dlsym_func = find_func("dlsym"); + + if (dlsym_func && dlsym_func->is_used && !dlsym_func->bbs) { + libdl_name = st_name; + elf_write_str(dynamic_sections.elf_dynstr, LIBDL_SO); + elf_write_byte(dynamic_sections.elf_dynstr, 0); + st_name += strlen(LIBDL_SO) + 1; + } + /* Perform the following steps for each external function. * - Add a new PLT relocation entry to .relplt section. * - Add a new dynamic symbol entry to .dynsym section. @@ -1086,6 +1110,8 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x3, dynamic_sections.elf_got_start); elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); + if (libdl_name) + elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, libdl_name); #if DYN_BIND_NOW == 1 /* Resolve every PLT entry at load time. This target's PLT[0] does not * arrange the GOT[1]/GOT[2] hand-off the lazy resolver needs, so the loader @@ -1263,18 +1289,13 @@ void elf_preprocess(void) /* To prevent two load segments from sharing a common page, add PAGESIZE * to elf_data_start, since the first section of the second load segment * is .data in static linking mode. ELF requires p_offset and p_vaddr to - * agree modulo p_align. ELF64 output pads the file to the next page - * before .data, so derive its virtual address from that same aligned - * file offset rather than merely adding a page to the preceding virtual - * end. + * agree modulo p_align. Derive its virtual address from the same + * aligned file offset used before .data, rather than merely adding a + * page to the preceding virtual end. */ -#if ELF_IS_64 == 1 elf_data_start = ELF_START + ALIGN_UP(elf_header_len + elf_offset + elf_rodata->size, PAGESIZE); -#else - elf_data_start = elf_rodata_start + elf_rodata->size + PAGESIZE; -#endif } elf_bss_start = elf_data_start + elf_data->size; elf_align(elf_symtab); @@ -1360,7 +1381,7 @@ void elf_generate(const char *outfile) elf_write_all(fp, dynamic_sections.elf_dynamic->elements, dynamic_sections.elf_dynamic->size); } -#if ELF_IS_64 == 1 + /* Statically linked, .data begins the second load segment and has to start * on a page boundary so that p_vaddr === p_offset (mod p_align). Linked * dynamically that segment starts back at .interp, and everything from @@ -1386,7 +1407,6 @@ void elf_generate(const char *outfile) left -= n; } } -#endif /* Readable and writable sections */ elf_write_all(fp, elf_data->elements, elf_data->size); @@ -1411,6 +1431,6 @@ void elf_generate(const char *outfile) * yields 0666. Every compiler that reaches here -- host-built, static * self-hosted, or dynamic -- therefore sets the bits explicitly. */ - if (chmod((char *) outfile, 0x1ed) < 0) /* 0755 */ + if (chmod(outfile, 0x1ed) < 0) /* 0755 */ usage_error("Unable to mark output executable"); } diff --git a/src/globals.c b/src/globals.c index dd89767b..2dad38be 100644 --- a/src/globals.c +++ b/src/globals.c @@ -15,6 +15,10 @@ #include "defs.h" +source_location_t *cur_token_loc(void); +__noreturn void error_at(char *msg, source_location_t *loc); +__noreturn void limit_error(char *msg); + /* Forward declaration for string interning */ char *intern_string(char *str); @@ -37,9 +41,20 @@ int types_idx = 0; type_t *TY_void; type_t *TY_char; +type_t *TY_schar; +type_t *TY_uchar; type_t *TY_bool; type_t *TY_int; +type_t *TY_uint; +type_t *TY_long; +type_t *TY_ulong; type_t *TY_short; +type_t *TY_ushort; +type_t *TY_long_long; +type_t *TY_ulong_long; +type_t *TY_float; +type_t *TY_double; +type_t *TY_long_double; /* Arenas */ @@ -106,6 +121,10 @@ bool expand_only = false; bool dump_ir = false; bool dump_dot = false; bool hard_mul_div = false; +bool warn_string_literals = false; +bool strict_c99 = false; +char *include_dirs[MAX_INCLUDE_DIRS]; +int include_dirs_idx = 0; /* Create a new arena block with given capacity. * @capacity: The capacity of the arena block. Must be positive. @@ -588,11 +607,33 @@ type_t *find_type(const char *type_name, int flag) continue; if (!strcmp(TYPES[i].type_name, type_name)) { /* If it is a forwardly declared alias of a structure, return - * the base structure type. + * the base structure type. A function type alias with a void + * return is no such alias despite its zero size. */ - if (TYPES[i].base_type == TYPE_typedef && TYPES[i].size == 0) - return TYPES[i].base_struct; - return &TYPES[i]; + type_t *alias = &TYPES[i]; + type_t *base = alias->base_struct; + + if (alias->base_type != TYPE_typedef || alias->size || + alias->ptr_level || alias->is_direct_function_type) + return alias; + + /* The base would drop the qualifiers of `typedef const struct S + * cs_t;`, so a qualified alias keeps its own descriptor and + * takes the layout once the tag has been completed. + */ + if (!base || (!alias->is_const_qualified && + !alias->is_volatile_qualified)) + return base; + if (!base->size) + return alias; + alias->size = base->size; + alias->alignment = base->alignment; + alias->fields = base->fields; + alias->num_fields = base->num_fields; + alias->is_union = base->is_union; + alias->has_flexible_array_member = + base->has_flexible_array_member; + return alias; } } } @@ -601,11 +642,8 @@ type_t *find_type(const char *type_name, int flag) ph2_ir_t *add_existed_ph2_ir(ph2_ir_t *ph2_ir) { - if (ph2_ir_idx >= MAX_IR_INSTR) { - printf("Error: too many phase-2 IR instructions\n"); - fflush(stdout); /* see fatal() */ - abort(); - } + if (ph2_ir_idx >= MAX_IR_INSTR) + limit_error("too many phase-2 IR instructions"); PH2_IR_FLATTEN[ph2_ir_idx++] = ph2_ir; return ph2_ir; } @@ -639,6 +677,7 @@ ph2_ir_t *add_ph2_ir(opcode_t op) ph2_ir->is_pointer = false; ph2_ir->src0_is_pointer = false; ph2_ir->src1_is_pointer = false; + ph2_ir->is_volatile = false; return add_existed_ph2_ir(ph2_ir); } @@ -651,6 +690,9 @@ block_t *add_block(block_t *parent, func_t *func) blk->locals.capacity = 16; blk->locals.elements = arena_alloc(BLOCK_ARENA, blk->locals.capacity * sizeof(var_t *)); + blk->type_tags = NULL; + blk->constants = NULL; + blk->typedefs = NULL; blk->parent = parent; blk->func = func; blk->next = NULL; @@ -702,6 +744,65 @@ int hex_digit_value(char c) return -1; } +/* Encode a valid Unicode scalar value into this implementation's UTF-8 + * execution character set. Narrow literals retain bytes, so this also keeps a + * UCN usable in strings, character constants, and #if character constants. + */ +static int append_utf8(char *output, int out, int limit, unsigned int value) +{ + if (value <= 0x7f) { + if (out + 1 >= limit) + return -1; + output[out++] = value; + } else if (value <= 0x7ff) { + if (out + 2 >= limit) + return -1; + output[out++] = 0xc0 | (value >> 6); + output[out++] = 0x80 | (value & 0x3f); + } else if (value <= 0xffff) { + if (out + 3 >= limit) + return -1; + output[out++] = 0xe0 | (value >> 12); + output[out++] = 0x80 | ((value >> 6) & 0x3f); + output[out++] = 0x80 | (value & 0x3f); + } else { + if (out + 4 >= limit) + return -1; + output[out++] = 0xf0 | (value >> 18); + output[out++] = 0x80 | ((value >> 12) & 0x3f); + output[out++] = 0x80 | ((value >> 6) & 0x3f); + output[out++] = 0x80 | (value & 0x3f); + } + return out; +} + +/* C99 6.4.4.4 requires the value of a hexadecimal escape in a character + * constant or narrow string literal to fit an unsigned char. Report whether a + * literal's spelling holds one that does not. Wide literals accept larger + * values, so only narrow callers ask. + */ +bool hex_escape_exceeds_byte(const char *text) +{ + for (int i = 0; text[i]; i++) { + if (text[i] != '\\') + continue; + i++; + if (!text[i]) + break; + if (text[i] != 'x') + continue; + + unsigned int value = 0; + while (isxdigit((unsigned char) text[i + 1])) { + i++; + value = (value << 4) + hex_digit_value(text[i]); + if (value > 0xff) + return true; + } + } + return false; +} + int unescape_string(const char *input, char *output, int output_size) { if (!input || !output || output_size == 0) @@ -732,6 +833,10 @@ int unescape_string(const char *input, char *output, int output_size) i++; break; case 'e': + if (strict_c99) { + output[j] = '\0'; + return -1; + } output[j++] = 27; i++; break; @@ -768,7 +873,9 @@ int unescape_string(const char *input, char *output, int output_size) i++; break; case 'x': { - /* Hexadecimal escape sequence: \xhh */ + /* C99 hexadecimal escapes consume the complete run of hex digits, + * unlike octal escapes which are limited to three. + */ i++; /* Skips 'x' */ if (!isxdigit(input[i])) { @@ -779,18 +886,52 @@ int unescape_string(const char *input, char *output, int output_size) return -1; } - int value = 0; - int count = 0; - - while (isxdigit(input[i]) && count < 2) { - value = (value << 4) + hex_digit_value(input[i]); + /* Only the low byte reaches the output, so keep only that much + * rather than shift a long digit run out of range. A narrow literal + * whose value does not fit has already been diagnosed by + * hex_escape_exceeds_byte(); a wide one takes its value from + * decode_wstring_units() instead. + */ + unsigned int value = 0; + while (isxdigit(input[i])) { + value = ((value << 4) + hex_digit_value(input[i])) & 0xff; i++; - count++; } output[j++] = (char) value; break; } + case 'u': + case 'U': { + int digits = input[i] == 'u' ? 4 : 8; + unsigned int value = 0; + + i++; + for (int digit = 0; digit < digits; digit++) { + if (!isxdigit(input[i])) { + output[j] = '\0'; + return -1; + } + value = (value << 4) + hex_digit_value(input[i++]); + } + + /* C99 6.4.3 excludes surrogate code points, values above the + * Unicode range, and basic-source characters other than $, @, and + * `. The latter must be spelt directly in source. + */ + if (value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff) || + (value < 0xa0 && value != '$' && value != '@' && + value != '`')) { + output[j] = '\0'; + return -1; + } + j = append_utf8(output, j, output_size, value); + if (j < 0) { + output[0] = '\0'; + return -1; + } + break; + } case '0': case '1': case '2': @@ -828,15 +969,180 @@ int unescape_string(const char *input, char *output, int output_size) return j; } +/* C99 permits multi-character constants with an implementation-defined int + * value. shecc packs their first four bytes left to right. + */ +int parse_character_constant(const char *literal) +{ + char unescaped[MAX_TOKEN_LEN]; + unsigned int value = 0; + int length = unescape_string(literal, unescaped, sizeof(unescaped)); + + if (length < 0) + return 0; + for (int i = 0; i < length && i < 4; i++) + value = (value << 8) | (unsigned char) unescaped[i]; + return (int) value; +} + +/* The narrow decoder translates UCNs to UTF-8, which is correct for char + * strings but not for a wide literal: one UCN is one wchar_t element. This + * execution-wide-character policy stores each source byte/escape value as an + * int unit and preserves UCN scalar values directly. + */ +int decode_wstring_units(const char *text, int *units, int capacity) +{ + int in = 0; + int out = 0; + + while (text[in]) { + unsigned int value; + + if (out >= capacity) + return -1; + if (text[in] != '\\') { + units[out++] = (unsigned char) text[in++]; + continue; + } + in++; + switch (text[in]) { + case 'a': + value = '\a'; + in++; + break; + case 'b': + value = '\b'; + in++; + break; + case 'f': + value = '\f'; + in++; + break; + case 'n': + value = '\n'; + in++; + break; + case 'r': + value = '\r'; + in++; + break; + case 't': + value = '\t'; + in++; + break; + case 'v': + value = '\v'; + in++; + break; + case '\\': + value = '\\'; + in++; + break; + case '\'': + value = '\''; + in++; + break; + case '"': + value = '"'; + in++; + break; + case '?': + value = '?'; + in++; + break; + case 'e': + if (strict_c99) + return -1; + value = 27; + in++; + break; + case 'x': + in++; + if (!isxdigit(text[in])) + return -1; + value = 0; + while (isxdigit(text[in])) { + if (value > 0x07ffffffU) + return -1; + value = (value << 4) + hex_digit_value(text[in++]); + } + if (value > 0x7fffffffU) + return -1; + break; + case 'u': + case 'U': { + int digits = text[in] == 'u' ? 4 : 8; + + value = 0; + in++; + for (int i = 0; i < digits; i++) { + if (!isxdigit(text[in])) + return -1; + if (value > 0x07ffffffU) + return -1; + value = (value << 4) + hex_digit_value(text[in++]); + } + if (value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff) || + (value < 0xa0 && value != '$' && value != '@' && value != '`')) + return -1; + break; + } + default: + if (text[in] < '0' || text[in] > '7') + value = (unsigned char) text[in++]; + else { + value = 0; + for (int i = 0; i < 3 && text[in] >= '0' && text[in] <= '7'; + i++) + value = value * 8 + (text[in++] - '0'); + } + break; + } + units[out++] = (int) value; + } + return out; +} + +/* A wide character constant that holds one execution-wide unit has the value of + * that unit. Its hexadecimal and octal escapes are therefore not cut to the + * byte a narrow constant keeps, and a UCN is not spread over UTF-8 bytes. A + * constant of several units keeps the implementation-defined packing of + * parse_character_constant(). + * + * Returns false when an escape does not fit a unit. + */ +bool wide_character_constant(const char *literal, int *value) +{ + int units[MAX_TOKEN_LEN]; + int length = decode_wstring_units(literal, units, MAX_TOKEN_LEN); + + *value = 0; + if (length < 0) + return false; + *value = length == 1 ? units[0] : parse_character_constant(literal); + return true; +} + +/* The value of an integer literal in one word. A literal past that word keeps + * its low bits; the accumulator is unsigned so that is a wrap, never the host + * compiler's signed overflow. Constant expressions that can hold such a literal + * fold it through the two-word evaluator instead. + */ int parse_numeric_constant(const char *buffer) { int i = 0; - int value = 0; + unsigned int value = 0; while (buffer[i]) { + /* The lexer keeps C99 integer suffixes in the token. Their type is + * selected by the parser; they are not digits of the value. + */ + if ((buffer[i] | 32) == 'u' || (buffer[i] | 32) == 'l') + break; if (i == 1 && (buffer[i] | 32) == 'x') { /* hexadecimal */ value = 0; i = 2; - while (buffer[i]) { + while (buffer[i] && (buffer[i] | 32) != 'u' && + (buffer[i] | 32) != 'l') { char c = buffer[i++]; value <<= 4; if (isdigit(c)) @@ -850,7 +1156,8 @@ int parse_numeric_constant(const char *buffer) if (i == 1 && (buffer[i] | 32) == 'b') { /* binary */ value = 0; i = 2; - while (buffer[i]) { + while (buffer[i] && (buffer[i] | 32) != 'u' && + (buffer[i] | 32) != 'l') { char c = buffer[i++]; value <<= 1; value += (c == '1'); @@ -893,11 +1200,11 @@ void type_ensure_fields(type_t *type) type_t *add_type(void) { - if (types_idx >= MAX_TYPES) { - printf("Error: Maximum number of types (%d) exceeded\n", MAX_TYPES); - fflush(stdout); /* see fatal() */ - abort(); - } + /* Every struct, union and enum specifier with a body, and every typedef, + * takes an entry, so a large enough input reaches the end of the table. + */ + if (types_idx >= MAX_TYPES) + limit_error("Maximum number of types exceeded"); type_t *t = &TYPES[types_idx++]; t->fields = NULL; return t; @@ -938,6 +1245,7 @@ void add_constant(char alias[], int value) /* Use interned string for constant name */ strcpy(constant->alias, intern_string(alias)); constant->value = value; + constant->next = NULL; hashmap_put(CONSTANTS_MAP, alias, constant); } @@ -946,13 +1254,345 @@ constant_t *find_constant(char alias[]) return hashmap_get(CONSTANTS_MAP, alias); } +/* Enum names declared in a block shadow enclosing names, but must disappear + * with that block. Keep their bindings on the parser's existing block tree. + */ +void add_scoped_constant(block_t *block, char alias[], int value) +{ + constant_t *constant = arena_alloc_constant(); + + if (!constant) + fatal("Failed to allocate scoped enum constant"); + strcpy(constant->alias, intern_string(alias)); + constant->value = value; + constant->next = block->constants; + block->constants = constant; +} + +/* The kinds of binding an ordinary identifier can have in one scope. Objects, + * functions, typedef names and enumeration constants share C99's ordinary + * identifier name space (6.2.3p1), so each scope binds a name to at most one of + * them. The values are bits, so a lookup can ask for several kinds at once. + */ +typedef enum { + ORDINARY_NONE = 0, + ORDINARY_CONSTANT = 1, /* enumeration constant declared in the block */ + ORDINARY_VARIABLE = 2, /* local object, or block-scope function alias */ + ORDINARY_TYPEDEF = 4, /* typedef name declared in the block */ + ORDINARY_PARAMETER = 8, /* parameter, seen from the function body's block */ + ORDINARY_ANY = 15 +} ordinary_kind_t; + +/* The first local of @block at or after index *@pos named @name, or NULL, with + * *@pos left just past it so that a caller can continue to the next one. + * + * A block's locals include all of its IR temporaries, and every declaration + * scans its block's locals at least twice, so the scan is quadratic in the size + * of a block and its compare is what a large one pays for. Settle the first two + * bytes before the library call: temporaries differ in the first, and names + * sharing a prefix letter, such as a run of generated declarations, usually in + * the second. A nonzero first byte that matches means neither name has ended. + */ +var_t *find_block_local(block_t *block, const char *name, int *pos) +{ + char head = name[0]; + char second = head ? name[1] : 0; + + for (int i = *pos; i < block->locals.size; i++) { + var_t *var = block->locals.elements[i]; + const char *var_name = var->var_name; + + if (var_name[0] != head || (head && var_name[1] != second)) + continue; + if (!strcmp(var_name, name)) { + *pos = i + 1; + return var; + } + } + *pos = block->locals.size; + return NULL; +} + +/* The binding @name has in @block's own scope among the @kinds asked for, or + * ORDINARY_NONE. The outermost block of a function body shares the scope of the + * function's parameters (C99 6.2.1p4); a nested block only hides them. When + * @binding is not NULL it receives the constant_t, var_t or typedef_binding_t + * found. A valid program binds a name once per scope, so the order in which the + * kinds are tried only decides what an invalid redeclaration reports. + */ +ordinary_kind_t find_block_ordinary(block_t *block, + const char *name, + int kinds, + void **binding) +{ + char head = name[0]; + void *unused; + + if (!binding) + binding = &unused; + if (kinds & ORDINARY_CONSTANT) { + for (constant_t *constant = block->constants; constant; + constant = constant->next) { + if (!strcmp(constant->alias, name)) { + *binding = constant; + return ORDINARY_CONSTANT; + } + } + } + if (kinds & ORDINARY_VARIABLE) { + int pos = 0; + + *binding = find_block_local(block, name, &pos); + if (*binding) + return ORDINARY_VARIABLE; + } + if (kinds & ORDINARY_TYPEDEF) { + for (typedef_binding_t *td = block->typedefs; td; td = td->next) { + if (td->name[0] == head && !strcmp(td->name, name)) { + *binding = td; + return ORDINARY_TYPEDEF; + } + } + } + if ((kinds & ORDINARY_PARAMETER) && !block->parent && block->func) { + func_t *func = block->func; + + for (int i = 0; i < func->num_params; i++) { + var_t *param = &func->param_defs[i]; + + if (param->var_name[0] == head && !strcmp(param->var_name, name)) { + *binding = param; + return ORDINARY_PARAMETER; + } + } + } + return ORDINARY_NONE; +} + +/* The enumeration constant @alias as seen from @block, or NULL when none is + * visible. Constants share the ordinary identifier name space, so an object, + * parameter or typedef name declared in a nearer scope hides an outer constant + * of the same name (C99 6.2.1p4). + * + * Every identifier operand asks, and nearly all of them name no constant, so + * find the constant first, which costs no scan of any block's locals. Only when + * there is one are the scopes between the use and the constant's own searched + * for a binding that hides it. + */ +constant_t *find_scoped_constant(char alias[], block_t *block) +{ + block_t *owner; + void *constant = NULL; + + for (owner = block; owner && owner != GLOBAL_BLOCK; owner = owner->parent) + if (find_block_ordinary(owner, alias, ORDINARY_CONSTANT, &constant)) + break; + if (!owner || owner == GLOBAL_BLOCK) + constant = find_constant(alias); + if (!constant) + return NULL; + for (; block != owner; block = block->parent) { + if (find_block_ordinary( + block, alias, + ORDINARY_VARIABLE | ORDINARY_TYPEDEF | ORDINARY_PARAMETER, + NULL)) + return NULL; + } + return constant; +} + +void add_type_tag(block_t *block, char name[], type_t *type) +{ + type_tag_t *tag = arena_alloc(BLOCK_ARENA, sizeof(type_tag_t)); + + if (strlen(name) >= MAX_TYPE_LEN) + fatal("Type name too long"); + strcpy(tag->name, intern_string(name)); + tag->type = type; + tag->next = block->type_tags; + block->type_tags = tag; +} + +type_t *find_local_type_tag(char name[], block_t *block) +{ + for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) + if (!strcmp(tag->name, name)) + return tag->type; + return NULL; +} + +/* An enum type is int based (see initialize_enum_type()), which is also what + * tells its tag apart from a struct or union tag. + */ +#define ENUM_TAG_KIND TYPE_int + +/* C99 6.7.2.3p3: every declaration of a tag names the same kind of type, and + * struct, union and enum tags share one name space, so a tag found under + * another keyword is an error rather than a miss. @kind is TYPE_struct, + * TYPE_union or ENUM_TAG_KIND. + */ +static type_t *check_tag_kind(type_t *type, base_type_t kind) +{ + if (type && type->base_type != kind) + error_at("tag was previously declared as a different kind of tag", + cur_token_loc()); + return type; +} + +/* The tag @name of @kind as seen from @block, or NULL when no tag of that name + * is visible. Tags are registered with the block that declares them, file-scope + * ones with GLOBAL_BLOCK, which a function body's chain does not reach on its + * own, so a NULL @block sees file scope only. A tag declared in some other + * block is never found. Looking in the tag table, not the type table, also + * keeps a typedef name out of it. + */ +static type_t *find_visible_tag(char name[], block_t *block, base_type_t kind) +{ + type_t *type = NULL; + + for (; block && !type; block = block->parent) + type = find_local_type_tag(name, block); + if (!type) + type = find_local_type_tag(name, GLOBAL_BLOCK); + return check_tag_kind(type, kind); +} + +/* Create the incomplete struct or union tag @name of @kind in @block. */ +static type_t *declare_record_tag(char name[], block_t *block, base_type_t kind) +{ + type_t *type = add_named_type(name); + + type->base_type = kind; + add_type_tag(block, name, type); + return type; +} + +/* The struct or union tag @name as seen from @block, spelled with the keyword + * for @kind, or NULL when no such tag is visible. + */ +type_t *find_record_tag(char name[], block_t *block, base_type_t kind) +{ + return find_visible_tag(name, block, kind); +} + +/* A struct or union specifier with no member list: the visible tag, or else a + * new incomplete one in the current scope (C99 6.7.2.3p8), file scope when + * @block is NULL. An object of that type is rejected once its declarator is + * read. + */ +type_t *reference_record_tag(char name[], block_t *block, base_type_t kind) +{ + type_t *type = find_record_tag(name, block, kind); + + return type ? type + : declare_record_tag(name, block ? block : GLOBAL_BLOCK, kind); +} + +/* The tag @name that @block itself declares, created incomplete when it has + * none yet. A member list or a bare "struct tag;" declares the tag in the + * current scope, shadowing any outer one (C99 6.7.2.3p5 and p7). + */ +type_t *local_record_tag(char name[], block_t *block, base_type_t kind) +{ + type_t *type = check_tag_kind(find_local_type_tag(name, block), kind); + + return type ? type : declare_record_tag(name, block, kind); +} + +/* Open the member list of the struct or union tag @tag. C99 6.7.2.3p1 lets a + * scope define a tag's content only once, and a definition of the same tag + * inside that member list is a second one in the same scope, since a member + * list opens no scope of its own. The tag is still incomplete there, so mark it + * now rather than rely on its field count. + */ +void begin_record_definition(type_t *tag) +{ + if (tag->num_fields || tag->definition_started) + error_at("redefinition of struct or union tag", cur_token_loc()); + tag->definition_started = true; +} + +/* The enum tag @name as seen from @block, or NULL when none is visible. */ +type_t *find_enum_tag(char name[], block_t *block) +{ + return find_visible_tag(name, block, ENUM_TAG_KIND); +} + +/* The enum tag @name named by a specifier seen from @block. Unlike a record + * tag, an enum tag is never incomplete (C99 6.7.2.3p2 requires an enumerator + * list before the type is used), so a name with no visible definition is an + * error. + */ +type_t *reference_enum_tag(char name[], block_t *block) +{ + type_t *type = find_enum_tag(name, block); + + if (!type) + error_at("Unknown enum type: C99 forbids forward references to enums", + cur_token_loc()); + return type; +} + +/* The enum tag @name that @block itself declares, or NULL. */ +type_t *local_enum_tag(char name[], block_t *block) +{ + return check_tag_kind(find_local_type_tag(name, block), ENUM_TAG_KIND); +} + +bool find_block_typedef(block_t *block, const char *name) +{ + return find_block_ordinary(block, name, ORDINARY_TYPEDEF, NULL) != + ORDINARY_NONE; +} + +/* A nested block may legally shadow a parameter with a typedef, but the + * function body's outermost block shares the parameters' scope. + */ +void add_block_typedef(block_t *block, char name[], type_t *type) +{ + if (find_block_ordinary(block, name, ORDINARY_ANY, NULL)) + error_at("typedef name conflicts with an ordinary identifier", + cur_token_loc()); + + typedef_binding_t *binding; + + binding = arena_alloc(BLOCK_ARENA, sizeof(*binding)); + binding->name = intern_string(name); + binding->type = type; + binding->next = block->typedefs; + block->typedefs = binding; +} + +/* Ordinary identifiers and typedef names share C's ordinary identifier + * namespace. Search each lexical scope inward-out so an object declaration + * masks an outer typedef before the global type table is considered. + */ +type_t *find_visible_type(const char *name, block_t *block) +{ + /* Parameters are tried with the function body's outermost block, so they + * too hide a file-scope typedef unless a nearer binding has hidden them. + */ + for (; block; block = block->parent) { + void *binding; + ordinary_kind_t kind = + find_block_ordinary(block, name, ORDINARY_ANY, &binding); + + if (kind == ORDINARY_TYPEDEF) + return ((typedef_binding_t *) binding)->type; + if (kind != ORDINARY_NONE) + return NULL; + } + return find_type(name, 1); +} + var_t *find_member(const char token[], type_t *type) { - /* If it is a forwardly declared alias of a structure, switch to the base - * structure type. A scalar -- or "void", whose size is also 0 -- has no - * base to switch to, and following the NULL was a SIGSEGV. + /* An alias that names a structure tag instead of defining the members, + * whether forward declared or as a pointer as in "typedef struct S *SP", + * finds them on the tag. A scalar -- or "void" -- has no base to switch to, + * and following the NULL was a SIGSEGV. */ - if (type->size == 0) + if (!type->num_fields) type = type->base_struct; if (!type) return NULL; @@ -1030,6 +1670,8 @@ var_t *find_var(char *token, block_t *parent) int size_var(var_t *var) { int size; + if (var->is_flexible_array_member) + return 0; if (var->ptr_level > 0 || var->is_func) { /* Pointers and function pointers occupy a target pointer, which is 8 * bytes on LP64 targets and 4 on the 32-bit ones. @@ -1198,7 +1840,7 @@ void *arena_grow(arena_t *arena, { int new_cap = *cap ? *cap << 1 : first; if (limit && new_cap > limit) - fatal(what); + limit_error(what); void *grown = arena_realloc(arena, ptr, *cap * elem_sz, new_cap * elem_sz); *cap = new_cap; return grown; @@ -1366,6 +2008,19 @@ void add_symbol(basic_block_t *bb, var_t *var) } } +/* Whether @var is a volatile object named by its declaration, as opposed to a + * temporary the parser generated. Temporaries are named ".tN". + */ +bool var_is_volatile_object(const var_t *var) +{ + return var && var->is_volatile && var->var_name[0] != '.'; +} + +/* A volatile object a primary expression has named and no instruction has read + * yet. See discard_operand(). + */ +var_t *unread_volatile_object; + void add_insn(block_t *block, basic_block_t *bb, opcode_t op, @@ -1400,6 +2055,13 @@ void add_insn(block_t *block, n->str = str ? intern_string(str) : NULL; + /* An instruction reading the object discard_operand() watches has given it + * the read it is owed. + */ + if (unread_volatile_object && + (rs1 == unread_volatile_object || rs2 == unread_volatile_object)) + unread_volatile_object = NULL; + /* Mark variables as address-taken to prevent incorrect constant * optimization */ @@ -1408,6 +2070,14 @@ void add_insn(block_t *block, rs1->is_const = false; /* disable constant optimization */ } + /* A volatile object can be read or written behind the program's back, and + * every access to it is a side effect (C99 6.7.3p6). Keep a local one in + * its slot as though its address had escaped, so that each access by name + * reaches memory rather than a register copy. + */ + if (op == OP_allocat && rd && rd->is_volatile && !rd->is_global) + rd->address_taken = true; + if (!bb->insn_list.head) bb->insn_list.head = n; else @@ -1425,6 +2095,7 @@ strbuf_t *strbuf_create(int init_capacity) array->size = 0; array->capacity = init_capacity; + array->plain_source = false; array->elements = malloc(array->capacity * sizeof(char)); if (!array->elements) { free(array); @@ -1732,6 +2403,18 @@ void global_release(void) if (TOKEN_ARENA) arena_free(TOKEN_ARENA); arena_free(GENERAL_ARENA); /* free TYPES and PH2_IR_FLATTEN */ + + /* Every value in TOKEN_CACHE is one heap-allocated token_stream_t: the + * tokens it spans come from TOKEN_ARENA, which is already gone, but the + * header itself is malloc'd by the two gen_*_token_stream functions and + * hashmap_free() releases only the table, never the values. + */ + if (TOKEN_CACHE) { + for (int i = 0; i < TOKEN_CACHE->cap; i++) { + if (TOKEN_CACHE->table[i].occupied) + free(TOKEN_CACHE->table[i].val); + } + } hashmap_free(TOKEN_CACHE); hashmap_free(SRC_FILE_MAP); hashmap_free(FUNC_MAP); @@ -1760,6 +2443,12 @@ void global_release(void) strbuf_free(dynamic_sections.elf_got); } +/* The function whose body the back half of the pipeline is working on, or NULL + * before register allocation. An internal failure there carries no source + * position, so naming the function is what points back at the input. + */ +char *fatal_function_context = NULL; + /* Reports a broken invariant, which has no position in the source to point at * because nothing in the source is necessarily wrong. This one abort()s: a core * dump is what makes an internal failure debuggable. A mistake in the input @@ -1767,18 +2456,21 @@ void global_release(void) */ __noreturn void fatal(const char *msg) { - printf("[Error]: %s\n", msg); + if (fatal_function_context) + printf("[Error]: %s (in function '%s')\n", msg, fatal_function_context); + else + printf("[Error]: %s\n", msg); /* abort() does not flush, so a diagnostic written to a pipe -- a build log, * or any invocation whose output is captured -- is discarded and the * compiler appears to die silently. * * The stream is NULL rather than stdout because a dynamically linked build - * resolves fflush through the PLT to the host libc, for which lib/c.h's - * 'stdout' -- the plain file descriptor 1 -- is not a FILE *. NULL means - * "every stream" there and is ignored by the unbuffered embedded libc, so - * it is right for both. That build needs the flush most, being the only one - * whose stdio actually buffers. + * resolves fflush through the PLT to the host libc, where error output may + * sit in any of its buffered streams. NULL means "every stream" there and + * is ignored by the unbuffered embedded libc, so it is right for both. That + * build needs the flush most, being the only one whose stdio actually + * buffers. */ fflush(NULL); abort(); @@ -1795,6 +2487,18 @@ __noreturn void usage_error(const char *msg) exit(1); } +/* Reports an input that exceeds one of the compiler's fixed limits, such as the + * size of the type table or the predecessors of one basic block. The program + * may be valid C, but the limit is not a broken invariant either, so it exits + * through error_at() rather than taking fatal()'s core dump. The current token + * is quoted while the parser still holds one; a limit reached after the token + * arena is released has no line to point at. + */ +__noreturn void limit_error(char *msg) +{ + error_at(msg, cur_token && TOKEN_ARENA ? cur_token_loc() : NULL); +} + /* Reports a mistake in the input, quoting the line it sits on. A program the * compiler refuses is not a broken invariant, so this exits the way * usage_error() does rather than abort()ing: an ordinary syntax error should diff --git a/src/lexer.c b/src/lexer.c index 562c0a48..3a1fc434 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -11,8 +11,8 @@ #include "globals.c" /* Hash table constants */ -#define NUM_DIRECTIVES 11 -#define NUM_KEYWORDS 18 +#define NUM_DIRECTIVES 12 +#define NUM_KEYWORDS 32 /* Token mapping structure for elegant initialization */ typedef struct { @@ -46,7 +46,7 @@ void lex_init_directives(void) {"#error", T_cppd_error}, {"#if", T_cppd_if}, {"#ifdef", T_cppd_ifdef}, {"#ifndef", T_cppd_ifndef}, {"#include", T_cppd_include}, {"#pragma", T_cppd_pragma}, - {"#undef", T_cppd_undef}, + {"#undef", T_cppd_undef}, {"#line", T_cppd_line}, }; /* hashmap insertion */ @@ -88,6 +88,20 @@ void lex_init_keywords(void) {"goto", T_goto}, {"union", T_union}, {"const", T_const}, + {"volatile", T_volatile}, + {"static", T_static}, + {"extern", T_extern}, + {"register", T_register}, + {"auto", T_auto}, + {"restrict", T_restrict}, + {"inline", T_inline}, + {"signed", T_signed}, + {"unsigned", T_unsigned}, + {"long", T_long}, + {"float", T_float}, + {"double", T_double}, + {"_Complex", T_complex}, + {"_Imaginary", T_imaginary}, }; /* hashmap insertion */ @@ -145,19 +159,136 @@ void lexer_cleanup(void) keyword_tokens_storage = NULL; } -char peek_char(strbuf_t *buf, int offset) +/* C99 translation phase 1 replaces trigraphs before every later lexical + * decision. Keep the source buffer immutable so locations and quoted-include + * recovery retain physical offsets; this view maps one logical character to + * either one physical byte or a three-byte trigraph spelling. + */ +char trigraph_char_at(strbuf_t *buf, int pos) { - if (buf->size + offset >= buf->capacity) + char third; + + if (buf->plain_source || pos + 2 >= buf->capacity || + buf->elements[pos] != '?' || buf->elements[pos + 1] != '?') return '\0'; - return buf->elements[buf->size + offset]; + third = buf->elements[pos + 2]; + switch (third) { + case '=': + return '#'; + case '/': + return '\\'; + case '\'': + return '^'; + case '(': + return '['; + case ')': + return ']'; + case '!': + return '|'; + case '<': + return '{'; + case '>': + return '}'; + case '-': + return '~'; + default: + return '\0'; + } +} + +int source_char_width(strbuf_t *buf, int pos) +{ + return trigraph_char_at(buf, pos) ? 3 : 1; +} + +char source_char_at(strbuf_t *buf, int pos) +{ + char replacement; + + if (pos >= buf->capacity) + return '\0'; + replacement = trigraph_char_at(buf, pos); + return replacement ? replacement : buf->elements[pos]; +} + +/* Phase 2 operates on the phase-1 trigraph view. Preserve physical source + * bytes, but skip every logical backslash/newline pair before token formation. + */ +int skip_splices(strbuf_t *buf, int pos) +{ + int width; + + if (buf->plain_source) + return pos; + while (pos < buf->capacity) { + width = source_char_width(buf, pos); + if (source_char_at(buf, pos) != '\\' || + source_char_at(buf, pos + width) != '\n') + break; + pos += width + 1; + } + return pos; +} + +char peek_char(strbuf_t *buf, int offset) +{ + int pos = buf->size; + + if (buf->plain_source) { + if (pos < buf->capacity) { + pos += offset; + if (pos > buf->capacity) + pos = buf->capacity; + } + return pos >= buf->capacity ? '\0' : buf->elements[pos]; + } + + pos = skip_splices(buf, pos); + + while (offset-- > 0 && pos < buf->capacity) { + pos += source_char_width(buf, pos); + pos = skip_splices(buf, pos); + } + return source_char_at(buf, pos); } char read_char(strbuf_t *buf) { - if (buf->size + 1 >= buf->capacity) - return buf->elements[buf->capacity - 1]; - buf->size++; - return buf->elements[buf->size]; + int pos = buf->size; + + if (buf->plain_source) { + if (pos + 1 >= buf->capacity) + return buf->elements[buf->capacity - 1]; + buf->size = pos + 1; + return buf->elements[buf->size]; + } + + pos = skip_splices(buf, pos); + + if (pos + source_char_width(buf, pos) >= buf->capacity) + return source_char_at(buf, buf->capacity - 1); + pos += source_char_width(buf, pos); + buf->size = skip_splices(buf, pos); + return source_char_at(buf, buf->size); +} + +/* Translation phases 1 and 2 are the identity on a source with no "??" and no + * backslash-newline, which is nearly every file. Check that once per buffer so + * the per-character readers above can index its bytes directly instead of + * testing each position for a trigraph and a splice. + */ +void classify_plain_source(strbuf_t *buf) +{ + buf->plain_source = true; + for (int i = 0; i + 1 < buf->capacity; i++) { + char c = buf->elements[i]; + + if ((c == '?' && buf->elements[i + 1] == '?') || + (c == '\\' && buf->elements[i + 1] == '\n')) { + buf->plain_source = false; + return; + } + } } /* Fill @dst with @len bytes of @f, returning how many arrived. @@ -240,10 +371,46 @@ token_t *new_token(token_kind_t kind, const source_location_t *loc, int len) return token; } +/* A '#' opens a directive only when nothing but white space precedes it on its + * logical line. The physical column cannot tell: a backslash-newline puts a '#' + * at column 1 of the next physical line while phase 2 has already joined it to + * the text before. So every token lex_token() returns updates this instead. + */ +bool lex_at_line_start = true; + /* Skipping a comment or a run of whitespace resumes the scan, and lex_layout() * below does that by starting a fresh token here. */ token_t *lex_token(strbuf_t *buf, source_location_t *loc); +char read_layout_char(strbuf_t *buf, source_location_t *loc); + +/* Readers consume logical characters, but diagnostics index immutable physical + * source bytes. The source span is finalized at each lex_token() exit; readers + * which cross a newline update the logical cursor themselves. + */ +#define RETURN_LEX_TOKEN(tk) \ + do { \ + int pos, line, column; \ + tk->location.len = buf->size - tk->location.pos; \ + pos = tk->location.pos; \ + line = tk->location.line; \ + column = tk->location.column; \ + while (pos < buf->size) { \ + if (buf->elements[pos] == '\n') { \ + line++; \ + column = 1; \ + } else \ + column++; \ + pos++; \ + } \ + loc->line = line; \ + loc->column = column; \ + if (tk->kind == T_newline) \ + lex_at_line_start = true; \ + else if (tk->kind != T_whitespace && tk->kind != T_tab) \ + lex_at_line_start = false; \ + return tk; \ + } while (0) /* Preprocessor directives, comments, and the whitespace between tokens. * @@ -255,50 +422,104 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) token_t *token; char token_buffer[MAX_TOKEN_LEN]; - if (ch == '#') { + if (ch == '#' || (ch == '%' && peek_char(buf, 1) == ':')) { + bool is_digraph_hash = ch == '%'; + int hash_len = is_digraph_hash ? 2 : source_char_width(buf, buf->size); + /* Inside a macro replacement list '#' stringifies the parameter that * follows and '##' pastes its neighbours. Neither can be a directive, * which only exists at the start of a line. */ - if (peek_char(buf, 1) == '#') { - read_char(buf); - read_char(buf); - token = new_token(T_hashhash, loc, 2); - loc->column += 2; + if ((ch == '#' && peek_char(buf, 1) == '#') || + (ch == '%' && peek_char(buf, 1) == ':' && + peek_char(buf, 2) == '%' && peek_char(buf, 3) == ':')) { + int paste_chars = ch == '#' ? 2 : 4; + int paste_len = 0; + + for (int i = 0; i < paste_chars; i++) { + paste_len += source_char_width(buf, buf->size); + read_char(buf); + } + token = new_token(T_hashhash, loc, paste_len); + loc->column += paste_len; return token; } - if (loc->column != 1) { - read_char(buf); - token = new_token(T_hash, loc, 1); - loc->column++; + if (!lex_at_line_start) { + int hash_chars = is_digraph_hash ? 2 : 1; + + for (int i = 0; i < hash_chars; i++) + read_char(buf); + token = new_token(T_hash, loc, hash_len); + loc->column += hash_len; return token; } - int sz = 0; + int sz = 0, source_len = hash_len; - do { + token_buffer[sz++] = '#'; + int hash_chars = is_digraph_hash ? 2 : 1; + for (int i = 0; i < hash_chars; i++) + ch = read_char(buf); + + /* White space may separate '#' from the directive name (C99 6.10p2), + * and a comment is white space by then. RETURN_LEX_TOKEN recounts the + * span from the source, so a comment across lines needs no care here. + */ + for (;;) { + if (ch == ' ' || ch == '\t') { + ch = read_char(buf); + continue; + } + if (ch == '/' && peek_char(buf, 1) == '*') { + read_char(buf); + ch = read_char(buf); + while (ch && !(ch == '*' && peek_char(buf, 1) == '/')) + ch = read_char(buf); + if (!ch) + error_at("Unenclosed C-style comment", loc); + read_char(buf); + ch = read_char(buf); + continue; + } + break; + } + + /* The null directive, a '#' alone on its line (C99 6.10.7), does + * nothing. What remains of the line is layout, and a line comment is + * left to be lexed as one. + */ + if (ch == '\n' || ch == '\0' || (ch == '/' && peek_char(buf, 1) == '/')) + return new_token(T_whitespace, loc, 1); + + while (isalnum(ch) || ch == '_') { if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz; error_at("Token too long", loc); } token_buffer[sz++] = ch; ch = read_char(buf); - } while (isalnum(ch) || ch == '_'); + source_len++; + } token_buffer[sz] = '\0'; + /* Whether a name shecc does not know is an error depends on the group + * it sits in: a skipped group may hold any line that starts with '#'. + * The preprocessor decides once it knows which group that is. + */ token_kind_t directive_kind = lookup_directive(token_buffer); - if (directive_kind == T_identifier) { - loc->len = sz; - error_at("Unsupported directive", loc); - } + if (directive_kind == T_identifier) + directive_kind = T_cppd_unknown; - token = new_token(directive_kind, loc, sz); - loc->column += sz; + token = new_token(directive_kind, loc, source_len); + loc->column += source_len; return token; } - if (ch == '\\') { + /* Leave a UCN to lex_word(); an ordinary backslash remains available for + * the preprocessor's line-splice handling. + */ + if (ch == '\\' && peek_char(buf, 1) != 'u' && peek_char(buf, 1) != 'U') { read_char(buf); token = new_token(T_backslash, loc, 1); loc->column++; @@ -318,33 +539,25 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) if (ch == '*') { /* C-style comment */ - int pos = buf->size; - do { - /* advance one char */ - pos++; - loc->column++; - ch = buf->elements[pos]; - if (ch == '*') { - /* look ahead */ - pos++; - loc->column++; - ch = buf->elements[pos]; - if (ch == '/') { - /* consume closing '/', then commit and skip trailing - * whitespaces - */ - pos++; - loc->column += 2; - buf->size = pos; - return lex_token(buf, loc); - } + loc->column += source_char_width(buf, loc->pos); + loc->column += source_char_width(buf, buf->size); + read_layout_char(buf, loc); + while (peek_char(buf, 0)) { + ch = peek_char(buf, 0); + if (ch == '*' && peek_char(buf, 1) == '/') { + loc->column += source_char_width(buf, buf->size); + read_layout_char(buf, loc); + loc->column += source_char_width(buf, buf->size); + read_layout_char(buf, loc); + return lex_token(buf, loc); } - if (ch == '\n') { - loc->line++; - loc->column = 1; + read_layout_char(buf, loc); + continue; } - } while (ch); + loc->column += source_char_width(buf, buf->size); + read_layout_char(buf, loc); + } error_at("Unenclosed C-style comment", loc); return NULL; @@ -352,13 +565,13 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) if (ch == '/') { /* C++-style comment */ - int pos = buf->size; - do { - pos++; - ch = buf->elements[pos]; - } while (ch && !is_newline(ch)); - loc->column += pos - buf->size + 1; - buf->size = pos; + loc->column += source_char_width(buf, loc->pos); + loc->column += source_char_width(buf, buf->size); + read_layout_char(buf, loc); + while (peek_char(buf, 0) && peek_char(buf, 0) != '\n') { + loc->column += source_char_width(buf, buf->size); + read_layout_char(buf, loc); + } return lex_token(buf, loc); } @@ -403,6 +616,22 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) return NULL; } +/* Append @ch to the numeric literal spelled so far in @token_buffer, reporting + * a literal too long for the buffer. + */ +static int number_append(char token_buffer[], + int sz, + char ch, + source_location_t *loc) +{ + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz; + error_at("Token too long", loc); + } + token_buffer[sz] = ch; + return sz + 1; +} + /* Integer literals, in every base the language accepts. * * Returns NULL when 'ch' is none of its business, so that lex_token() can offer @@ -413,42 +642,47 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) token_t *token; char token_buffer[MAX_TOKEN_LEN]; - if (isdigit(ch)) { + if (isdigit(ch) || (ch == '.' && isdigit(peek_char(buf, 1)))) { int sz = 0; - token_buffer[sz++] = ch; - ch = read_char(buf); - - if (token_buffer[0] == '0' && ((ch | 32) == 'x')) { - /* Hexadecimal: starts with 0x or 0X */ - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz; - error_at("Token too long", loc); + bool is_floating = ch == '.'; + bool is_hex = false; + bool has_hex_exponent = false; + bool has_hex_significand = false; + if (is_floating) { + token_buffer[sz++] = ch; + ch = read_char(buf); + while (isdigit(ch)) { + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); } + } else { token_buffer[sz++] = ch; + ch = read_char(buf); + } + + if (!is_floating && token_buffer[0] == '0' && ((ch | 32) == 'x')) { + /* Hexadecimal: starts with 0x or 0X */ + is_hex = true; + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); - if (!isxdigit(ch)) { - loc->len = 3; - error_at("Invalid hex literal: expected hex digit after 0x", - loc); - } - do { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz; - error_at("Token too long", loc); - } - token_buffer[sz++] = ch; + /* C99 also permits the first hexadecimal significand digit after + * the point (`0x.8p2`), so defer the nonempty-significand check + * until the floating spelling has been recognized. + */ + while (isxdigit(ch)) { + sz = number_append(token_buffer, sz, ch, loc); + has_hex_significand = true; ch = read_char(buf); - } while (isxdigit(ch)); + } - } else if (token_buffer[0] == '0' && ((ch | 32) == 'b')) { + } else if (!is_floating && token_buffer[0] == '0' && + ((ch | 32) == 'b')) { /* Binary literal: 0b or 0B */ - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz; - error_at("Token too long", loc); - } - token_buffer[sz++] = ch; + if (strict_c99) + error_at("binary literals are a GNU extension in C99", loc); + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); if (ch != '0' && ch != '1') { @@ -457,42 +691,125 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) } do { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz; - error_at("Token too long", loc); - } - token_buffer[sz++] = ch; + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); } while (ch == '0' || ch == '1'); - } else if (token_buffer[0] == '0') { + } else if (!is_floating && token_buffer[0] == '0') { /* Octal: starts with 0 but not followed by 'x' or 'b' */ while (isdigit(ch)) { - if (ch >= '8') { - loc->pos += sz; - loc->column += sz; - error_at("Invalid octal digit, must be in range 0-7", loc); - } - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz; - error_at("Token too long", loc); - } - token_buffer[sz++] = ch; + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); } - } else { + } else if (!is_floating) { /* Decimal */ while (isdigit(ch)) { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz; - error_at("Token too long", loc); + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); + } + } + + /* Decimal floating forms are admitted lexically now. They retain the + * original spelling as payload; semantic float types/lowering remain + * deliberately outside this lexer stage. + */ + if (ch == '.') { + is_floating = true; + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); + if (is_hex) { + while (isxdigit(ch)) { + sz = number_append(token_buffer, sz, ch, loc); + has_hex_significand = true; + ch = read_char(buf); } - token_buffer[sz++] = ch; + } else { + while (isdigit(ch)) { + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); + } + } + } + if ((!is_hex && (ch | 32) == 'e') || (is_hex && (ch | 32) == 'p')) { + is_floating = true; + if (is_hex) + has_hex_exponent = true; + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); + if (ch == '+' || ch == '-') { + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); } + if (!isdigit(ch)) + error_at("Floating literal needs an exponent", loc); + do { + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); + } while (isdigit(ch)); + } + if (is_floating) { + if (is_hex && !has_hex_exponent) + error_at("Hexadecimal floating literal needs a p exponent", + loc); + if (is_hex && !has_hex_significand) + error_at("Hexadecimal floating literal needs a significand", + loc); + if ((ch | 32) == 'f' || (ch | 32) == 'l') { + sz = number_append(token_buffer, sz, ch, loc); + ch = read_char(buf); + } + token_buffer[sz] = '\0'; + token = new_token(T_floating, loc, sz); + token->literal = intern_string(token_buffer); + loc->column += sz; + return token; } + /* The floating path above admits a significand digit after the point, + * so only an integer spelling can still lack one here. + */ + if (is_hex && !has_hex_significand) { + loc->len = sz; + error_at("Invalid hex literal: expected hex digit after 0x", loc); + } + if (!is_hex && token_buffer[0] == '0') { + for (int i = 1; i < sz; i++) { + if (token_buffer[i] >= '8' && token_buffer[i] <= '9') { + loc->pos += i; + loc->column += i; + error_at("Invalid octal digit, must be in range 0-7", loc); + } + } + } + if ((ch | 32) == 'p') + error_at("Hexadecimal floating literal needs a significand", loc); + + /* C99 integer suffixes belong to the numeric token rather than starting + * an adjacent identifier. The existing `long` spelling has the same + * 32-bit representation as int. Keep a double-long suffix for the + * parser, which selects the distinct 64-bit type on targets that can + * lower it. + */ + bool has_unsigned_suffix = false; + int long_suffix_count = 0; + while ((ch | 32) == 'u' || (ch | 32) == 'l') { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz; + error_at("Token too long", loc); + } + if ((ch | 32) == 'u') { + if (has_unsigned_suffix) + error_at("Invalid integer literal suffix", loc); + has_unsigned_suffix = true; + } else { + long_suffix_count++; + if (long_suffix_count > 2) + error_at("Invalid integer literal suffix", loc); + } + token_buffer[sz++] = ch; + ch = read_char(buf); + } token_buffer[sz] = '\0'; token = new_token(T_numeric, loc, sz); token->literal = intern_string(token_buffer); @@ -517,23 +834,40 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) int sz = 0; bool special = false; + /* read_char() has already removed every backslash-newline, so a newline + * still here ends the line inside the literal, which neither an s-char + * nor an escape may do (C99 6.4.5). + */ ch = read_char(buf); while (ch != '"' || special) { + if (ch == '\n' || !ch) { + loc->len = 1; + error_at("Unenclosed string literal", loc); + } if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; error_at("String literal too long", loc); } token_buffer[sz++] = ch; - if (ch == '\\') - special = true; - else - special = false; + /* A backslash escapes the character after it, and an escaped + * backslash escapes nothing further, so "\\" still ends at its + * closing quote. + */ + special = ch == '\\' && !special; ch = read_char(buf); } token_buffer[sz] = '\0'; + /* Validate every escape while the literal is still represented by one + * token. Some later string-initializer paths only need its decoded + * bytes and historically did not inspect unescape_string()'s status. + */ + char unescaped[MAX_TOKEN_LEN]; + if (unescape_string(token_buffer, unescaped, sizeof(unescaped)) < 0) + error_at("Invalid escape sequence", loc); + read_char(buf); token = new_token(T_string, loc, sz + 2); token->literal = intern_string(token_buffer); @@ -543,34 +877,41 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) if (ch == '\'') { int sz = 0; - bool escaped = false; + /* As in a string literal, a newline is never part of the constant. */ ch = read_char(buf); - if (ch == '\\') { + while (ch && ch != '\'' && ch != '\n') { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz + 1; + error_at("Character literal too long", loc); + } token_buffer[sz++] = ch; - ch = read_char(buf); - - do { + if (ch == '\\') { + ch = read_char(buf); + if (!ch || ch == '\n') + break; if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; error_at("Character literal too long", loc); } token_buffer[sz++] = ch; - ch = read_char(buf); - escaped = true; - } while (ch && ch != '\''); - } else { - token_buffer[sz++] = ch; + } + ch = read_char(buf); } token_buffer[sz] = '\0'; - if (!escaped) - ch = read_char(buf); - if (ch != '\'') { loc->len = 2; error_at("Unenclosed character literal", loc); } + if (!sz) { + loc->len = 2; + error_at("Empty character constant", loc); + } + + char unescaped[MAX_TOKEN_LEN]; + if (unescape_string(token_buffer, unescaped, sizeof(unescaped)) < 0) + error_at("Invalid escape sequence", loc); read_char(buf); token = new_token(T_char, loc, sz + 2); @@ -612,6 +953,14 @@ token_t *lex_punct(strbuf_t *buf, source_location_t *loc, char ch) return token; } + if (ch == '<' && peek_char(buf, 1) == '%') { + read_char(buf); + read_char(buf); + token = new_token(T_open_curly, loc, 2); + loc->column += 2; + return token; + } + if (ch == '}') { ch = read_char(buf); token = new_token(T_close_curly, loc, 1); @@ -619,6 +968,14 @@ token_t *lex_punct(strbuf_t *buf, source_location_t *loc, char ch) return token; } + if (ch == '%' && peek_char(buf, 1) == '>') { + read_char(buf); + read_char(buf); + token = new_token(T_close_curly, loc, 2); + loc->column += 2; + return token; + } + if (ch == '[') { ch = read_char(buf); token = new_token(T_open_square, loc, 1); @@ -626,6 +983,14 @@ token_t *lex_punct(strbuf_t *buf, source_location_t *loc, char ch) return token; } + if (ch == '<' && peek_char(buf, 1) == ':') { + read_char(buf); + read_char(buf); + token = new_token(T_open_square, loc, 2); + loc->column += 2; + return token; + } + if (ch == ']') { ch = read_char(buf); token = new_token(T_close_square, loc, 1); @@ -633,6 +998,14 @@ token_t *lex_punct(strbuf_t *buf, source_location_t *loc, char ch) return token; } + if (ch == ':' && peek_char(buf, 1) == '>') { + read_char(buf); + read_char(buf); + token = new_token(T_close_square, loc, 2); + loc->column += 2; + return token; + } + if (ch == ',') { ch = read_char(buf); token = new_token(T_comma, loc, 1); @@ -928,6 +1301,71 @@ token_t *lex_operator(strbuf_t *buf, source_location_t *loc, char ch) return NULL; } +/* UCNs in identifiers are lexical spellings, rather than string escapes: the + * token keeps their UTF-8 spelling while its source location counts the raw + * six- or ten-byte `\\u`/`\\U` sequence. Keep this decoder here and + * deliberately leaf-sized. Calling the general literal unescaper from + * lex_word() made the self-hosted compiler's stage-1 build fail before it + * reached user input. + */ +static bool lex_ucn_starts(strbuf_t *buf) +{ + return peek_char(buf, 0) == '\\' && + (peek_char(buf, 1) == 'u' || peek_char((strbuf_t *) buf, 1) == 'U'); +} + +static int lex_ucn_identifier(char *out, int out_size, strbuf_t *buf) +{ + int digits = peek_char(buf, 1) == 'u' ? 4 : 8; + unsigned int value = 0; + int out_len; + + for (int i = 0; i < digits; i++) { + int digit = hex_digit_value(peek_char(buf, i + 2)); + + if (digit < 0) + return -1; + value = (value << 4) | digit; + } + + /* C99 6.4.3 forbids surrogates, out-of-range scalars, and a UCN spelling of + * a basic-source character except $, @, and `. Those three are still + * grammar-level UCN nondigits, even though their direct spellings are not + * ordinary identifier characters. + */ + if ((value < 0xa0 && value != '$' && value != '@' && value != '`') || + value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff)) + return -1; + + if (value <= 0x7f) + out_len = 1; + else if (value <= 0x7ff) + out_len = 2; + else if (value <= 0xffff) + out_len = 3; + else + out_len = 4; + if (out_len >= out_size) + return -1; + + if (out_len == 1) { + out[0] = value; + } else if (out_len == 2) { + out[0] = 0xc0 | (value >> 6); + out[1] = 0x80 | (value & 0x3f); + } else if (out_len == 3) { + out[0] = 0xe0 | (value >> 12); + out[1] = 0x80 | ((value >> 6) & 0x3f); + out[2] = 0x80 | (value & 0x3f); + } else { + out[0] = 0xf0 | (value >> 18); + out[1] = 0x80 | ((value >> 12) & 0x3f); + out[2] = 0x80 | ((value >> 6) & 0x3f); + out[3] = 0x80 | (value & 0x3f); + } + return out_len; +} + /* Identifiers, and the keywords spelled like them. * * Returns NULL when 'ch' is none of its business, so that lex_token() can offer @@ -938,8 +1376,9 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) token_t *token; char token_buffer[MAX_TOKEN_LEN]; - if (isalnum(ch) || ch == '_') { + if (isalnum(ch) || ch == '_' || lex_ucn_starts(buf)) { int sz = 0; + int source_len = 0; do { /* Bounded by the smallest buffer an identifier is ever copied into, * not by the token buffer's own size: lex_ident() and lex_peek() @@ -951,9 +1390,27 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) loc->len = sz; error_at("Identifier too long", loc); } - token_buffer[sz++] = ch; - ch = read_char(buf); - } while (isalnum(ch) || ch == '_'); + if (lex_ucn_starts(buf)) { + int ucn_len = + lex_ucn_identifier(token_buffer + sz, MAX_ID_LEN - sz, buf); + int raw_len = peek_char(buf, 1) == 'u' ? 6 : 10; + + if (ucn_len < 0) { + loc->len = raw_len; + error_at("Invalid universal character name in identifier", + loc); + } + sz += ucn_len; + source_len += raw_len; + for (int i = 0; i < raw_len; i++) + read_char(buf); + ch = peek_char(buf, 0); + } else { + token_buffer[sz++] = ch; + source_len++; + ch = read_char(buf); + } + } while (isalnum(ch) || ch == '_' || lex_ucn_starts(buf)); token_buffer[sz] = 0; /* Fast path for common keywords - avoid hashmap lookup */ @@ -988,7 +1445,7 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) kind = T_goto; break; - case 5: /* 5-letter keywords: while, break, union, const */ + case 5: /* 5-letter keywords: while, break, union, const, float */ if (token_buffer[0] == 'w' && !memcmp(token_buffer, "while", 5)) kind = T_while; else if (token_buffer[0] == 'b' && @@ -1000,11 +1457,25 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) else if (token_buffer[0] == 'c' && !memcmp(token_buffer, "const", 5)) kind = T_const; + else if (token_buffer[0] == 'f' && + !memcmp(token_buffer, "float", 5)) + kind = T_float; break; - case 6: /* 6-letter keywords: return, struct, switch, sizeof */ + case 6: /* 6-letter keywords: return, struct, switch, sizeof, static, + extern, inline + */ if (token_buffer[0] == 'r' && !memcmp(token_buffer, "return", 6)) kind = T_return; + else if (token_buffer[0] == 'e' && + !memcmp(token_buffer, "extern", 6)) + kind = T_extern; + else if (token_buffer[0] == 'i' && + !memcmp(token_buffer, "inline", 6)) + kind = T_inline; + else if (token_buffer[0] == 'd' && + !memcmp(token_buffer, "double", 6)) + kind = T_double; else if (token_buffer[0] == 's') { if (!memcmp(token_buffer, "struct", 6)) kind = T_struct; @@ -1012,6 +1483,8 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) kind = T_switch; else if (!memcmp(token_buffer, "sizeof", 6)) kind = T_sizeof; + else if (!memcmp(token_buffer, "static", 6)) + kind = T_static; } break; @@ -1022,9 +1495,15 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) kind = T_default; break; - case 8: /* 8-letter keywords: continue */ + case 8: /* 8-letter keywords: continue, register, restrict, volatile */ if (!memcmp(token_buffer, "continue", 8)) kind = T_continue; + else if (!memcmp(token_buffer, "register", 8)) + kind = T_register; + else if (!memcmp(token_buffer, "restrict", 8)) + kind = T_restrict; + else if (!memcmp(token_buffer, "volatile", 8)) + kind = T_volatile; break; default: @@ -1037,12 +1516,14 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) * name outside that range cannot be one and needs no lookup -- which is * most of the identifiers in a real program. */ - if (kind == T_identifier && sz >= 2 && sz <= 8) + if (kind == T_identifier && !strcmp(token_buffer, "_Imaginary")) + kind = T_imaginary; + else if (kind == T_identifier && sz >= 2 && sz <= 8) kind = lookup_keyword(token_buffer); - token = new_token(kind, loc, sz); + token = new_token(kind, loc, source_len); token->literal = intern_string(token_buffer); - loc->column += sz; + loc->column += source_len; return token; } @@ -1057,38 +1538,158 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) token_t *lex_token(strbuf_t *buf, source_location_t *loc) { token_t *token; - char ch = peek_char(buf, 0); + char ch; + while (source_char_at(buf, buf->size) == '\\' && + source_char_at(buf, buf->size + source_char_width(buf, buf->size)) == + '\n') { + buf->size += source_char_width(buf, buf->size) + 1; + loc->line++; + loc->column = 1; + } + ch = peek_char(buf, 0); loc->pos = buf->size; token = lex_layout(buf, loc, ch); if (token) - return token; + RETURN_LEX_TOKEN(token); token = lex_number(buf, loc, ch); if (token) - return token; + RETURN_LEX_TOKEN(token); + if (ch == 'L' && peek_char(buf, 1) == '\'') { + /* Keep the prefix in the source span, while the literal reader owns + * escape validation and the quoted payload. Wide strings remain a + * separate object-layout task. + */ + read_char(buf); + token = lex_literal(buf, loc, '\''); + token->kind = T_wchar; + token->location.len++; + loc->column++; + RETURN_LEX_TOKEN(token); + } + if (ch == 'L' && peek_char(buf, 1) == '"') { + /* Keep the prefix in the source span. The parser owns the execution + * wide-character representation, but the lexer must preserve this as a + * distinct literal so phase 6 can make a join with it wide. + */ + read_char(buf); + token = lex_literal(buf, loc, '"'); + token->kind = T_wstring; + token->location.len++; + loc->column++; + RETURN_LEX_TOKEN(token); + } token = lex_literal(buf, loc, ch); - if (token) - return token; + if (token) { + /* A narrow string literal is checked once phase 6 has joined it, since + * a wide neighbour makes the joined literal wide. + */ + if (token->kind == T_char && hex_escape_exceeds_byte(token->literal)) + error_at("Hexadecimal escape sequence out of range", + &token->location); + RETURN_LEX_TOKEN(token); + } token = lex_punct(buf, loc, ch); if (token) - return token; + RETURN_LEX_TOKEN(token); token = lex_operator(buf, loc, ch); if (token) - return token; + RETURN_LEX_TOKEN(token); token = lex_word(buf, loc, ch); if (token) - return token; + RETURN_LEX_TOKEN(token); error_at("Unexpected token", loc); return NULL; } -token_stream_t *gen_file_token_stream(char *filename) +#undef RETURN_LEX_TOKEN + +/* read_char() hides phase-2 pairs. Comments do not produce a token for the + * finalizer above, so account for any physical newlines it crosses here. + */ +char read_layout_char(strbuf_t *buf, source_location_t *loc) { - /* FIXME: We should normalize filename first to make cache works as expected - */ + int pos = buf->size; + char ch = read_char(buf); + while (pos < buf->size) { + if (buf->elements[pos] == '\n') { + loc->line++; + loc->column = 1; + } + pos++; + } + return ch; +} + +/* Return one lexical spelling for a source path. This deliberately does not + * call realpath(3): headers need not exist until after the preprocessor has + * formed their name, and preserving a lexical path keeps diagnostics useful. It + * is nevertheless important that the token cache and #pragma once see "a/./b.h" + * and "a/x/../b.h" as the same header. + */ +static char *normalize_filename(const char *filename) +{ + char path[MAX_LINE_LEN]; + int component_start[MAX_LINE_LEN]; + bool component_is_normal[MAX_LINE_LEN]; + int component_count = 0; + int in = 0; + int out = 0; + bool absolute = filename[0] == '/'; + + if (strlen(filename) >= MAX_LINE_LEN) + fatal("Source filename is too long"); + + if (absolute) + path[out++] = '/'; + + while (filename[in]) { + int start; + int len; + + while (filename[in] == '/') + in++; + start = in; + while (filename[in] && filename[in] != '/') + in++; + len = in - start; + + if (!len || (len == 1 && filename[start] == '.')) + continue; + + if (len == 2 && filename[start] == '.' && filename[start + 1] == '.') { + if (component_count && component_is_normal[component_count - 1]) + out = component_start[--component_count]; + else if (!absolute) { + if (out) + path[out++] = '/'; + component_start[component_count++] = out; + component_is_normal[component_count - 1] = false; + path[out++] = '.'; + path[out++] = '.'; + } + continue; + } + + if (out && path[out - 1] != '/') + path[out++] = '/'; + component_start[component_count++] = out; + component_is_normal[component_count - 1] = true; + memcpy(path + out, filename + start, len); + out += len; + } + + if (!out) + path[out++] = '.'; + path[out] = '\0'; + return intern_string(path); +} + +token_stream_t *gen_file_token_stream(char *filename) +{ token_t head; token_t *cur = &head; token_stream_t *tks; @@ -1097,7 +1698,8 @@ token_stream_t *gen_file_token_stream(char *filename) * len is 1 for reporting convenience, and the column and line number are * set to 1. */ - source_location_t loc = {0, 1, 1, 1, filename}; + filename = normalize_filename(filename); + source_location_t loc = {0, 1, 1, 1, filename, filename}; strbuf_t *buf; tks = hashmap_get(TOKEN_CACHE, filename); @@ -1107,9 +1709,11 @@ token_stream_t *gen_file_token_stream(char *filename) return tks; buf = get_file_buf(filename); + classify_plain_source(buf); /* Borrows strbuf_t#size to use as source index */ buf->size = 0; + lex_at_line_start = true; while (buf->size < buf->capacity) { cur->next = lex_token(buf, &loc); @@ -1144,7 +1748,7 @@ token_stream_t *gen_libc_token_stream(void) token_stream_t *tks; char *filename = dynlink ? "lib/c.h" : "lib/c.c"; strbuf_t *buf = LIBC_SRC; - source_location_t loc = {0, 1, 1, 1, filename}; + source_location_t loc = {0, 1, 1, 1, filename, filename}; tks = hashmap_get(TOKEN_CACHE, filename); @@ -1166,9 +1770,11 @@ token_stream_t *gen_libc_token_stream(void) if (!buf->size || buf->elements[buf->size - 1]) strbuf_putc(buf, 0); buf->capacity = buf->size; + classify_plain_source(buf); /* Borrows strbuf_t#size to use as source index */ buf->size = 0; + lex_at_line_start = true; while (buf->size < buf->capacity) { tk = lex_token(buf, &loc); @@ -1250,9 +1856,9 @@ bool lex_peek(token_kind_t kind, char *value) } /* Copies a token literal into a caller buffer of n bytes. Identifiers are - * bounded by MAX_ID_LEN when scanned, but some destinations are narrower -- - * 'type_name' is only MAX_TYPE_LEN -- so the bound has to travel with the - * destination rather than be assumed from the source. + * bounded by MAX_ID_LEN when scanned, but numeric literals run to + * MAX_TOKEN_LEN, so the bound has to travel with the destination rather than be + * assumed from the source. */ void lex_copy_literal(token_t *tk, char *value, int n) { @@ -1263,20 +1869,6 @@ void lex_copy_literal(token_t *tk, char *value, int n) strcpy(value, tk->literal); } -/* Peek the next token and, when it matches, copy its literal into a caller - * buffer of n bytes. lex_peek() copies unbounded, which is safe only for - * identifiers: numeric, char and string literals are scanned up to - * MAX_TOKEN_LEN and overflow a MAX_ID_LEN destination. - */ -bool lex_peek_n(token_kind_t token, char *value, int n) -{ - if (!cur_token->next || cur_token->next->kind != token) - return false; - if (value) - lex_copy_literal(cur_token->next, value, n); - return true; -} - /* Strictly match next token with given token type and copy token's literal to * value, which is n bytes wide. */ diff --git a/src/main.c b/src/main.c index 1ccd5134..4b3aa5d6 100644 --- a/src/main.c +++ b/src/main.c @@ -109,6 +109,10 @@ int main(int argc, char *argv[]) for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--dump-ir")) dump_ir = true; + else if (!strcmp(argv[i], "--warn-string-literals")) + warn_string_literals = true; + else if (!strcmp(argv[i], "--std=c99")) + strict_c99 = true; else if (!strcmp(argv[i], "--dot")) dump_dot = true; else if (!strcmp(argv[i], "+m")) @@ -119,7 +123,17 @@ int main(int argc, char *argv[]) dynlink = true; else if (!strcmp(argv[i], "-E")) expand_only = true; - else if (!strcmp(argv[i], "-o")) { + else if (!strcmp(argv[i], "-I")) { + if (i + 1 >= argc) + usage_error("-I requires an include directory"); + if (include_dirs_idx == MAX_INCLUDE_DIRS) + usage_error("Too many include directories"); + include_dirs[include_dirs_idx++] = argv[++i]; + } else if (!strncmp(argv[i], "-I", 2) && argv[i][2]) { + if (include_dirs_idx == MAX_INCLUDE_DIRS) + usage_error("Too many include directories"); + include_dirs[include_dirs_idx++] = argv[i] + 2; + } else if (!strcmp(argv[i], "-o")) { if (i + 1 < argc) { out = argv[i + 1]; i++; @@ -133,7 +147,8 @@ int main(int argc, char *argv[]) if (!in) { printf( - "Usage: shecc [-o output] [+m] [--dot] [--dump-ir] [--no-libc] " + "Usage: shecc [-I directory] [-o output] [+m] [--dot] [--dump-ir] " + "[--warn-string-literals] [--std=c99] [--no-libc] " "[--dynlink] [-E] \n"); usage_error("Missing source file"); } @@ -248,6 +263,9 @@ int main(int argc, char *argv[]) /* Compact arenas after SSA optimization to free temporary SSA structures */ compact_all_arenas(); + /* Give each register-pair operation operands of its width. */ + widen_pair_operands(); + /* SSA-based liveness analyses */ liveness_analysis(); diff --git a/src/opt-sccp.c b/src/opt-sccp.c index 3421139b..d5ce04e6 100644 --- a/src/opt-sccp.c +++ b/src/opt-sccp.c @@ -12,10 +12,9 @@ * which runs over the same IR immediately afterwards. */ -/* Narrow a constant to 'size' bytes, keeping its sign. Every integer type in - * this language is signed -- there is no 'unsigned' keyword -- and widening - * sign-extends, so masking alone would make "char c = -1" compare as 255. A - * size the caller does not narrow is returned unchanged. +/* Narrow a constant to 'size' bytes, keeping its sign. A signed narrow type + * sign-extends when widened, so masking alone would make "char c = -1" compare + * as 255. A size the caller does not narrow is returned unchanged. */ int sign_extend_const(int value, int size) { @@ -60,11 +59,27 @@ bool optimize_constant_casts(func_t *func) continue; /* not a width we narrow to */ result = sign_extend_const(value, next_insn->sz); + /* An unsigned narrow type zero-extends instead: folding + * "unsigned short x = 0xfffe" to -2 made (int) x compare as -2. + */ + bool is_unsigned = next_insn->rd->type && + next_insn->rd->type->is_unsigned && + !next_insn->rd->ptr_level; + if (is_unsigned && next_insn->sz < 4) + result = value & ((1 << (next_insn->sz * 8)) - 1); + /* Optimize: Replace both instructions with single const */ insn->rd = next_insn->rd; /* Update dest to final target */ insn->rd->is_const = true; insn->rd->init_val = result; + /* The high word belongs to the narrowed value too. A 64-bit + * target loads an argument constant at full width, and a zero + * high word made "f(0xe231fffab478ULL)" pass its int parameter + * as 0xfffab478 rather than -347016. + */ + insn->rd->init_val_hi = result < 0 && !is_unsigned ? -1 : 0; + /* Remove the truncation instruction by converting it to * NOP-like */ diff --git a/src/parser-call.c b/src/parser-call.c new file mode 100644 index 00000000..8d759fac --- /dev/null +++ b/src/parser-call.c @@ -0,0 +1,2472 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Direct and indirect calls, the offsetof and va_arg builtins, bit-field + * values, and the address-of and dereference operators. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +void read_logical(opcode_t op, block_t *parent, basic_block_t **bb); +var_t *materialize_function_designator(block_t *parent, + basic_block_t **bb, + var_t *value); +void read_func_parameters_with_sret(func_t *func, + var_t *sret, + block_t *parent, + basic_block_t **bb) +{ + int param_num = 0; + var_t *params[MAX_PARAMS], *param; + + lex_expect(T_open_bracket); + while (!lex_accept(T_close_bracket)) { + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + + param = opstack_pop(); + param = materialize_function_designator(parent, bb, param); + + /* Writing past 'params' corrupts this frame, and the damage only + * surfaces later as a wrong argument value. The check has to come + * before the conversions below: those index func->param_defs[], a + * MAX_PARAMS-element array embedded in func_t, so an over-long argument + * list reads past it and dereferences a garbage type pointer -- the + * compiler crashed instead of reporting the limit. + */ + if (param_num >= MAX_PARAMS) + error_at("Too many arguments in function call", cur_token_loc()); + + if (func && param_num < func->num_params) { + var_t *target = &func->param_defs[param_num]; + if (incompatible_character_pointer_conversion(param, target)) + error_at( + "incompatible character pointer types for function " + "argument", + cur_token_loc()); + if (incompatible_pointee_callback_conversion(param, target)) + error_at( + "incompatible callback slot types for function " + "argument", + cur_token_loc()); + diagnose_const_pointer_conversion(param, target); + diagnose_integer_to_pointer_conversion(param, target, true); + diagnose_function_pointer_conversion(param, target); + if (is_record_type(target->type) && !target->is_func && + !has_effective_pointer(target) && !target->array_size) { + /* A record parameter is a by-value object. Keep that promise + * without teaching every backend its native aggregate ABI: give + * the callee an addressable caller-side copy in one + * pointer-sized ABI slot. + */ + if (!is_record_object(param)) + error_at("Record argument required", cur_token_loc()); + + var_t *copy = require_typed_var(parent, target->type); + copy->var_name = gen_name(); + add_insn(parent, *bb, OP_allocat, copy, NULL, NULL, 0, NULL); + emit_record_copy(parent, bb, copy, param); + + param = require_ref_var(parent, target->type, 0); + param->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, param, copy, NULL, 0, + NULL); + } else if (!has_effective_pointer(target) && !target->array_size) { + param = + scalarize_array_literal(parent, bb, param, target->type); + } + } + + /* Handle parameter type conversion whenever the callee has a known + * prototype. Function-pointer declarations retain one too. + */ + if (func && param_num >= func->num_params && func->va_args) { + /* Default promotions apply to scalar varargs, but pointer-like + * values (including array literals) must flow through unchanged so + * "%p" and friends see an address rather than a scalarized value. + */ + if (is_record_type(param->type) && !has_effective_pointer(param)) { + if (!is_record_object(param)) + error_at("Record argument required", cur_token_loc()); + var_t *copy = require_typed_var(parent, param->type); + + copy->var_name = gen_name(); + add_insn(parent, *bb, OP_allocat, copy, NULL, NULL, 0, NULL); + emit_record_copy(parent, bb, copy, param); + param = require_ref_var(parent, param->type, 0); + param->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, param, copy, NULL, 0, + NULL); + } else if (!is_pointer_like_value(param) && + param->type->size <= TY_int->size) { + /* A long long is not promoted, and promote() warns about a + * value wider than its int target. + */ + param = promote(parent, bb, param, TY_int, 0); + } + } else if (func && param_num < func->num_params) { + /* Only a declared parameter has a type to convert towards. Beyond + * num_params the param_defs[] entry was never filled in, so its + * type is NULL and resize_var() dereferenced it: passing more + * arguments than a non-variadic function declares crashed the + * compiler instead of compiling or diagnosing the call. + */ + if (!is_record_type(func->param_defs[param_num].type) || + func->param_defs[param_num].ptr_level) + param = + resize_var(parent, bb, param, &func->param_defs[param_num]); + } + + params[param_num++] = param; + + /* A comma separates two arguments (C99 6.5.2p1): it cannot end the + * list, and two arguments cannot go without one. Neither is an + * extension any compiler offers, so no mode accepts them. + */ + if (lex_accept(T_close_bracket)) + break; + lex_expect(T_comma); + if (lex_peek(T_close_bracket, NULL)) + error_at("Expected an argument after ',' in function call", + cur_token_loc()); + } + + if (func && func->has_prototype && + (param_num < func->num_params || + (!func->va_args && param_num > func->num_params))) + error_at(param_num < func->num_params + ? "Too few arguments in function call" + : "Too many arguments in function call", + cur_token_loc()); + + if (sret) + add_insn(parent, *bb, OP_push, NULL, sret, NULL, param_num + 1, NULL); + for (int i = 0; i < param_num; i++) { + /* The operand should keep alive before calling function. Pass the + * number of remained parameters to allocator to extend their liveness. + */ + add_insn(parent, *bb, OP_push, NULL, params[i], NULL, param_num - i, + NULL); + } +} + +void read_func_call_with_sret(func_t *func, + var_t *sret, + block_t *parent, + basic_block_t **bb) +{ + /* direct function call */ + read_func_parameters_with_sret(func, sret, parent, bb); + + add_insn(parent, *bb, OP_call, NULL, NULL, NULL, 0, + func->return_def.var_name); +} + +void read_func_call(func_t *func, block_t *parent, basic_block_t **bb) +{ + read_func_call_with_sret(func, NULL, parent, bb); +} + +/* A call returning `row *`, where `typedef int row[2]`, carries the row shape + * on the return declarator rather than the scalar element type. Preserve that + * shape on OP_func_ret and consume immediate postfix subscripts in one shared + * path for direct, indirect, and grouped calls. + */ +void copy_call_result_array_shape(var_t *result, const var_t *return_def) +{ + fixed_array_shape_t shape; + + if (!result || !return_def) + return; + shape = return_def->pointee_array_size + ? fixed_array_shape_from_pointee_var(return_def) + : fixed_array_shape_from_type(return_def->type); + fixed_array_shape_to_pointee_var(result, &shape); + + /* A row typedef of pointers, `typedef int *row[2]`, keeps its element depth + * in the alias's own stars, exactly as a declared `row *p` leaves it there. + * A copy here would count them twice. A row of callbacks spells no star on + * the alias, so its element depth still comes from the alias. + */ + result->pointee_array_element_ptr_level = + return_def->pointee_array_element_ptr_level + ? return_def->pointee_array_element_ptr_level + : (return_def->type->ptr_level + ? 0 + : return_def->type->array_element_ptr_level); + result->is_const_qualified = + return_def->is_const_qualified || return_def->type->is_const_qualified; +} + +/* The element type of array typedef @type, or @type itself when it is none. A + * row of callbacks keeps its typedef, which carries their prototype. + */ +static type_t *array_typedef_element_type(type_t *type) +{ + if (type->array_size && type->array_element_type && !type->func_signature) + return type->array_element_type; + return type; +} + +/* What @consumed subscripts leave of an array of @shape at @address, whose + * elements are @type with @element_ptr_level stars. sizeof observes that + * remaining array; any other use decays it to a pointer that keeps the + * remaining bounds, so its own subscripts and arithmetic step by whole rows. + */ +static var_t *array_subscript_remainder(block_t *parent, + basic_block_t **bb, + type_t *type, + int element_ptr_level, + fixed_array_shape_t shape, + int consumed, + var_t *address) +{ + var_t *result; + + for (int i = 0; i < consumed; i++) + fixed_array_shape_drop_outer(&shape); + if (unevaluated_expression_depth) { + result = require_typed_ptr_var(parent, type, element_ptr_level); + fixed_array_shape_to_var(result, &shape); + result->var_name = gen_name(); + return result; + } + fixed_array_shape_drop_outer(&shape); + result = require_typed_ptr_var(parent, type, element_ptr_level + 1); + fixed_array_shape_to_pointee_var(result, &shape); + if (shape.rank) + result->pointee_array_element_ptr_level = element_ptr_level; + result->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, result, address, NULL, 0, NULL); + return result; +} + +void lower_call_result_array_postfix(var_t **value, + block_t *parent, + basic_block_t **bb) +{ + var_t *base; + var_t *address; + type_t *element_type; + fixed_array_shape_t shape; + int dimensions; + int element_ptr_level; + int depth = 0; + + if (!value || !(base = *value) || !base->pointee_array_size || + !lex_peek(T_open_square, NULL)) + return; + + shape = fixed_array_shape_from_pointee_var(base); + + /* A `row *` result keeps the row typedef as its type. What the subscripts + * reach is an element of that row, which must not report the row's extent. + * The alias carries the element's own stars where the result does not. + */ + element_type = array_typedef_element_type(base->type); + element_ptr_level = base->pointee_array_element_ptr_level + ? base->pointee_array_element_ptr_level + : base->type->array_element_ptr_level; + + /* One subscript selects the pointed-to array; its outer bound and every + * inner bound then consume their own postfix subscript. + */ + dimensions = shape.rank + 1; + opstack_pop(); + address = base; + do { + var_t *index; + int stride = element_ptr_level ? PTR_SIZE : base->type->size; + + if (depth >= dimensions) + error_at("Too many subscripts for function result", + cur_token_loc()); + if (depth == 0) { + stride *= base->pointee_array_size; + } else + stride = fixed_array_shape_stride(&shape, depth - 1, stride); + + lex_expect(T_open_square); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (stride != 1) { + var_t *scale = require_var(parent); + var_t *scaled = require_var(parent); + + scale->var_name = gen_name(); + scale->init_val = stride; + add_insn(parent, *bb, OP_load_constant, scale, NULL, NULL, 0, NULL); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, index, scale, 0, NULL); + index = scaled; + } + var_t *indexed = require_typed_ptr_var(parent, base->type, 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, 0, NULL); + address = indexed; + depth++; + } while (lex_peek(T_open_square, NULL)); + + if (depth == dimensions) { + var_t *element = require_typed_var(parent, element_type); + opcode_t compound_op = OP_generic; + bool assignment; + bool record; + + element->ptr_level = element_ptr_level; + + /* A call returning a pointer to an array of callbacks carries their + * prototype on the result rather than on its scalar base type. + */ + element->func_signature = base->type->func_signature + ? base->type->func_signature + : base->func_signature; + element->is_const_qualified = + base->is_const_qualified || base->type->is_const_qualified; + element->var_name = gen_name(); + + /* A record element is no register value: it stays at its address for a + * member selection or a record copy to read, as push_object_at() leaves + * one. + */ + record = is_record_object(element) && !element->func_signature && + !is_incomplete_record_object(element); + if (record) + element->defers_record_copy = true; + else + add_insn(parent, *bb, OP_read, element, address, NULL, + element->ptr_level ? PTR_SIZE : base->type->size, NULL); + element->is_compound_literal_reference = true; + element->compound_literal_address = address; + + assignment = + lex_accept(T_assign) || accept_compound_assign_op(&compound_op); + if (assignment) { + var_t *assigned; + + if (element->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + assigned = opstack_pop(); + if (record) { + if (compound_op != OP_generic || !is_record_object(assigned)) + error_at("Invalid record assignment", cur_token_loc()); + emit_record_copy_to_address(parent, bb, address, assigned); + *value = assigned; + opstack_push(*value); + return; + } + if (compound_op != OP_generic) { + if (is_pointer_operation(compound_op, element, assigned)) { + handle_pointer_arithmetic(parent, bb, compound_op, element, + assigned); + assigned = opstack_pop(); + } else { + var_t *current = + integer_promote_operand(parent, bb, element); + + assigned = integer_promote_operand(parent, bb, assigned); + normalize_integer_binary_operands(parent, bb, compound_op, + ¤t, &assigned); + var_t *combined = require_var(parent); + + combined->var_name = gen_name(); + combined->type = integer_binary_result_type( + compound_op, current, assigned); + add_insn(parent, *bb, compound_op, combined, current, + assigned, 0, NULL); + assigned = combined; + } + } + assigned = resize_var(parent, bb, assigned, element); + add_insn(parent, *bb, OP_write, NULL, address, assigned, + element->ptr_level ? PTR_SIZE : base->type->size, NULL); + *value = assigned; + opstack_push(*value); + return; + } + + if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + opcode_t op = lex_accept(T_increment) ? OP_add : OP_sub; + var_t *one; + var_t *updated; + + if (record) + error_at( + "Increment or decrement requires a scalar modifiable " + "lvalue", + cur_token_loc()); + if (element->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + if (is_pointer_operation(op, element, one)) { + handle_pointer_arithmetic(parent, bb, op, element, one); + updated = opstack_pop(); + } else { + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, element, one); + add_insn(parent, *bb, op, updated, element, one, 0, NULL); + } + updated = resize_var(parent, bb, updated, element); + add_insn(parent, *bb, OP_write, NULL, address, updated, + element->ptr_level ? PTR_SIZE : base->type->size, NULL); + } + *value = element; + } else { + /* The first subscript selected the pointed-to array itself. */ + *value = array_subscript_remainder(parent, bb, element_type, + element_ptr_level, shape, depth - 1, + address); + (*value)->is_const_qualified = + base->is_const_qualified || base->type->is_const_qualified; + } + opstack_push(*value); + var_t *callable = *value; + if (callable->func_signature && lex_peek(T_open_bracket, NULL)) { + func_t *signature = callable->func_signature; + + var_t *result = + emit_indirect_call_result(callable, signature, true, parent, bb); + *value = result; + lower_call_result_array_postfix(value, parent, bb); + } +} + +void lower_call_result_prefix_update(var_t **value, + opcode_t op, + block_t *parent, + basic_block_t **bb) +{ + if (op == OP_generic) + return; + *value = lower_reference_update(opstack_pop(), op, parent, bb); + opstack_push(*value); +} + +/* The freestanding maps offsetof(type, member-designator) here. Keep + * it unevaluated: no null pointer or temporary object is materialized. + */ +void read_builtin_offsetof(block_t *parent, basic_block_t **bb) +{ + char name[MAX_ID_LEN]; + type_t *record; + var_t *field; + var_t *result; + int offset = 0; + bool has_nested_member; + + lex_expect(T_identifier); + lex_expect(T_open_bracket); + base_type_t record_kind = accept_record_keyword(); + if (record_kind) { + lex_ident(T_identifier, name); + record = find_record_tag(name, parent, record_kind); + } else { + lex_ident(T_identifier, name); + record = find_type(name, true); + } + if (!is_record_type(record)) + error_at("offsetof requires a struct or union type", cur_token_loc()); + lex_expect(T_comma); + do { + lex_ident(T_identifier, name); + field = find_member(name, record); + if (!field) + error_at("Unknown record member", cur_token_loc()); + offset += field->offset; + offset += read_offsetof_array_subscripts(field, parent); + record = field->type; + has_nested_member = lex_accept(T_dot); + if (has_nested_member && (field->ptr_level || !is_record_type(record))) + error_at("Nested offsetof member requires a record", + cur_token_loc()); + } while (has_nested_member); + lex_expect(T_close_bracket); + result = require_typed_var(parent, find_type("size_t", true)); + result->var_name = gen_name(); + result->init_val = offset; + result->is_const = true; + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); +} + +/* Parse a C99 type name for the va_arg intrinsic. A type name has no + * identifier, but it may have an abstract declarator: `int (*)[4]` and `int + * (*)(int)` are pointer types, whereas `int *[4]` and `int (int)` are + * array/function types and are not objects that va_arg may fetch. The current + * IR only needs the base type and pointer depth for a pointer value; consume + * the remaining abstract-declarator syntax here so type names are not + * accidentally restricted to the spelling of a declaration. + */ +typedef struct va_arg_type { + type_t *type; + int ptr_level; + bool is_array; + bool is_function; + func_t *func_signature; + int pointee_array_size; + int pointee_array_dim2; + int pointee_array_dim3; + int pointee_array_dim4; + int pointee_element_size; + int pointee_element_ptr_level; +} va_arg_type_t; + +void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) +{ + type_t *type; + + result->ptr_level = 0; + result->is_array = false; + result->is_function = false; + result->func_signature = NULL; + result->pointee_array_size = 0; + result->pointee_array_dim2 = 0; + result->pointee_array_dim3 = 0; + result->pointee_array_dim4 = 0; + result->pointee_element_size = 0; + result->pointee_element_ptr_level = 0; + type = read_type_name_specifiers(parent); + if (!type) + error_at("Unknown va_arg type", cur_token_loc()); + while (lex_accept(T_const) || lex_accept(T_volatile)) + ; + while (lex_accept(T_asterisk)) { + result->ptr_level++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + + /* An array typedef is not itself an object type suitable for va_arg, but + * `typedef int row[2]; row *` is a pointer to a row. Preserve the row + * extent separately: type_t supplies the scalar element type while the + * derived declarator supplies the stride for postfix indexing. + */ + if (type->array_size) { + type_t *element_type; + + if (type->base_type == TYPE_void) + error_at("va_arg pointer-to-array cannot have void elements", + cur_token_loc()); + + /* A pointer introduced by a typedef lives on type_t rather than the + * abstract declarator's direct-star count. Both spellings make an array + * typedef into a pointer-to-array object for va_arg. + */ + if (!(result->ptr_level + type->ptr_level)) + error_at("va_arg array type is not an object type", + cur_token_loc()); + result->pointee_array_size = type->array_size; + result->pointee_array_dim2 = type->array_dim2; + result->pointee_array_dim3 = type->array_dim3; + result->pointee_array_dim4 = type->array_dim4; + + /* A pointer-to-array typedef has already folded its bound into the + * alias size. Its va_arg result still indexes scalar elements. + */ + element_type = pointee_type_from_pointer_typedef(type); + result->pointee_element_size = element_type->size; + result->pointee_element_ptr_level = type->array_element_ptr_level; + } + + /* Parenthesized abstract pointer declarators keep the pointer next to the + * base type even when it is followed by an array or prototype suffix. + */ + if (lex_accept(T_open_bracket)) { + int nested_ptr = 0; + int return_ptr_level = result->ptr_level; + bool has_function_suffix = false; + + while (lex_accept(T_asterisk)) { + nested_ptr++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!nested_ptr) + error_at("va_arg type name needs an abstract pointer declarator", + cur_token_loc()); + result->ptr_level += nested_ptr; + lex_expect(T_close_bracket); + + while (lex_peek(T_open_square, NULL) || + lex_peek(T_open_bracket, NULL)) { + if (lex_accept(T_open_square)) { + int dims = 0; + int total = 0; + + if (type->base_type == TYPE_void) + error_at( + "va_arg pointer-to-array cannot have void elements", + cur_token_loc()); + + if (nested_ptr != 1) + error_at( + "va_arg pointer-element array type is not yet " + "supported", + cur_token_loc()); + + /* `int (*)[2][3]`: the parenthesized star denotes the value + * fetched from va_list; each suffix describes that pointer's + * pointee. Keep the familiar flattened-array representation + * used by normal declarations so the first subscript advances a + * whole row (or plane), not one scalar element. + */ + do { + int bound; + + if (dims >= 4) + error_at( + "Array type names support at most four dimensions", + cur_token_loc()); + if (lex_peek(T_close_square, NULL)) + error_at("pointer-to-array type needs a bound", + cur_token_loc()); + bound = read_const_expr(parent); + if (bound <= 0) + error_at("pointer-to-array bound must be positive", + cur_token_loc()); + lex_expect(T_close_square); + if (!dims) + total = bound; + else { + total *= bound; + if (dims == 1) + result->pointee_array_dim2 = bound; + else if (dims == 2) + result->pointee_array_dim3 = bound; + else + result->pointee_array_dim4 = bound; + } + dims++; + } while (lex_accept(T_open_square)); + result->pointee_array_size = total; + result->pointee_element_size = type->size; + result->pointee_element_ptr_level = + return_ptr_level + type->ptr_level; + } else { + func_t *func = arena_alloc_func(); + + /* This is a function suffix on the pointed-to declarator. + * Preserve its prototype just as a named function-pointer + * declaration does, so the value returned by va_arg remains + * directly callable. + */ + func->return_def.type = type; + func->return_def.ptr_level = return_ptr_level; + func->returns_aggregate = + is_record_type(type) && !return_ptr_level; + read_parameter_list_decl(func, true); + result->func_signature = func; + has_function_suffix = true; + } + } + + /* Stars before the parenthesized declarator belong to the function's + * return type (`int *(*)(int)`), not to the pointer-sized function + * pointer value. + */ + if (has_function_suffix) + result->ptr_level = nested_ptr; + } + + /* A suffix after the ordinary pointer chain makes the result an array or a + * function, not a pointer to one. Parse it so the diagnostic below is about + * va_arg's object requirement instead of a stray closing token. + */ + while (lex_peek(T_open_square, NULL) || lex_peek(T_open_bracket, NULL)) { + if (lex_accept(T_open_square)) { + result->is_array = true; + if (!lex_peek(T_close_square, NULL)) + read_const_expr(parent); + lex_expect(T_close_square); + } else { + int depth = 0; + + result->is_function = true; + do { + if (lex_accept(T_open_bracket)) + depth++; + else if (lex_accept(T_close_bracket)) + depth--; + else + lex_next(); + } while (depth > 0); + } + } + + /* A floating value would enter the integer-only IR and argument ABI, + * whether the type name spells it directly or through a typedef. + */ + if (type->is_floating && !result->ptr_level && !type->ptr_level) + error_at("Floating point types are not yet supported", cur_token_loc()); + result->type = type; +} + +void emit_record_copy_from_address(block_t *parent, + basic_block_t **bb, + var_t *dest, + var_t *src_addr) +{ + var_t *dest_addr = require_ref_var(parent, dest->type, 0); + + dest_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); + emit_record_copy_between(parent, bb, dest_addr, src_addr, size_var(dest)); +} + +/* Reject a record operand where C99 requires an arithmetic or scalar one: the + * operators of 6.5.3.3 and 6.5.5 to 6.5.15, and a controlling expression. A + * record reaching integer lowering has no register representation, which a + * narrow backend reported by aborting. + */ +void reject_record_operand(const var_t *var) +{ + if (is_record_object(var)) + error_at("Operand of record type requires a scalar value", + cur_token_loc()); +} + +/* Whether @var is a declared object rather than a generated temporary. */ +bool is_named_object(var_t *var, block_t *parent) +{ + return var->var_name[0] != '.' && find_var(var->var_name, parent) == var; +} + +/* Record on @value, just loaded from @address, that it designates the object + * stored there, so a following ++, -- or member selection reaches that object + * rather than the temporary. @bitfield is the field of a bit-field object. + */ +void mark_value_reference(var_t *value, var_t *address, var_t *bitfield) +{ + value->is_compound_literal_reference = true; + value->compound_literal_address = address; + value->compound_literal_bitfield = bitfield; +} + +/* Load the object of @value's type stored at @address into @value and push it. + * A record is not a register value: one OP_read of its whole size loaded only a + * word of it. Nor is it copied into storage of its own, which cost instructions + * in proportion to its size even when only a member was then selected: @value + * stands for the object in place, and whatever copies the record reads it + * there. + */ +void push_object_at(block_t *parent, + basic_block_t **bb, + var_t *value, + var_t *address, + int size) +{ + if (is_record_object(value) && !value->func_signature && + !is_incomplete_record_object(value)) + value->defers_record_copy = true; + else + add_insn(parent, *bb, OP_read, value, address, NULL, size, NULL); + mark_value_reference(value, address, NULL); + opstack_push(value); +} + +/* Apply ++ or -- (@op is OP_add or OP_sub) to @object, a value that records the + * address it was loaded from, and store the result there. + * + * Returns the value the object then holds. + */ +var_t *lower_reference_update(var_t *object, + opcode_t op, + block_t *parent, + basic_block_t **bb) +{ + var_t *one; + var_t *updated; + + if (!object->is_compound_literal_reference || is_record_object(object) || + object->array_size || object->is_func || object->func_signature) + error_at("Increment or decrement requires a scalar modifiable lvalue", + cur_token_loc()); + + /* As for an assignment through a reference, a pointer object is read-only + * through its own qualifier, not its pointee's. + */ + if (effective_pointer_depth(object) ? object->is_const_pointer + : object->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + if (is_pointer_operation(op, object, one)) { + handle_pointer_arithmetic(parent, bb, op, object, one); + updated = opstack_pop(); + } else { + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, object, one); + add_insn(parent, *bb, op, updated, object, one, 0, NULL); + } + updated = resize_var(parent, bb, updated, object); + if (object->compound_literal_bitfield) { + write_bitfield_value(parent, bb, object->compound_literal_address, + updated, object->compound_literal_bitfield); + updated = + read_bitfield_value(parent, bb, object->compound_literal_address, + object->compound_literal_bitfield); + updated->is_bitfield = false; + } else + add_insn(parent, *bb, OP_write, NULL, object->compound_literal_address, + updated, + object->ptr_level || object->type->ptr_level + ? PTR_SIZE + : object->type->size, + NULL); + return updated; +} + +void read_builtin_va_arg(block_t *parent, basic_block_t **bb) +{ + va_arg_type_t requested; + var_t *ap_addr; + var_t *old; + var_t *step; + var_t *next; + + lex_expect(T_identifier); + lex_expect(T_open_bracket); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + ap_addr = opstack_pop(); + if (!has_effective_pointer(ap_addr)) + error_at("va_arg first argument must be va_list lvalue", + cur_token_loc()); + lex_expect(T_comma); + read_builtin_va_arg_type(parent, &requested); + lex_expect(T_close_bracket); + if (requested.type->base_type == TYPE_void && !requested.ptr_level) + error_at("va_arg cannot request void", cur_token_loc()); + if (is_record_type(requested.type) && !requested.ptr_level && + !requested.type->num_fields) + error_at("va_arg cannot request incomplete record type", + cur_token_loc()); + if (requested.is_array || requested.is_function) + error_at("va_arg type must be an object type", cur_token_loc()); + + old = require_typed_ptr_var(parent, TY_int, 1); + old->var_name = gen_name(); + add_insn(parent, *bb, OP_read, old, ap_addr, NULL, PTR_SIZE, NULL); + step = require_typed_var(parent, TY_int); + step->var_name = gen_name(); + + /* A long long on a 32-bit target takes two ABI words starting at an even + * one, both for AAPCS32 and for a variadic argument on RV32, and the callee + * saves its argument words from an eight-byte aligned slot. Round the + * cursor up to the pair's first word and step over both. + */ + if (PTR_SIZE < 8 && !requested.ptr_level && + !is_record_type(requested.type) && requested.type->size == 8) { + var_t *mask = require_typed_var(parent, TY_int); + var_t *aligned = require_typed_ptr_var(parent, TY_int, 1); + + step->init_val = 7; + step->is_const = true; + add_insn(parent, *bb, OP_load_constant, step, NULL, NULL, 0, NULL); + aligned->var_name = gen_name(); + add_insn(parent, *bb, OP_add, aligned, old, step, 0, NULL); + mask->var_name = gen_name(); + mask->init_val = -8; + mask->is_const = true; + add_insn(parent, *bb, OP_load_constant, mask, NULL, NULL, 0, NULL); + old = require_typed_ptr_var(parent, TY_int, 1); + old->var_name = gen_name(); + add_insn(parent, *bb, OP_bit_and, old, aligned, mask, 0, NULL); + step = require_typed_var(parent, TY_int); + step->var_name = gen_name(); + } + + /* This direct IR add is byte-addressed; unlike parsed pointer arithmetic it + * does not apply the pointee-size scale itself. + */ + step->init_val = PTR_SIZE < 8 && !requested.ptr_level && + !is_record_type(requested.type) && + requested.type->size == 8 + ? 8 + : PTR_SIZE; + step->is_const = true; + add_insn(parent, *bb, OP_load_constant, step, NULL, NULL, 0, NULL); + next = require_typed_ptr_var(parent, TY_int, 1); + next->var_name = gen_name(); + add_insn(parent, *bb, OP_add, next, old, step, 0, NULL); + add_insn(parent, *bb, OP_write, NULL, ap_addr, next, PTR_SIZE, NULL); + + if (is_record_type(requested.type) && !requested.ptr_level) { + var_t *source = require_ref_var(parent, requested.type, 0); + var_t *result = require_typed_var(parent, requested.type); + + source->var_name = gen_name(); + add_insn(parent, *bb, OP_read, source, old, NULL, PTR_SIZE, NULL); + result->var_name = gen_name(); + add_insn(parent, *bb, OP_allocat, result, NULL, NULL, 0, NULL); + emit_record_copy_from_address(parent, bb, result, source); + opstack_push(result); + } else { + var_t *result = + require_typed_ptr_var(parent, requested.type, requested.ptr_level); + + result->var_name = gen_name(); + add_insn(parent, *bb, OP_read, result, old, NULL, + requested.ptr_level ? PTR_SIZE : requested.type->size, NULL); + result->func_signature = requested.func_signature; + opstack_push(result); + + /* A builtin result is a temporary, not a named lvalue, so it never + * reaches read_lvalue()'s multidimensional subscript lowering. Apply + * the derived pointee bounds here. For `int (*)[2][3]`, index zero + * advances 6 ints, index one advances 3 ints, and the final index reads + * the scalar selected by the address. + */ + if (requested.pointee_array_size && lex_peek(T_open_square, NULL)) { + int dims[4] = { + requested.pointee_array_size, requested.pointee_array_dim2, + requested.pointee_array_dim3, requested.pointee_array_dim4}; + int dimension_count = 1 + !!requested.pointee_array_dim2 + + !!requested.pointee_array_dim3 + + !!requested.pointee_array_dim4; + int depth = 0; + var_t *base = opstack_pop(); + + /* An element's pointer depth is counted in full by + * pointee_element_ptr_level. A typedef such as "typedef int + * *pointer; typedef pointer row[2];" leaves that same depth on the + * row's type_t as well, and building the element on top of it + * counted the star twice: dereferencing the element then read a + * pointer's width where an int was stored. + */ + type_t *element_type = + requested.pointee_element_ptr_level + ? pointee_type_from_pointer_typedef(requested.type) + : requested.type; + + while (lex_accept(T_open_square)) { + var_t *index; + var_t *address; + int element_size = requested.pointee_element_ptr_level + ? PTR_SIZE + : requested.pointee_element_size + ? requested.pointee_element_size + : requested.type->size; + int multiplier = element_size; + + /* The first subscript selects the array object itself; its + * elements are reached by one further subscript than there are + * declared array dimensions. + */ + if (depth > dimension_count) + error_at("Too many subscripts for pointer-to-array", + cur_token_loc()); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + index = opstack_pop(); + lex_expect(T_close_square); + if (!depth) + multiplier *= dims[0]; + else + for (int i = depth; i < dimension_count; i++) + multiplier *= dims[i]; + if (multiplier != 1) { + var_t *scale = require_typed_var(parent, TY_int); + scale->var_name = gen_name(); + scale->init_val = multiplier; + scale->is_const = true; + add_insn(parent, *bb, OP_load_constant, scale, NULL, NULL, + 0, NULL); + var_t *scaled = require_var(parent); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, index, scale, 0, + NULL); + index = scaled; + } + address = require_typed_ptr_var( + parent, element_type, + requested.pointee_element_ptr_level + 1); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); + depth++; + if (depth == dimension_count + 1 && + !requested.pointee_element_ptr_level && + is_record_type(requested.type)) { + /* A record element is an object: copy it whole rather than + * read one scalar of the record's size, which drops bytes + * and is a width no narrow backend can load. + */ + var_t *value = require_typed_var(parent, requested.type); + + value->var_name = gen_name(); + add_insn(parent, *bb, OP_allocat, value, NULL, NULL, 0, + NULL); + emit_record_copy_from_address(parent, bb, value, address); + base = value; + } else if (depth == dimension_count + 1) { + var_t *value = require_typed_ptr_var( + parent, element_type, + requested.pointee_element_ptr_level); + value->var_name = gen_name(); + add_insn(parent, *bb, OP_read, value, address, NULL, + element_size, NULL); + base = value; + } else + base = address; + } + opstack_push(base); + } + + if (requested.func_signature && lex_peek(T_open_bracket, NULL)) { + result = emit_indirect_call_result(result, requested.func_signature, + true, parent, bb); + } + } +} + +/* Function-pointer prototypes are stored as opaque data in var_t so defs.h + * remains parseable before func_t is complete. + */ +func_t *get_func_signature(var_t *var) +{ + if (!var) + return NULL; + return var->func_signature; +} + +/* A function-pointer object is represented as an is_func declaration, while a + * value read from that object is an ordinary pointer-sized SSA value carrying + * the declaration's prototype. Keeping the two distinct matters for calls: the + * former must be addressed and loaded; the latter can be passed to the + * indirect-call instruction directly. + */ +var_t *load_function_pointer_object(block_t *parent, + basic_block_t **bb, + var_t *object) +{ + /* SSA versions of parameters already contain the incoming pointer value. + * Preserve it through an ordinary pointer temporary: is_func denotes a + * storage object, whereas the indirect-call backend expects a value. + */ + if (parent && parent->func && !object->address_taken) { + for (int i = 0; i < parent->func->num_params; i++) { + var_t *param = &parent->func->param_defs[i]; + + if (object == param || object->base == param) { + func_t *param_signature = get_func_signature(object); + var_t *value = require_typed_ptr_var( + parent, param_signature->return_def.type, 1); + + value->var_name = gen_name(); + value->func_signature = param_signature; + add_insn(parent, *bb, OP_assign, value, object, NULL, 0, NULL); + return value; + } + } + } + + var_t *address = require_ref_var(parent, object->type, object->ptr_level); + func_t *signature = get_func_signature(object); + var_t *target = require_typed_ptr_var( + parent, signature ? signature->return_def.type : object->type, 1); + + address->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, address, object, NULL, 0, NULL); + target->var_name = gen_name(); + target->func_signature = object->func_signature; + add_insn(parent, *bb, OP_read, target, address, NULL, PTR_SIZE, NULL); + return target; +} + +/* C99 function designators decay to a pointer value in an argument expression. + * A raw symbol deliberately has no storage or defining IR so global + * initializers can emit a relocation; feeding it straight to OP_push makes + * register allocation reload an unallocated local. Reuse the established + * temporary-object path, which lowers the symbol through OP_address_of_func and + * then reads the resulting pointer-sized value. + */ +var_t *materialize_function_designator(block_t *parent, + basic_block_t **bb, + var_t *value) +{ + func_t *func; + var_t *object; + + /* An array of function pointers is no designator: it decays to the address + * of its first element, not to the pointer stored there. + */ + if (!value || !value->is_func || value->array_size || + value->has_unsized_array) + return value; + if (find_var(value->var_name, parent) == value) + return load_function_pointer_object(parent, bb, value); + func = find_func(value->var_name); + if (!func) + return value; + + object = require_typed_ptr_var(parent, func->return_def.type, + func->return_def.ptr_level); + object->var_name = gen_name(); + object->is_func = true; + object->func_signature = func; + add_insn(parent, *bb, OP_allocat, object, NULL, NULL, 0, NULL); + emit_object_assignment(parent, bb, object, value); + return load_function_pointer_object(parent, bb, object); +} + +void read_indirect_call_with_sret(var_t *callee, + func_t *signature, + var_t *sret, + block_t *parent, + basic_block_t **bb) +{ + /* A function pointer carries its parsed prototype on the declaration. This + * makes indirect calls obey the same record-by-value lowering and scalar + * conversions as a direct call. Legacy/unprototyped pointers keep the old + * generic behaviour. Keep the evaluated target as the OP_indirect source: + * the call's liveness then protects it while ABI argument staging reuses + * the argument registers. + */ + var_t *target = opstack_pop(); + + UNUSED(callee); + + read_func_parameters_with_sret(signature, sret, parent, bb); + + add_insn(parent, *bb, OP_indirect, NULL, target, NULL, 0, NULL); +} + +void read_indirect_call(var_t *callee, block_t *parent, basic_block_t **bb) +{ + read_indirect_call_with_sret(callee, get_func_signature(callee), NULL, + parent, bb); +} + +/* Aggregate calls write their value to caller-owned storage. The object is a + * temporary expression value, not a modifiable lvalue; callers only receive it + * on the operand stack after the call has completed. + */ +var_t *prepare_aggregate_call_result(block_t *parent, + basic_block_t **bb, + func_t *signature, + var_t **sret) +{ + var_t *result = require_typed_var(parent, signature->return_def.type); + var_t *destination; + + result->var_name = gen_name(); + add_insn(parent, *bb, OP_allocat, result, NULL, NULL, 0, NULL); + + destination = require_ref_var(parent, signature->return_def.type, 0); + destination->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, destination, result, NULL, 0, NULL); + *sret = destination; + return result; +} + +/* The value of a call returning @return_def, when that is a pointer to a + * callback typedef such as `callback_t *` or `int (**)(int)`, at any depth: a + * callback slot, which the declarator of that type would also be. NULL for any + * other return. + */ +static var_t *callback_slot_call_result(block_t *parent, + const var_t *return_def) +{ + func_t *callback = return_def->type->func_signature; + func_t *slot = return_def->pointee_func_signature + ? return_def->pointee_func_signature + : return_def->type->pointee_func_signature; + var_t *result; + + /* A slot typedef, `slot_t` for int (**)(int), or a pointer to one already + * carries the slot's prototype; keep it on the returned value. + */ + if (slot && !return_def->type->array_size && !return_def->array_size) { + result = require_typed_ptr_var(parent, return_def->type, + return_def->ptr_level); + result->var_name = gen_name(); + result->pointee_func_signature = slot; + return result; + } + if (!callback || return_def->type->is_direct_function_type || + return_def->type->array_size || return_def->ptr_level < 1) + return NULL; + result = require_typed_ptr_var( + parent, callback->return_def.type, + callback->return_def.ptr_level + return_def->ptr_level); + result->var_name = gen_name(); + result->pointee_func_signature = callback; + return result; +} + +/* Keep the ABI-only aggregate destination coupled to call emission. Call sites + * that merely discard an aggregate result still must provide it. + */ +var_t *emit_direct_call_result(func_t *func, + bool want_value, + block_t *parent, + basic_block_t **bb) +{ + func_t *returned_signature = func->return_def.type->func_signature; + var_t *result = NULL; + + if (func->returns_aggregate) { + var_t *sret; + func->aggregate_call_used = true; + result = prepare_aggregate_call_result(parent, bb, func, &sret); + read_func_call_with_sret(func, sret, parent, bb); + } else { + read_func_call(func, parent, bb); + if (want_value) + result = callback_slot_call_result(parent, &func->return_def); + if (result) { + add_insn(parent, *bb, OP_func_ret, result, NULL, NULL, 0, NULL); + } else if (want_value) { + result = require_typed_ptr_var( + parent, + returned_signature ? returned_signature->return_def.type + : func->return_def.type, + returned_signature ? 1 : func->return_def.ptr_level); + result->var_name = gen_name(); + result->func_signature = returned_signature; + copy_call_result_array_shape(result, &func->return_def); + add_insn(parent, *bb, OP_func_ret, result, NULL, NULL, 0, NULL); + } + } + if (want_value) + opstack_push(result); + return result; +} + +var_t *emit_indirect_call_result(var_t *callee, + func_t *signature, + bool want_value, + block_t *parent, + basic_block_t **bb) +{ + var_t *result = NULL; + + if (signature && signature->returns_aggregate) { + var_t *sret; + + /* The destination-pointer convention is shecc's own, so the callee must + * be a shecc-defined function. That cannot be proven per pointer + * object: a file-scope pointer, record member or array element can be + * assigned anywhere. prune_unused_funcs() instead rejects taking the + * address of any aggregate-returning function without a body, which + * leaves every such pointer value naming a shecc-defined target. + */ + result = prepare_aggregate_call_result(parent, bb, signature, &sret); + read_indirect_call_with_sret(callee, signature, sret, parent, bb); + } else { + read_indirect_call(callee, parent, bb); + if (want_value && signature) + result = callback_slot_call_result(parent, &signature->return_def); + if (result) { + add_insn(parent, *bb, OP_func_ret, result, NULL, NULL, 0, NULL); + } else if (want_value && signature) { + result = require_typed_ptr_var(parent, signature->return_def.type, + signature->return_def.ptr_level); + result->var_name = gen_name(); + result->func_signature = signature->return_def.type->func_signature; + copy_call_result_array_shape(result, &signature->return_def); + add_insn(parent, *bb, OP_func_ret, result, NULL, NULL, 0, NULL); + } + } + if (want_value) + opstack_push(result); + return result; +} + +var_t *bitfield_constant(block_t *parent, basic_block_t **bb, unsigned value) +{ + var_t *result = require_var(parent); + result->var_name = gen_name(); + result->init_val = (int) value; + result->is_const = true; + result->type = TY_uint; + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return result; +} + +var_t *bitfield_binary(block_t *parent, + basic_block_t **bb, + opcode_t op, + var_t *left, + var_t *right, + type_t *type) +{ + var_t *result = require_var(parent); + result->var_name = gen_name(); + result->type = type; + add_insn(parent, *bb, op, result, left, right, 0, NULL); + return result; +} + +unsigned bitfield_mask(const var_t *field) +{ + return field->bit_width == 32 ? ~0U : (1U << field->bit_width) - 1; +} + +var_t *read_bitfield_value(block_t *parent, + basic_block_t **bb, + var_t *address, + const var_t *field) +{ + var_t *value = require_var(parent); + value->var_name = gen_name(); + value->type = TY_uint; + add_insn(parent, *bb, OP_read, value, address, NULL, + field->bit_storage_size, NULL); + if (field->bit_offset) + value = bitfield_binary( + parent, bb, OP_rshift, value, + bitfield_constant(parent, bb, field->bit_offset), TY_uint); + if (field->bit_width != 32) + value = bitfield_binary( + parent, bb, OP_bit_and, value, + bitfield_constant(parent, bb, bitfield_mask(field)), TY_uint); + if (!is_bool_type(field->type) && !field->type->is_unsigned && + field->bit_width != 32) { + unsigned sign_bit = 1U << (field->bit_width - 1); + + /* Avoid depending on a target's right-shift instruction being + * arithmetic: (x ^ sign) - sign is sign extension in pure integer + * arithmetic for every width below the allocation unit. + */ + value = + bitfield_binary(parent, bb, OP_bit_xor, value, + bitfield_constant(parent, bb, sign_bit), TY_int); + value = + bitfield_binary(parent, bb, OP_sub, value, + bitfield_constant(parent, bb, sign_bit), TY_int); + } + if (is_bool_type(field->type)) + value = normalize_bool(parent, bb, value); + + /* C99's integer promotions apply to bit-fields too. A narrow unsigned int + * bit-field fits in int on the supported targets, so retain its extracted + * value but mark it as int before ordinary expression lowering. + */ + value->type = field->type->is_unsigned && + field->bit_width < field->bit_storage_size * 8 + ? TY_int + : field->type; + + /* Retain this constraint-only provenance until an operator creates a new + * expression value. `sizeof` must reject a direct bit-field operand. + */ + value->is_bitfield = true; + return value; +} + +/* The value of an assignment to a bit-field: the field read back from the unit + * just written. C11 6.5.16p3 permits that read without requiring it, so mark it + * removable when nothing uses the value; otherwise "s.flag = 1;" on a volatile + * record would access the object once more than the program asks. + */ +var_t *reload_assigned_bitfield(block_t *parent, + basic_block_t **bb, + var_t *address, + const var_t *field) +{ + insn_t *before = (*bb)->insn_list.tail; + basic_block_t *start = *bb; + var_t *value = read_bitfield_value(parent, bb, address, field); + + /* read_bitfield_value() emits the read first, into the given block. */ + insn_t *read = before ? before->next : start->insn_list.head; + read->rd->is_assignment_reload = true; + value->is_bitfield = false; + return value; +} + +void write_bitfield_value(block_t *parent, + basic_block_t **bb, + var_t *address, + var_t *value, + const var_t *field) +{ + if (is_bool_type(field->type)) + value = normalize_bool(parent, bb, value); + var_t *old = require_var(parent); + old->var_name = gen_name(); + old->type = TY_uint; + add_insn(parent, *bb, OP_read, old, address, NULL, field->bit_storage_size, + NULL); + value = bitfield_binary(parent, bb, OP_bit_and, value, + bitfield_constant(parent, bb, bitfield_mask(field)), + TY_uint); + if (field->bit_offset) + value = bitfield_binary( + parent, bb, OP_lshift, value, + bitfield_constant(parent, bb, field->bit_offset), TY_uint); + unsigned mask = bitfield_mask(field) << field->bit_offset; + var_t *clear = require_var(parent); + clear->var_name = gen_name(); + clear->type = TY_uint; + add_insn(parent, *bb, OP_bit_not, clear, + bitfield_constant(parent, bb, mask), NULL, 0, NULL); + old = bitfield_binary(parent, bb, OP_bit_and, old, clear, TY_uint); + value = bitfield_binary(parent, bb, OP_bit_or, old, value, TY_uint); + add_insn(parent, *bb, OP_write, NULL, address, value, + field->bit_storage_size, NULL); +} + +void read_lvalue(lvalue_t *lvalue, + var_t *var, + block_t *parent, + basic_block_t **bb, + bool eval, + opcode_t op); + +static bool is_function_parameter(const var_t *var, block_t *parent); + +/* A pointer to a whole array: `int (*)[N]`, or `char *(*)[N]` whose elements + * are themselves pointers. Arithmetic on it steps over complete arrays. + * + * Where the pointer is spelled directly, its own stars are the element's plus + * one, and a pointer typedef element as in `ip (*p)[N]` only deepens the base. + * Where a typedef such as `int *(*T)[N]` carries the whole declarator, its + * element depth is relative to the typedef's stars, and any star on the object + * makes it a pointer to T instead. + */ +static bool is_pointee_array_pointer(const var_t *var) +{ + int element_depth; + + if (!var || !var->type || var->pointee_array_size <= 0) + return false; + element_depth = var->pointee_array_element_ptr_level; + if (var->type->pointee_array_size) + return !var->ptr_level && var->type->ptr_level == element_depth + 1; + return var->ptr_level == element_depth + 1; +} + +static void copy_pointee_array_shape(var_t *destination, const var_t *source) +{ + fixed_array_shape_t shape = fixed_array_shape_from_pointee_var(source); + + fixed_array_shape_to_pointee_var(destination, &shape); + destination->pointee_array_element_ptr_level = + source->pointee_array_element_ptr_level; +} + +/* A dereference of a pointer to a pointer to an array, `*pp` for an int + * (**pp)[3], still points to that array. Keep its shape on the result @vd. + */ +static void keep_pointee_array_shape(var_t *vd, const var_t *source) +{ + if (source->pointee_array_size && + effective_pointer_depth(vd) > source->pointee_array_element_ptr_level) + copy_pointee_array_shape(vd, source); +} + +/* A dereference of a deeper callback slot, `*slot` for an int (***slot)(int), + * is still a callback slot, one level shallower. Keep its prototype on @vd. + */ +static void keep_callback_slot(var_t *vd, const var_t *source) +{ + if (callback_slot_depth(source) > 1) { + vd->pointee_func_signature = source->pointee_func_signature; + vd->callback_is_const = source->callback_is_const; + vd->callback_is_volatile = source->callback_is_volatile; + } else if (source->pointee_func_signature && vd->func_signature) { + /* The last dereference reaches the callback pointer object. */ + if (source->callback_is_const) { + vd->pointer_const_mask |= 1U; + vd->is_const_pointer = true; + } + if (source->callback_is_volatile) + vd->is_volatile = true; + } +} + +/* The address of an operand that does not start with an identifier: `&*p`, + * `&(*q).member`, `&2[arr]` or `&(int){1}`. + */ +static void take_expression_address(block_t *parent, basic_block_t **bb) +{ + var_t *operand; + var_t *address; + var_t *result; + + if (lex_accept(T_asterisk)) { + /* &*E is E, except that it is not an lvalue; neither operator is + * evaluated (C99 6.5.3.2p3). + */ + read_expr_operand(parent, bb); + operand = opstack_pop(); + if (operand->is_func) { + opstack_push(operand); + return; + } + if (!effective_pointer_depth(operand) && !operand->array_size) + error_at("Cannot dereference from non-pointer typed variable", + cur_token_loc()); + result = require_typed_ptr_var( + parent, operand->type, operand->ptr_level + !!operand->array_size); + result->var_name = gen_name(); + result->is_const_qualified = operand->is_const_qualified; + result->pointer_const_mask = operand->pointer_const_mask; + result->func_signature = operand->func_signature; + result->pointee_func_signature = operand->pointee_func_signature; + result->pointee_array_size = operand->pointee_array_size; + result->pointee_array_dim2 = operand->pointee_array_dim2; + result->pointee_array_dim3 = operand->pointee_array_dim3; + result->pointee_array_dim4 = operand->pointee_array_dim4; + result->pointee_array_element_ptr_level = + operand->pointee_array_element_ptr_level; + add_insn(parent, *bb, OP_assign, result, operand, NULL, 0, NULL); + opstack_push(result); + return; + } + + read_expr_operand(parent, bb); + operand = opstack_pop(); + if (operand->is_func && !operand->is_compound_literal) { + /* A function designator, possibly parenthesized, is its address. A + * function pointer compound literal is an object instead. + */ + opstack_push(operand); + return; + } + if (operand->compound_literal_bitfield || operand->is_bitfield) + error_at("cannot take address of bit-field", cur_token_loc()); + if (operand->is_compound_literal_reference) { + address = operand->compound_literal_address; + } else if (is_named_object(operand, parent) || + operand->is_compound_literal) { + if (operand->is_register) + error_at("cannot take address of register object", cur_token_loc()); + address = operand; + } else { + error_at("Address-of requires an lvalue operand", cur_token_loc()); + } + result = + require_typed_ptr_var(parent, operand->type, operand->ptr_level + 1); + result->var_name = gen_name(); + result->is_const_qualified = operand->is_const_qualified; + result->is_string_literal = operand->is_string_literal; + if (operand->func_signature) + result->pointee_func_signature = operand->func_signature; + else if (operand->pointee_func_signature) + result->pointee_func_signature = operand->pointee_func_signature; + add_insn( + parent, *bb, + address == operand && !operand->array_size ? OP_address_of : OP_assign, + result, address, NULL, 0, NULL); + opstack_push(result); +} + +/* Maintain a stack of expression values and operators, depending on next + * operators' priority. Either apply it or operator on stack first. + */ +void handle_address_of_operator(block_t *parent, basic_block_t **bb) +{ + char token[MAX_VAR_LEN]; + lvalue_t lvalue; + var_t *vd, *rs1, *var = NULL; + + if (!lex_peek(T_identifier, token) || + ((var = find_var(token, parent)) && is_swapped_subscript_base(var))) { + take_expression_address(parent, bb); + return; + } + if (!strcmp(token, "__func__")) { + if (!parent->func || !parent->func->return_def.var_name[0]) + error_at("__func__ is only defined inside a function", + next_token_loc()); + lex_expect(T_identifier); + vd = require_typed_ptr_var(parent, TY_char, 1); + vd->var_name = gen_name(); + vd->init_val = write_symbol(parent->func->return_def.var_name); + vd->is_string_literal = true; + vd->is_func_name_array_address = true; + opstack_push(vd); + add_insn(parent, *bb, OP_load_rodata_address, vd, NULL, NULL, 0, NULL); + return; + } + + /* A function designator already converts to its address. Preserve the + * symbol instead of sending it through lvalue lookup, which only knows + * objects and previously made `&function` fail outside file-scope scalar + * initializers. + */ + func_t *addressed_func = find_visible_func(token, parent); + if (addressed_func) { + if (parent->func && parent->func->is_inline && + !parent->func->is_static && addressed_func->is_static) + error_at( + "external inline definition references internal-linkage " + "function", + next_token_loc()); + lex_expect(T_identifier); + vd = require_func_symbol_var(parent); + vd->is_func = true; + vd->var_name = intern_string(token); + opstack_push(vd); + return; + } + if (var && var->is_register) + error_at("cannot take address of register object", next_token_loc()); + read_lvalue(&lvalue, var, parent, bb, false, OP_generic); + + if (is_bitfield(lvalue.decl)) + error_at("cannot take address of bit-field", next_token_loc()); + + if (lvalue.subscript_depth && is_pointee_array_pointer(lvalue.decl)) { + /* p[i] of a pointer to an array designates the array at that index, and + * p[i][j] one of its rows unless the subscripts reach an element. The + * subscripts have already computed that address; give it the designated + * array as its pointee rather than taking the address of the temporary + * holding it. + */ + fixed_array_shape_t shape = + fixed_array_shape_from_pointee_var(lvalue.decl); + + for (int i = 1; i < lvalue.subscript_depth; i++) + fixed_array_shape_drop_outer(&shape); + if (shape.rank) { + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = lvalue.decl->type; + vd->ptr_level = lvalue.decl->ptr_level; + vd->is_const_qualified = lvalue.decl->is_const_qualified; + copy_pointee_array_shape(vd, lvalue.decl); + fixed_array_shape_to_pointee_var(vd, &shape); + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + opstack_push(vd); + return; + } + } + + if (!lvalue.is_reference) { + rs1 = opstack_pop(); + vd = require_ref_var(parent, lvalue.type, lvalue.ptr_level); + vd->var_name = gen_name(); + + /* Address-of moves a pointer object's qualification one level inward: + * `&p`, for `int * const p`, is `int * const *`, not `const int **`. + */ + vd->is_const_qualified = lvalue.decl ? lvalue.decl->is_const_qualified + : lvalue.is_const_qualified; + vd->pointer_const_mask = lvalue.pointer_const_mask; + vd->pointee_func_signature = + lvalue.decl ? (lvalue.decl->pointee_func_signature + ? lvalue.decl->pointee_func_signature + : lvalue.decl->func_signature) + : NULL; + if (lvalue.decl && lvalue.decl->pointee_func_signature) { + vd->callback_is_const = lvalue.decl->callback_is_const; + vd->callback_is_volatile = lvalue.decl->callback_is_volatile; + } else if (lvalue.decl && lvalue.decl->func_signature) { + vd->callback_is_const = lvalue.decl->is_const_pointer; + vd->callback_is_volatile = lvalue.decl->is_volatile; + } + opstack_push(vd); + + /* A parameter declared as an array is a pointer object (C99 6.7.5.3p7), + * so its address is that of its own slot, one level above the element + * pointer it holds. + */ + bool array_parameter = lvalue.decl && !lvalue.subscript_depth && + is_array_declarator(lvalue.decl) && + is_function_parameter(lvalue.decl, parent); + + if (lvalue.decl && is_array_declarator(lvalue.decl) && + !array_parameter) { + /* An array expression already denotes its backing address in the + * IR. Taking OP_address_of of its variable would instead point at + * the compiler's local array-base slot, so `&row` passed a pointer + * to that slot rather than a pointer to row. Keep the same runtime + * address while adding the source-level pointer indirection + * required by the address-of operator. + * + * The result points to the whole array (C99 6.5.3.2p3), so it + * carries the array's bounds as its pointee shape, the form a + * declared `int (*p)[N]` has. Typed as a pointer to the first + * element instead, `&arr + 1` advanced by a single element. + */ + if (!lvalue.decl->is_flexible_array_member && + !lvalue.decl->pointee_array_size && !lvalue.decl->is_func) { + fixed_array_shape_t shape = + fixed_array_shape_from_var(lvalue.decl); + + for (int i = 0; i < lvalue.subscript_depth; i++) + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_pointee_var(vd, &shape); + vd->pointee_array_element_ptr_level = lvalue.decl->ptr_level; + } + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + } else { + if (array_parameter) + vd->ptr_level++; + add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); + } + } else if (!operand_stack[operand_stack_idx - 1]->ptr_level) { + /* A subscript types the address it computes, but a member selection + * leaves a plain int temporary, since the lvalue is normally loaded + * next. As the operand of '&' that address is the result: without the + * member's pointer type, "&s.a + 1" advanced by one byte and "sizeof + * &s.a" measured an int. + */ + vd = operand_stack[operand_stack_idx - 1]; + vd->type = lvalue.type; + vd->ptr_level = lvalue.ptr_level + 1; + } + + /* The address of a function-pointer element or member, `&fps[i]` or + * `&s.fp`, points to a callback slot. + */ + if (lvalue.is_reference && lvalue.decl && lvalue.decl->is_func && + lvalue.decl->func_signature && !lvalue.decl->array_dim2 && + lvalue.subscript_depth == (lvalue.decl->array_size ? 1 : 0)) { + vd = operand_stack[operand_stack_idx - 1]; + vd->ptr_level = 1; + vd->pointee_func_signature = lvalue.decl->func_signature; + } +} + +/* A pointer-to-array dereference designates the addressed row; it does not load + * an object. Retain that row as an array descriptor so a following grouped + * postfix subscript can use the ordinary array path. The row may hold pointers, + * as `*p` does for `int *(*p)[2]`. + */ +static bool lower_pointee_array_dereference(var_t *source, + block_t *parent, + basic_block_t **bb) +{ + type_t *element_type; + var_t *row; + + if (!is_pointee_array_pointer(source)) + return false; + + element_type = source->type->pointee_array_element_type + ? source->type->pointee_array_element_type + : source->type; + + /* For `row *p`, the row typedef is p's type, not the type of its elements. + * A row of pointers keeps it, as its element depth is counted there. + */ + if (!element_type->ptr_level) + element_type = array_typedef_element_type(element_type); + row = require_var(parent); + row->type = element_type; + fixed_array_shape_t shape = fixed_array_shape_from_pointee_var(source); + + fixed_array_shape_to_var(row, &shape); + row->ptr_level = source->pointee_array_element_ptr_level; + row->is_const_qualified = + source->is_const_qualified || element_type->is_const_qualified; + + /* A row of const callbacks, `cfn_t (*rows)[2]`, stores none of them. */ + if (!row->ptr_level && element_type->func_signature && + !element_type->is_direct_function_type && + (element_type->pointer_const_mask & 1U)) + row->is_const_qualified = true; + + /* A pointer element keeps its own qualifiers, `int *const (*r)[2]`: the + * lower levels of the pointer-to-array's mask belong to the element. + */ + if (row->ptr_level > 0 && row->ptr_level < 32) { + row->pointer_const_mask = + effective_pointer_const_mask(source) & ((1U << row->ptr_level) - 1); + row->is_const_pointer = + row->pointer_const_mask & (1U << (row->ptr_level - 1)); + } + row->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, row, source, NULL, 0, NULL); + opstack_push(row); + return true; +} + +static int pointee_array_row_stride(const var_t *var) +{ + type_t *element_type = var->type->pointee_array_element_type + ? var->type->pointee_array_element_type + : var->type; + + if (var->pointee_array_element_ptr_level) + return var->pointee_array_size * PTR_SIZE; + return var->pointee_array_size * element_type->size; +} + +/* Byte extent remaining after direct-array subscript depth. This is shared by + * ordinary postfix indexing and the bounded grouped pointer-to-array path. + */ +static int fixed_array_shape_stride(const fixed_array_shape_t *shape, + int subscript_depth, + int element_size) +{ + int stride = element_size; + + for (int i = subscript_depth + 1; i < shape->rank; i++) + stride *= shape->bounds[i]; + return stride; +} + +static bool fixed_array_shape_drop_outer(fixed_array_shape_t *shape) +{ + if (!shape->rank) + return false; + for (int i = 1; i < shape->rank; i++) + shape->bounds[i - 1] = shape->bounds[i]; + shape->rank--; + return true; +} + +static int fixed_array_subscript_stride(const var_t *var, + int subscript_depth, + int element_size) +{ + fixed_array_shape_t shape = fixed_array_shape_from_var(var); + + return fixed_array_shape_stride(&shape, subscript_depth, element_size); +} + +static int fixed_pointee_array_subscript_stride(const var_t *var, + int subscript_depth, + int element_size) +{ + fixed_array_shape_t shape = fixed_array_shape_from_pointee_var(var); + + if (subscript_depth == 0) + return var->pointee_array_size * element_size; + return fixed_array_shape_stride(&shape, subscript_depth - 1, element_size); +} + +static int fixed_array_decay_stride(const var_t *var) +{ + return fixed_array_subscript_stride(var, 0, var->type->size); +} + +static bool pointee_array_shapes_compatible(const var_t *left, + const var_t *right) +{ + type_t *left_element; + type_t *right_element; + + if (!is_pointee_array_pointer(left) || !is_pointee_array_pointer(right)) + return false; + left_element = left->type->pointee_array_element_type + ? left->type->pointee_array_element_type + : left->type; + right_element = right->type->pointee_array_element_type + ? right->type->pointee_array_element_type + : right->type; + return left->pointee_array_size == right->pointee_array_size && + left->pointee_array_dim2 == right->pointee_array_dim2 && + left->pointee_array_dim3 == right->pointee_array_dim3 && + left->pointee_array_dim4 == right->pointee_array_dim4 && + left->pointee_array_element_ptr_level == + right->pointee_array_element_ptr_level && + compatible_decl_type(left_element, right_element); +} + +/* Whether unary `*` on @array reads through the pointer to its first element + * that the array decays to. An array has no pointer level of its own, and the + * dereference paths read it as an int-sized word: a record, a row, a pointer or + * a long long element was wrongly loaded. An array of callbacks keeps its own + * paths. + */ +static bool decays_when_dereferenced(const var_t *array) +{ + return is_array_declarator(array) && !array->is_func && + !array->func_signature && !array->pointee_func_signature; +} + +/* The pointer to its first element that the array @array decays to, for a unary + * `*` to read or store through. + */ +static var_t *decay_dereferenced_array(block_t *parent, + basic_block_t **bb, + var_t *array) +{ + var_t *pointer = + require_typed_ptr_var(parent, array->type, array->ptr_level + 1); + + if (array->array_dim2) { + fixed_array_shape_t shape = fixed_array_shape_from_var(array); + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_pointee_var(pointer, &shape); + pointer->pointee_array_element_ptr_level = array->ptr_level; + } + pointer->var_name = gen_name(); + pointer->is_const_qualified = array->is_const_qualified; + add_insn(parent, *bb, OP_assign, pointer, array, NULL, 0, NULL); + return pointer; +} + +/* Whether the identifier at the next token is called, as in `*get(1)`, where + * the dereference applies to the call result rather than to the name. + */ +static bool dereferenced_call_follows(void) +{ + return lex_peek(T_identifier, NULL) && cur_token->next->next && + cur_token->next->next->kind == T_open_bracket; +} + +/* Dereference the pointer value @rs1 and push the object it designates. */ +void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1) +{ + var_t *vd; + int sz; + + if (decays_when_dereferenced(rs1)) + rs1 = decay_dereferenced_array(parent, bb, rs1); + if (rs1->is_func_name_array_address) { + /* The address of an array has the same runtime value as its first + * element. Dereferencing it restores the array, which immediately + * decays back to char * in this expression context. + */ + vd = require_typed_ptr_var(parent, TY_char, 1); + vd->var_name = gen_name(); + vd->is_string_literal = true; + opstack_push(vd); + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + return; + } + + if (lower_pointee_array_dereference(rs1, parent, bb)) + return; + + /* A loaded function pointer, as `(fps[1])` yields, designates its function + * (C99 6.5.3.2p4): `*(fps[1])` calls through the same pointer value. + */ + if (rs1->func_signature && !rs1->is_func && rs1->ptr_level == 1 && + rs1->type && !rs1->type->ptr_level && !rs1->type->func_signature) { + opstack_push(rs1); + return; + } + + /* For pointer dereference, we need to determine the target type and size. + * Since we do not have full type tracking in expressions, use defaults + */ + type_t *deref_type = rs1->type ? rs1->type : TY_int; + int deref_ptr = rs1->ptr_level + deref_type->ptr_level - 1; + + /* require_deref_var() takes the *source* pointer level and returns a + * variable one level shallower. Passing the already-decremented value + * dropped two levels per dereference, so "**(q + 0)" on an int** ended up + * reading an int-sized word where a pointer was stored. The sizes coincide + * on the 32-bit targets, which is why it only surfaces here. + */ + vd = require_deref_var(parent, deref_type, rs1->ptr_level); + sz = deref_ptr > 0 + ? PTR_SIZE + : pointer_typedef_pointee_size( + deref_type, pointee_type_from_pointer_typedef(deref_type)); + vd->var_name = gen_name(); + vd->is_const_qualified = rs1->is_const_qualified; + vd->is_string_literal = rs1->is_string_literal; + vd->pointer_const_mask = dereferenced_pointer_const_mask(rs1); + vd->is_const_pointer = + vd->ptr_level > 0 && vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + + /* Pointer arithmetic can produce a pointer to a callback typedef. The + * actual dereference consumes that object-pointer level and yields the + * pointer-valued callback result for a following postfix call. + */ + if (rs1->pointee_func_signature + ? callback_slot_depth(rs1) == 1 + : (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1)) { + vd->func_signature = rs1->pointee_func_signature + ? rs1->pointee_func_signature + : deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } else if (rs1->pointee_func_signature && + effective_pointer_depth(rs1) == 1) { + /* So does a pointer to function pointers, as `fps + 1` yields. */ + vd->func_signature = rs1->pointee_func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } + keep_callback_slot(vd, rs1); + keep_pointee_array_shape(vd, rs1); + push_object_at(parent, bb, vd, rs1, sz); +} + +/* Push the result of unary `*` on the function pointer read by read_lvalue() + * into @rs1 from @var, and return true; return false if it is no function + * pointer. C99 6.3.2.1 and 6.5.3.2p4 make the result a function designator, + * which is called through the pointer value: a function-pointer object needs + * one load, not a dereference of its return type. An element or member, as in + * `*fps[i]` or `*s.fp`, was already loaded, and a load through it would read + * the code as data; the call path takes its prototype the same way. + */ +static bool push_dereferenced_function(block_t *parent, + basic_block_t **bb, + lvalue_t *lvalue, + var_t *var, + var_t *rs1) +{ + if (lvalue->is_reference && !lvalue->value_ptr_level && + !lvalue->pointee_func_signature && !rs1->pointee_func_signature) { + func_t *signature = get_func_signature(lvalue->decl); + + if (!signature && lvalue->type) + signature = lvalue->type->func_signature; + if (!signature) + signature = rs1->func_signature; + if (signature) { + rs1->func_signature = signature; + rs1->ptr_level = 1; + opstack_push(rs1); + return true; + } + } + if (var->is_func && !lvalue->is_reference && rs1->array_size) { + /* An array of function pointers decays to the address of its first + * element, from which `*fps` reads the pointer, as fps[0] does. + */ + var_t *address = require_typed_ptr_var(parent, rs1->type, 1); + var_t *target = require_typed_ptr_var(parent, rs1->type, 1); + + address->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, address, rs1, NULL, 0, NULL); + target->var_name = gen_name(); + target->func_signature = rs1->func_signature; + add_insn(parent, *bb, OP_read, target, address, NULL, PTR_SIZE, NULL); + opstack_push(target); + return true; + } + if (var->is_func) { + opstack_push(load_function_pointer_object(parent, bb, rs1)); + return true; + } + return false; +} + +void handle_single_dereference(block_t *parent, basic_block_t **bb) +{ + var_t *vd, *rs1; + int sz; + + if (lex_peek(T_open_bracket, NULL) || lex_peek(T_increment, NULL) || + lex_peek(T_decrement, NULL) || lex_peek(T_ampersand, NULL) || + dereferenced_call_follows()) { + /* Handle general expression dereference: *(expr), and a prefix update, + * which is itself a unary expression: `*++pointer` dereferences its + * updated result. The group is a whole unary operand, so postfix + * operators after it apply before the dereference: `*(*q).p` loads + * through the member, and `*get(1)` through the call result. + */ + read_expr_operand(parent, bb); + push_dereference(parent, bb, opstack_pop()); + } else { + /* Handle simple identifier dereference: *var */ + char token[MAX_VAR_LEN]; + lvalue_t lvalue; + + if (!lex_peek(T_identifier, token)) + error_at("Expected an identifier", next_token_loc()); + + /* Builtins are expression operands, not objects in the local symbol + * table. Keep the identifier lvalue path below for `*pointer` + * assignments, but let a pointer-valued builtin such as `*va_arg(...)` + * use the same rvalue dereference lowering as `*(expression)`. + */ + if (!strcmp(token, "__builtin_va_arg")) { + type_t *deref_type; + int deref_ptr; + + read_expr_operand(parent, bb); + rs1 = opstack_pop(); + deref_type = rs1->type ? rs1->type : TY_int; + deref_ptr = rs1->ptr_level + deref_type->ptr_level - 1; + vd = require_deref_var(parent, deref_type, rs1->ptr_level); + sz = deref_ptr > 0 ? PTR_SIZE : deref_type->size; + vd->var_name = gen_name(); + vd->is_const_qualified = rs1->is_const_qualified; + vd->pointer_const_mask = dereferenced_pointer_const_mask(rs1); + vd->is_const_pointer = + vd->ptr_level > 0 && vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + push_object_at(parent, bb, vd, rs1, sz); + return; + } + var_t *var = find_var(token, parent); + + /* A raw function name is a function designator, not an object lvalue. + * Dereferencing it is a no-op in C99 (`*f` is another designator), so + * route it through the same symbol representation used by ordinary + * direct calls instead of asking read_lvalue() to find object storage. + */ + if (!var && find_visible_func(token, parent)) { + lex_expect(T_identifier); + rs1 = require_func_symbol_var(parent); + rs1->var_name = intern_string(token); + rs1->is_func = true; + opstack_push(rs1); + return; + } + read_lvalue(&lvalue, var, parent, bb, true, OP_generic); + + rs1 = opstack_pop(); + if (decays_when_dereferenced(rs1)) { + push_dereference(parent, bb, rs1); + return; + } + if (push_dereferenced_function(parent, bb, &lvalue, var, rs1)) + return; + if (lower_pointee_array_dereference(rs1, parent, bb)) + return; + + /* A member function-pointer typedef was already loaded by + * read_lvalue(). Unary `*` turns that pointer into a function + * designator; it does not read from the code address. + */ + if (rs1->func_signature && !effective_pointer_depth(rs1)) { + rs1->ptr_level = 1; + opstack_push(rs1); + return; + } + + /* `read_lvalue()` may have resolved a member expression. Derive the + * final indirection from its evaluated value rather than from the + * initial record object, so `*record.pointer` dereferences the member + * pointer instead of attempting to dereference the record itself. + */ + type_t *deref_type = rs1->type ? rs1->type : TY_int; + int deref_ptr = rs1->ptr_level + deref_type->ptr_level - 1; + + vd = require_deref_var(parent, deref_type, rs1->ptr_level); + sz = deref_ptr > 0 ? PTR_SIZE : deref_type->size; + vd->var_name = gen_name(); + vd->is_const_qualified = rs1->is_const_qualified; + vd->pointer_const_mask = dereferenced_pointer_const_mask(rs1); + vd->is_const_pointer = + vd->ptr_level > 0 && vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + + /* `*slot` is the function-pointer value when slot is a pointer to a + * callback typedef. The outer declarator's signature was deliberately + * cleared to prevent the invalid `slot(...)` form, so restore the + * element signature only after the real dereference has occurred. + */ + if (rs1->pointee_func_signature + ? callback_slot_depth(rs1) == 1 + : ((rs1->func_signature || + (deref_type && deref_type->func_signature)) && + effective_pointer_depth(rs1) == 1)) { + vd->func_signature = + rs1->pointee_func_signature + ? rs1->pointee_func_signature + : (rs1->func_signature ? rs1->func_signature + : deref_type->func_signature); + vd->ptr_level = 1; + sz = PTR_SIZE; + } + keep_callback_slot(vd, rs1); + keep_pointee_array_shape(vd, rs1); + push_object_at(parent, bb, vd, rs1, sz); + } +} + +void handle_multiple_dereference(block_t *parent, basic_block_t **bb) +{ + var_t *vd, *rs1; + int sz; + + /* Handle consecutive asterisks for multiple dereference: **pp, ***ppp, and + * the parenthesized ***(expr) form. + */ + int deref_count = 1; /* We already consumed one asterisk */ + while (lex_accept(T_asterisk)) + deref_count++; + + /* Check if we have a parenthesized expression or simple identifier */ + if (lex_peek(T_open_bracket, NULL) || lex_peek(T_ampersand, NULL) || + dereferenced_call_follows()) { + /* Handle ***(expr) case, with any postfix operators after the group */ + read_expr_operand(parent, bb); + + /* Apply dereferences one by one */ + for (int i = 0; i < deref_count; i++) + push_dereference(parent, bb, opstack_pop()); + } else { + /* Handle **pp, ***ppp case with simple identifier */ + char token[MAX_VAR_LEN]; + lvalue_t lvalue; + + if (!lex_peek(T_identifier, token)) + error_at("Expected an identifier", next_token_loc()); + var_t *var = find_var(token, parent); + read_lvalue(&lvalue, var, parent, bb, true, OP_generic); + + /* Apply dereferences one by one. Once they reach a function, the + * remaining ones are no-ops: `**fp` designates the function `*fp` does. + */ + bool designator = false; + for (int i = 0; i < deref_count; i++) { + rs1 = opstack_pop(); + if (designator) { + opstack_push(rs1); + continue; + } + if (i == 0 && !decays_when_dereferenced(rs1) && + push_dereferenced_function(parent, bb, &lvalue, var, rs1)) { + designator = true; + continue; + } + if (decays_when_dereferenced(rs1)) { + push_dereference(parent, bb, rs1); + continue; + } + if (lower_pointee_array_dereference(rs1, parent, bb)) + continue; + + /* A member lvalue has already been read into rs1. Each unary + * asterisk must consume that evaluated pointer, not re-derive its + * type from the initial record identifier. + */ + type_t *deref_type = rs1->type ? rs1->type : TY_int; + int deref_ptr = rs1->ptr_level + deref_type->ptr_level - 1; + + vd = require_deref_var(parent, deref_type, rs1->ptr_level); + sz = deref_ptr > 0 + ? PTR_SIZE + : pointer_typedef_pointee_size( + deref_type, + pointee_type_from_pointer_typedef(deref_type)); + vd->var_name = gen_name(); + vd->is_const_qualified = rs1->is_const_qualified; + vd->pointer_const_mask = dereferenced_pointer_const_mask(rs1); + vd->is_const_pointer = + vd->ptr_level > 0 && vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + + /* Only the dereference of a pointer to a callback, the last read of + * `**slots` or `**fpp`, reaches the callback object. Earlier reads + * still produce a pointer-to-callback and must not be callable. + */ + if (rs1->pointee_func_signature + ? callback_slot_depth(rs1) == 1 + : ((rs1->func_signature || + (deref_type && deref_type->func_signature)) && + effective_pointer_depth(rs1) == 1)) { + vd->func_signature = + rs1->pointee_func_signature + ? rs1->pointee_func_signature + : (rs1->func_signature ? rs1->func_signature + : deref_type->func_signature); + vd->ptr_level = 1; + sz = PTR_SIZE; + designator = true; + } + keep_callback_slot(vd, rs1); + keep_pointee_array_shape(vd, rs1); + push_object_at(parent, bb, vd, rs1, sz); + } + } +} + +/* Lower a postfix member chain from the record at @address, of @record_type, + * and push the selected value on the operand stack. The chain starts with `->` + * when @arrow_first, @address then being the pointer operand, and with `.` + * otherwise. @is_lvalue says the record designates an object; a chain that + * follows a pointer member reaches an object in any case. An array member left + * with dimensions unconsumed decays to a pointer, and a record member is pushed + * through push_object_at(), as a record element is. + */ +void lower_member_postfix(var_t *address, + type_t *record_type, + bool arrow_first, + bool is_lvalue, + block_t *parent, + basic_block_t **bb) +{ + var_t *field = NULL; + var_t *result; + fixed_array_shape_t shape = {0}; + int depth = 0; + + /* A member of a const record is not a modifiable lvalue (C99 6.3.2.1p1). + * The operand's address points to that record, and each further record + * member or pointee it selects may add the qualifier. + */ + bool record_const = address->is_const_qualified; + + while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL) || + (field && depth < shape.rank && lex_peek(T_open_square, NULL))) { + char name[MAX_ID_LEN]; + + if (lex_accept(T_open_square)) { + int element_size = field->ptr_level || field->is_func + ? PTR_SIZE + : field->type->size; + int stride = fixed_array_shape_stride(&shape, depth, element_size); + var_t *index; + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (stride != 1) { + var_t *scale = require_var(parent); + var_t *scaled = require_var(parent); + + scale->var_name = gen_name(); + scale->init_val = stride; + add_insn(parent, *bb, OP_load_constant, scale, NULL, NULL, 0, + NULL); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, index, scale, 0, NULL); + index = scaled; + } + var_t *indexed = require_typed_ptr_var(parent, field->type, 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, 0, NULL); + address = indexed; + depth++; + if (depth == shape.rank && is_record_type(field->type) && + !field->ptr_level) + record_type = field->type; + continue; + } + + if (!field) { + /* The first selection applies to the operand itself. */ + if (!lex_accept(arrow_first ? T_arrow : T_dot)) + error_at(arrow_first ? "Cannot apply dot operator to pointer" + : "Cannot apply arrow operator to record", + next_token_loc()); + } else if (lex_accept(T_arrow)) { + /* Only a selected pointer-to-record member can be followed. Its + * star may be spelled on the member or hidden in a pointer typedef + * such as `typedef struct node *link`. + */ + type_t *pointee = + field->ptr_level + ? field->type + : pointee_type_from_pointer_typedef(field->type); + + if (depth < shape.rank || effective_pointer_depth(field) != 1 || + field->is_func || !is_record_type(pointee)) + error_at("Invalid record member access", cur_token_loc()); + var_t *pointer = + require_typed_ptr_var(parent, field->type, field->ptr_level); + + pointer->var_name = gen_name(); + add_insn(parent, *bb, OP_read, pointer, address, NULL, PTR_SIZE, + NULL); + address = pointer; + record_type = pointee; + is_lvalue = true; + record_const = + field->is_const_qualified || field->type->is_const_qualified; + } else { + lex_expect(T_dot); + if (field) + record_const = record_const || field->is_const_qualified || + field->type->is_const_qualified; + } + if (!record_type) + error_at("Member access requires a record", cur_token_loc()); + lex_ident(T_identifier, name); + field = find_member(name, record_type); + if (!field) + error_at("Unknown struct or union member", cur_token_loc()); + address = compute_field_address(parent, bb, address, field); + shape = fixed_array_shape_from_var(field); + depth = 0; + record_type = is_record_type(field->type) && !field->ptr_level && + !field->array_size + ? field->type + : NULL; + } + + bool decayed = depth < shape.rank; + + if (decayed && unevaluated_expression_depth) { + /* sizeof observes the array itself, before any decay. */ + for (int i = 0; i < depth; i++) + fixed_array_shape_drop_outer(&shape); + result = require_typed_ptr_var(parent, field->type, field->ptr_level); + fixed_array_shape_to_var(result, &shape); + result->var_name = gen_name(); + opstack_push(result); + } else if (decayed) { + for (int i = 0; i <= depth; i++) + fixed_array_shape_drop_outer(&shape); + result = + require_typed_ptr_var(parent, field->type, field->ptr_level + 1); + fixed_array_shape_to_pointee_var(result, &shape); + result->pointee_array_element_ptr_level = field->ptr_level; + result->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, result, address, NULL, 0, NULL); + opstack_push(result); + } else if (is_bitfield(field)) { + result = read_bitfield_value(parent, bb, address, field); + opstack_push(result); + if (is_lvalue) + mark_value_reference(result, address, field); + } else { + result = require_typed_ptr_var(parent, field->type, field->ptr_level); + result->var_name = gen_name(); + result->func_signature = field->func_signature; + + /* A function-pointer member is loaded as the pointer value a call + * consumes, not as an object to be loaded again. + */ + if (field->func_signature && !result->ptr_level) + result->ptr_level = 1; + push_object_at( + parent, bb, result, address, + field->ptr_level || field->type->ptr_level || field->is_func + ? PTR_SIZE + : field->type->size); + if (!is_lvalue) + result->is_compound_literal_reference = false; + } + result->is_const_qualified = + field->is_const_qualified || field->type->is_const_qualified; + if (record_const && effective_pointer_depth(result) && !field->is_func && + !field->func_signature && !decayed) { + /* A pointer member keeps its pointee's qualifier; the record's const + * makes the pointer object itself read-only. An array member that + * decays instead yields a pointer to the record's const elements, which + * the else branch qualifies. + */ + result->is_const_pointer = true; + if (result->ptr_level > 0 && result->ptr_level <= 32) + result->pointer_const_mask |= 1U << (result->ptr_level - 1); + } else if (record_const) { + result->is_const_qualified = true; + } + if (!is_lvalue && + (lex_peek(T_assign, NULL) || lex_peek(T_increment, NULL) || + lex_peek(T_decrement, NULL))) + error_at("member of a function call result is not assignable", + next_token_loc()); +} diff --git a/src/parser-const.c b/src/parser-const.c new file mode 100644 index 00000000..25bafd69 --- /dev/null +++ b/src/parser-const.c @@ -0,0 +1,1611 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Evaluation of constant expressions and address constants in file-scope and + * static initializers. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +/* Use the ordinary sizeof expression parser in a detached block. Global + * initializers need the operand's type, never its generated instructions or + * side effects. cur_token remains the already-consumed sizeof token. + */ +static int read_global_sizeof_expression(block_t *scope) +{ + basic_block_t *unevaluated_bb = bb_create(scope); + var_t *result; + + handle_sizeof_operator(scope, &unevaluated_bb); + result = opstack_pop(); + return result->init_val; +} + +int read_const_wstring_size(void) +{ + int values[MAX_STRING_LEN]; + type_t *wide_type = find_type("wchar_t", true); + return (read_wstring_units(values, MAX_STRING_LEN) + 1) * wide_type->size; +} + +/* Evaluate a sizeof operator, already consumed, in an integer constant + * expression: a file-scope or static initializer, an array bound, a case label + * or an enumerator. A parenthesized type name goes through the declaration-only + * evaluator. Every other operand goes through the ordinary sizeof parser in a + * detached block, which keeps a declared extent through grouping, members, + * subscripts, '*' and '&' exactly as a block-scope sizeof does. + */ +int read_sizeof_constant(block_t *scope) +{ + /* The detached expression parser needs a block to hold its values. */ + if (!scope) + scope = GLOBAL_BLOCK; + if (lex_peek(T_open_bracket, NULL) && + sizeof_cast_starts_at(scope, cur_token->next->next)) + return read_const_sizeof_type(scope); + return read_global_sizeof_expression(scope); +} + +int eval_expression_imm(opcode_t op, int op1, int op2) +{ + /* return immediate result */ + int tmp = op2; + int res = 0; + switch (op) { + case OP_add: + if (checking_enum_constant && ((op2 > 0 && op1 > INT_MAX - op2) || + (op2 < 0 && op1 < INT_MIN - op2))) + error_at("Enumerator value exceeds int range", cur_token_loc()); + res = op1 + op2; + break; + case OP_sub: + if (checking_enum_constant && ((op2 < 0 && op1 > INT_MAX + op2) || + (op2 > 0 && op1 < INT_MIN + op2))) + error_at("Enumerator value exceeds int range", cur_token_loc()); + res = op1 - op2; + break; + case OP_mul: + if (checking_enum_constant && op1 && op2 && + ((op1 > 0 && op2 > 0 && op1 > INT_MAX / op2) || + (op1 > 0 && op2 < 0 && op2 < INT_MIN / op1) || + (op1 < 0 && op2 > 0 && op1 < INT_MIN / op2) || + (op1 < 0 && op2 < 0 && op1 < INT_MAX / op2))) + error_at("Enumerator value exceeds int range", cur_token_loc()); + res = op1 * op2; + break; + case OP_div: + if (!op2) + error_at("Division by zero in constant expression", + cur_token_loc()); + + /* INT_MIN / -1 has no representable result; on x86 it raises SIGFPE + * rather than producing one. + */ + if (op1 == INT_MIN && op2 == -1) + error_at("Overflow in constant expression", cur_token_loc()); + res = op1 / op2; + break; + case OP_mod: + if (!op2) + error_at("Modulo by zero in constant expression", cur_token_loc()); + if (op1 == INT_MIN && op2 == -1) + error_at("Overflow in constant expression", cur_token_loc()); + /* Use bitwise AND for modulo optimization when divisor is power of 2 */ + if (tmp == INT_MIN) { + res = op1 % op2; + break; + } + tmp = tmp < 0 ? -tmp : tmp; + tmp &= (tmp - 1); + if (tmp != 0) { + res = op1 % op2; + break; + } + op2 = op2 < 0 ? -op2 : op2; + res = op1 & (op2 - 1); + if (op1 < 0 && res != 0) + res -= op2; + break; + case OP_lshift: + if (checking_enum_constant && + (op2 < 0 || op2 >= 32 || op1 < 0 || op1 > (INT_MAX >> op2))) + error_at("Enumerator value exceeds int range", cur_token_loc()); + + /* A count outside the int width has no defined result, and the host + * compiler must not be asked to produce one. Shift the unsigned bit + * pattern so a set sign bit is not host undefined behavior either. + */ + if (op2 < 0 || op2 >= 32) + error_at("Shift count out of range in constant expression", + cur_token_loc()); + res = (int) ((unsigned int) op1 << op2); + break; + case OP_rshift: + if (op2 < 0 || op2 >= 32) + error_at("Shift count out of range in constant expression", + cur_token_loc()); + res = op1 >> op2; + break; + case OP_log_and: + res = op1 && op2; + break; + case OP_log_or: + res = op1 || op2; + break; + case OP_eq: + res = op1 == op2; + break; + case OP_neq: + res = op1 != op2; + break; + case OP_lt: + res = op1 < op2; + break; + case OP_gt: + res = op1 > op2; + break; + case OP_leq: + res = op1 <= op2; + break; + case OP_geq: + res = op1 >= op2; + break; + case OP_bit_and: + res = op1 & op2; + break; + case OP_bit_or: + res = op1 | op2; + break; + case OP_bit_xor: + res = op1 ^ op2; + break; + default: + error_at("The requested operation is not supported.", cur_token_loc()); + } + return res; +} + +bool read_global_assignment_var(var_t *var); + +/* Diagnose the conversion of the function designator @symbol to @var, or of the + * enclosing cast to a function pointer type when there is one. + */ +static void diagnose_global_function_conversion(var_t *symbol, var_t *var) +{ + var_t cast = {0}; + + if (!global_function_cast_signature) { + diagnose_function_pointer_conversion(symbol, var); + return; + } + cast.type = global_function_cast_signature->return_def.type; + cast.ptr_level = 1; + cast.func_signature = global_function_cast_signature; + diagnose_function_pointer_conversion(&cast, var); +} + +/* The integer evaluators below yield constants only, so a nonzero value for a + * pointer object, a function pointer among them, is an integer converted + * without a cast. Under a cast to a function pointer type, the conversion is + * the explicit one the cast performs. + */ +static void reject_global_integer_pointer(const var_t *dest, const var_t *src) +{ + if ((effective_pointer_depth(dest) || dest->is_func) && !dest->array_size) { + if (global_function_cast_signature) + diagnose_global_function_conversion((var_t *) src, (var_t *) dest); + else if (!is_pointer_like_value((var_t *) src) && !src->is_func && + !src->is_string_literal && (src->init_val || src->init_val_hi)) + error_at("integer converted to pointer without a cast", + cur_token_loc()); + } +} + +void emit_global_scalar_assignment(block_t *parent, + basic_block_t *bb, + var_t *dest, + var_t *src) +{ + reject_global_integer_pointer(dest, src); + if (is_bool_scalar(dest->type, dest->ptr_level)) + src->init_val = src->init_val != 0; + add_insn(parent, bb, OP_assign, dest, src, NULL, 0, NULL); +} + +/* Return whether token, an opening parenthesis, begins a cast to an integer + * type: nothing but integer type specifiers and qualifiers before its closing + * parenthesis. + */ +static bool global_integer_cast_starts_at(token_t *token, block_t *scope) +{ + bool has_type = false; + + if (!token || token->kind != T_open_bracket) + return false; + for (token = token->next; token && token->kind != T_close_bracket; + token = token->next) { + if (token->kind == T_signed || token->kind == T_unsigned || + token->kind == T_long) { + has_type = true; + } else if (token->kind == T_identifier) { + type_t *type = find_visible_type(token->literal, scope); + + if (!type || type->ptr_level || type->array_size || + type->is_floating || type->func_signature || + type->is_direct_function_type || is_record_type(type) || + type == TY_void) + return false; + has_type = true; + } else if (token->kind != T_const && token->kind != T_volatile) { + return false; + } + } + return token && has_type; +} + +/* Whether the initializer that starts at @token needs the two-word reader: some + * token of it, before the ',' or ';' that ends it outside any parenthesis it + * contains or the ')' that closes an enclosing one, is a wide or typed literal, + * or with @casts an integer cast in @scope, of which the word-sized evaluator + * has no notion. + */ +static bool initializer_needs_wide_reader(token_t *token, + block_t *scope, + bool casts) +{ + int bracket_depth = 0; + + for (; token; token = token->next) { + if ((token->kind == T_numeric && + (numeric_literal_needs_wide_path(token->literal) || + numeric_literal_needs_typed_global_path(token->literal))) || + (casts && global_integer_cast_starts_at(token, scope))) + return true; + if (token->kind == T_open_bracket) + bracket_depth++; + else if (token->kind == T_close_bracket) { + if (bracket_depth == 0) + return false; + bracket_depth--; + } else if (bracket_depth == 0 && + (token->kind == T_semicolon || token->kind == T_comma)) + return false; + } + return false; +} + +/* Keep the legacy word-sized evaluator for ordinary constants and casts, but + * select the two-word path whenever a literal-only initializer contains a wide + * token. Looking past leading narrow operands matters for expressions such as + * `(3 + 0x100000000LL)`: the old evaluator would consume the later token before + * it had a chance to preserve its high word. + */ +bool typed_global_literal_appears_before_initializer_end(token_t *token) +{ + return initializer_needs_wide_reader(token, NULL, false); +} + +/* Whether a subscripted string literal, as in `"ab"[1]`, appears in the static + * initializer that starts at @token. Only the literal expression reader folds + * it; integer constant expressions such as enumerators do not admit it. + */ +bool string_element_appears_before_initializer_end(token_t *token) +{ + int bracket_depth = 0; + + for (; token; token = token->next) { + if ((token->kind == T_string || token->kind == T_wstring) && + token->next && token->next->kind == T_open_square) + return true; + if (token->kind == T_open_bracket) + bracket_depth++; + else if (token->kind == T_close_bracket) { + if (bracket_depth == 0) + return false; + bracket_depth--; + } else if (bracket_depth == 0 && + (token->kind == T_semicolon || token->kind == T_comma || + token->kind == T_close_curly)) + return false; + } + return false; +} + +var_t *read_wide_global_literal_expression(block_t *parent, + basic_block_t *bb, + block_t *scope); + +/* Global address construction currently carries its scaled index as an int. + * Accept a wide constant expression when its final value is representable by + * that index, but never silently discard its high word. + */ +int narrow_wide_global_address_offset(var_t *value) +{ + if ((value->type->is_unsigned && + (value->init_val_hi || (unsigned int) value->init_val > INT_MAX)) || + (!value->type->is_unsigned && + value->init_val_hi != (value->init_val < 0 ? -1 : 0))) + error_at("Global address offset exceeds supported integer range", + cur_token_loc()); + return value->init_val; +} + +/* A global pointer offset may use the same literal-only wide expression as a + * scalar global initializer. Its final index still flows through the current + * word-sized address relocation representation. + */ +int read_global_address_offset(block_t *scope, + block_t *parent, + basic_block_t *bb) +{ + token_t *operator_token = cur_token->next; + + if (operator_token && operator_token->next && + typed_global_literal_appears_before_initializer_end( + operator_token->next)) { + bool negate = lex_accept(T_minus); + var_t *wide_offset; + int index; + + if (!negate) + lex_expect(T_plus); + wide_offset = read_wide_global_literal_expression(parent, bb, scope); + index = narrow_wide_global_address_offset(wide_offset); + return negate ? -index : index; + } + return read_const_expr(scope); +} + +static int wide_global_unevaluated_depth; + +/* The high word an int-sized value of this type has once it is widened: a + * signed source sign-extends and an unsigned source zero-extends. + */ +static unsigned int wide_global_narrow_high(unsigned int lo, bool is_unsigned) +{ + return is_unsigned || !(lo & 0x80000000U) ? 0 : 0xffffffffU; +} + +/* Whether @value is an address constant: a string literal, the address of a + * global object or a function designator. Such a value is never null, so the + * operators that only test it know its truth without its address. + */ +static bool wide_global_address_operand(const var_t *value) +{ + return value->is_string_literal || value->is_global_address || + value->is_func; +} + +static bool wide_global_operand_is_true(const var_t *value) +{ + return wide_global_address_operand(value) || + (value->init_val || value->init_val_hi); +} + +/* Record a folded result. An int-sized result keeps only its low word, so a + * carry, borrow or product overflow computed in the high word cannot leak into + * a later wide reduction or a truth test of this value. + */ +static void store_wide_global_word_result(block_t *parent, + basic_block_t *bb, + var_t *result, + unsigned int lo, + unsigned int hi) +{ + if (result->type && result->type->size <= TY_int->size) + hi = wide_global_narrow_high(lo, result->type->is_unsigned); + result->init_val = lo; + result->init_val_hi = hi; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); +} + +/* Fold the operations that only need word arithmetic before emitting global + * setup code. That setup block is deliberately conservative about constants, + * and previously allowed a paired add/subtract to lose its high half. + */ +bool emit_wide_global_word_arithmetic(block_t *parent, + basic_block_t *bb, + var_t *result, + opcode_t op, + var_t *left, + var_t *right) +{ + unsigned int lo = (unsigned int) left->init_val; + unsigned int hi = (unsigned int) left->init_val_hi; + unsigned int rhs_lo = right ? (unsigned int) right->init_val : 0; + unsigned int rhs_hi = right ? (unsigned int) right->init_val_hi : 0; + unsigned int out_lo, out_hi; + + if (wide_global_unevaluated_depth) { + result->init_val = 0; + result->init_val_hi = 0; + result->is_const = true; + return true; + } + + /* An address constant plus or minus an integer constant is itself an + * address constant (C99 6.6p7), with the integer first in a sum as well. + */ + if ((op == OP_add || op == OP_sub) && right) { + var_t *address = wide_global_address_operand(left) ? left + : op == OP_add ? right + : NULL; + var_t *index = address == left ? right : left; + int stride = !address || address->is_func ? 0 + : address->is_string_literal ? address->type->size + : address->address_stride; + + if (stride && wide_global_address_operand(address) && + !wide_global_address_operand(index)) { + var_t *offset = require_var(parent); + int step = narrow_wide_global_address_offset(index); + + if (step > INT_MAX / stride || step < -(INT_MAX / stride)) + error_at( + "Global address offset exceeds supported integer range", + cur_token_loc()); + offset->var_name = gen_name(); + offset->init_val = step * (op == OP_sub ? -stride : stride); + add_insn(parent, bb, OP_load_constant, offset, NULL, NULL, 0, NULL); + result->type = address->type; + result->ptr_level = address->ptr_level; + result->is_global_address = address->is_global_address; + result->is_string_literal = address->is_string_literal; + result->is_const_qualified = address->is_const_qualified; + result->pointee_func_signature = address->pointee_func_signature; + result->address_stride = address->address_stride; + add_insn(parent, bb, OP_add, result, address, offset, 0, NULL); + return true; + } + } + + /* An address constant is otherwise an operand only of the operators that + * test it. Other arithmetic on one is not a constant expression here. + */ + if ((wide_global_address_operand(left) || + (right && wide_global_address_operand(right))) && + op != OP_log_and && op != OP_log_or) + error_at("Global initializer requires a constant value", + cur_token_loc()); + + /* Materialize the usual arithmetic conversion in word form before any + * operation reads a high word, rather than trusting the operand's stored + * one: an enumeration constant never sets it. When the common type is + * int-sized the operands convert to that type, so an int meeting an + * unsigned int zero-extends; otherwise each narrow operand extends by its + * own signedness. A shift converts only its left operand, by promotion. + */ + if (right && op != OP_lshift && op != OP_rshift) { + type_t *common = integer_common_type(left, right); + bool narrow_unsigned = + common->size <= TY_int->size && common->is_unsigned; + + if (right->type->size <= TY_int->size) + rhs_hi = wide_global_narrow_high( + rhs_lo, right->type->is_unsigned || narrow_unsigned); + if (left->type->size <= TY_int->size) + hi = wide_global_narrow_high( + lo, left->type->is_unsigned || narrow_unsigned); + } else if (left->type->size <= TY_int->size) { + hi = wide_global_narrow_high(lo, left->type->is_unsigned); + } + + /* The restricted global evaluator selects a conditional arm while parsing, + * so comparison results must carry their constant payload rather than exist + * only as setup-block IR. Keep this in the compiler's existing two-word + * representation: it must also self-host on targets without a complete + * host-level 64-bit value ABI. + */ + if (op == OP_eq || op == OP_neq || op == OP_lt || op == OP_leq || + op == OP_gt || op == OP_geq) { + type_t *common = integer_common_type(left, right); + bool equal; + bool less; + bool comparison; + + if (common->size <= TY_int->size) { + equal = lo == rhs_lo; + if (common->is_unsigned) + less = lo < rhs_lo; + else if ((lo ^ rhs_lo) & 0x80000000U) + less = lo & 0x80000000U; + else + less = lo < rhs_lo; + } else { + equal = hi == rhs_hi && lo == rhs_lo; + if (common->is_unsigned) + less = hi < rhs_hi || (hi == rhs_hi && lo < rhs_lo); + else if ((hi ^ rhs_hi) & 0x80000000U) + less = hi & 0x80000000U; + else + less = hi < rhs_hi || (hi == rhs_hi && lo < rhs_lo); + } + + switch (op) { + case OP_eq: + comparison = equal; + break; + case OP_neq: + comparison = !equal; + break; + case OP_lt: + comparison = less; + break; + case OP_leq: + comparison = less || equal; + break; + case OP_gt: + comparison = !less && !equal; + break; + default: + comparison = !less; + break; + } + store_wide_global_word_result(parent, bb, result, comparison, 0); + return true; + } + + if (op == OP_log_and || op == OP_log_or) { + bool left_true = wide_global_operand_is_true(left); + bool right_true = right && wide_global_operand_is_true(right); + + store_wide_global_word_result(parent, bb, result, + op == OP_log_and + ? left_true && right_true + : left_true || right_true, + 0); + return true; + } + + if (op == OP_div || op == OP_mod) { + unsigned int rem_lo = 0, rem_hi = 0, quo_lo = 0, quo_hi = 0; + + /* The usual arithmetic conversions choose the signedness: a signed long + * long divided by an unsigned int stays signed long long. + */ + type_t *common = integer_common_type(left, right); + bool is_unsigned = common->is_unsigned; + bool neg_left = !is_unsigned && (hi >> 31); + bool neg_right = !is_unsigned && (rhs_hi >> 31); + + if (rhs_lo == 0 && rhs_hi == 0) + error_at("division by zero in global constant expression", + cur_token_loc()); + + /* The minimum of the signed common type divided by -1 has no + * representable quotient, and its remainder is undefined as well. + * Diagnose it as the word-sized evaluator does INT_MIN / -1. + */ + if (!is_unsigned && rhs_lo == 0xffffffffU && rhs_hi == 0xffffffffU && + (common->size <= TY_int->size ? lo == 0x80000000U + : hi == 0x80000000U && lo == 0)) + error_at("Overflow in constant expression", cur_token_loc()); + if (neg_left) { + lo = ~lo + 1; + hi = ~hi + (lo == 0); + } + if (neg_right) { + rhs_lo = ~rhs_lo + 1; + rhs_hi = ~rhs_hi + (rhs_lo == 0); + } + for (int i = 0; i < 64; i++) { + unsigned int incoming = hi >> 31; + + hi = (hi << 1) | (lo >> 31); + lo <<= 1; + rem_hi = (rem_hi << 1) | (rem_lo >> 31); + rem_lo = (rem_lo << 1) | incoming; + quo_hi = (quo_hi << 1) | (quo_lo >> 31); + quo_lo <<= 1; + if (rem_hi > rhs_hi || (rem_hi == rhs_hi && rem_lo >= rhs_lo)) { + unsigned int borrow = rem_lo < rhs_lo; + + rem_lo -= rhs_lo; + rem_hi = rem_hi - rhs_hi - borrow; + quo_lo |= 1; + } + } + if (op == OP_div) { + out_lo = quo_lo; + out_hi = quo_hi; + if (neg_left != neg_right) { + out_lo = ~out_lo + 1; + out_hi = ~out_hi + (out_lo == 0); + } + } else { + out_lo = rem_lo; + out_hi = rem_hi; + if (neg_left) { + out_lo = ~out_lo + 1; + out_hi = ~out_hi + (out_lo == 0); + } + } + store_wide_global_word_result(parent, bb, result, out_lo, out_hi); + return true; + } + + switch (op) { + case OP_add: + out_lo = lo + rhs_lo; + out_hi = hi + rhs_hi + (out_lo < lo); + break; + case OP_sub: + out_lo = lo - rhs_lo; + out_hi = hi - rhs_hi - (lo < rhs_lo); + break; + case OP_mul: { + unsigned int p0 = (lo & 0xffffU) * (rhs_lo & 0xffffU); + unsigned int p1 = (lo & 0xffffU) * (rhs_lo >> 16); + unsigned int p2 = (lo >> 16) * (rhs_lo & 0xffffU); + unsigned int p3 = (lo >> 16) * (rhs_lo >> 16); + unsigned int carry = (p0 >> 16) + (p1 & 0xffffU) + (p2 & 0xffffU); + + out_lo = (p0 & 0xffffU) | (carry << 16); + out_hi = p3 + (p1 >> 16) + (p2 >> 16) + (carry >> 16) + hi * rhs_lo + + lo * rhs_hi; + break; + } + case OP_lshift: + if (rhs_lo >= 64) { + out_lo = 0; + out_hi = 0; + } else if (rhs_lo >= 32) { + out_lo = 0; + out_hi = lo << (rhs_lo - 32); + } else if (rhs_lo == 0) { + out_lo = lo; + out_hi = hi; + } else { + out_lo = lo << rhs_lo; + out_hi = (hi << rhs_lo) | (lo >> (32 - rhs_lo)); + } + break; + case OP_rshift: + if (rhs_lo >= 64) { + out_hi = left->type->is_unsigned || !(hi >> 31) ? 0 : ~0U; + out_lo = out_hi; + } else if (rhs_lo >= 32) { + out_lo = left->type->is_unsigned + ? hi >> (rhs_lo - 32) + : (unsigned int) ((int) hi >> (rhs_lo - 32)); + out_hi = left->type->is_unsigned || !(hi >> 31) ? 0 : ~0U; + } else if (rhs_lo == 0) { + out_lo = lo; + out_hi = hi; + } else { + out_lo = (lo >> rhs_lo) | (hi << (32 - rhs_lo)); + out_hi = left->type->is_unsigned + ? hi >> rhs_lo + : (unsigned int) ((int) hi >> rhs_lo); + } + break; + case OP_bit_and: + out_lo = lo & rhs_lo; + out_hi = hi & rhs_hi; + break; + case OP_bit_or: + out_lo = lo | rhs_lo; + out_hi = hi | rhs_hi; + break; + case OP_bit_xor: + out_lo = lo ^ rhs_lo; + out_hi = hi ^ rhs_hi; + break; + case OP_bit_not: + out_lo = ~lo; + out_hi = ~hi; + break; + default: + return false; + } + store_wide_global_word_result(parent, bb, result, out_lo, out_hi); + return true; +} + +/* Apply the integer promotions to @value, an operand of a unary arithmetic + * operator or of a shift or other binary operator. A value narrower than int + * already holds its converted bit pattern in the low word; retyping it as int + * lets the negation, complement or shift that follows extend its result as an + * int rather than as the unsigned char or short the cast named. + */ +static var_t *promote_wide_global_operand(block_t *parent, + basic_block_t *bb, + var_t *value) +{ + var_t *promoted; + + /* An address constant has no integer promotion to make: its type names the + * pointee, so a char pointer must not be retyped as the int its target + * width would suggest. + */ + if (value->ptr_level || wide_global_address_operand(value) || + value->type->size >= TY_int->size) + return value; + promoted = require_typed_var(parent, TY_int); + promoted->var_name = gen_name(); + promoted->init_val = value->init_val; + promoted->init_val_hi = wide_global_narrow_high(value->init_val, false); + promoted->is_const = true; + add_insn(parent, bb, OP_load_constant, promoted, NULL, NULL, 0, NULL); + return promoted; +} + +/* A grouped primary is lowered into the same global setup block as its parent. + * The caller owns the closing parenthesis, so get_operator() naturally stops an + * inner precedence stack without consuming its delimiter. + */ +var_t *read_wide_global_literal_primary(block_t *parent, + basic_block_t *bb, + block_t *scope) +{ + char literal[MAX_TOKEN_LEN]; + var_t *value; + + /* Keep wide unary operators out of the legacy word-sized constant + * evaluator. Besides making `~0ULL` usable in a static initializer, the + * recursive form gives grouped operands and repeated unary operators the + * same semantics as ordinary expression parsing. + */ + if (lex_accept(T_plus)) + return read_wide_global_literal_primary(parent, bb, scope); + if (lex_accept(T_minus)) { + var_t *zero = require_var(parent); + var_t *result; + + /* Preserve the special lexical treatment of the magnitude of LLONG_MIN. + * read_numeric_param() needs to know that the immediately preceding + * unary minus will consume 2^63. + */ + if (lex_peek(T_numeric, literal)) { + read_numeric_param(parent, bb, true); + value = opstack_pop(); + force_wide_global_literal_type(value, literal); + return value; + } + value = promote_wide_global_operand( + parent, bb, read_wide_global_literal_primary(parent, bb, scope)); + zero->var_name = gen_name(); + zero->type = value->type; + zero->init_val = 0; + add_insn(parent, bb, OP_load_constant, zero, NULL, NULL, 0, NULL); + result = require_var(parent); + result->var_name = gen_name(); + result->type = value->type; + emit_wide_global_word_arithmetic(parent, bb, result, OP_sub, zero, + value); + return result; + } + if (lex_accept(T_bit_not)) { + var_t *result; + + value = promote_wide_global_operand( + parent, bb, read_wide_global_literal_primary(parent, bb, scope)); + result = require_var(parent); + result->var_name = gen_name(); + result->type = value->type; + emit_wide_global_word_arithmetic(parent, bb, result, OP_bit_not, value, + NULL); + return result; + } + if (lex_accept(T_log_not)) { + var_t *result; + + value = read_wide_global_literal_primary(parent, bb, scope); + result = require_typed_var(parent, TY_int); + result->var_name = gen_name(); + result->init_val = !wide_global_operand_is_true(value); + result->init_val_hi = 0; + result->is_const = true; + if (!wide_global_unevaluated_depth) + add_insn(parent, bb, OP_log_not, result, value, NULL, 0, NULL); + return result; + } + + /* An address constant may be an operand here, as the arms of `1 ? "a" : + * "b"` are. Every address form belongs to the aggregate constant reader, + * which reads this one operand rather than the whole expression it is part + * of. A subscripted literal such as `"ab"[1]` is an integer and was read + * above. + */ + if (!subscripted_string_literal_starts_here() && + (lex_peek(T_string, NULL) || + global_address_operand_starts_here(scope))) { + basic_block_t *address_bb = bb; + var_t *address; + + global_tested_operand_depth++; + address = parse_global_constant_value(parent, &address_bb); + global_tested_operand_depth--; + return address; + } + if (lex_accept(T_sizeof)) { + value = require_typed_var(parent, find_type("size_t", true)); + value->var_name = gen_name(); + value->init_val = read_sizeof_constant(scope); + value->init_val_hi = 0; + value->is_const = true; + add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); + return value; + } + if (global_integer_cast_starts_at(cur_token->next, scope)) { + basic_block_t *unevaluated_bb = bb_create(parent); + token_t *cast_start = cur_token; + var_t *operand; + type_t *type; + unsigned int hi; + + /* A cast of a constant operand is still an integer constant expression. + * The ordinary operand parser owns type-name syntax, so let it name the + * target type in a detached block. Then return to the operand and read + * it as a wide primary, so a grouped operand keeps its high word. + */ + read_expr_operand(parent, &unevaluated_bb); + operand = opstack_pop(); + type = operand->type; + cur_token = cast_start; + lex_expect(T_open_bracket); + while (!lex_peek(T_close_bracket, NULL)) + cur_token = cur_token->next; + lex_expect(T_close_bracket); + + operand = read_wide_global_literal_primary(parent, bb, scope); + hi = (unsigned int) operand->init_val_hi; + if (operand->type->size <= TY_int->size) + hi = wide_global_narrow_high(operand->init_val, + operand->type->is_unsigned); + value = require_typed_var(parent, type); + value->var_name = gen_name(); + fold_integer_constant_cast(value, (unsigned int) operand->init_val, hi); + value->is_const = true; + add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); + return value; + } + if (lex_accept(T_open_bracket)) { + value = read_wide_global_literal_expression(parent, bb, scope); + lex_expect(T_close_bracket); + return value; + } + if (lex_peek(T_identifier, literal)) { + constant_t *constant = find_scoped_constant(literal, scope); + + if (!constant) + error_at("Typed global initializer needs a constant operand", + next_token_loc()); + lex_expect(T_identifier); + value = require_typed_var(parent, TY_int); + value->var_name = gen_name(); + + /* Every operand this reader returns carries its high word, the + * extension of an int-sized value, so no consumer rebuilds it. + */ + value->init_val = constant->value; + value->init_val_hi = wide_global_narrow_high(constant->value, false); + value->is_const = true; + add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); + return value; + } + if (subscripted_string_literal_starts_here()) { + int element; + + /* An element of a narrow literal is an arithmetic constant in an + * initializer (C99 6.6p10), as gcc folds it; gcc rejects a wide one. + */ + if (lex_peek(T_wstring, NULL)) + error_at("Wide string literal element is not a constant", + next_token_loc()); + read_string_literal_element(scope, &element); + value = require_typed_var(parent, TY_int); + value->var_name = gen_name(); + value->init_val = element; + value->init_val_hi = wide_global_narrow_high(element, false); + value->is_const = true; + add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); + return value; + } + if (lex_peek(T_char, NULL) || lex_peek(T_wchar, NULL)) { + /* A character constant is an integer constant expression operand like + * any number. Reuse the expression readers so its value and type match + * an ordinary expression; the high word is its sign extension. + */ + if (lex_peek(T_wchar, NULL)) + read_wchar_param(parent, bb); + else + read_char_param(parent, bb); + value = opstack_pop(); + value->init_val_hi = wide_global_narrow_high(value->init_val, false); + return value; + } + if (!lex_peek(T_numeric, literal)) + error_at("Wide global initializer needs a literal operand", + next_token_loc()); + read_numeric_param(parent, bb, false); + value = opstack_pop(); + force_wide_global_literal_type(value, literal); + return value; +} + +/* Parse arithmetic literal-only global expressions, including grouped + * subexpressions, without sending a high word through the legacy int-only + * constant evaluator. + */ +var_t *read_wide_global_literal_expression(block_t *parent, + basic_block_t *bb, + block_t *scope) +{ + opcode_t op_stack[MAX_OPERATOR_STACK_SIZE]; + bool protected_rhs[MAX_OPERATOR_STACK_SIZE] = {0}; + var_t *val_stack[MAX_OPERATOR_STACK_SIZE]; + int op_stack_index = 0, val_stack_index = 0; + opcode_t op; + + val_stack[val_stack_index++] = promote_wide_global_operand( + parent, bb, read_wide_global_literal_primary(parent, bb, scope)); + op = get_operator(); + while (op != OP_generic) { + if (op == OP_ternary) { + var_t *condition; + var_t *when_true; + var_t *when_false; + type_t *common; + bool condition_true; + + /* `?:` has the lowest precedence. Fold every pending binary + * operator first so the condition is the complete expression, not + * merely the primary immediately before `?`. + */ + while (op_stack_index > 0) { + var_t *right = val_stack[--val_stack_index]; + var_t *left = val_stack[--val_stack_index]; + var_t *result = require_var(parent); + + op_stack_index--; + if (protected_rhs[op_stack_index]) + wide_global_unevaluated_depth--; + result->var_name = gen_name(); + result->type = integer_binary_result_type( + op_stack[op_stack_index], left, right); + if (!emit_wide_global_word_arithmetic(parent, bb, result, + op_stack[op_stack_index], + left, right)) + add_insn(parent, bb, op_stack[op_stack_index], result, left, + right, 0, NULL); + val_stack[val_stack_index++] = result; + } + condition = val_stack[--val_stack_index]; + condition_true = wide_global_operand_is_true(condition); + + /* A global conditional expression is still an integer constant + * expression, but both arms undergo the usual arithmetic + * conversions before the constant condition selects one. Parse both + * through the wide reader so a discarded high word can never leak + * through the legacy int-only evaluator. + */ + lex_expect(T_question); + if (!condition_true) + wide_global_unevaluated_depth++; + when_true = read_wide_global_literal_expression(parent, bb, scope); + if (!condition_true) + wide_global_unevaluated_depth--; + lex_expect(T_colon); + if (condition_true) + wide_global_unevaluated_depth++; + when_false = read_wide_global_literal_expression(parent, bb, scope); + if (condition_true) + wide_global_unevaluated_depth--; + + /* An address constant has no arithmetic conversion to make with the + * other arm; the selected one is the value. + */ + if (wide_global_address_operand(when_true) || + wide_global_address_operand(when_false)) + return condition_true ? when_true : when_false; + common = integer_common_type(when_true, when_false); + normalize_integer_binary_operands(parent, &bb, OP_add, &when_true, + &when_false); + if (when_true->type != common) + when_true = resize_to(parent, &bb, when_true, common, 0); + if (when_false->type != common) + when_false = resize_to(parent, &bb, when_false, common, 0); + return condition_true ? when_true : when_false; + } + while (op_stack_index > 0 && + get_operator_prio(op_stack[op_stack_index - 1]) >= + get_operator_prio(op)) { + var_t *right = val_stack[--val_stack_index]; + var_t *left = val_stack[--val_stack_index]; + var_t *result = require_var(parent); + + op_stack_index--; + if (protected_rhs[op_stack_index]) + wide_global_unevaluated_depth--; + result->var_name = gen_name(); + result->type = integer_binary_result_type(op_stack[op_stack_index], + left, right); + if (!emit_wide_global_word_arithmetic( + parent, bb, result, op_stack[op_stack_index], left, right)) + add_insn(parent, bb, op_stack[op_stack_index], result, left, + right, 0, NULL); + val_stack[val_stack_index++] = result; + } + if (op_stack_index >= MAX_OPERATOR_STACK_SIZE || + val_stack_index >= MAX_OPERATOR_STACK_SIZE) + fatal("Wide global initializer is too complex"); + + /* An address constant is true without a value of its own, so `array + * || 1 / 0` never evaluates its right operand either. + */ + bool lhs_true = + wide_global_operand_is_true(val_stack[val_stack_index - 1]); + + protected_rhs[op_stack_index] = + op == OP_log_and ? !lhs_true : op == OP_log_or && lhs_true; + op_stack[op_stack_index++] = op; + if (protected_rhs[op_stack_index - 1]) { + wide_global_unevaluated_depth++; + } + val_stack[val_stack_index++] = promote_wide_global_operand( + parent, bb, read_wide_global_literal_primary(parent, bb, scope)); + op = get_operator(); + } + while (op_stack_index > 0) { + var_t *right = val_stack[--val_stack_index]; + var_t *left = val_stack[--val_stack_index]; + var_t *result = require_var(parent); + + op_stack_index--; + if (protected_rhs[op_stack_index]) + wide_global_unevaluated_depth--; + result->var_name = gen_name(); + result->type = + integer_binary_result_type(op_stack[op_stack_index], left, right); + if (!emit_wide_global_word_arithmetic( + parent, bb, result, op_stack[op_stack_index], left, right)) + add_insn(parent, bb, op_stack[op_stack_index], result, left, right, + 0, NULL); + val_stack[val_stack_index++] = result; + } + return val_stack[0]; +} + +/* Skip the arm of a conditional expression that its constant condition + * discards. The arm may itself hold grouped or nested conditionals, so count + * the brackets and the '?' still waiting for their ':' rather than stopping at + * the first ':'. A second operand ends at its matching ':'; a third ends at the + * ',' or ';' ending the declarator, the ':' of an enclosing conditional or the + * bracket closing an enclosing initializer. + */ +static void skip_discarded_conditional_arm(bool second_operand) +{ + int depth = 0; + int pending = 0; + + for (;;) { + token_t *next = cur_token->next; + + if (!next || next->kind == T_eof) + return; + if (next->kind == T_open_bracket || next->kind == T_open_square || + next->kind == T_open_curly) { + depth++; + } else if (next->kind == T_close_bracket || + next->kind == T_close_square || + next->kind == T_close_curly) { + if (depth == 0) + return; + depth--; + } else if (depth == 0 && next->kind == T_question) { + pending++; + } else if (depth == 0 && next->kind == T_colon) { + if (pending == 0) + return; + pending--; + } else if (depth == 0 && !second_operand && + (next->kind == T_comma || next->kind == T_semicolon)) { + return; + } + lex_next(); + } +} + +void eval_ternary_imm(int cond, var_t *var) +{ + if (cond == 0) { + skip_discarded_conditional_arm(true); + lex_expect(T_colon); + read_global_assignment_var(var); + } else { + read_global_assignment_var(var); + lex_expect(T_colon); + skip_discarded_conditional_arm(false); + } +} + +bool read_global_assignment_var(var_t *var) +{ + var_t *vd, *rs1; + + /* A block-scope static is lowered in the global setup block, but its + * initializer is parsed in the declaration's lexical scope. In particular + * an enumerator declared by an enclosing block remains an integer constant + * expression here. + */ + block_t *scope = var->scope ? var->scope : GLOBAL_BLOCK; + block_t *parent = GLOBAL_BLOCK; + basic_block_t *bb = GLOBAL_FUNC->bbs; + + validate_string_array_initializer(var); + if ((var->array_size > 0 || var->has_unsized_array) && is_char_array(var) && + lex_peek(T_string, NULL)) { + parse_string_array_init(var, parent, &bb); + return true; + } + if ((var->array_size > 0 || var->has_unsized_array) && + is_wchar_array(var) && lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(var, parent, &bb); + return true; + } + + /* An address constant that an operator only tests, as in `"a" && 1`, is the + * operand of a constant expression rather than this object's value. + */ + if (global_tested_operand_starts_here(scope)) { + var_t *tested = read_wide_global_literal_expression(parent, bb, scope); + + reject_global_integer_pointer(var, tested); + add_insn(parent, bb, OP_assign, var, tested, NULL, 0, NULL); + return true; + } + + /* A cast to a function pointer type converts the function designator or + * null pointer constant it applies to. + */ + { + func_t *cast_signature = read_global_function_pointer_cast(scope); + + if (cast_signature) { + func_t *saved_signature = global_function_cast_signature; + + global_function_cast_signature = cast_signature; + read_global_assignment_var(var); + global_function_cast_signature = saved_signature; + return true; + } + } + + /* global initialization must be constant */ + if (global_pointer_cast_starts_here(scope)) { + int saved_stride = global_pointer_cast_stride; + func_t *slot_signature; + int stride = read_global_pointer_cast(scope, &slot_signature); + + /* In a chain of casts the outermost one decides the stride. */ + if (saved_stride) + stride = saved_stride; + if (!global_address_operand_starts_here(scope)) { + rs1 = read_global_cast_integer_address(parent, bb, scope, stride, + slot_signature); + if (rs1->pointee_func_signature) + diagnose_callback_slot_initializer(rs1, var); + emit_global_scalar_assignment(parent, bb, var, rs1); + return true; + } + global_pointer_cast_stride = stride; + read_global_assignment_var(var); + global_pointer_cast_stride = saved_stride; + return true; + } + { + /* A function designator is a valid address constant. Keep it as the + * function symbol until lowering: OP_address_of_func has the deferred + * relocation needed because the target function's code offset is not + * known while global initializers are parsed. + */ + bool address_dereference = + global_function_address_dereference_starts_here(); + token_t *address_dereference_identifier = NULL; + bool explicit_address; + bool grouped_function_designator = false; + + if (address_dereference) { + var_t *addr; + var_t *symbol; + + address_dereference_identifier = + consume_global_function_address_dereference(); + if (!find_visible_func(address_dereference_identifier->literal, + scope)) + error_at("Function address requires a visible declaration", + cur_token_loc()); + if (!var->is_func && !var->ptr_level && + !(var->type && var->type->ptr_level)) + error_at("Function address requires a pointer initializer", + cur_token_loc()); + addr = require_ref_var(parent, var->type, var->ptr_level); + symbol = require_func_symbol_var(parent); + addr->var_name = gen_name(); + symbol->is_func = true; + symbol->var_name = + intern_string(address_dereference_identifier->literal); + diagnose_global_function_conversion(symbol, var); + add_insn(parent, bb, OP_address_of, addr, var, NULL, 0, NULL); + add_insn(parent, bb, OP_write, NULL, addr, symbol, PTR_SIZE, NULL); + return true; + } + explicit_address = lex_accept(T_ampersand); + if (grouped_global_function_designator_starts_here(false)) { + lex_expect(T_open_bracket); + grouped_function_designator = true; + } + char token[MAX_ID_LEN]; + if (lex_peek(T_identifier, token)) { + func_t *func = find_visible_func(token, scope); + if (func) { + if (!var->is_func && !var->ptr_level && + !(var->type && var->type->ptr_level)) + error_at("Function address requires a pointer initializer", + cur_token_loc()); + var_t *addr = + require_ref_var(parent, var->type, var->ptr_level); + var_t *symbol = require_func_symbol_var(parent); + + addr->var_name = gen_name(); + symbol->is_func = true; + symbol->var_name = intern_string(token); + diagnose_global_function_conversion(symbol, var); + lex_expect(T_identifier); + if (grouped_function_designator) + lex_expect(T_close_bracket); + add_insn(parent, bb, OP_address_of, addr, var, NULL, 0, NULL); + add_insn(parent, bb, OP_write, NULL, addr, symbol, PTR_SIZE, + NULL); + return true; + } + + /* Static locals have global storage but lexical visibility. Use the + * declaration scope for name resolution while continuing to emit + * their initializer into the synthetic global block. + */ + var_t *object = find_var(token, scope); + if (object && object->is_global && + (explicit_address || object->array_size)) { + fixed_array_shape_t shape = fixed_array_shape_from_var(object); + var_t *object_addr = + require_ref_var(parent, object->type, object->ptr_level); + + object_addr->var_name = gen_name(); + object_addr->is_global_address = true; + + /* Taking the address of a callback object creates a slot. + * Retain the callback prototype on that non-callable outer + * pointer so the global initializer conversion below has the + * same information as block-scope `&callback`. + */ + object_addr->pointee_func_signature = + object->pointee_func_signature + ? object->pointee_func_signature + : object->func_signature; + lex_expect(T_identifier); + add_insn(parent, bb, OP_address_of, object_addr, object, NULL, + 0, NULL); + if (!explicit_address && object->array_dim2 && + lex_peek(T_open_square, NULL)) { + object_addr = read_global_address_designator( + scope, parent, &bb, &object, object_addr, true); + } else if (!explicit_address && object->array_size && + (lex_peek(T_plus, NULL) || + lex_peek(T_minus, NULL))) { + int index = read_global_address_offset(scope, parent, bb); + int stride = object->ptr_level || object->is_func + ? PTR_SIZE + : object->type->size; + var_t *byte_offset = require_var(parent); + var_t *offset_addr = require_ref_var(parent, object->type, + object->ptr_level); + + stride = global_pointer_cast_stride + ? global_pointer_cast_stride + : fixed_array_shape_stride(&shape, 0, stride); + byte_offset->var_name = gen_name(); + byte_offset->init_val = index * stride; + add_insn(parent, bb, OP_load_constant, byte_offset, NULL, + NULL, 0, NULL); + offset_addr->var_name = gen_name(); + offset_addr->is_global_address = true; + offset_addr->pointee_func_signature = + object_addr->pointee_func_signature; + add_insn(parent, bb, OP_add, offset_addr, object_addr, + byte_offset, 0, NULL); + object_addr = offset_addr; + } + if (explicit_address) + object_addr = read_global_address_designator( + scope, parent, &bb, &object, object_addr, false); + diagnose_callback_slot_initializer(object_addr, var); + add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, + NULL); + return true; + } + } + if (explicit_address && subscripted_string_literal_starts_here()) { + rs1 = read_string_literal_element_address(parent, bb, scope); + diagnose_const_pointer_conversion(rs1, var); + emit_global_scalar_assignment(parent, bb, var, rs1); + return true; + } + if (explicit_address && scope == GLOBAL_BLOCK && + global_compound_literal_starts_here()) { + /* A compound literal at file scope has static storage (C99 + * 6.5.2.5p6), so `&(int){8}` is an address constant. Give the + * literal an unnamed global and initialize the pointer with its + * address. + */ + type_t *literal_type; + var_t *literal; + var_t *literal_addr; + int literal_ptr_level = 0; + func_t *callback = NULL; + + lex_expect(T_open_bracket); + literal_type = read_type_name_specifiers(GLOBAL_BLOCK); + while (lex_accept(T_asterisk)) + literal_ptr_level++; + + /* `&(int (*)(int)){f}` and `&(callback_t){f}` address a callback + * object; the pointer they initialize is a callback slot. + */ + if (literal_type && abstract_function_pointer_follows()) { + int callback_level; + + callback = read_abstract_function_pointer( + literal_type, literal_ptr_level, &callback_level); + if (callback_level != 1) + error_at("Incompatible compound literal address", + cur_token_loc()); + } else if (literal_type && literal_type->func_signature && + !literal_type->is_direct_function_type && + !literal_type->array_size && !literal_ptr_level) { + callback = literal_type->func_signature; + } + lex_expect(T_close_bracket); + if (callback) { + func_t *slot = var->pointee_func_signature; + + if (!slot && var->ptr_level == 1 && var->type->func_signature && + !var->type->is_direct_function_type) + slot = var->type->func_signature; + if (!slot || var->array_size || + !compatible_function_signature(slot, callback)) + error_at("Incompatible compound literal address", + cur_token_loc()); + literal = require_typed_var(GLOBAL_BLOCK, literal_type); + literal->var_name = gen_name(); + literal->is_global = true; + literal->ptr_level = + literal_type->func_signature ? 0 : literal_ptr_level; + literal->is_func = true; + literal->func_signature = callback; + add_insn(GLOBAL_BLOCK, bb, OP_allocat, literal, NULL, NULL, 0, + NULL); + lex_expect(T_open_curly); + if (lex_peek(T_close_curly, NULL)) + error_at("Scalar compound literal needs an initializer", + next_token_loc()); + read_global_assignment_var(literal); + lex_accept(T_comma); + lex_expect(T_close_curly); + literal_addr = + require_ref_var(parent, literal->type, literal->ptr_level); + literal_addr->var_name = gen_name(); + literal_addr->is_global_address = true; + literal_addr->pointee_func_signature = callback; + add_insn(parent, bb, OP_address_of, literal_addr, literal, NULL, + 0, NULL); + add_insn(parent, bb, OP_assign, var, literal_addr, NULL, 0, + NULL); + return true; + } + if (!literal_type || literal_type->array_size || + literal_type->func_signature || var->array_size || + var->ptr_level != literal_ptr_level + 1 || + !(compatible_decl_type(var->type, literal_type) || + (var->type == TY_void && !literal_ptr_level))) + error_at("Incompatible compound literal address", + cur_token_loc()); + literal = require_typed_var(GLOBAL_BLOCK, literal_type); + literal->var_name = gen_name(); + literal->is_global = true; + literal->ptr_level = literal_ptr_level; + add_insn(GLOBAL_BLOCK, bb, OP_allocat, literal, NULL, NULL, 0, + NULL); + if (!literal_ptr_level && is_record_type(literal_type)) { + parse_global_record_init(literal, GLOBAL_BLOCK); + } else { + lex_expect(T_open_curly); + if (lex_peek(T_close_curly, NULL)) + error_at("Scalar compound literal needs an initializer", + next_token_loc()); + read_global_assignment_var(literal); + lex_accept(T_comma); + lex_expect(T_close_curly); + } + literal_addr = + require_ref_var(parent, literal->type, literal->ptr_level); + literal_addr->var_name = gen_name(); + literal_addr->is_global_address = true; + add_insn(parent, bb, OP_address_of, literal_addr, literal, NULL, 0, + NULL); + add_insn(parent, bb, OP_assign, var, literal_addr, NULL, 0, NULL); + return true; + } + if (explicit_address) + error_at("Expected a global object or function after '&'", + cur_token_loc()); + if (string_address_offset_starts_here()) { + rs1 = read_string_address_offset(parent, bb, scope); + diagnose_const_pointer_conversion(rs1, var); + emit_global_scalar_assignment(parent, bb, var, rs1); + return true; + } + + /* The legacy global evaluator stores operands in int. Parse a wide + * literal-only expression separately so its upper payload survives; + * lower each reduction into the global setup block instead of trying to + * narrow the expression through that evaluator. + */ + if (initializer_needs_wide_reader(cur_token->next, scope, true) || + string_element_appears_before_initializer_end(cur_token->next)) { + rs1 = read_wide_global_literal_expression(parent, bb, scope); + reject_global_integer_pointer(var, rs1); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + return true; + } + if ((lex_peek(T_string, NULL) || lex_peek(T_wstring, NULL)) && + !subscripted_string_literal_starts_here()) { + /* String literal global initialization: String literals are now + * stored in .rodata section. TODO: Implement compile-time address + * resolution for global pointer initialization with rodata + * addresses (e.g., char *p = "str";) + */ + if (lex_peek(T_wstring, NULL)) + read_wstring_param(parent, bb); + else + read_literal_param(parent, bb); + rs1 = opstack_pop(); + vd = var; + diagnose_const_pointer_conversion(rs1, vd); + emit_global_scalar_assignment(parent, bb, vd, rs1); + return true; + } + + opcode_t op_stack[MAX_OPERATOR_STACK_SIZE]; + opcode_t op, next_op; + int val_stack[MAX_OPERATOR_STACK_SIZE]; + int op_stack_index = 0, val_stack_index = 0; + int operand1, operand2; + operand1 = read_const_expr_operand(scope); + op = get_operator(); + /* only one value after assignment */ + if (op == OP_generic) { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = operand1; + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + + rs1 = vd; + emit_global_scalar_assignment(parent, bb, var, rs1); + return true; + } + if (op == OP_ternary) { + lex_expect(T_question); + eval_ternary_imm(operand1, var); + return true; + } + operand2 = read_const_expr_operand(scope); + next_op = get_operator(); + if (next_op == OP_generic) { + /* only two operands, apply and return */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = eval_expression_imm(op, operand1, operand2); + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + + rs1 = vd; + reject_global_integer_pointer(var, rs1); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + return true; + } + + /* using stack if operands more than two */ + op_stack[op_stack_index++] = op; + op = next_op; + val_stack[val_stack_index++] = operand1; + val_stack[val_stack_index++] = operand2; + + while (op != OP_generic && op != OP_ternary) { + if (op_stack_index > 0) { + /* we have a continuation, use stack */ + int same_op = 0; + do { + opcode_t stack_op = op_stack[op_stack_index - 1]; + if (get_operator_prio(stack_op) >= get_operator_prio(op)) { + operand1 = val_stack[val_stack_index - 2]; + operand2 = val_stack[val_stack_index - 1]; + val_stack_index -= 2; + + /* apply stack operator and push result back */ + val_stack[val_stack_index++] = + eval_expression_imm(stack_op, operand1, operand2); + + /* pop op stack */ + op_stack_index--; + } else { + same_op = 1; + } + /* continue util next operation is higher prio */ + } while (op_stack_index > 0 && same_op == 0); + } + /* push next operand on stack */ + if (val_stack_index >= MAX_OPERATOR_STACK_SIZE || + op_stack_index >= MAX_OPERATOR_STACK_SIZE) + fatal("Constant expression too complex"); + val_stack[val_stack_index++] = read_const_expr_operand(scope); + /* push operator on stack */ + op_stack[op_stack_index++] = op; + op = get_operator(); + } + /* unwind stack and apply operations */ + while (op_stack_index > 0) { + opcode_t stack_op = op_stack[op_stack_index - 1]; + + /* pop stack and apply operators */ + operand1 = val_stack[val_stack_index - 2]; + operand2 = val_stack[val_stack_index - 1]; + val_stack_index -= 2; + + /* apply stack operator and push value back on stack */ + val_stack[val_stack_index++] = + eval_expression_imm(stack_op, operand1, operand2); + + if (op_stack_index == 1) { + if (op == OP_ternary) { + lex_expect(T_question); + eval_ternary_imm(val_stack[0], var); + } else { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = val_stack[0]; + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + + rs1 = vd; + emit_global_scalar_assignment(parent, bb, var, rs1); + } + return true; + } + + /* pop op stack */ + op_stack_index--; + } + if (op == OP_ternary) { + lex_expect(T_question); + eval_ternary_imm(val_stack[0], var); + } else { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = val_stack[0]; + add_insn(parent, GLOBAL_FUNC->bbs, OP_load_constant, vd, NULL, NULL, + 0, NULL); + + rs1 = vd; + emit_global_scalar_assignment(parent, GLOBAL_FUNC->bbs, var, rs1); + } + return true; + } + return false; +} diff --git a/src/parser-decl.c b/src/parser-decl.c new file mode 100644 index 00000000..268d9663 --- /dev/null +++ b/src/parser-decl.c @@ -0,0 +1,2564 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Abstract type names, integer constant expressions, declarators, parameter + * lists, and numeric, character and string literal operands. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +/* Integer constant-expression parser. + * + * Array dimensions, and other places C requires an integer constant expression, + * accept far more than a bare literal. These evaluate such an expression at + * parse time without emitting any IR, folding through the same precedence table + * (get_operator_prio()) and the same operator semantics (eval_expression_imm()) + * the rest of the parser already uses, so there is only one statement of what + * C's operators mean. + */ +#define MAX_CONST_EXPR_OPS 16 + +int eval_expression_imm(opcode_t op, int op1, int op2); +int read_const_expr(block_t *scope); +int read_const_wstring_size(void); + +void read_sizeof_function_prototype(void) +{ + func_t *func = arena_alloc_func(); + bool saved_sizeof_signature = parsing_sizeof_function_signature; + + parsing_sizeof_function_signature = true; + read_parameter_list_decl(func, true); + parsing_sizeof_function_signature = saved_sizeof_signature; +} + +/* The outermost derivation a sizeof abstract declarator applies. It alone + * decides the object size: a pointer hides the type it points to, an array only + * multiplies its element, and a function type has no object size at all. + */ +typedef enum { + SIZEOF_DERIVED_NONE, + SIZEOF_DERIVED_POINTER, + SIZEOF_DERIVED_FUNCTION +} sizeof_derivation_t; + +/* Consume one array bound of a sizeof type name after its '[' and return the + * element count scaled by it. VLA is outside shecc's C99 scope. + */ +static int read_sizeof_array_bound(block_t *scope, int elements) +{ + int bound = read_const_expr(scope); + + lex_expect(T_close_square); + if (bound <= 0) + error_at("sizeof array type needs a positive constant bound", + cur_token_loc()); + if (elements > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + return elements * bound; +} + +/* Consume the abstract declarator of a sizeof type name, such as the "*[2]" of + * `int *[2]` or the "(*(*[2])(int))[3]" of `int (*(*[2])(int))[3]`, and return + * its outermost derivation. *elements receives the element count of the arrays + * that derivation builds, or 1. + * + * A declarator reads inside out: the pointers of one level apply first, then + * that level's suffixes, and a parenthesized inner declarator applies last. An + * inner pointer or function derivation therefore decides the whole type, while + * an inner declarator with neither only adds array bounds. + */ +sizeof_derivation_t read_sizeof_abstract_declarator(block_t *scope, + int *elements) +{ + sizeof_derivation_t derivation = SIZEOF_DERIVED_NONE; + sizeof_derivation_t inner = SIZEOF_DERIVED_NONE; + int inner_elements = 1; + int count = 1; + bool nested = false; + bool has_array = false; + + while (lex_accept(T_asterisk)) { + derivation = SIZEOF_DERIVED_POINTER; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + + /* A parenthesis opens an inner declarator only where one can begin; + * otherwise it is the parameter list of a function suffix. + */ + if (lex_peek(T_open_bracket, NULL) && cur_token->next->next && + (cur_token->next->next->kind == T_asterisk || + cur_token->next->next->kind == T_open_bracket || + cur_token->next->next->kind == T_open_square)) { + lex_expect(T_open_bracket); + inner = read_sizeof_abstract_declarator(scope, &inner_elements); + lex_expect(T_close_bracket); + nested = true; + } + + while (true) { + if (lex_accept(T_open_square)) { + if (derivation == SIZEOF_DERIVED_FUNCTION) + error_at("function cannot return an array", cur_token_loc()); + count = read_sizeof_array_bound(scope, count); + has_array = true; + } else if (lex_peek(T_open_bracket, NULL)) { + if (derivation == SIZEOF_DERIVED_FUNCTION) + error_at("function cannot return a function", cur_token_loc()); + if (has_array) + error_at("array of functions is invalid", cur_token_loc()); + read_sizeof_function_prototype(); + derivation = SIZEOF_DERIVED_FUNCTION; + } else + break; + } + + if (nested && inner != SIZEOF_DERIVED_NONE) { + *elements = inner_elements; + return inner; + } + if (count > INT_MAX / inner_elements) + error_at("sizeof array type is too large", cur_token_loc()); + *elements = count * inner_elements; + return derivation; +} + +/* Return the size of a sizeof type name: its specifier type with the derivation + * read by read_sizeof_abstract_declarator() applied. + */ +int sizeof_type_name_size(const type_t *type, + sizeof_derivation_t derivation, + int elements) +{ + int size; + + if (derivation == SIZEOF_DERIVED_FUNCTION) + error_at("sizeof(function) is invalid", cur_token_loc()); + if (derivation == SIZEOF_DERIVED_POINTER) { + size = PTR_SIZE; + } else { + if (type == TY_void) + error_at("sizeof(void) is invalid", cur_token_loc()); + if (type->is_direct_function_type) + error_at("sizeof(function) is invalid", cur_token_loc()); + + /* A typedef names a record through base_struct; an incomplete record + * has no size to report. + */ + size = type->size; + if (!size && type->base_struct) + size = type->base_struct->size; + if (!size) + error_at("sizeof cannot be applied to an incomplete type", + cur_token_loc()); + if (type->array_size) { + if (size > INT_MAX / type->array_size) + error_at("sizeof array type is too large", cur_token_loc()); + size *= type->array_size; + } + } + if (size > INT_MAX / elements) + error_at("sizeof array type is too large", cur_token_loc()); + return size * elements; +} + +/* Consume the scalar type specifiers at the next token, together with the + * qualifiers mixed in among them, which set @is_const and @is_volatile, and the + * inline specifier when @is_inline is not NULL. C99 6.7.2p2 lets these words + * appear in any order: "unsigned long int", "long unsigned int" and "int + * unsigned long" all name the same type. + * + * Returns the scalar type named, or NULL when no type word was read, which + * leaves a struct, union, enum or typedef name to the caller. + */ +type_t *read_scalar_type_specifiers(bool *is_const, + bool *is_volatile, + bool *is_inline) +{ + char token[MAX_ID_LEN]; + type_t *type; + bool is_signed = false; + bool is_unsigned = false; + int long_count = 0; + bool has_int = false; + type_t *base = NULL; + + while (true) { + if (lex_accept(T_signed)) { + if (is_signed) + error_at("duplicate signed type specifier", cur_token_loc()); + is_signed = true; + } else if (lex_accept(T_unsigned)) { + if (is_unsigned) + error_at("duplicate unsigned type specifier", cur_token_loc()); + is_unsigned = true; + } else if (lex_accept(T_long)) { + if (++long_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + } else if (lex_accept(T_const)) { + *is_const = true; + } else if (lex_accept(T_volatile)) { + *is_volatile = true; + } else if (is_inline && lex_accept(T_inline)) { + if (*is_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + *is_inline = true; + } else if (lex_peek(T_identifier, token) && !strcmp(token, "int")) { + /* `char`, `short` and `int` reach the parser as identifiers. `int` + * may also accompany `short` or `long`. + */ + if (has_int) + error_at("duplicate type specifier", next_token_loc()); + lex_expect(T_identifier); + has_int = true; + } else if (lex_peek(T_float, NULL) || lex_peek(T_double, NULL) || + (lex_peek(T_identifier, token) && + (!strcmp(token, "char") || !strcmp(token, "short")))) { + if (base) + error_at("duplicate type specifier", next_token_loc()); + if (lex_accept(T_float)) + base = TY_float; + else if (lex_accept(T_double)) + base = TY_double; + else { + lex_expect(T_identifier); + base = token[0] == 'c' ? TY_char : TY_short; + } + } else + break; + } + if (!base && !has_int && !is_signed && !is_unsigned && !long_count) + return NULL; + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (base == TY_float && (is_signed || is_unsigned || long_count || has_int)) + error_at("invalid float type specifiers", cur_token_loc()); + if (base == TY_double && + (is_signed || is_unsigned || long_count > 1 || has_int)) + error_at("invalid double type specifiers", cur_token_loc()); + if (base == TY_char && has_int) + error_at("int cannot be combined with char", cur_token_loc()); + if (base == TY_char && long_count) + error_at("long cannot be combined with char", cur_token_loc()); + if (base == TY_short && long_count) + error_at("long cannot be combined with short", cur_token_loc()); + if (lex_peek(T_enum, NULL)) + error_at("enum type cannot be combined with integer specifiers", + next_token_loc()); + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) + error_at("record type cannot be combined with integer specifiers", + next_token_loc()); + + if (base == TY_float) + type = TY_float; + else if (base == TY_double) + type = long_count ? TY_long_double : TY_double; + else if (base == TY_char) + type = is_unsigned ? TY_uchar : (is_signed ? TY_schar : TY_char); + else if (base == TY_short) + type = is_unsigned ? TY_ushort : TY_short; + else if (long_count == 2) + type = is_unsigned ? TY_ulong_long : TY_long_long; + else if (long_count) + type = is_unsigned ? TY_ulong : TY_long; + else + type = is_unsigned ? TY_uint : TY_int; + return type; +} + +/* Consume the specifier list of a type name used only for its metadata, and + * return the type it names, or NULL when none of it names a type. + */ +type_t *read_type_name_specifiers(block_t *scope) +{ + char token[MAX_ID_LEN]; + bool is_const = false; + bool is_volatile = false; + type_t *type = read_scalar_type_specifiers(&is_const, &is_volatile, NULL); + base_type_t record_kind; + + if (type) + return type; + record_kind = accept_record_keyword(); + if (record_kind) { + lex_ident(T_identifier, token); + type = find_record_tag(token, scope, record_kind); + } else if (lex_accept(T_enum)) { + lex_ident(T_identifier, token); + type = reference_enum_tag(token, scope); + } else if (lex_peek(T_identifier, token)) { + type = find_visible_type(token, scope); + if (type) + lex_expect(T_identifier); + } + /* A qualifier may follow the record, enum or typedef name as well. */ + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + return type; +} + +/* Integer constant expressions may contain sizeof(type-name). This parser only + * needs type metadata, so keep it separate from expression lowering and avoid + * emitting the otherwise unevaluated sizeof IR into an array bound or + * enumerator declaration. + */ +int read_const_sizeof_type(block_t *scope) +{ + type_t *type; + sizeof_derivation_t derivation; + int elements; + + lex_expect(T_open_bracket); + type = read_type_name_specifiers(scope); + if (!type) + error_at( + "sizeof in an integer constant expression requires a type name", + cur_token_loc()); + derivation = read_sizeof_abstract_declarator(scope, &elements); + lex_expect(T_close_bracket); + return sizeof_type_name_size(type, derivation, elements); +} + +/* Return the row-major offset contributed by a fixed terminal array member in + * offsetof. A one-past designator is valid only for the final dimension. + */ +static int read_offsetof_array_subscripts(var_t *field, block_t *scope) +{ + int offset = 0; + fixed_array_shape_t shape; + int dimensions; + + if (!lex_accept(T_open_square)) + return 0; + if (field->array_size <= 0) + error_at("offsetof subscript requires a fixed array member", + cur_token_loc()); + shape = fixed_array_shape_from_var(field); + dimensions = shape.rank; + for (int dim = 0;; dim++) { + int index = read_const_expr(scope); + + lex_expect(T_close_square); + if (index < 0 || index > shape.bounds[dim]) + error_at("offsetof array subscript is out of bounds", + cur_token_loc()); + offset += index * fixed_array_shape_stride(&shape, dim, 1) * + field->type->size; + if (!lex_accept(T_open_square)) + return offset; + if (dim + 1 == dimensions) + error_at("offsetof subscript exceeds array dimensions", + cur_token_loc()); + if (index == shape.bounds[dim]) + error_at("offsetof one-past array row cannot be subscripted", + cur_token_loc()); + } +} + +/* Whether the next operand is a string literal, with any adjacent ones joined + * to it, followed by a subscript, as in `"ab"[1]`. + */ +bool subscripted_string_literal_starts_here(void) +{ + token_t *token = cur_token->next; + token_kind_t kind = token ? token->kind : T_eof; + + if (kind != T_string && kind != T_wstring) + return false; + while (token->next && token->next->kind == kind) + token = token->next; + return token->next && token->next->kind == T_open_square; +} + +/* Read a subscripted string literal and its constant index, returning the index + * and storing the element's value in @value. A character element takes the + * value a character constant would. Without @value only an address is wanted, + * which may also point one past the terminating null. + */ +int read_string_literal_element(block_t *scope, int *value) +{ + char combined[MAX_STRING_LEN]; + int units[MAX_STRING_LEN]; + bool wide = lex_peek(T_wstring, NULL); + int length = wide ? read_wstring_units(units, MAX_STRING_LEN) + : read_concatenated_string(combined); + int index; + + lex_expect(T_open_square); + index = read_const_expr(scope); + lex_expect(T_close_square); + if (index < 0 || index > length + !value) + error_at("String literal subscript is out of bounds", cur_token_loc()); + if (!value) + return index; + if (index == length) + *value = 0; + else + *value = wide ? units[index] : (signed char) combined[index]; + return index; +} + +int read_const_expr_operand(block_t *scope) +{ + char buffer[MAX_TOKEN_LEN]; + + if (lex_accept(T_minus)) { + int value = read_const_expr_operand(scope); + + if (checking_enum_constant && value == INT_MIN) + error_at("Enumerator value exceeds int range", cur_token_loc()); + return -value; + } + if (lex_accept(T_plus)) + return read_const_expr_operand(scope); + if (lex_accept(T_bit_not)) + return ~read_const_expr_operand(scope); + if (lex_accept(T_log_not)) + return !read_const_expr_operand(scope); + if (lex_accept(T_sizeof)) + return read_sizeof_constant(scope); + + if (lex_accept(T_open_bracket)) { + int res = read_const_expr(scope); + lex_expect(T_close_bracket); + return res; + } + if (lex_peek(T_numeric, buffer)) { + lex_expect(T_numeric); + return parse_numeric_constant(buffer); + } + if (lex_peek(T_char, buffer) || lex_peek(T_wchar, buffer)) { + char unescaped[MAX_TOKEN_LEN]; + token_kind_t kind = lex_peek(T_wchar, NULL) ? T_wchar : T_char; + lex_expect(kind); + if (unescape_string(buffer, unescaped, MAX_TOKEN_LEN) < 0) + error_at("Invalid escape sequence", cur_token_loc()); + return kind == T_wchar ? parse_wide_character_constant(buffer) + : parse_character_constant(buffer); + } + if (lex_peek(T_identifier, buffer)) { + if (!strcmp(buffer, "__builtin_offsetof")) { + char type_name[MAX_ID_LEN]; + char member_name[MAX_ID_LEN]; + type_t *record; + var_t *field; + int offset = 0; + bool has_nested_member; + + lex_expect(T_identifier); + lex_expect(T_open_bracket); + base_type_t record_kind = accept_record_keyword(); + if (record_kind) { + lex_ident(T_identifier, type_name); + record = find_record_tag(type_name, scope, record_kind); + } else { + lex_ident(T_identifier, type_name); + record = find_type(type_name, true); + } + if (!is_record_type(record)) + error_at("offsetof requires a struct or union type", + cur_token_loc()); + lex_expect(T_comma); + do { + lex_ident(T_identifier, member_name); + field = find_member(member_name, record); + if (!field) + error_at("Unknown record member", cur_token_loc()); + offset += field->offset; + offset += read_offsetof_array_subscripts(field, scope); + record = field->type; + has_nested_member = lex_accept(T_dot); + if (has_nested_member && + (field->ptr_level || !is_record_type(record))) + error_at("Nested offsetof member requires a record", + cur_token_loc()); + } while (has_nested_member); + lex_expect(T_close_bracket); + return offset; + } + lex_expect(T_identifier); + constant_t *con = find_scoped_constant(buffer, scope); + if (con) + return con->value; + error_at("Identifier is not an integer constant", next_token_loc()); + } + error_at("Expected an integer constant expression", next_token_loc()); + return 0; +} + +bool constant_expression_needs_typed_value(token_t *token); + +int read_const_expr(block_t *scope) +{ + opcode_t op_stack[MAX_CONST_EXPR_OPS]; + int val_stack[MAX_CONST_EXPR_OPS]; + int op_n = 0, val_n = 0; + + /* An int cannot hold a literal such as 4294967296 or give 3000000000U its + * unsigned rank. Fold an expression holding one in the typed two-word + * evaluator, and accept its value only if the int result keeps it. + */ + if (constant_expression_needs_typed_value(cur_token->next)) { + pp_integer_t typed_value; + block_t *saved_scope = pp_integer_constant_scope; + unsigned int extension; + + pp_integer_constant_scope = scope ? scope : GLOBAL_BLOCK; + cur_token = + pp_read_constant_infix_expr(0, cur_token, &typed_value, true); + pp_integer_constant_scope = saved_scope; + pp_enum_normalize(&typed_value); + extension = typed_value.is_unsigned || !(typed_value.lo & 0x80000000U) + ? 0 + : ~0U; + if (typed_value.hi != extension) + error_at("Integer constant expression exceeds int range", + cur_token_loc()); + return typed_value.lo; + } + + val_stack[val_n++] = read_const_expr_operand(scope); + + while (true) { + opcode_t op = get_operator(); + + if (op == OP_generic) + break; + + /* The conditional binds loosest, so everything folded so far is its + * condition, and its arms are whole constant expressions of their own. + */ + if (op == OP_ternary) { + while (op_n > 0) { + val_n--; + op_n--; + val_stack[val_n - 1] = eval_expression_imm( + op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); + } + lex_expect(T_question); + bool saved_checking = checking_enum_constant; + checking_enum_constant = saved_checking && val_stack[0]; + int then_val = read_const_expr(scope); + lex_expect(T_colon); + checking_enum_constant = saved_checking && !val_stack[0]; + int else_val = read_const_expr(scope); + checking_enum_constant = saved_checking; + return val_stack[0] ? then_val : else_val; + } + + /* Everything at least as tight as the operator just read is complete, + * so fold it before pushing. + */ + int prio = get_operator_prio(op); + while (op_n > 0 && get_operator_prio(op_stack[op_n - 1]) >= prio) { + val_n--; + op_n--; + val_stack[val_n - 1] = eval_expression_imm( + op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); + } + if (op_n >= MAX_CONST_EXPR_OPS - 1) + error_at("Constant expression nests too deeply", next_token_loc()); + op_stack[op_n++] = op; + val_stack[val_n++] = read_const_expr_operand(scope); + } + + while (op_n > 0) { + val_n--; + op_n--; + val_stack[val_n - 1] = eval_expression_imm( + op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); + } + return val_stack[0]; +} + +/* Whether the declarator ahead is a parenthesized name, `int (x)` or `int + * (a)[3]`, which C99 6.7.5p6 declares exactly as the name alone. In a parameter + * a typedef name in parentheses is instead an abstract function declarator. + */ +static bool grouped_declarator_name_follows(block_t *scope, bool is_param) +{ + token_t *open = cur_token->next; + token_t *name = open ? open->next : NULL; + + return open && open->kind == T_open_bracket && name && + name->kind == T_identifier && name->next && + name->next->kind == T_close_bracket && + !(is_param && find_visible_type(name->literal, scope)); +} + +/* Whether the abstract declarator of a function pointer type name, such as + * `(*)(int)` in `(int (*)(int))` or `(**)(void)`, starts at the next token. + */ +bool abstract_function_pointer_follows(void) +{ + token_t *token = cur_token->next; + + if (!token || token->kind != T_open_bracket || !(token = token->next) || + token->kind != T_asterisk) + return false; + while (token && (token->kind == T_asterisk || token->kind == T_const || + token->kind == T_volatile || token->kind == T_restrict)) + token = token->next; + return token && token->kind == T_close_bracket && token->next && + token->next->kind == T_open_bracket; +} + +/* Read that abstract declarator for a type name whose specifiers and leading + * stars named @type and @ptr_level, the return type. + * + * Return the prototype and set @pointer_level to the stars inside the + * parentheses. + */ +func_t *read_abstract_function_pointer(type_t *type, + int ptr_level, + int *pointer_level) +{ + func_t *func = arena_alloc_func(); + + *pointer_level = 0; + lex_expect(T_open_bracket); + while (lex_accept(T_asterisk)) { + (*pointer_level)++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + lex_expect(T_close_bracket); + func->return_def.type = type; + func->return_def.ptr_level = ptr_level; + func->returns_aggregate = + is_record_type(type) && !ptr_level && !type->ptr_level; + read_parameter_list_decl(func, true); + return func; +} + +/* Whether a parameter's declarator ahead is an abstract function declarator: a + * parenthesis opening a parameter list, which starts with a type name or is + * empty, rather than a nested declarator. + */ +static bool abstract_function_parameter_follows(block_t *scope) +{ + token_t *open = cur_token->next; + token_t *first = open ? open->next : NULL; + + if (!open || open->kind != T_open_bracket || !first) + return false; + if (first->kind == T_identifier) + return find_visible_type(first->literal, scope); + return first->kind == T_close_bracket || first->kind == T_struct || + first->kind == T_union || first->kind == T_enum || + first->kind == T_signed || first->kind == T_unsigned || + first->kind == T_long || first->kind == T_const || + first->kind == T_volatile; +} + +/* Read the parameter list of a parameter declared with a function type, which + * C99 6.7.5.3p8 adjusts to a pointer to that function, into @vd with the + * representation of `int (*fn)(int)`. + */ +static void read_adjusted_function_parameter(var_t *vd) +{ + func_t *func = arena_alloc_func(); + + memcpy(&func->return_def, vd, sizeof(var_t)); + func->returns_aggregate = is_record_type(func->return_def.type) && + !has_effective_pointer(&func->return_def); + read_parameter_list_decl(func, true); + vd->func_signature = func; + vd->is_func = true; + vd->parenthesized_function_pointer_level = 1; +} + +void read_inner_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member); + +/* The closing parenthesis matching the opening one at @open. */ +static token_t *matching_close_bracket(token_t *open) +{ + int depth = 0; + + for (; open; open = open->next) { + if (open->kind == T_open_bracket) + depth++; + else if (open->kind == T_close_bracket && !--depth) + return open; + } + return NULL; +} + +/* Whether the declarator ahead declares a function returning a function + * pointer, `(*get(void))(int)`, or a pointer to one, `(*(*pg)(void))(int)`: a + * star inside a parenthesis followed by a parameter list, and another parameter + * list after that parenthesis. + */ +static bool function_pointer_return_follows(void) +{ + token_t *open = cur_token->next; + token_t *token; + token_t *close; + + if (!open || open->kind != T_open_bracket || !(token = open->next) || + token->kind != T_asterisk) + return false; + while (token && (token->kind == T_asterisk || token->kind == T_const || + token->kind == T_volatile || token->kind == T_restrict)) + token = token->next; + if (!token || !(token->kind == T_open_bracket || + (token->kind == T_identifier && token->next && + token->next->kind == T_open_bracket))) + return false; + close = matching_close_bracket(open); + return close && close->next && close->next->kind == T_open_bracket; +} + +/* Read the declarator that function_pointer_return_follows() recognized. The + * type it derives from @vd's base is that of a callback typedef: read the + * trailing parameter list into an unnamed callback type, then read the + * parenthesized rest of the declarator against that type, just as for + * `callback_t get(void)` or `callback_t (*pg)(void)`. + */ +static void read_function_pointer_return_declarator(var_t *vd, + bool anon, + bool is_param, + bool is_record_member) +{ + token_t *start = cur_token; + token_t *close = matching_close_bracket(cur_token->next); + token_t *end; + func_t *callback = arena_alloc_func(); + type_t *callback_type = add_type(); + int stars = 0; + + cur_token = close; + memcpy(&callback->return_def, vd, sizeof(var_t)); + callback->return_def.var_name = NULL; + callback->returns_aggregate = is_record_type(vd->type) && + !has_effective_pointer(&callback->return_def); + read_parameter_list_decl(callback, true); + end = cur_token; + cur_token = start; + + memcpy(callback_type, vd->type, sizeof(type_t)); + if (vd->type->base_type == TYPE_struct || vd->type->base_type == TYPE_union) + callback_type->base_type = TYPE_typedef; + callback_type->type_name[0] = '\0'; + callback_type->ptr_level = 0; + callback_type->pointer_const_mask = 0; + callback_type->size = PTR_SIZE; + callback_type->alignment = PTR_SIZE; + callback_type->func_signature = callback; + callback_type->is_direct_function_type = false; + callback_type->pointee_func_signature = NULL; + + lex_expect(T_open_bracket); + while (lex_accept(T_asterisk)) { + stars++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + + /* `(**get(void))(int)` returns a pointer to such a callback, and each + * further star one more pointer. + */ + vd->type = callback_type; + vd->ptr_level = stars - 1; + vd->pointer_const_mask = 0; + vd->is_const_pointer = false; + read_inner_var_decl(vd, anon, is_param, is_record_member); + + /* `get(void)`: a function returning the callback. */ + if (!vd->is_func && lex_peek(T_open_bracket, NULL)) { + func_t *func = arena_alloc_func(); + type_t *function_type = add_type(); + + memcpy(&func->return_def, vd, sizeof(var_t)); + read_parameter_list_decl(func, true); + function_type->base_type = TYPE_typedef; + function_type->size = PTR_SIZE; + function_type->func_signature = func; + function_type->is_direct_function_type = true; + vd->type = function_type; + vd->func_signature = func; + vd->is_func = true; + vd->is_direct_function_declarator = true; + } + lex_expect(T_close_bracket); + if (cur_token != close) + error_at("Unexpected token in function declarator", cur_token_loc()); + cur_token = end; +} + +void read_inner_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member) +{ + bool grouped_name; + + /* Preserve typedef pointer level - don't reset if already inherited */ + vd->init_val = 0; + vd->has_direct_array_declarator = false; + vd->has_direct_pointee_array_declarator = false; + + /* A plain typedef alias inherits every recorded bound, including the fourth + * one. Fresh declarators start with no bound metadata; a derived suffix is + * later composed against its base by the declaration path. + */ + if (!vd->type || !vd->type->array_size) + vd->array_dim4 = 0; + if (is_param) { + /* However, if the parsed variable is a function parameter, reset its + * pointer level to zero. + */ + vd->ptr_level = 0; + } + + /* `typedef int (*const cfn_t)(int)` makes a cfn_t object read-only, but + * `cfn_t *p` only points to such a callback: its qualifier stays on the + * type rather than on p's own pointer level. + */ + if (lex_peek(T_asterisk, NULL) && vd->type && vd->type->func_signature && + !vd->type->is_direct_function_type && !vd->type->ptr_level && + !vd->ptr_level && (vd->type->pointer_const_mask & 1U)) { + vd->pointer_const_mask &= ~1U; + vd->is_const_pointer = false; + } + + while (lex_accept(T_asterisk)) { + vd->ptr_level++; + + /* Check for const after asterisk (e.g., int * const ptr). For now, we + * just consume const qualifiers after pointer. Full support would + * require tracking const-ness of the pointer itself vs the pointed-to + * data separately. + */ + while (true) { + if (lex_accept(T_const)) { + vd->is_const_pointer = true; + if (vd->ptr_level <= 32) + vd->pointer_const_mask |= 1U << (vd->ptr_level - 1); + } else if (lex_accept(T_volatile)) + vd->is_volatile = true; + else if (lex_accept(T_restrict)) + ; /* restrict is an aliasing contract, not storage state. */ + else + break; + } + } + + /* `typedef int row[2]; row *p` declares a pointer object, not an array + * object. read_partial_var_decl() copied row's bounds into vd before the + * declarator was known; move them to the pointer's pointee descriptor so + * allocation loads p's value and postfix indexing still advances a row. + */ + if (vd->ptr_level && vd->type && vd->type->array_size) { + fixed_array_shape_t shape = fixed_array_shape_from_type(vd->type); + fixed_array_shape_t empty_shape = {0}; + + fixed_array_shape_to_pointee_var(vd, &shape); + fixed_array_shape_to_var(vd, &empty_shape); + } + + grouped_name = (!anon || is_param) && + grouped_declarator_name_follows(vd->scope, is_param); + + if (function_pointer_return_follows()) { + read_function_pointer_return_declarator(vd, anon, is_param, + is_record_member); + return; + } + + /* In a parameter, `int (T)` for a typedef name T and `int (void)` are + * abstract function declarators, adjusted to pointers like `int fn(T)`. + */ + if (is_param && abstract_function_parameter_follows(vd->scope)) { + read_adjusted_function_parameter(vd); + return; + } + + /* is it function pointer declaration? */ + if (!grouped_name && lex_accept(T_open_bracket)) { + func_t *func = arena_alloc_func(); + char temp_name[MAX_VAR_LEN]; + int nested_ptr_level = 0; + bool inner_array = false; + bool callback_const = false; + bool callback_volatile = false; + bool callback_restrict = false; + + do { + lex_expect(T_asterisk); + nested_ptr_level++; + while (true) { + /* The first star is the callback pointer itself. */ + if (nested_ptr_level == 1) { + if (lex_peek(T_const, NULL)) + callback_const = true; + else if (lex_peek(T_volatile, NULL)) + callback_volatile = true; + else if (lex_peek(T_restrict, NULL)) + callback_restrict = true; + } + if (lex_accept(T_const)) { + vd->parenthesized_function_pointer_const = true; + if (nested_ptr_level >= 2) + vd->parenthesized_function_pointer_outer_const = true; + else + vd->parenthesized_function_pointer_inner_qualified = + true; + vd->is_const_pointer = true; + if (vd->ptr_level + nested_ptr_level <= 32) + vd->pointer_const_mask |= + 1U << (vd->ptr_level + nested_ptr_level - 1); + } else if (lex_accept(T_volatile)) { + if (nested_ptr_level >= 2) + vd->parenthesized_function_pointer_outer_volatile = + true; + else + vd->parenthesized_function_pointer_inner_qualified = + true; + vd->is_volatile = true; + } else if (lex_accept(T_restrict)) { + vd->parenthesized_function_pointer_restrict = true; + if (nested_ptr_level >= 2) + vd->parenthesized_function_pointer_outer_restrict = + true; + else + vd->parenthesized_function_pointer_inner_qualified = + true; + } else + break; + } + } while (lex_peek(T_asterisk, NULL)); + if (lex_peek(T_identifier, NULL)) { + lex_ident(T_identifier, temp_name); + vd->var_name = intern_string(temp_name); + } else if (!anon || !is_param) + lex_ident(T_identifier, temp_name); + + /* The array suffix belongs inside the parenthesized pointer declarator + * in `int (*callbacks[2])(int)`. It is an array whose elements are + * function pointers, not a function returning an array. Keep the + * representation used by ordinary arrays so indexing and storage + * allocation can share their existing paths. + */ + for (int dim = 0; lex_accept(T_open_square); dim++) { + bool parameter_static_bound = false; + bool parameter_const_bound = false; + + inner_array = true; + if (dim >= 4) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL) || + lex_peek(T_volatile, NULL) || lex_peek(T_restrict, NULL)) { + if (!is_param || dim) + error_at("array bracket qualifiers require a parameter", + cur_token_loc()); + if (lex_accept(T_static)) { + if (parameter_static_bound) + error_at("duplicate static array parameter qualifier", + cur_token_loc()); + parameter_static_bound = true; + } else if (lex_accept(T_const)) + parameter_const_bound = true; + else if (lex_accept(T_volatile)) + vd->is_volatile = true; + else + lex_expect(T_restrict); + } + if (lex_peek(T_close_square, NULL)) { + if (parameter_static_bound) + error_at("static array parameter needs a bound", + cur_token_loc()); + if (dim) + error_at( + "Only the outer function-pointer array bound may " + "be omitted", + cur_token_loc()); + vd->has_unsized_array = true; + } else { + int bound = read_const_expr(vd->scope); + + if (parameter_static_bound && bound <= 0) + error_at("static array parameter needs a positive bound", + cur_token_loc()); + if (bound <= 0) + error_at("Array size must be positive", cur_token_loc()); + if (dim == 0) + vd->array_size = bound; + else { + if (dim == 1) + vd->array_dim2 = bound; + else if (dim == 2) + vd->array_dim3 = bound; + else if (dim == 3) + vd->array_dim4 = bound; + vd->array_size *= bound; + } + } + if (parameter_const_bound && dim == 0) { + vd->is_const_pointer = true; + vd->pointer_const_mask |= 1U; + } + lex_expect(T_close_square); + } + lex_expect(T_close_bracket); + + /* A parenthesized pointer followed by array suffixes is a pointer to an + * array, not a function pointer. Keep the pointee's row bounds apart + * from the pointer object's own storage extent. + */ + if (lex_peek(T_open_square, NULL)) { + int dims = 0; + + /* The nested stars make this a pointer-to-array. Preserve whether + * its array element was plain void before adding them; a typedef or + * explicit pointer to void is a valid element. + */ + bool void_array_element = vd->type && + vd->type->base_type == TYPE_void && + !effective_pointer_depth(vd); + + vd->ptr_level += nested_ptr_level; + vd->pointee_array_element_ptr_level = + vd->ptr_level - nested_ptr_level; + vd->has_direct_pointee_array_declarator = true; + + /* A const element, `int *const (*r)[2]`, leaves r modifiable: only + * the outermost level's bit qualifies the object. + */ + vd->is_const_pointer = + vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + while (lex_accept(T_open_square)) { + int bound; + + if (dims >= 4) + error_at( + "Array declarators support at most four dimensions", + cur_token_loc()); + if (lex_peek(T_close_square, NULL)) + error_at("Pointer-to-array needs a bound", cur_token_loc()); + bound = read_const_expr(vd->scope); + if (bound <= 0) + error_at("Array size must be positive", cur_token_loc()); + if (dims == 0) + vd->pointee_array_size = bound; + else { + if (dims == 1) + vd->pointee_array_dim2 = bound; + else if (dims == 2) + vd->pointee_array_dim3 = bound; + else + vd->pointee_array_dim4 = bound; + vd->pointee_array_size *= bound; + } + lex_expect(T_close_square); + dims++; + } + if (dims && void_array_element) + error_at("void type cannot be an array element", + cur_token_loc()); + return; + } + + /* With neither a parameter list nor an array suffix after it, the + * parentheses only group the declarator: `int (*p)` is `int *p`, and + * `int (*a[2])` is `int *a[2]`. + */ + if (!lex_peek(T_open_bracket, NULL)) { + if (vd->type->array_size && !vd->ptr_level) { + fixed_array_shape_t shape = + fixed_array_shape_from_type(vd->type); + fixed_array_shape_t empty_shape = {0}; + + /* A pointer to an array typedef, as after the plain stars. */ + if (inner_array) + error_at( + "array of pointers to an array typedef is not yet " + "supported", + cur_token_loc()); + fixed_array_shape_to_pointee_var(vd, &shape); + fixed_array_shape_to_var(vd, &empty_shape); + } + vd->ptr_level += nested_ptr_level; + vd->parenthesized_function_pointer_const = false; + vd->parenthesized_function_pointer_restrict = false; + vd->parenthesized_function_pointer_outer_const = false; + vd->parenthesized_function_pointer_outer_volatile = false; + vd->parenthesized_function_pointer_outer_restrict = false; + vd->parenthesized_function_pointer_inner_qualified = false; + if (vd->ptr_level == 1 && !vd->array_size && + vd->type->is_direct_function_type && vd->type->func_signature) + vd->func_signature = vd->type->func_signature; + else + vd->func_signature = NULL; + vd->is_func = false; + if (vd->array_size > 0 && !vd->ptr_level && !vd->type->ptr_level && + type_has_flexible_array_member(vd->type)) + error_at( + "A struct with a flexible array member cannot be an " + "array element", + cur_token_loc()); + + /* As for an unparenthesized name, the global initializer parser + * finds a file-scope object on the operand stack. + */ + if (vd->is_global && vd->var_name && !is_param) + opstack_push(vd); + if (vd->ptr_level > 0 && vd->ptr_level <= 32) + vd->is_const_pointer = + vd->pointer_const_mask & (1U << (vd->ptr_level - 1)); + return; + } + + /* The return declaration was parsed before the parenthesized + * declarator. Copy it into the syntax-only signature before the + * function-pointer marker is set on vd. + */ + memcpy(&func->return_def, vd, sizeof(var_t)); + func->returns_aggregate = is_record_type(func->return_def.type) && + !has_effective_pointer(&func->return_def); + read_parameter_list_decl(func, true); + + /* A slot typedef return type, `slot_t (*get)(void)`, keeps its slot + * prototype on the signature's return, not on the callback pointer. + */ + vd->pointee_func_signature = NULL; + vd->func_signature = func; + vd->is_func = true; + vd->parenthesized_function_pointer_level = nested_ptr_level; + if (nested_ptr_level >= 2) { + /* `int (**slot)(int)` is an ordinary pointer object whose pointee + * is the compact one-pointer callback representation. Keep that + * outer object pointer in var_t; the final unary dereference + * restores the callback signature before a call. Each further star, + * as in `int (***slot)(int)`, adds one more object pointer. + */ + int return_depth = vd->ptr_level; + + /* A qualifier on the callback pointer itself, `(*const **slot)`, + * belongs to the callback the slot finally reaches. restrict may + * qualify only a pointer to an object type (C99 6.7.3p2). + */ + if (callback_restrict) + error_at("restrict requires a pointer to an object type", + cur_token_loc()); + vd->callback_is_const = callback_const; + vd->callback_is_volatile = callback_volatile; + if (callback_volatile && + !vd->parenthesized_function_pointer_outer_volatile) + vd->is_volatile = false; + + /* The second star is the outer slot pointer. After collapsing the + * compact callback representation to one retained pointer level, + * move each slot star's const bit one level down, dropping the + * callback pointer's own. + */ + if (return_depth < 32) { + unsigned int return_mask = (1U << return_depth) - 1; + unsigned int slot_mask = vd->pointer_const_mask >> return_depth; + + vd->pointer_const_mask = + (vd->pointer_const_mask & return_mask) | + ((slot_mask >> 1) << return_depth); + } + vd->ptr_level += nested_ptr_level - 1; + vd->is_const_pointer = + vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + vd->pointee_func_signature = vd->func_signature; + vd->func_signature = NULL; + vd->is_func = false; + } + } else { + /* Parameter declarations may use an abstract declarator in a prototype, + * but a spelled identifier still has to be consumed even when callers + * permit it to be omitted. Other anonymous type-name paths retain their + * original no-identifier grammar. + */ + if (grouped_name) + lex_expect(T_open_bracket); + if ((!anon || (is_param && lex_peek(T_identifier, NULL))) && + !lex_peek(T_colon, NULL)) { + char temp_name[MAX_VAR_LEN]; + lex_ident(T_identifier, temp_name); + vd->var_name = intern_string(temp_name); + if (grouped_name) + lex_expect(T_close_bracket); + if (!lex_peek(T_open_bracket, NULL) && !is_param) { + if (vd->is_global) { + opstack_push(vd); + } + } + } + if (!lex_peek(T_open_square, NULL) && + !(vd->type && vd->type->array_size)) { + vd->array_size = 0; + vd->array_dim2 = 0; + vd->array_dim3 = 0; + vd->array_dim4 = 0; + } + + /* An object whose type is an array typedef is an array of that + * typedef's rows: `typedef int row[2]; row m[3]` is `int m[3][2]`. Only + * the first declarator had row's bounds copied in by + * read_full_var_decl(), so restate them for `row a, b` too. A direct + * suffix is read on its own and the row bounds appended to it after the + * loop below. Block typedefs compose their own aliases in + * compose_block_typedef_array(). + */ + fixed_array_shape_t element_shape = {0}; + if (vd->type && vd->type->array_size && !vd->ptr_level && + !parsing_block_typedef_declarator) { + element_shape = fixed_array_shape_from_type(vd->type); + fixed_array_shape_to_var(vd, &element_shape); + if (lex_peek(T_open_square, NULL)) { + vd->array_size = 0; + vd->array_dim2 = vd->array_dim3 = vd->array_dim4 = 0; + } else + element_shape.rank = 0; + } + + /* Every dimension multiplies into array_size, so "int matrix[3][4]" + * becomes an array of 12 elements. The second dimension is kept + * separately as well, because indexing needs the row length; further + * dimensions only contribute to the total. A dimension left empty + * contributes no size. + */ + bool first_dim_empty = false; + int dims = 0; + + /* Preserve the element shape before an unsized parameter array is + * adjusted to a pointer. Arrays of void pointers remain valid. + */ + bool void_array_element = vd->type && + vd->type->base_type == TYPE_void && + !effective_pointer_depth(vd); + + /* A block typedef's base array shape is retained in vd->type for + * compose_block_typedef_array(). A new direct suffix contributes only + * its own bounds; otherwise inherited inner bounds are appended twice + * when aliases are composed. + */ + if (parsing_block_typedef_declarator && vd->type && + vd->type->array_size && lex_peek(T_open_square, NULL)) { + vd->array_size = 0; + vd->array_dim2 = vd->array_dim3 = vd->array_dim4 = 0; + } + + for (int dim = 0; lex_accept(T_open_square); dim++) { + vd->has_direct_array_declarator = true; + bool parameter_static_bound = false; + bool parameter_const_bound = false; + + if (dim >= 4) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL) || + lex_peek(T_volatile, NULL) || lex_peek(T_restrict, NULL)) { + if (!is_param || dim) + error_at("array bracket qualifiers require a parameter", + cur_token_loc()); + if (lex_accept(T_static)) { + if (parameter_static_bound || dim) + error_at( + "static is only valid in the outermost array " + "parameter bound", + cur_token_loc()); + parameter_static_bound = true; + } else if (lex_accept(T_const)) + parameter_const_bound = true; + else if (lex_accept(T_volatile)) + vd->is_volatile = true; + else + lex_expect(T_restrict); + } + if (lex_peek(T_close_square, NULL)) { + if (parameter_static_bound) + error_at("static array parameter needs a bound", + cur_token_loc()); + + /* An omitted leading size is only a pointer when nothing + * follows it: "int a[]" is "int *", but "int a[][4]" points at + * rows of four and is indexed exactly like "int a[3][4]". + * Raising the pointer level for the latter would scale the row + * index by a pointer instead of by the row, and add a + * dereference that is not there. + */ + if (dim == 0) + first_dim_empty = true; + else + vd->ptr_level++; + } else { + int next_dim = read_const_expr(vd->scope); + + if (parameter_static_bound && next_dim <= 0) + error_at("static array parameter needs a positive bound", + cur_token_loc()); + + if (dim == 0) { + vd->array_size = next_dim; + } else { + if (dim == 1) + vd->array_dim2 = next_dim; + else if (dim == 2) + vd->array_dim3 = next_dim; + else if (dim == 3) + vd->array_dim4 = next_dim; + if (vd->array_size > 0) + vd->array_size *= next_dim; + else + vd->array_size = next_dim; + } + } + if (parameter_const_bound && dim == 0) { + vd->is_const_pointer = true; + vd->pointer_const_mask |= 1U; + } + lex_expect(T_close_square); + dims++; + } + if (element_shape.rank) { + fixed_array_shape_t decl_shape = fixed_array_shape_from_var(vd); + fixed_array_shape_t shape; + + /* An omitted outer bound counts as one row, which is how an unsized + * `int t[][2]` records its inner extent as well. + */ + if (first_dim_empty) { + if (!decl_shape.rank) + decl_shape.rank = 1; + decl_shape.bounds[0] = 1; + } + shape = fixed_array_shape_prepend(&decl_shape, &element_shape); + fixed_array_shape_to_var(vd, &shape); + dims += element_shape.rank; + } + if (first_dim_empty && is_record_member) { + /* A record's omitted outer bound is its flexible array member. + * Inner dimensions still describe the complete element type, so + * retain their product and row stride for member indexing. + */ + vd->has_unsized_array = true; + } else if (first_dim_empty && !is_param) { + /* An object declarator such as `char text[] = "hi"` has an inferred + * outer bound. This also applies when it carries known inner + * dimensions, e.g. `int table[][4]`. + */ + vd->has_unsized_array = true; + } else if (first_dim_empty && dims == 1) { + /* Only a one-dimensional parameter needs the pointer adjustment; a + * multidimensional parameter retains its inner row stride. + */ + vd->ptr_level++; + } + + /* Parameter adjustment turns `void values[]` into a pointer-shaped + * declarator, but C99 still forbids void as an array element type. The + * pre-suffix shape distinguishes it from an array of void pointers, + * including a pointer typedef. + */ + if (dims && void_array_element) + error_at("void type cannot be an array element", cur_token_loc()); + + /* C99 permits an object of a flexible-record type, but not an array + * whose elements have no fixed extent. An explicitly pointer-typed + * element remains valid, including through a pointer typedef. + */ + if (vd->array_size > 0 && !vd->ptr_level && !vd->type->ptr_level && + type_has_flexible_array_member(vd->type)) + error_at( + "A struct with a flexible array member cannot be an array " + "element", + cur_token_loc()); + + /* A parameter declared as a function, `int fn(int)`, is adjusted to a + * pointer to it (C99 6.7.5.3p8): read it as `int (*fn)(int)`. + */ + if (is_param && lex_peek(T_open_bracket, NULL) && !vd->array_size && + !vd->has_unsized_array) { + read_adjusted_function_parameter(vd); + return; + } + + /* The ordinary declarator spelling `result name(parameters)` is a + * function type just as the parenthesized pointer form above is. Keep + * this syntax-only signature on the declaration; block typedefs can + * then bind a direct function alias without creating an object or IR. + */ + if ((parsing_block_typedef_declarator || + (strict_c99 && parsing_for_initializer_declaration)) && + lex_peek(T_open_bracket, NULL) && !vd->array_size && + !vd->has_unsized_array) { + func_t *func = arena_alloc_func(); + bool saved_block_typedef_declarator = + parsing_block_typedef_declarator; + + memcpy(&func->return_def, vd, sizeof(var_t)); + func->returns_aggregate = is_record_type(func->return_def.type) && + !has_effective_pointer(&func->return_def); + + /* The enclosing typedef is the only declaration entitled to use + * this direct-function syntax. Parameter declarators continue + * through their established parsing path. + */ + parsing_block_typedef_declarator = false; + read_parameter_list_decl(func, true); + parsing_block_typedef_declarator = saved_block_typedef_declarator; + vd->func_signature = func; + vd->is_func = true; + vd->is_direct_function_declarator = true; + return; + } + + /* An ordinary declarator can name a function-pointer typedef. Its + * prototype belongs to the typedef's type descriptor, while the object + * retains the existing function-pointer representation. + */ + if (vd->ptr_level == 1 && !vd->array_size && + vd->type->is_direct_function_type && vd->type->func_signature) { + /* A pointer applied directly to a function typedef is a callable + * function-pointer object. It is not a function designator, but + * indirect-call lowering needs its prototype. + */ + vd->func_signature = vd->type->func_signature; + vd->is_func = false; + } else if (vd->ptr_level == 1 && + (vd->array_size || vd->has_unsized_array) && + !vd->array_dim2 && vd->type->is_direct_function_type && + vd->type->func_signature) { + /* `unary_t *table[2]` is `int (*table[2])(int)`: an array of + * callbacks, which takes that declarator's representation, typed by + * the return type and carrying the prototype. + */ + func_t *signature = vd->type->func_signature; + + vd->type = signature->return_def.type; + vd->ptr_level = signature->return_def.ptr_level; + vd->func_signature = signature; + vd->is_func = true; + } else if (vd->ptr_level == 2 && !vd->array_size && + !vd->has_unsized_array && + vd->type->is_direct_function_type && + vd->type->func_signature) { + /* `unary_t **slot` is `int (**slot)(int)`, a pointer to a callback + * slot such as an element of the array above. + */ + func_t *signature = vd->type->func_signature; + + vd->type = signature->return_def.type; + vd->ptr_level = signature->return_def.ptr_level + 1; + vd->pointee_func_signature = signature; + vd->func_signature = NULL; + vd->is_func = false; + } else if (vd->ptr_level || vd->type->ptr_level || vd->array_size) { + /* A derived declarator is not itself a callable callback object. + * Preserve no direct-call marker until dereference/subscript + * lowering can carry the element prototype separately. + */ + vd->func_signature = NULL; + vd->is_func = false; + } else { + vd->is_func = vd->func_signature != NULL; + } + } + + /* The legacy flag remains the outermost pointer qualifier for lvalue + * writes. Intermediate qualifiers are retained in pointer_const_mask. + */ + if (vd->ptr_level > 0 && vd->ptr_level <= 32) + vd->is_const_pointer = + vd->pointer_const_mask & (1U << (vd->ptr_level - 1)); + + /* C99 permits void only as a function return type, a pointer target, or the + * separately validated lone parameter-list sentinel. A by-value void + * declarator has no object size and must not reach allocation or layout. + */ + if (vd->type && vd->type->base_type == TYPE_void && + !effective_pointer_depth(vd) && !vd->is_func && + !lex_peek(T_open_bracket, NULL)) + error_at("void type cannot define an object", cur_token_loc()); +} + +/* C99 6.7p2 lets the storage-class specifiers appear anywhere among the + * declaration specifiers: "int static x;" is "static int x;" and "unsigned + * register int z;" is "register unsigned int z;". The order carries no meaning, + * and every declaration reader looks for storage classes before the type, so + * move each one that follows another specifier to the front of the declaration, + * right after the current token. + * + * The scan covers only tokens that can continue the specifiers: qualifiers, + * inline, scalar keywords, a struct, union or enum specifier with its body, and + * one type name. A scalar type word may follow another, as in "int short", but + * any other identifier after the type is the declarator, which ends the scan, + * so "int x static;" stays an error. + */ +void hoist_storage_class_specifiers(void) +{ + token_t *insert = cur_token; + token_t *prev = cur_token; + bool saw_specifier = false; + bool saw_type_name = false; + + while (prev->next) { + token_t *tk = prev->next; + token_kind_t kind = tk->kind; + + if (kind == T_static || kind == T_extern || kind == T_register || + kind == T_auto || kind == T_typedef) { + if (saw_specifier) { + prev->next = tk->next; + tk->next = insert->next; + insert->next = tk; + } else + prev = tk; + insert = tk; + continue; + } + if (kind == T_struct || kind == T_union || kind == T_enum) { + prev = tk; + if (prev->next && prev->next->kind == T_identifier) + prev = prev->next; + if (prev->next && prev->next->kind == T_open_curly) { + int depth = 0; + + do { + prev = prev->next; + if (!prev) + return; + if (prev->kind == T_open_curly) + depth++; + else if (prev->kind == T_close_curly) + depth--; + } while (depth); + } + saw_specifier = true; + saw_type_name = true; + continue; + } + if (kind == T_identifier) { + /* `char`, `short` and `int` are keywords that reach the parser as + * identifiers, so they never begin the declarator. + */ + if (saw_type_name && strcmp(tk->literal, "int") && + strcmp(tk->literal, "short") && strcmp(tk->literal, "char")) + return; + saw_type_name = true; + } else if (kind != T_const && kind != T_volatile && + kind != T_restrict && kind != T_inline && kind != T_signed && + kind != T_unsigned && kind != T_long && kind != T_float && + kind != T_double) + return; + saw_specifier = true; + prev = tk; + } +} + +/* C99 6.7 lets declaration specifiers appear in any order, so a type qualifier + * may follow a struct, union, enum or typedef name as well as precede it: + * `struct S volatile s` qualifies s exactly as `volatile struct S s` does. + * Callers that read such a specifier themselves use this to collect the + * qualifiers after it, leaving the declarator's stars and their own qualifiers + * to read_inner_var_decl(). restrict qualifies only a pointer, so it is valid + * here only when @base_is_pointer says the specifier names one. + */ +void read_type_qualifiers(bool *is_const, + bool *is_volatile, + bool base_is_pointer) +{ + for (;;) { + if (lex_accept(T_const)) + *is_const = true; + else if (lex_accept(T_volatile)) + *is_volatile = true; + else if (lex_accept(T_restrict)) { + if (!base_is_pointer) + error_at("restrict requires a pointer type", cur_token_loc()); + } else + return; + } +} + +void read_full_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member); + +/* The member list of a struct or union of @kind, starting at its opening brace, + * declared in block @parent or at file scope when @parent is NULL. A tagged + * definition completes the tag @token of that scope; an untagged one names a + * type no other declaration can reach, so it needs no tag table entry. A member + * may itself define a record, whose tag then belongs to the same scope, since a + * member list opens no scope of its own. + * + * Returns the completed record type. + */ +type_t *read_record_body(block_t *parent, + base_type_t kind, + bool has_tag, + char token[]) +{ + type_t *type; + bool is_union = kind == TYPE_union; + int i = 0; + int size = 0; + int alignment = 1; + int max_size = 0; + bitfield_layout_t bits = {0}; + bool has_flexible_array_member = false; + block_t *scope = parent ? parent : GLOBAL_BLOCK; + + if (has_tag) { + type = local_record_tag(token, scope, kind); + begin_record_definition(type); + } else { + type = add_type(); + type->base_type = kind; + } + + lex_expect(T_open_curly); + do { + var_t *v = type_add_field(type, &i); + var_t *last = v; + + /* A member's type names tags visible in this scope. */ + v->scope = parent; + read_full_var_decl(v, false, false, true); + + /* Each declarator of the member declaration, sharing its specifier. */ + while (true) { + read_bitfield_width(last, scope); + + /* C99 6.7.2.1p2 keeps a struct with a flexible array member out of + * a struct or array, but a union may hold one and then inherits it. + */ + if (is_union) + has_flexible_array_member |= + is_flexible_array_member_container(last); + else + reject_flexible_array_member_container(last); + mark_flexible_array_member(last, is_union); + if (is_union) { + last->offset = 0; + int field_size = + is_bitfield(last) + ? (last->bit_width ? last->bit_storage_size : 0) + : size_var(last); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(last) > alignment) + alignment = alignment_var(last); + } else { + size = + is_bitfield(last) + ? layout_bitfield_field(size, last, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), last, + &alignment); + } + if (!lex_accept(T_comma)) + break; + if (!is_union && last->is_flexible_array_member) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); + last = type_add_field(type, &i); + initialize_struct_field(last, v, 0); + read_inner_var_decl(last, false, false, true); + } + + lex_expect(T_semicolon); + if (!is_union && last->is_flexible_array_member) { + if (!lex_peek(T_close_curly, NULL)) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); + if (i == 1) + error_at( + "Struct needs a named member before its flexible array " + "member", + cur_token_loc()); + has_flexible_array_member = true; + } + } while (!lex_accept(T_close_curly)); + + type->alignment = alignment; + type->size = is_union + ? ALIGN_UP(max_size, alignment) + : ALIGN_UP(flush_bitfield_layout(size, &bits), alignment); + type->num_fields = i; + type->is_union = is_union; + type->has_flexible_array_member = has_flexible_array_member; + return type; +} + +/* starting next_token, need to check the type */ +void read_full_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member) +{ + char type_name[MAX_ID_LEN]; + + /* Callers which have already consumed a leading qualifier leave it on the + * declaration. Keep it separate from qualification inherited from a + * typedef: `const int_pointer` qualifies the pointer object, while an + * unqualified `const_int_pointer` only qualifies the pointed-to object. + */ + bool declaration_const = vd->is_const_qualified; + bool declaration_inline = vd->is_inline; + bool declaration_volatile = vd->is_volatile; + bool is_const = false; + bool is_inline = declaration_inline; + bool is_volatile = false; + type_t *type = + read_scalar_type_specifiers(&is_const, &is_volatile, &is_inline); + + if (!type && lex_accept(T_enum)) { + lex_ident(T_identifier, type_name); + type = reference_enum_tag(type_name, vd->scope); + } else if (!type) { + base_type_t record_kind = accept_record_keyword(); + + if (record_kind && is_record_member && + (lex_peek(T_open_curly, NULL) || + (lex_peek(T_identifier, type_name) && + cur_token->next->next->kind == T_open_curly))) { + /* A member may define the record it has, tagged or not, as in + * `struct { int a; } in;`. + */ + bool has_tag = lex_accept(T_identifier); + + type = read_record_body(vd->scope, record_kind, has_tag, type_name); + } else { + lex_ident(T_identifier, type_name); + type = record_kind + ? reference_record_tag(type_name, vd->scope, record_kind) + : find_visible_type(type_name, vd->scope); + } + } + + if (!type) { + char message[MAX_LINE_LEN]; + + snprintf(message, MAX_LINE_LEN, "Could not find type %s", type_name); + error_at(message, cur_token_loc()); + } + + vd->type = type; + vd->func_signature = type->ptr_level ? NULL : type->func_signature; + vd->pointee_func_signature = type->pointee_func_signature; + vd->is_func = vd->func_signature != NULL; + vd->is_const_qualified = type->is_const_qualified; + if (type->func_signature && !type->is_direct_function_type && + (type->pointer_const_mask & 1U)) { + vd->is_const_pointer = true; + vd->pointer_const_mask |= 1U; + } + if (type->array_size) { + fixed_array_shape_t shape = fixed_array_shape_from_type(type); + + fixed_array_shape_to_var(vd, &shape); + } + if (type->pointee_array_size) { + fixed_array_shape_t shape = fixed_array_shape_from_pointee_type(type); + + fixed_array_shape_to_pointee_var(vd, &shape); + vd->pointee_array_element_ptr_level = + type->pointee_array_element_ptr_level; + } + if (type->ptr_level && type->ptr_level <= 32) + vd->is_const_pointer = + type->pointer_const_mask & (1U << (type->ptr_level - 1)); + + /* A qualifier may follow the base type as well as precede it: both "const + * int" and "int const" qualify the object. Consume it before parsing + * pointer declarators, where a following const instead qualifies the + * pointer itself ("int * const"). + */ + while (true) { + if (lex_accept(T_const)) + is_const = true; + else if (lex_accept(T_volatile)) + is_volatile = true; + else if (lex_accept(T_restrict)) { + /* A typedef-hidden array may be qualified when its elements are + * pointers. As for `const` and `volatile`, the qualifier then + * applies to the element type rather than to an array object. + */ + if (!type->ptr_level && + !(type->array_size && type->array_element_ptr_level && + (!type->func_signature || + type->array_element_pointee_func_signature))) + error_at("restrict requires a pointer type", cur_token_loc()); + } else if (lex_accept(T_inline)) { + if (is_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + is_inline = true; + } else + break; + } + vd->is_inline = is_inline; + vd->is_volatile = + declaration_volatile || is_volatile || type->is_volatile_qualified; + if (is_const || declaration_const) { + if (type->ptr_level) { + vd->is_const_pointer = true; + if (type->pointee_func_signature && type->ptr_level == 1) + vd->pointer_const_mask |= 1U; + } else + vd->is_const_qualified = true; + } + + read_inner_var_decl(vd, anon, is_param, is_record_member); + + if (!parsing_sizeof_function_signature && vd->func_signature && + function_signature_has_floating(vd->func_signature)) + error_at("Floating point function types are not yet supported", + cur_token_loc()); + + /* Typedef aliases preserve floating type identity for composition and + * sizeof, but no floating value may enter the integer-only IR/ABI path. + */ + if (vd->type && vd->type->is_floating && !parsing_sizeof_function_signature) + error_at("Floating point types are not yet supported", cur_token_loc()); +} + +/* starting next_token, need to check the type */ +void read_partial_var_decl(var_t *vd, var_t *template) +{ + UNUSED(template); + read_inner_var_decl(vd, false, false, false); +} + +/* Consume what follows one parameter declaration. Parameters are separated by + * commas, and a comma must introduce another parameter or the ellipsis: C99 + * 6.7.5 has no trailing comma in a parameter list, in any dialect gcc accepts. + * + * Returns true when a comma was read, so another parameter or '...' follows. + */ +static bool read_parameter_separator(void) +{ + if (!lex_accept(T_comma)) + return false; + if (lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in parameter list", cur_token_loc()); + return true; +} + +void read_parameter_list_decl(func_t *func, bool anon) +{ + int vn = 0; + bool expect_parameter = false; + lex_expect(T_open_bracket); + + char token[MAX_ID_LEN]; + /* C99's empty parameter list is deliberately not a prototype. */ + if (lex_accept(T_close_bracket)) + return; + + /* An identifier list, `f(x, y)`, names the parameters of an old-style + * definition, whose declaration list gives their types (C99 6.9.1p6). It is + * no prototype either. + */ + if (lex_peek(T_identifier, token) && strcmp(token, "void") && + !find_visible_type(token, func->return_def.scope) && + cur_token->next->next && + (cur_token->next->next->kind == T_comma || + cur_token->next->next->kind == T_close_bracket)) { + do { + if (vn >= MAX_PARAMS) + error_at("Too many parameters", cur_token_loc()); + lex_ident(T_identifier, token); + for (int i = 0; i < vn; i++) + if (!strcmp(func->param_defs[i].var_name, token)) + error_at("duplicate parameter name", cur_token_loc()); + func->param_defs[vn].var_name = intern_string(token); + func->param_defs[vn].scope = func->return_def.scope; + vn++; + } while (lex_accept(T_comma)); + func->num_params = vn; + lex_expect(T_close_bracket); + return; + } + if (lex_peek(T_identifier, token) && !strcmp(token, "void")) { + lex_next(); + if (lex_accept(T_close_bracket)) { + func->has_prototype = true; + return; + } + func->param_defs[vn].type = TY_void; + func->param_defs[vn].scope = func->return_def.scope; + read_inner_var_decl(&func->param_defs[vn], anon, true, false); + if (!func->param_defs[vn].ptr_level && !func->param_defs[vn].is_func && + !func->param_defs[vn].array_size) + error_at("'void' must be the only parameter and unnamed", + cur_token_loc()); + vn++; + expect_parameter = read_parameter_separator(); + } + + if (floating_type_starts_here() && !parsing_sizeof_function_signature) + error_at("Floating point types are not yet supported", cur_token_loc()); + + while ((vn == 0 || expect_parameter) && + ((parsing_sizeof_function_signature && + (lex_peek(T_float, NULL) || lex_peek(T_double, NULL))) || + lex_peek(T_identifier, NULL) || lex_peek(T_const, NULL) || + lex_peek(T_volatile, NULL) || lex_peek(T_register, NULL) || + lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL) || lex_peek(T_struct, NULL) || + lex_peek(T_union, NULL) || lex_peek(T_enum, NULL))) { + /* Check for const qualifier */ + bool is_const = false; + bool is_register = false; + hoist_storage_class_specifiers(); + if (lex_accept(T_const)) + is_const = true; + if (lex_accept(T_register)) + is_register = true; + + if (vn >= MAX_PARAMS) + error_at("Too many parameters", cur_token_loc()); + func->param_defs[vn].scope = func->return_def.scope; + read_full_var_decl(&func->param_defs[vn], anon, true, false); + + /* An array of function pointers is adjusted to a pointer to function + * pointers (C99 6.7.5.3p7), which int (**cb)(int) spells directly. + */ + var_t *param = &func->param_defs[vn]; + if (param->is_func && param->func_signature && !param->ptr_level && + !param->array_dim2 && + (param->array_size || param->has_unsized_array)) { + param->pointee_func_signature = param->func_signature; + param->func_signature = NULL; + param->is_func = false; + param->ptr_level = 1; + param->array_size = 0; + param->has_unsized_array = false; + } + if (func->param_defs[vn].is_inline) + error_at("inline specifier requires a function declarator", + cur_token_loc()); + func->param_defs[vn].is_const_qualified |= is_const; + func->param_defs[vn].is_register = is_register; + func->param_defs[vn].is_aggregate_param = + is_record_type(func->param_defs[vn].type) && + !func->param_defs[vn].ptr_level; + vn++; + expect_parameter = read_parameter_separator(); + } + func->num_params = vn; + + /* Up to 'MAX_PARAMS' parameters are accepted for the variadic function. + * After a named parameter the ellipsis needs its own comma. + */ + if ((vn == 0 || expect_parameter) && lex_accept(T_elipsis)) { + if (strict_c99 && vn == 0) + error_at("ellipsis requires at least one named parameter in C99", + cur_token_loc()); + func->va_args = 1; + } + + func->has_prototype = true; + lex_expect(T_close_bracket); +} + +void read_literal_param(block_t *parent, basic_block_t *bb) +{ + char combined[MAX_STRING_LEN]; + int length = read_concatenated_string(combined); + const int index = write_string_symbol(combined, length); + + var_t *vd = require_typed_ptr_var(parent, TY_char, true); + vd->var_name = gen_name(); + vd->init_val = index; + vd->is_const_qualified = true; + vd->is_string_literal = true; + opstack_push(vd); + /* String literals are now in .rodata section */ + add_insn(parent, bb, OP_load_rodata_address, vd, NULL, NULL, 0, NULL); +} + +/* A character array initialized from a string owns writable object storage; + * unlike a char * initializer, it must not retain the string literal's + * read-only address. `parent` selects normal local stores or the synthetic + * global block used by static-storage arrays. + */ +void parse_string_array_init(var_t *var, block_t *parent, basic_block_t **bb) +{ + char combined[MAX_STRING_LEN]; + int len; + int count; + + len = read_concatenated_string(combined) + 1; + if (var->has_unsized_array) { + var->array_size = len; + var->has_unsized_array = false; + } else if (len - 1 > var->array_size) + error_at("String initializer is too long for character array", + cur_token_loc()); + + /* The terminating null is dropped when the array has room only for the + * characters (C99 6.7.8p14), so never store past the array. + */ + if (len > var->array_size) + len = var->array_size; + + /* Elements past the string are zero. Static storage already starts out + * zeroed, but an automatic array is reinitialized on every entry to its + * declaration and must clear whatever the slot held before. + */ + count = parent == GLOBAL_BLOCK ? len : var->array_size; + for (int i = 0; i < count; i++) { + var_t *value = require_var(parent); + var_t *addr; + + value->var_name = gen_name(); + value->init_val = i < len ? (unsigned char) combined[i] : 0; + value->is_const = true; + add_insn(parent, *bb, OP_load_constant, value, NULL, NULL, 0, NULL); + addr = compute_element_address(parent, bb, var, i, 1); + add_insn(parent, *bb, OP_write, NULL, addr, value, 1, NULL); + } +} + +void parse_wstring_array_init(var_t *var, block_t *parent, basic_block_t **bb) +{ + int values[MAX_STRING_LEN]; + int length; + int units; + + length = read_wstring_units(values, MAX_STRING_LEN); + + /* As for a char array, the terminating null may be dropped (C99 6.7.8p15). + */ + units = length + 1; + if (var->has_unsized_array) { + var->array_size = units; + var->has_unsized_array = false; + } else if (length > var->array_size) + error_at("Wide string initializer is too long for array", + cur_token_loc()); + + for (int i = 0; i < var->array_size; i++) { + var_t *value = require_typed_var(parent, var->type); + var_t *addr; + + value->var_name = gen_name(); + value->init_val = i < length ? values[i] : 0; + value->is_const = true; + add_insn(parent, *bb, OP_load_constant, value, NULL, NULL, 0, NULL); + addr = compute_element_address(parent, bb, var, i, var->type->size); + add_insn(parent, *bb, OP_write, NULL, addr, value, var->type->size, + NULL); + } +} + +bool numeric_has_unsigned_suffix(const char *token) +{ + for (int i = 0; token[i]; i++) + if ((token[i] | 32) == 'u') + return true; + return false; +} + +/* C99 permits U, L, LL, UL, ULL, LU, and LLU (case-insensitively). Keep this + * separate from type selection: malformed suffixes must not become a valid wide + * literal merely because their letters happen to be counted. + */ +bool numeric_suffix_is_valid(const char *suffix) +{ + int pos = 0; + + if ((suffix[pos] | 32) == 'u') + pos++; + if ((suffix[pos] | 32) == 'l') { + pos++; + if ((suffix[pos] | 32) == 'l') + pos++; + } + if ((suffix[pos] | 32) == 'u') + pos++; + return suffix[pos] == '\0'; +} + +bool numeric_has_long_long_suffix(const char *token) +{ + int long_suffix_count = 0; + + for (int i = 0; token[i]; i++) + if ((token[i] | 32) == 'l') + long_suffix_count++; + return long_suffix_count == 2; +} + +int numeric_long_suffix_count(const char *token) +{ + int count = 0; + + for (int i = 0; token[i]; i++) + if ((token[i] | 32) == 'l') + count++; + return count; +} + +/* The file-scope constant evaluator is still word-sized. Decide from the token + * spelling whether it must use the two-word literal path before that evaluator + * consumes and narrows it. + */ +bool numeric_literal_needs_wide_path(const char *token) +{ + const char *digits = token; + int count = 0; + bool is_unsigned = numeric_has_unsigned_suffix(token); + + if (numeric_has_long_long_suffix(token)) + return true; + if (digits[0] == '0' && (digits[1] | 32) == 'x') { + digits += 2; + while (isxdigit(digits[count])) + count++; + return count > 8; + } + if (digits[0] == '0' && (digits[1] | 32) == 'b') { + digits += 2; + while (digits[count] == '0' || digits[count] == '1') + count++; + return count > 32; + } + if (digits[0] == '0') { + while (digits[count] >= '0' && digits[count] <= '7') + count++; + return count > 12 || + (count == 12 && strncmp(digits, "037777777777", count) > 0); + } + while (isdigit(digits[count])) + count++; + if (count > 10) + return true; + if (count < 10) + return false; + return strncmp(digits, is_unsigned ? "4294967295" : "2147483647", count) > + 0; +} + +/* Some bootstrap stages still materialize the exact decimal 2^31 token as an + * int bit pattern before the global initializer path sees it. This path was + * selected from the original token spelling, so restore the required wide + * candidate type before phase-2 chooses its constant-load width. + */ +void force_wide_global_literal_type(var_t *value, const char *token) +{ + /* The typed global path also handles a one-word unsigned literal such as + * 0xffffffff: it must retain TY_uint for shifts, comparisons, and division, + * but it is not a long-long candidate. Promoting it here made every 32-bit + * target reject a valid C99 unsigned-int initializer merely because that + * path was selected. + */ + if (!numeric_literal_needs_wide_path(token)) + return; + if (value->type->size >= 8) + return; + value->type = numeric_has_unsigned_suffix(token) || + (unsigned int) value->init_val_hi > 0x7fffffffU + ? TY_ulong_long + : TY_long_long; +} + +/* Accumulate a 64-bit token in four 16-bit limbs. Each intermediate stays small + * enough for the self-hosted compiler's unsigned arithmetic. + */ +bool numeric_mul_add_wide(unsigned int *hi, + unsigned int *lo, + unsigned int base, + unsigned int digit) +{ + unsigned int a = *lo & 0xffffU; + unsigned int b = *lo >> 16; + unsigned int c = *hi & 0xffffU; + unsigned int d = *hi >> 16; + unsigned int t; + + t = a * base + digit; + a = t & 0xffffU; + t = b * base + (t >> 16); + b = t & 0xffffU; + t = c * base + (t >> 16); + c = t & 0xffffU; + t = d * base + (t >> 16); + if (t > 0xffffU) + return false; + *lo = (b << 16) | a; + *hi = (t << 16) | c; + return true; +} + +/* The global-initializer fast path historically carries only an int value. + * Route literals whose C99 candidate is unsigned through the typed path even + * when they fit in one word: otherwise 0xffffffff is folded as -1 before a + * right shift, comparison, or division sees its unsigned rank. + */ +bool numeric_literal_needs_typed_global_path(const char *token) +{ + unsigned int hi = 0; + unsigned int lo = 0; + int i = 0; + int base = 10; + bool is_decimal = true; + + if (numeric_has_unsigned_suffix(token)) + return true; + if (token[0] == '0') { + if ((token[1] | 32) == 'x') { + i = 2; + base = 16; + is_decimal = false; + while (isxdigit(token[i])) { + char digit = token[i++]; + + if (isdigit(digit)) + digit -= '0'; + else + digit = (digit | 32) - 'a' + 10; + numeric_mul_add_wide(&hi, &lo, base, digit); + } + } else if ((token[1] | 32) == 'b') { + i = 2; + base = 2; + is_decimal = false; + while (token[i] == '0' || token[i] == '1') + numeric_mul_add_wide(&hi, &lo, base, token[i++] - '0'); + } else { + base = 8; + is_decimal = false; + while (token[i] >= '0' && token[i] <= '7') + numeric_mul_add_wide(&hi, &lo, base, token[i++] - '0'); + } + } + if (is_decimal) + return false; + return hi != 0 || lo > 0x7fffffffU; +} + +void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) +{ + char token[MAX_TOKEN_LEN]; + unsigned int value = 0; + unsigned int value_hi = 0; + int i = 0; + char c; + int base = 10; + bool is_decimal = true; + bool has_unsigned_suffix; + int long_suffix_count; + bool is_long_long; + + lex_ident_n(T_numeric, token, MAX_TOKEN_LEN); + has_unsigned_suffix = numeric_has_unsigned_suffix(token); + long_suffix_count = numeric_long_suffix_count(token); + is_long_long = long_suffix_count >= 2; + + if (token[0] == '-') { + is_neg = !is_neg; + i++; + } + if (token[0] == '0') { + if ((token[1] | 32) == 'x') { /* hexdecimal */ + i = 2; + base = 16; + is_decimal = false; + do { + c = token[i++]; + if (isdigit(c)) + c -= '0'; + else { + c |= 32; /* convert to lower case */ + if (c >= 'a' && c <= 'f') + c = (c - 'a') + 10; + else + error_at("Invalid numeric constant", cur_token_loc()); + } + + if (!numeric_mul_add_wide(&value_hi, &value, base, c)) + error_at("Integer literal exceeds supported range", + cur_token_loc()); + } while (isxdigit(token[i])); + } else if ((token[1] | 32) == 'b') { /* binary */ + i = 2; + base = 2; + is_decimal = false; + do { + c = token[i++]; + if (c != '0' && c != '1') + error_at("Invalid binary constant", cur_token_loc()); + c -= '0'; + if (!numeric_mul_add_wide(&value_hi, &value, base, c)) + error_at("Integer literal exceeds supported range", + cur_token_loc()); + } while (token[i] == '0' || token[i] == '1'); + } else { /* octal */ + base = 8; + is_decimal = false; + do { + c = token[i++]; + if (c > '7') + error_at("Invalid numeric constant", cur_token_loc()); + c -= '0'; + if (!numeric_mul_add_wide(&value_hi, &value, base, c)) + error_at("Integer literal exceeds supported range", + cur_token_loc()); + } while (isdigit(token[i])); + } + } else { + do { + c = token[i++] - '0'; + if (!numeric_mul_add_wide(&value_hi, &value, base, c)) + error_at("Integer literal exceeds supported range", + cur_token_loc()); + } while (isdigit(token[i])); + } + + if (!numeric_suffix_is_valid(token + i)) + error_at("Invalid integer literal suffix", cur_token_loc()); + + /* Decimal constants have only signed candidates unless they carry U: C99 + * may not silently select unsigned long long for 2^63 or above. The one + * exception is the magnitude in the standard spelling of LLONG_MIN, where + * the separately parsed unary minus consumes exactly 2^63. + */ + if (is_decimal && !has_unsigned_suffix && + (value_hi > 0x80000000U || + (value_hi == 0x80000000U && (value != 0 || !is_neg)))) + error_at("Decimal integer literal exceeds signed long long range", + cur_token_loc()); + + /* C99 gives the magnitude in -2147483648 type long long. A target without + * paired 64-bit lowering cannot hold a long long value, yet the negated + * result is INT_MIN, which int represents exactly; typing that one spelling + * as int keeps INT_MIN usable there instead of rejecting it. + */ + bool narrow_int_min = PTR_SIZE < 8 && is_neg && is_decimal && + !has_unsigned_suffix && !long_suffix_count && + !value_hi && value == 0x80000000U; + + var_t *vd = require_var(parent); + vd->var_name = gen_name(); + if (narrow_int_min) { + vd->type = TY_int; + } else if (is_long_long || value_hi || + (is_decimal && !has_unsigned_suffix && + numeric_literal_needs_wide_path(token)) || + (is_decimal && !has_unsigned_suffix && value > 0x80000000U)) { + if (has_unsigned_suffix || (!is_decimal && value_hi > 0x7fffffffU)) + vd->type = TY_ulong_long; + else + vd->type = TY_long_long; + } else if (has_unsigned_suffix || (!is_decimal && value > 0x7fffffffU)) + vd->type = long_suffix_count ? TY_ulong : TY_uint; + else if (long_suffix_count) + vd->type = TY_long; + + /* Unary minus is applied after candidate selection. In particular, + * `-2147483648` is a negated long-long literal on this 32-bit-long ABI, + * rather than a nonstandard signed-int bit pattern. + */ + if (is_neg) { + value = 0 - value; + value_hi = ~value_hi + (value == 0); + + /* An unsigned int wraps within its own width: -9U has no high word. */ + if (vd->type && vd->type->is_unsigned && vd->type->size == 4) + value_hi = 0; + } + vd->init_val = value; + vd->init_val_hi = value_hi; + vd->is_const = true; + opstack_push(vd); + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); +} + +void read_char_param(block_t *parent, basic_block_t *bb) +{ + char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; + + lex_ident(T_char, literal); + unescape_string(literal, unescaped, MAX_TOKEN_LEN); + + var_t *vd = require_typed_var(parent, TY_int); + vd->var_name = gen_name(); + vd->init_val = parse_character_constant(literal); + vd->is_const = true; + opstack_push(vd); + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); +} + +/* The current execution wide-character representation is int. Decode the same + * escape spelling as an ordinary character constant; a wide string needs + * separate element-array lowering and is intentionally not accepted here. + */ +void read_wchar_param(block_t *parent, basic_block_t *bb) +{ + char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; + + lex_ident(T_wchar, literal); + unescape_string(literal, unescaped, MAX_TOKEN_LEN); + + var_t *vd = require_typed_var(parent, TY_int); + vd->var_name = gen_name(); + vd->init_val = parse_wide_character_constant(literal); + vd->is_const = true; + opstack_push(vd); + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); +} + +void read_wstring_param(block_t *parent, basic_block_t *bb) +{ + int values[MAX_STRING_LEN]; + int length = read_wstring_units(values, MAX_STRING_LEN); + + var_t *vd = require_typed_ptr_var(parent, find_type("wchar_t", true), 1); + vd->var_name = gen_name(); + vd->init_val = write_wide_symbol(values, length); + vd->is_const_qualified = true; + vd->is_string_literal = true; + opstack_push(vd); + add_insn(parent, bb, OP_load_rodata_address, vd, NULL, NULL, 0, NULL); +} + +/* Whether the static initializer at the next token is a string literal plus or + * minus an integer constant, as in `"abc" + 1` or `2 + "abcd"`: an address + * constant (C99 6.6p7). The literal must follow a '+' or start the initializer + * with an additive operator after it; a subscripted one is an element instead. + */ +bool string_address_offset_starts_here(void) +{ + token_t *prev = NULL; + int bracket_depth = 0; + + for (token_t *token = cur_token->next; token; token = token->next) { + if (token->kind == T_open_bracket) + bracket_depth++; + else if (token->kind == T_close_bracket) { + if (bracket_depth == 0) + return false; + bracket_depth--; + } else if (bracket_depth == 0 && + (token->kind == T_semicolon || token->kind == T_comma || + token->kind == T_close_curly)) + return false; + else if (bracket_depth == 0 && + (token->kind == T_string || token->kind == T_wstring)) { + token_t *after = token; + + while (after->next && after->next->kind == token->kind) + after = after->next; + after = after->next; + if (after && after->kind == T_open_square) + return false; + if (!prev) + return after && + (after->kind == T_plus || after->kind == T_minus); + return prev->kind == T_plus; + } + prev = token; + } + return false; +} + +/* The address constant string_address_offset_starts_here() accepted: the + * literal's address advanced by the integer constants added to or subtracted + * from it, in element units. + */ +var_t *read_string_address_offset(block_t *parent, + basic_block_t *bb, + block_t *scope) +{ + var_t *literal; + var_t *address; + int index = 0; + + if (!lex_peek(T_string, NULL) && !lex_peek(T_wstring, NULL)) { + token_t *token = cur_token->next; + token_t *plus = NULL; + + /* Find the '+' before the literal and end the integer operand there + * while it is read, so the constant reader does not meet the literal. + */ + while (token->kind != T_string && token->kind != T_wstring) { + plus = token; + token = token->next; + } + plus->kind = T_semicolon; + index = read_const_expr(scope); + plus->kind = T_plus; + lex_expect(T_plus); + } + if (lex_peek(T_wstring, NULL)) + read_wstring_param(parent, bb); + else + read_literal_param(parent, bb); + literal = opstack_pop(); + if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) + index += read_global_address_offset(scope, parent, bb); + address = compute_element_address(parent, &bb, literal, index, + literal->type->size); + if (address != literal) { + address->type = literal->type; + address->ptr_level = literal->ptr_level; + address->is_const_qualified = true; + address->is_string_literal = true; + } + return address; +} + +/* The address constant `&"ab"[1]` in a static initializer, with the '&' already + * consumed: the literal's address advanced by the constant index. + */ +var_t *read_string_literal_element_address(block_t *parent, + basic_block_t *bb, + block_t *scope) +{ + token_t *literal_token = cur_token; + var_t *literal; + var_t *address; + int index; + + /* Check the index against the literal, then read the literal again to emit + * its address. + */ + index = read_string_literal_element(scope, NULL); + cur_token = literal_token; + if (lex_peek(T_wstring, NULL)) + read_wstring_param(parent, bb); + else + read_literal_param(parent, bb); + literal = opstack_pop(); + lex_expect(T_open_square); + read_const_expr(scope); + lex_expect(T_close_square); + address = compute_element_address(parent, &bb, literal, index, + literal->type->size); + if (address != literal) { + address->type = literal->type; + address->ptr_level = literal->ptr_level; + address->is_const_qualified = true; + address->is_string_literal = true; + } + return address; +} diff --git a/src/parser-expr.c b/src/parser-expr.c new file mode 100644 index 00000000..508ec68d --- /dev/null +++ b/src/parser-expr.c @@ -0,0 +1,5704 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Expressions: operands, pointer arithmetic, lvalues, logical and conditional + * operators, and assignment. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +/* The grouped row forms below accept only `(*name)` where name points to a + * scalar fixed array; any other operand, such as `(*pp)[1]` on an int **, + * belongs to ordinary expression parsing. + */ +static bool names_scalar_row_pointer(token_t *token, block_t *parent) +{ + var_t *var = find_var(token->literal, parent); + + return var && var->pointee_array_size && + !var->pointee_array_element_ptr_level && + effective_pointer_depth(var) == 1; +} + +/* Keep grouped row-element postfix support deliberately exact until general + * grouped lvalues have an address representation. This scanner never consumes + * tokens, so every non-match remains owned by ordinary expression parsing. + */ +static bool grouped_scalar_pointee_row_postfix_starts(block_t *parent) +{ + token_t *token = cur_token ? cur_token->next : NULL; + int suffixes = 0; + + if (!token || token->kind != T_open_bracket || !(token = token->next) || + token->kind != T_asterisk || !(token = token->next) || + token->kind != T_identifier || + !names_scalar_row_pointer(token, parent) || !(token = token->next) || + token->kind != T_close_bracket || !(token = token->next)) + return false; + + while (suffixes < 4 && token && token->kind == T_open_square) { + int depth = 0; + + for (; token; token = token->next) { + if (token->kind == T_open_square || token->kind == T_open_bracket || + token->kind == T_open_curly) + depth++; + else if (token->kind == T_close_square || + token->kind == T_close_bracket || + token->kind == T_close_curly) { + if (!--depth) { + token = token->next; + break; + } + } + } + if (!token) + return false; + suffixes++; + } + + return suffixes && + (token->kind == T_increment || token->kind == T_decrement); +} + +static void handle_grouped_scalar_pointee_row_postfix(block_t *parent, + basic_block_t **bb) +{ + char name[MAX_VAR_LEN]; + var_t *source, *row, *index, *address, *old, *one, *updated; + fixed_array_shape_t shape; + int dimensions; + int depth = 0; + opcode_t op; + + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_ident(T_identifier, name); + source = find_var(name, parent); + if (!source) + error_at("Undeclared identifier", cur_token_loc()); + if (!lower_pointee_array_dereference(source, parent, bb)) + error_at("Grouped pointer-to-array update requires a scalar fixed row", + cur_token_loc()); + row = opstack_pop(); + if (row->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + shape = fixed_array_shape_from_var(row); + dimensions = shape.rank; + if (dimensions >= 3 && + (is_record_type(row->type) || row->type->is_floating)) + error_at( + "Grouped rank-three/four postfix update requires an integer scalar", + cur_token_loc()); + if (dimensions > 4) + error_at( + "Grouped pointer-to-array postfix update supports at most four " + "dimensions", + cur_token_loc()); + lex_expect(T_close_bracket); + address = row; + while (lex_accept(T_open_square)) { + int stride; + + if (depth >= dimensions) + error_at("Too many grouped array subscripts", cur_token_loc()); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + stride = fixed_array_shape_stride(&shape, depth, row->type->size); + if (stride != 1) { + one = require_var(parent); + one->var_name = gen_name(); + one->init_val = stride; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + updated = require_var(parent); + updated->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, updated, index, one, 0, NULL); + index = updated; + } + updated = require_typed_ptr_var(parent, row->type, 1); + updated->var_name = gen_name(); + add_insn(parent, *bb, OP_add, updated, address, index, 0, NULL); + address = updated; + depth++; + } + if (depth != dimensions) + error_at("Grouped pointer-to-array postfix update needs all subscripts", + cur_token_loc()); + old = require_typed_var(parent, row->type); + old->var_name = gen_name(); + add_insn(parent, *bb, OP_read, old, address, NULL, row->type->size, NULL); + if (lex_accept(T_increment)) + op = OP_add; + else { + lex_expect(T_decrement); + op = OP_sub; + } + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, old, one); + add_insn(parent, *bb, op, updated, old, one, 0, NULL); + updated = resize_to(parent, bb, updated, row->type, 0); + add_insn(parent, *bb, OP_write, NULL, address, updated, row->type->size, + NULL); + opstack_push(old); +} + +/* Keep prefix support equally narrow: the generic prefix path has no general + * grouped-lvalue address representation. This scanner is non-mutating so a + * non-match remains entirely owned by that generic path. + */ +static bool grouped_scalar_pointee_row_prefix_starts(block_t *parent) +{ + token_t *token = cur_token ? cur_token->next : NULL; + int suffixes = 0; + + if (!token || (token->kind != T_increment && token->kind != T_decrement) || + !(token = token->next) || token->kind != T_open_bracket || + !(token = token->next) || token->kind != T_asterisk || + !(token = token->next) || token->kind != T_identifier || + !names_scalar_row_pointer(token, parent) || !(token = token->next) || + token->kind != T_close_bracket || !(token = token->next)) + return false; + + while (suffixes < 4 && token && token->kind == T_open_square) { + int depth = 0; + + for (; token; token = token->next) { + if (token->kind == T_open_square || token->kind == T_open_bracket || + token->kind == T_open_curly) + depth++; + else if (token->kind == T_close_square || + token->kind == T_close_bracket || + token->kind == T_close_curly) { + if (!--depth) { + token = token->next; + break; + } + } + } + if (!token) + return false; + suffixes++; + } + return suffixes; +} + +static void handle_grouped_scalar_pointee_row_prefix(block_t *parent, + basic_block_t **bb) +{ + char name[MAX_VAR_LEN]; + var_t *source, *row, *index, *address, *current, *one, *updated; + fixed_array_shape_t shape; + int dimensions; + int depth = 0; + opcode_t op; + + if (lex_accept(T_increment)) + op = OP_add; + else { + lex_expect(T_decrement); + op = OP_sub; + } + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_ident(T_identifier, name); + source = find_var(name, parent); + if (!source) + error_at("Undeclared identifier", cur_token_loc()); + if (!lower_pointee_array_dereference(source, parent, bb)) + error_at("Grouped pointer-to-array update requires a scalar fixed row", + cur_token_loc()); + row = opstack_pop(); + if (row->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + shape = fixed_array_shape_from_var(row); + dimensions = shape.rank; + if (dimensions >= 3 && + (is_record_type(row->type) || row->type->is_floating)) + error_at( + "Grouped rank-three/four prefix update requires an integer scalar", + cur_token_loc()); + if (dimensions > 4) + error_at( + "Grouped pointer-to-array prefix update supports at most four " + "dimensions", + cur_token_loc()); + lex_expect(T_close_bracket); + address = row; + while (lex_accept(T_open_square)) { + int stride; + + if (depth >= dimensions) + error_at("Too many grouped array subscripts", cur_token_loc()); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + stride = fixed_array_shape_stride(&shape, depth, row->type->size); + if (stride != 1) { + one = require_var(parent); + one->var_name = gen_name(); + one->init_val = stride; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + updated = require_var(parent); + updated->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, updated, index, one, 0, NULL); + index = updated; + } + updated = require_typed_ptr_var(parent, row->type, 1); + updated->var_name = gen_name(); + add_insn(parent, *bb, OP_add, updated, address, index, 0, NULL); + address = updated; + depth++; + } + if (depth != dimensions) + error_at("Grouped pointer-to-array prefix update needs all subscripts", + cur_token_loc()); + current = require_typed_var(parent, row->type); + current->var_name = gen_name(); + add_insn(parent, *bb, OP_read, current, address, NULL, row->type->size, + NULL); + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, current, one); + add_insn(parent, *bb, op, updated, current, one, 0, NULL); + updated = resize_to(parent, bb, updated, row->type, 0); + add_insn(parent, *bb, OP_write, NULL, address, updated, row->type->size, + NULL); + opstack_push(updated); +} + +/* The type name read after "(" by read_parenthesized_operand(), for the cast or + * compound literal it begins. + */ +typedef struct { + type_t *type; + int ptr_level; + int array_size; + int array_dim2; + int array_dim3; + int array_dim4; + int array_dims; + int array_element_ptr_level; + int pointee_array_size; + int pointee_array_dim2; + int pointee_array_dim3; + int pointee_array_dim4; + bool array_outer_unsized; + bool parenthesized_array; + bool const_qualified; + bool const_pointer; + unsigned int pointer_const_mask; + bool volatile_qualified; + + /* A function pointer type name, `(int (*)(int))`: its prototype and the + * stars inside the parentheses. + */ + func_t *func_signature; + int func_pointer_level; +} paren_type_name_t; + +/* A subscript applied to the value on top of the operand stack, which has no + * declaration for read_lvalue() to follow. + */ +static void lower_value_subscript(block_t *parent, basic_block_t **bb) +{ + var_t *base = opstack_pop(); + var_t *index; + var_t *address; + var_t *vd; + + lex_expect(T_open_square); + if (base->array_size || base->has_unsized_array) { + int subscript_depth = 0; + int array_dims = + 1 + !!base->array_dim2 + !!base->array_dim3 + !!base->array_dim4; + func_t *element_signature = NULL; + + /* A row of callbacks, `(*rows)[i]` for a pointer to `fn_t[2]`, holds + * pointer-sized callback values that a following call consumes. + */ + if (!base->ptr_level && !base->pointee_func_signature) { + if (base->func_signature) + element_signature = base->func_signature; + else if (base->type->func_signature && + !base->type->is_direct_function_type) + element_signature = base->type->func_signature; + } + int element_size = + base->ptr_level || element_signature ? PTR_SIZE : base->type->size; + + /* A grouping around an array, such as an array compound literal, + * preserves its array type. Lower each following index against the + * original shape, just as the direct compound-literal postfix path + * does. An array of pointers, as `*p` is for a pointer p to one, has + * pointer-sized elements whatever their base type. + */ + address = base; + do { + int stride = element_size; + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (subscript_depth == 0 && base->array_dim2) { + stride *= base->array_dim2; + if (base->array_dim3) + stride *= base->array_dim3; + if (base->array_dim4) + stride *= base->array_dim4; + } else if (subscript_depth == 1 && base->array_dim3) { + stride *= base->array_dim3; + if (base->array_dim4) + stride *= base->array_dim4; + } else if (subscript_depth == 2 && base->array_dim4) { + stride *= base->array_dim4; + } + if (stride != 1) { + var_t *scale = require_var(parent); + var_t *scaled = require_var(parent); + + scale->var_name = gen_name(); + scale->init_val = stride; + add_insn(parent, *bb, OP_load_constant, scale, NULL, NULL, 0, + NULL); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, index, scale, 0, NULL); + index = scaled; + } + var_t *indexed = + require_typed_ptr_var(parent, base->type, base->ptr_level + 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, 0, NULL); + address = indexed; + subscript_depth++; + } while (subscript_depth < array_dims && lex_accept(T_open_square)); + + if (subscript_depth < array_dims && !element_signature) { + opstack_push(array_subscript_remainder( + parent, bb, base->type, base->ptr_level, + fixed_array_shape_from_var(base), subscript_depth, address)); + } else if (subscript_depth < array_dims) { + opstack_push(address); + } else { + /* A row of a pointer typedef array, `typedef int *row[2]`, keeps + * that alias as its type. Its element is one of those pointers, not + * another whole row. + */ + type_t *element_type = base->type; + int element_ptr_level = base->ptr_level; + + if (!element_signature && element_type->ptr_level && + element_type->array_size && element_type->array_element_type) { + element_ptr_level += element_type->array_element_ptr_level; + element_type = element_type->array_element_type; + } + vd = require_typed_ptr_var(parent, element_type, element_ptr_level); + vd->var_name = gen_name(); + vd->is_const_qualified = base->is_const_qualified; + vd->pointer_const_mask = base->pointer_const_mask; + vd->is_const_pointer = base->is_const_pointer; + if (element_signature) { + vd->func_signature = element_signature; + vd->ptr_level = 1; + if (base->type->pointer_const_mask & 1U) { + vd->pointer_const_mask |= 1U; + vd->is_const_pointer = true; + } + } + push_object_at(parent, bb, vd, address, element_size); + } + return; + } + + /* E1[E2] is (*((E1)+(E2))): pointer arithmetic scales the index by the + * element size, and the dereference yields the element object. + */ + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (is_pointer_like_value(base) == is_pointer_like_value(index) || + is_record_object(base) || is_record_object(index)) + error_at("Cannot apply square operator to non-pointer", + cur_token_loc()); + handle_pointer_arithmetic(parent, bb, OP_add, base, index); + push_dereference(parent, bb, opstack_pop()); +} + +/* Whether the next tokens are an integer object @var followed by a subscript, + * the E1[E2] spelling whose array is the second operand: `i[arr]`. + */ +bool is_swapped_subscript_base(const var_t *var) +{ + return cur_token->next && cur_token->next->next && + cur_token->next->next->kind == T_open_square && + !has_effective_pointer(var) && !is_array_declarator(var) && + !var->has_unsized_array && !var->is_func && !var->func_signature && + !is_record_type(var->type); +} + +/* A member selection with `.` or `->` applied to the value on top of the + * operand stack. + */ +static void lower_value_member(block_t *parent, basic_block_t **bb) +{ + var_t *base = opstack_pop(); + var_t *address; + type_t *record_type; + bool is_lvalue = true; + + if (lex_peek(T_arrow, NULL)) { + record_type = pointee_type_from_pointer_typedef(base->type); + if (base->func_signature || base->is_func || + (effective_pointer_depth(base) != 1 && + !(base->array_size && !effective_pointer_depth(base))) || + !is_record_type(record_type)) + error_at("Cannot apply arrow operator to non-record pointer", + cur_token_loc()); + if (effective_pointer_depth(base) != 1) + record_type = base->type; + address = base; + lower_member_postfix(address, record_type, true, true, parent, bb); + return; + } + + if (!base->type || !is_record_type(base->type) || + effective_pointer_depth(base) || base->array_size) + error_at("Cannot apply dot operator to non-record", cur_token_loc()); + record_type = base->type; + if (base->is_compound_literal_reference || base->defers_record_copy) { + /* A record loaded from an object: select from the object itself. It is + * no lvalue when the record was not, as a call result's member is. The + * address points to a const record when the record is const. + */ + is_lvalue = base->is_compound_literal_reference; + address = base->compound_literal_address; + if (base->is_const_qualified) + address->is_const_qualified = true; + } else { + /* A named record or a compound literal is an lvalue; a temporary, such + * as a call or assignment result, is not. + */ + is_lvalue = base->is_compound_literal || is_named_object(base, parent); + address = require_ref_var(parent, base->type, 0); + address->var_name = gen_name(); + address->is_const_qualified = base->is_const_qualified; + add_insn(parent, *bb, OP_address_of, address, base, NULL, 0, NULL); + } + lower_member_postfix(address, record_type, false, is_lvalue, parent, bb); +} + +/* The postfix operators C99 6.5.2 lets follow any postfix expression, applied + * to the value on top of the operand stack: a parenthesized expression or a + * call result, neither of which has a declaration for read_lvalue() to follow. + * A value loaded from an object records its address, so subscripts, member + * selections and updates reach that object. + */ +void lower_postfix_operators(block_t *parent, basic_block_t **bb) +{ + for (;;) { + var_t *top = operand_stack[operand_stack_idx - 1]; + + if (lex_peek(T_open_square, NULL)) { + if (top->pointee_array_size) + lower_call_result_array_postfix(&top, parent, bb); + else + lower_value_subscript(parent, bb); + } else if (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { + lower_value_member(parent, bb); + } else if (lex_peek(T_open_bracket, NULL)) { + /* Function calls are postfix expressions, so a parenthesized + * function designator remains callable: `(fn)(...)` and + * `(*fp)(...)` have the same meaning as `fn(...)` and `fp(...)`. + */ + func_t *signature = get_func_signature(top); + + if (signature) { + if (!top->ptr_level) { + opstack_pop(); + opstack_push(load_function_pointer_object(parent, bb, top)); + } + emit_indirect_call_result(top, signature, true, parent, bb); + } else if (top->is_func) { + signature = find_func(top->var_name); + if (!signature) + error_at("Called object is not a function", + cur_token_loc()); + opstack_pop(); + emit_direct_call_result(signature, true, parent, bb); + } else { + error_at("Called object is not a function pointer", + cur_token_loc()); + } + } else + break; + } + + if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + var_t *object = opstack_pop(); + opcode_t op = OP_sub; + + if (lex_accept(T_increment)) + op = OP_add; + else + lex_expect(T_decrement); + if (!object->is_compound_literal_reference && + is_named_object(object, parent)) { + /* A grouped named object, `(count)++`, updates the variable. */ + var_t *old = + require_typed_ptr_var(parent, object->type, object->ptr_level); + var_t *one = require_typed_var(parent, TY_int); + var_t *updated; + + if (object->array_size) + error_at("assignment to expression with array type", + cur_token_loc()); + if (object->is_const_qualified || is_record_object(object) || + object->is_func) + error_at( + "Increment or decrement requires a scalar modifiable " + "lvalue", + cur_token_loc()); + old->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, old, object, NULL, 0, NULL); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + if (is_pointer_operation(op, object, one)) { + handle_pointer_arithmetic(parent, bb, op, object, one); + updated = opstack_pop(); + } else { + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, object, one); + add_insn(parent, *bb, op, updated, object, one, 0, NULL); + } + updated = resize_var(parent, bb, updated, object); + mark_var_mutated(object); + add_insn(parent, *bb, OP_assign, object, updated, NULL, 0, NULL); + opstack_push(old); + return; + } + lower_reference_update(object, op, parent, bb); + + /* The expression has the old value and is not itself an lvalue. */ + object->is_compound_literal_reference = false; + opstack_push(object); + } +} + +/* A parenthesized expression, possibly a comma expression, and the postfix + * operators applied to its value. + */ +static void read_grouped_operand(block_t *parent, basic_block_t **bb) +{ + /* Regular parenthesized expression */ + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + while (lex_accept(T_comma)) { + /* A comma inside an explicit parenthesized expression is the C99 + * sequencing operator, not an argument or initializer delimiter. + * Discard the completed left value after its IR is emitted; the final + * expression supplies the result, which is not an lvalue. + */ + discard_operand(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + operand_stack[operand_stack_idx - 1]->is_compound_literal_reference = + false; + } + lex_expect(T_close_bracket); + lower_postfix_operators(parent, bb); +} + +/* Subscripts, and the updates or stores that follow them, applied directly to + * an array compound literal. + */ +static void lower_array_literal_subscripts(block_t *parent, + basic_block_t **bb, + paren_type_name_t *tn, + var_t *compound_var) +{ + var_t *base = opstack_pop(); + var_t *address = base; + var_t *element; + int element_size = compound_var->type->size; + int subscript_depth = 0; + + while (lex_accept(T_open_square)) { + var_t *index; + int stride = element_size; + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (subscript_depth == 0 && compound_var->array_dim2) { + stride *= compound_var->array_dim2; + if (compound_var->array_dim3) + stride *= compound_var->array_dim3; + if (compound_var->array_dim4) + stride *= compound_var->array_dim4; + } else if (subscript_depth == 1 && compound_var->array_dim3) { + stride *= compound_var->array_dim3; + if (compound_var->array_dim4) + stride *= compound_var->array_dim4; + } else if (subscript_depth == 2 && compound_var->array_dim4) { + stride *= compound_var->array_dim4; + } + if (stride != 1) { + var_t *scale = require_var(parent); + scale->var_name = gen_name(); + scale->init_val = stride; + add_insn(parent, *bb, OP_load_constant, scale, NULL, NULL, 0, NULL); + var_t *scaled = require_var(parent); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, index, scale, 0, NULL); + index = scaled; + } + var_t *indexed = require_typed_ptr_var(parent, compound_var->type, 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, 0, NULL); + address = indexed; + subscript_depth++; + } + + if (subscript_depth < tn->array_dims) { + /* A partially selected row decays to its first element. It remains a + * pointer value, not a scalar read, and keeps the bounds the subscripts + * left for sizeof and for its own later subscripts. + */ + opstack_push(array_subscript_remainder( + parent, bb, compound_var->type, compound_var->ptr_level, + fixed_array_shape_from_var(compound_var), subscript_depth, + address)); + } else { + opcode_t compound_op = OP_generic; + if (lex_accept(T_assign) || accept_compound_assign_op(&compound_op)) { + if (compound_var->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + var_t *value; + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + value = opstack_pop(); + if (compound_op != OP_generic) { + var_t *current = require_typed_var(parent, compound_var->type); + current->var_name = gen_name(); + add_insn(parent, *bb, OP_read, current, address, NULL, + element_size, NULL); + /* A pointer object steps by its element size. */ + if (is_pointer_operation(compound_op, current, value)) { + handle_pointer_arithmetic(parent, bb, compound_op, current, + value); + value = opstack_pop(); + } else { + current = integer_promote_operand(parent, bb, current); + value = integer_promote_operand(parent, bb, value); + normalize_integer_binary_operands(parent, bb, compound_op, + ¤t, &value); + var_t *combined = require_var(parent); + combined->var_name = gen_name(); + combined->type = + integer_binary_result_type(compound_op, current, value); + add_insn(parent, *bb, compound_op, combined, current, value, + 0, NULL); + value = combined; + } + } + value = convert_stored_value(parent, bb, value, compound_var->type, + compound_var->ptr_level); + add_insn(parent, *bb, OP_write, NULL, address, value, element_size, + NULL); + } + if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + opcode_t op = OP_sub; + var_t *one; + var_t *updated; + + if (lex_accept(T_increment)) + op = OP_add; + else + lex_expect(T_decrement); + + if (compound_var->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + element = require_typed_var(parent, compound_var->type); + element->var_name = gen_name(); + add_insn(parent, *bb, OP_read, element, address, NULL, element_size, + NULL); + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, element, one); + add_insn(parent, *bb, op, updated, element, one, 0, NULL); + updated = resize_var(parent, bb, updated, element); + add_insn(parent, *bb, OP_write, NULL, address, updated, + element_size, NULL); + opstack_push(element); + } else { + element = require_typed_var(parent, compound_var->type); + element->var_name = gen_name(); + add_insn(parent, *bb, OP_read, element, address, NULL, element_size, + NULL); + element->is_compound_literal_reference = true; + element->is_const_qualified = compound_var->is_const_qualified; + element->compound_literal_address = address; + opstack_push(element); + } + } +} + +/* Member selections on a record compound literal, with the subscripts, updates + * and stores that may follow the selected member. + */ +static void lower_record_literal_members(block_t *parent, + basic_block_t **bb, + paren_type_name_t *tn, + var_t *compound_object) +{ + char field_name[MAX_ID_LEN]; + var_t *base = opstack_pop(); + var_t *base_addr = require_ref_var(parent, base->type, 0); + var_t *field; + var_t *address; + var_t *element; + opcode_t compound_op = OP_generic; + type_t *value_type; + int value_ptr_level; + int value_size; + type_t *record_type = tn->type; + + base_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, base_addr, base, NULL, 0, NULL); + address = base_addr; + while (1) { + lex_ident(T_identifier, field_name); + field = find_member(field_name, record_type); + if (!field) + error_at("Unknown record member", cur_token_loc()); + address = compute_field_address(parent, bb, address, field); + + /* The member-chain delimiter is already a token stream property. + * Advance it directly rather than depending on a boolean helper result + * after the current field's IR lowering; this keeps the syntactic chain + * intact through self-hosted target builds as well. + */ + if (!cur_token->next || cur_token->next->kind != T_dot) + break; + lex_next(); + if (!is_record_type(field->type) || field->ptr_level || + field->array_size) + error_at("Member access requires a record", cur_token_loc()); + record_type = field->type; + } + value_type = field->type; + value_ptr_level = field->ptr_level; + value_size = field->type->size; + + /* Keep array-member subscripts as lvalues. The field address starts at + * element zero; each index is scaled by the complete extent of the + * remaining dimensions, just as ordinary array postfix parsing does. + */ + int subscript_depth = 0; + while (field->array_size && lex_accept(T_open_square)) { + var_t *index; + int stride = value_size; + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (subscript_depth == 0 && field->array_dim2 > 0) { + stride *= field->array_dim2; + if (field->array_dim3 > 0) + stride *= field->array_dim3; + if (field->array_dim4 > 0) + stride *= field->array_dim4; + } else if (subscript_depth == 1 && field->array_dim3 > 0) { + stride *= field->array_dim3; + if (field->array_dim4 > 0) + stride *= field->array_dim4; + } else if (subscript_depth == 2 && field->array_dim4 > 0) { + stride *= field->array_dim4; + } + if (stride != 1) { + var_t *scale = require_var(parent); + scale->var_name = gen_name(); + scale->init_val = stride; + add_insn(parent, *bb, OP_load_constant, scale, NULL, NULL, 0, NULL); + var_t *scaled = require_var(parent); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, index, scale, 0, NULL); + index = scaled; + } + var_t *indexed = require_typed_ptr_var(parent, value_type, 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, 0, NULL); + address = indexed; + value_ptr_level = 0; + subscript_depth++; + } + + if (lex_accept(T_assign) || accept_compound_assign_op(&compound_op)) { + if (compound_object->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + var_t *value; + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + value = opstack_pop(); + if (compound_op != OP_generic) { + var_t *current; + if (is_bitfield(field)) + current = read_bitfield_value(parent, bb, address, field); + else { + current = require_typed_var(parent, value_type); + current->ptr_level = value_ptr_level; + current->var_name = gen_name(); + add_insn(parent, *bb, OP_read, current, address, NULL, + value_size, NULL); + } + /* A pointer object steps by its element size. */ + if (is_pointer_operation(compound_op, current, value)) { + handle_pointer_arithmetic(parent, bb, compound_op, current, + value); + value = opstack_pop(); + } else { + current = integer_promote_operand(parent, bb, current); + value = integer_promote_operand(parent, bb, value); + normalize_integer_binary_operands(parent, bb, compound_op, + ¤t, &value); + var_t *combined = require_var(parent); + combined->var_name = gen_name(); + combined->type = + integer_binary_result_type(compound_op, current, value); + add_insn(parent, *bb, compound_op, combined, current, value, 0, + NULL); + value = combined; + } + } + if (is_bitfield(field)) + write_bitfield_value(parent, bb, address, value, field); + else { + value = convert_stored_value(parent, bb, value, value_type, + value_ptr_level); + add_insn(parent, *bb, OP_write, NULL, address, value, value_size, + NULL); + } + } + + if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + opcode_t op = lex_accept(T_increment) ? OP_add : OP_sub; + var_t *old; + var_t *one; + var_t *updated; + + if (compound_object->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + if (is_bitfield(field)) + old = read_bitfield_value(parent, bb, address, field); + else { + old = require_typed_var(parent, value_type); + old->ptr_level = value_ptr_level; + old->var_name = gen_name(); + add_insn(parent, *bb, OP_read, old, address, NULL, value_size, + NULL); + } + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(op, old, one); + add_insn(parent, *bb, op, updated, old, one, 0, NULL); + updated = resize_var(parent, bb, updated, old); + if (is_bitfield(field)) + write_bitfield_value(parent, bb, address, updated, field); + else + add_insn(parent, *bb, OP_write, NULL, address, updated, value_size, + NULL); + element = old; + } else if (is_bitfield(field)) { + element = read_bitfield_value(parent, bb, address, field); + element->is_bitfield = false; + } else { + element = require_typed_var(parent, value_type); + element->ptr_level = value_ptr_level; + element->var_name = gen_name(); + add_insn(parent, *bb, OP_read, element, address, NULL, value_size, + NULL); + } + + /* A scalar member of a compound literal remains a modifiable lvalue. Retain + * its storage location for a surrounding prefix ++/--, including bit-fields + * which need the masked write path. Partially selected array rows decay + * instead. + */ + int field_dims = field->array_size ? 1 : 0; + if (field->array_dim2) + field_dims++; + if (field->array_dim3) + field_dims++; + if (field->array_dim4) + field_dims++; + if (!field->array_size || subscript_depth >= field_dims) { + element->is_compound_literal_reference = true; + element->is_const_qualified = compound_object->is_const_qualified; + element->compound_literal_address = address; + if (is_bitfield(field)) + element->compound_literal_bitfield = field; + } + opstack_push(element); +} + +/* A compound literal whose type name read_parenthesized_operand() has just + * parsed: materialize the object, lower its initializer, and apply postfix + * operators to it. + */ +static void read_compound_literal_operand(block_t *parent, + basic_block_t **bb, + paren_type_name_t *tn) +{ + var_t *compound_object = NULL; + + /* Create variable for compound literal result */ + var_t *compound_var = require_typed_var(parent, tn->type); + compound_var->var_name = gen_name(); + compound_var->is_compound_literal = true; + compound_var->is_const_qualified = tn->const_qualified; + compound_var->is_const_pointer = tn->const_pointer; + compound_var->pointer_const_mask = tn->pointer_const_mask; + + /* `(callback_t){f}` is a function pointer compound literal as well. */ + if (!tn->func_signature && !tn->ptr_level && tn->type->func_signature && + !tn->type->is_direct_function_type && !tn->type->array_size) { + tn->func_signature = tn->type->func_signature; + tn->func_pointer_level = 1; + } + + /* Check if this is an array compound literal (int[]){...} */ + bool is_array_literal = (tn->ptr_level == -1); + if (is_array_literal) + tn->ptr_level = 0; /* Reset for normal processing */ + bool consumed_close_brace = false; + + /* parse_array_init() consumes its own opening brace. The older + * one-dimensional literal helper expects it already consumed. + */ + if (!is_array_literal || + (tn->array_dims <= 1 && !tn->array_element_ptr_level)) + lex_expect(T_open_curly); + if (!is_array_literal || + (tn->array_dims <= 1 && !tn->array_element_ptr_level)) + reject_empty_initializer_in_strict_c99(); + /* Check if this is a pointer compound literal */ + if (is_array_literal) { + compound_var->array_size = tn->array_size; + compound_var->array_dim2 = tn->array_dim2; + compound_var->array_dim3 = tn->array_dim3; + compound_var->array_dim4 = tn->array_dim4; + compound_var->has_unsized_array = tn->array_outer_unsized; + compound_var->ptr_level = tn->array_element_ptr_level; + compound_var->pointee_array_size = tn->pointee_array_size; + compound_var->pointee_array_dim2 = tn->pointee_array_dim2; + compound_var->pointee_array_dim3 = tn->pointee_array_dim3; + compound_var->pointee_array_dim4 = tn->pointee_array_dim4; + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); + if (tn->array_dims > 1 || tn->array_element_ptr_level) + parse_array_init(compound_var, parent, bb); + else + parse_array_compound_literal(compound_var, parent, bb); + + if (compound_var->array_size == 0) { + compound_var->init_val = 0; + add_insn(parent, *bb, OP_load_constant, compound_var, NULL, NULL, 0, + NULL); + } + opstack_push(compound_var); + consumed_close_brace = true; + } else if (tn->func_signature) { + /* `(int (*)(int)){f}` is an unnamed object of a function pointer type, + * represented as a declared callback, or with two stars a declared + * callback slot, of that type would be. + */ + var_t *initializer; + + if (tn->func_pointer_level == 1) { + compound_var->ptr_level = + tn->type->func_signature + ? 0 + : tn->func_signature->return_def.ptr_level; + compound_var->is_func = true; + compound_var->func_signature = tn->func_signature; + compound_var->parenthesized_function_pointer_level = 1; + } else { + compound_var->ptr_level = tn->func_signature->return_def.ptr_level + + tn->func_pointer_level - 1; + compound_var->pointee_func_signature = tn->func_signature; + } + if (lex_peek(T_close_curly, NULL)) { + initializer = require_var(parent); + initializer->var_name = gen_name(); + initializer->is_const = true; + add_insn(parent, *bb, OP_load_constant, initializer, NULL, NULL, 0, + NULL); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + initializer = opstack_pop(); + if (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in scalar compound literal", + cur_token_loc()); + } + diagnose_function_pointer_conversion(initializer, compound_var); + if (incompatible_pointee_callback_conversion(initializer, compound_var)) + error_at("incompatible callback slot types in compound literal", + cur_token_loc()); + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); + emit_object_assignment(parent, bb, compound_var, initializer); + compound_object = compound_var; + opstack_push(compound_var); + } else if (tn->ptr_level > 0) { + /* Pointer compound literal: (int*){&x} */ + var_t *initializer; + + compound_var->ptr_level = tn->ptr_level; + + /* Materialize the pointer object from the expression value, rather than + * treating a local address as an init_val constant. Compound literals + * have automatic storage and must retain a usable pointer value for + * later updates. + */ + if (!lex_peek(T_close_curly, NULL)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + initializer = opstack_pop(); + + if (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in scalar compound literal", + cur_token_loc()); + diagnose_integer_to_pointer_conversion(initializer, compound_var, + false); + } else { + /* Empty pointer compound literal: (int*){} */ + initializer = require_typed_var(parent, tn->type); + initializer->ptr_level = tn->ptr_level; + initializer->var_name = gen_name(); + initializer->init_val = 0; + add_insn(parent, *bb, OP_load_constant, initializer, NULL, NULL, 0, + NULL); + } + + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); + emit_object_assignment(parent, bb, compound_var, initializer); + opstack_push(compound_var); + + /* Like scalar integer compound literals, a pointer compound literal is + * a block-lived modifiable object. Keep it as the assignment target for + * an immediately following `=`. + */ + compound_object = compound_var; + } else if (is_record_type(tn->type)) { + /* Record compound literals use the same aggregate initializer path for + * structs, unions, and their typedef aliases. + */ + type_t *struct_type = tn->type; + if (struct_type->base_type == TYPE_typedef && struct_type->base_struct) + struct_type = struct_type->base_struct; + + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); + var_t *compound_addr = require_ref_var(parent, compound_var->type, 0); + compound_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, compound_addr, compound_var, NULL, + 0, NULL); + parse_struct_field_init(parent, bb, struct_type, compound_addr); + compound_object = compound_var; + opstack_push(compound_var); + } else if (tn->type->base_type == TYPE_int || + tn->type->base_type == TYPE_short || + tn->type->base_type == TYPE_char || + tn->type->base_type == TYPE_long || + tn->type->base_type == TYPE_long_long) { + /* Handle empty compound literals */ + if (lex_peek(T_close_curly, NULL)) { + /* Empty compound literal: (int){} */ + compound_var->init_val = 0; + compound_var->array_size = 0; + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, + NULL); + var_t *zero = require_typed_var(parent, tn->type); + zero->var_name = gen_name(); + zero->init_val = 0; + add_insn(parent, *bb, OP_load_constant, zero, NULL, NULL, 0, NULL); + emit_object_assignment(parent, bb, compound_var, zero); + opstack_push(compound_var); + compound_object = compound_var; + } else { + /* Parse first element, any assignment expression */ + read_expr(parent, bb); + read_ternary_operation(parent, bb); + + /* A scalar compound literal has one initializer; a trailing comma + * is permitted, but a second value is a C99 constraint violation. + */ + if (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in scalar compound literal", + cur_token_loc()); + + /* Retained for the parser's array-literal extension; scalar + * spellings above have already rejected a second initializer. + */ + if (lex_peek(T_comma, NULL)) { + /* Array compound literal: (int[]){1, 2, 3} */ + var_t *first_element = opstack_pop(); + + /* Store elements temporarily */ + var_t *elements[256]; + elements[0] = first_element; + int element_count = 1; + + /* Parse remaining elements */ + while (lex_accept(T_comma)) { + if (lex_peek(T_close_curly, NULL)) + break; /* Trailing comma */ + + read_expr(parent, bb); + read_ternary_operation(parent, bb); + if (element_count < 256) { + elements[element_count] = opstack_pop(); + } else { + opstack_pop(); /* Discard if too many */ + } + element_count++; + } + + /* Set array metadata */ + compound_var->array_size = element_count; + compound_var->init_val = first_element->init_val; + + /* Allocate space for the array on stack */ + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, + NULL); + + /* Initialize each element */ + for (int i = 0; i < element_count && i < 256; i++) { + if (!elements[i]) + continue; + + /* Store element at offset i * sizeof(element) */ + var_t *elem_offset = require_var(parent); + elem_offset->init_val = i * tn->type->size; + elem_offset->var_name = gen_name(); + add_insn(parent, *bb, OP_load_constant, elem_offset, NULL, + NULL, 0, NULL); + + /* Calculate address of element */ + var_t *elem_addr = require_var(parent); + elem_addr->ptr_level = 1; + elem_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_add, elem_addr, compound_var, + elem_offset, 0, NULL); + + /* Store the element value */ + add_insn(parent, *bb, OP_write, NULL, elem_addr, + elements[i], tn->type->size, NULL); + } + + /* Store first element value for array-to-scalar */ + compound_var->init_val = first_element->init_val; + + /* Create result that provides first element access. This + * enables array compound literals in scalar contexts: int x = + * (int[]){1,2,3}; // x gets 1 int y = 5 + (int[]){10}; // adds + * 5 + 10 + */ + var_t *result_var = require_var(parent); + result_var->var_name = gen_name(); + result_var->type = compound_var->type; + result_var->ptr_level = 0; + result_var->array_size = 0; + + /* Read first element from the array */ + add_insn(parent, *bb, OP_read, result_var, compound_var, NULL, + compound_var->type->size, NULL); + opstack_push(result_var); + } else { + /* Single value: (int){42} - scalar compound literal */ + var_t *initializer = opstack_pop(); + diagnose_integer_to_pointer_conversion(initializer, + compound_var, false); + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, + NULL); + emit_object_assignment(parent, bb, compound_var, initializer); + compound_object = compound_var; + opstack_push(compound_var); + } + } + } + + if (!consumed_close_brace) + lex_expect(T_close_curly); + + /* Array compound literals are addressable automatic arrays. Carry every + * direct postfix subscript through the flattened backing object, scaling + * each one by the extent of the dimensions still to its right. This keeps + * `(int[2][3]){...}[1][2]` an lvalue just like an ordinary multidimensional + * array access. + */ + if (is_array_literal && lex_peek(T_open_square, NULL)) { + lower_array_literal_subscripts(parent, bb, tn, compound_var); + } + + /* A record compound literal is likewise an automatic object, so preserve a + * direct member postfix as an addressable lvalue. + */ + if (compound_object && is_record_type(tn->type) && lex_accept(T_dot)) { + lower_record_literal_members(parent, bb, tn, compound_object); + } + + /* A function pointer compound literal may be called directly. */ + if (tn->func_signature && tn->func_pointer_level == 1 && + lex_peek(T_open_bracket, NULL)) + lower_postfix_operators(parent, bb); + + /* A non-const scalar or record compound literal is a block-lived object in + * C99, hence a modifiable lvalue. The ordinary parser previously collapsed + * scalar literals to their initializer value, losing that property before + * an immediately following assignment could be recognized. + */ + if (compound_object && !is_record_type(tn->type) && + (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL))) { + opcode_t op = lex_accept(T_increment) ? OP_add : OP_sub; + var_t *old; + var_t *one; + var_t *updated; + + if (compound_object->is_const_qualified || + compound_object->is_const_pointer) + error_at("assignment of read-only location", cur_token_loc()); + opstack_pop(); + old = require_typed_var(parent, compound_object->type); + old->ptr_level = compound_object->ptr_level; + old->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, old, compound_object, NULL, 0, NULL); + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + if (is_pointer_operation(op, compound_object, one)) { + handle_pointer_arithmetic(parent, bb, op, compound_object, one); + updated = opstack_pop(); + } else { + updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = + integer_binary_result_type(op, compound_object, one); + add_insn(parent, *bb, op, updated, compound_object, one, 0, NULL); + } + updated = resize_var(parent, bb, updated, compound_object); + add_insn(parent, *bb, OP_assign, compound_object, updated, NULL, 0, + NULL); + opstack_push(old); + } + opcode_t scalar_compound_op = OP_generic; + if (compound_object && (lex_accept(T_assign) || + accept_compound_assign_op(&scalar_compound_op))) { + if (compound_object->is_const_qualified || + compound_object->is_const_pointer) + error_at("assignment of read-only location", cur_token_loc()); + opstack_pop(); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + var_t *value = opstack_pop(); + if (scalar_compound_op != OP_generic) { + var_t *current = compound_object; + + if (is_pointer_operation(scalar_compound_op, current, value)) { + handle_pointer_arithmetic(parent, bb, scalar_compound_op, + current, value); + value = opstack_pop(); + } else { + current = integer_promote_operand(parent, bb, current); + value = integer_promote_operand(parent, bb, value); + normalize_integer_binary_operands( + parent, bb, scalar_compound_op, ¤t, &value); + var_t *combined = require_var(parent); + combined->var_name = gen_name(); + combined->type = integer_binary_result_type(scalar_compound_op, + current, value); + add_insn(parent, *bb, scalar_compound_op, combined, current, + value, 0, NULL); + value = combined; + } + } + emit_object_assignment(parent, bb, compound_object, value); + opstack_push(compound_object); + } +} + +/* Store in result, whose integer type is the target of a cast, the payload of + * the integer constant lo:hi converted to that type. + * + * Mirror the scalar cast's stored representation instead of merely copying its + * source payload: `(unsigned char)256`, `(short)65536`, and a 64-to-32 cast all + * become the integer constant zero and may therefore serve as null pointers. + */ +void fold_integer_constant_cast(var_t *result, unsigned int lo, unsigned int hi) +{ + if (result->type->is_bool) { + /* `_Bool` conversion is boolean, not truncation: any nonzero source + * becomes one, including a high 64-bit word that a 32-bit truncation + * would otherwise lose. + */ + result->init_val = lo || hi; + result->init_val_hi = 0; + } else if (result->type->size < TY_int->size) { + unsigned int bits = result->type->size * 8; + unsigned int mask = (1U << bits) - 1; + + lo &= mask; + if (!result->type->is_unsigned && (lo & (1U << (bits - 1)))) + lo |= ~mask; + result->init_val = (int) lo; + result->init_val_hi = result->init_val < 0 ? -1 : 0; + } else if (result->type->size == TY_int->size) { + result->init_val = (int) lo; + result->init_val_hi = + result->type->is_unsigned ? 0 : (result->init_val < 0 ? -1 : 0); + } else { + result->init_val = (int) lo; + result->init_val_hi = (int) hi; + } +} + +/* A cast whose type name read_parenthesized_operand() has just parsed: read the + * operand and convert it. + */ +static void read_cast_operand(block_t *parent, + basic_block_t **bb, + paren_type_name_t *tn) +{ + /* Process cast: (type)expr Parse the expression to be cast */ + read_expr_operand(parent, bb); + + /* Get the expression result */ + var_t *expr_var = opstack_pop(); + + /* Apart from a cast to void, a cast converts a scalar to a scalar (C99 + * 6.5.4p2). + */ + if (tn->type->base_type != TYPE_void || tn->ptr_level) { + reject_record_operand(expr_var); + if (!tn->ptr_level && !tn->type->ptr_level && is_record_type(tn->type)) + error_at("Cast requires a scalar type", cur_token_loc()); + } + + /* A cast of a function designator is still a pointer value. Raw symbols + * have no local defining IR, so materialize the code address before OP_cast + * treats it as an ordinary operand. + */ + if (tn->type->func_signature || tn->func_signature) + expr_var = materialize_function_designator(parent, bb, expr_var); + + /* Create variable for cast result */ + var_t *cast_var = require_typed_ptr_var(parent, tn->type, tn->ptr_level); + cast_var->var_name = gen_name(); + cast_var->is_const_qualified = tn->const_qualified; + cast_var->is_const_pointer = tn->const_pointer; + cast_var->pointer_const_mask = tn->pointer_const_mask; + cast_var->is_volatile = tn->volatile_qualified; + + /* A callback typedef names a function pointer, and a function typedef takes + * one star to name one; a further star, as in `(callback_t *)`, points to a + * pointer object and stays an object pointer. + */ + if (tn->type->func_signature && + tn->ptr_level == (tn->type->is_direct_function_type ? 1 : 0)) { + cast_var->ptr_level = 1; + cast_var->func_signature = tn->type->func_signature; + } + + /* A spelled function pointer type takes the representation of a declarator + * of that type: `int (*)(int)` a callback value, `int (**)(int)` a pointer + * to a callback slot, and each further star one more pointer. + */ + if (tn->func_signature && tn->func_pointer_level == 1) { + cast_var->ptr_level = 1; + cast_var->func_signature = tn->func_signature; + } else if (tn->func_signature) { + cast_var->ptr_level = tn->func_signature->return_def.ptr_level + + tn->func_pointer_level - 1; + cast_var->pointee_func_signature = tn->func_signature; + } + + /* A cast of an integer constant expression remains an integer constant + * expression. Preserve that payload for consumers such as the + * null-pointer-constant constraint of `?:`; pointer casts deliberately do + * not receive this integer classification. + */ + if (!tn->ptr_level && expr_var->is_const && + !is_pointer_like_value(expr_var) && !expr_var->is_func && + cast_var->type->base_type != TYPE_void) { + cast_var->is_const = true; + fold_integer_constant_cast(cast_var, (unsigned int) expr_var->init_val, + (unsigned int) expr_var->init_val_hi); + } + + /* An explicit C cast is permitted to remove qualifiers, but it is almost + * always a bug. Keep compiling it while making that loss visible, unlike + * implicit pointer assignments which are rejected. + */ + if (incompatible_const_pointer_conversion(expr_var, cast_var)) + printf("Warning: discarding const qualifier in cast\n"); + cast_var->is_void_null_pointer = tn->ptr_level == 1 && + tn->type == TY_void && + is_null_pointer_constant(expr_var); + + /* Conversion to _Bool compares against zero (C99 6.3.1.2) rather than + * truncating: (_Bool) 0x100 is 1, as is a nonzero high word or pointer. + */ + if (is_bool_scalar(cast_var->type, cast_var->ptr_level)) { + var_t *converted = normalize_bool(parent, bb, expr_var); + + if (converted != expr_var) + converted->type = cast_var->type; + opstack_push(converted); + return; + } + + /* A cast to void only discards its operand, so a volatile object gets the + * read an expression statement would give it, and no conversion. The + * zero-width truncation a void cast otherwise emits is dead code, but one + * of a volatile object would be kept, and no backend has such a thing. + */ + if (cast_var->type->base_type == TYPE_void && !cast_var->ptr_level && + var_is_volatile_object(expr_var)) { + opstack_push(expr_var); + discard_operand(parent, *bb); + opstack_push(cast_var); + return; + } + + /* Only a cast to void takes a record, and it discards the value. */ + if (expr_var->defers_record_copy) { + opstack_push(cast_var); + return; + } + + /* Generate cast IR. A cast down to a narrower type has to discard the high + * bits: OP_cast is only a move, so "(char) 300" kept the whole 300 and + * compared unequal to 44, even though assigning the same value to a char + * produced 44. get_size() decides that the same way the rest of the parser + * does, including for pointers, arrays and typedefs. + * + * Widening an integer to long long is, for the same reason, the extension + * an assignment performs. A move kept whatever a 32-bit instruction left in + * the upper half, so a negative int product cast to long long came out + * positive on AArch64, and an unsigned char came out sign-extended. + * + * An array operand is its address, whatever the width of its elements: + * sized by a record element wider than a pointer, `(char *) records` was + * lowered as a truncation of the array. + */ + opcode_t cast_op = OP_cast; + int cast_size = get_size(cast_var); + int expr_size = expr_var->array_size ? PTR_SIZE : get_size(expr_var); + if (cast_size < expr_size) + cast_op = OP_trunc; + else if (cast_size > TY_int->size && expr_size <= TY_int->size && + !cast_var->ptr_level && !is_pointer_like_value(expr_var) && + !expr_var->is_func && !is_record_type(expr_var->type) && + !is_record_type(cast_var->type)) { + cast_op = OP_sign_ext; + cast_size |= expr_size << 16; + } + add_insn(parent, *bb, cast_op, cast_var, expr_var, NULL, cast_size, NULL); + + /* Push the cast result */ + opstack_push(cast_var); +} + +/* The operand after an opening parenthesis: a cast, a compound literal, or a + * parenthesized expression, together with any postfix operators applied to the + * result. + */ +static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) +{ + /* Check if this is a cast, compound literal, or parenthesized expression */ + char lookahead_token[MAX_ID_LEN]; + bool is_compound_literal = false; + bool is_cast = false; + paren_type_name_t tn = {0}; + + if (floating_type_starts_here()) + error_at("Floating point types are not yet supported", cur_token_loc()); + + /* Look ahead to see if we have a type name followed by ")". The shared + * specifier reader accepts the scalar words and qualifiers in any order; + * otherwise the name is a record, an enum or a typedef visible here, which + * a variable of the same name hides. + */ + token_t *saved_token = cur_token; + bool has_const_type = false; + bool has_volatile_type = false; + type_t *type = + read_scalar_type_specifiers(&has_const_type, &has_volatile_type, NULL); + + if (!type) { + base_type_t record_kind = accept_record_keyword(); + + if (record_kind) { + lex_ident(T_identifier, lookahead_token); + type = find_record_tag(lookahead_token, parent, record_kind); + } else if (lex_accept(T_enum)) { + lex_ident(T_identifier, lookahead_token); + type = reference_enum_tag(lookahead_token, parent); + } else if (lex_peek(T_identifier, lookahead_token)) { + type = find_visible_type(lookahead_token, parent); + if (type) + lex_expect(T_identifier); + } + } + tn.volatile_qualified = has_volatile_type; + + if (type) { + if (type->is_floating) + error_at("Floating point types are not yet supported", + cur_token_loc()); + + /* A qualifier may also follow a record, enum or typedef name; restrict + * qualifies a pointer typedef. + */ + while (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL) || + lex_peek(T_restrict, NULL)) { + if (lex_accept(T_const)) + has_const_type = true; + else if (lex_accept(T_volatile)) + tn.volatile_qualified = true; + else { + lex_expect(T_restrict); + if (!type->ptr_level) + error_at("restrict requires a pointer type", + cur_token_loc()); + } + } + + /* Check for pointer types: int*, char*, etc. */ + int ptr_level = 0; + while (lex_accept(T_asterisk)) { + ptr_level++; + while (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL) || + lex_peek(T_restrict, NULL)) { + if (lex_accept(T_const)) { + tn.const_pointer = true; + if (ptr_level <= 32) + tn.pointer_const_mask |= 1U << (ptr_level - 1); + } else if (lex_accept(T_volatile)) + tn.volatile_qualified = true; + else + lex_expect(T_restrict); + } + } + + bool is_array = false; + + if (abstract_function_pointer_follows()) { + tn.func_signature = read_abstract_function_pointer( + type, ptr_level, &tn.func_pointer_level); + ptr_level = 0; + } + + /* A parenthesized abstract declarator, such as `(int (*[])[2]){...}`, + * is an array whose elements are pointers to rows. Keep the outer array + * and pointee bounds separate, matching named pointer-to-array + * declarations. + */ + if (lex_accept(T_open_bracket)) { + int pointee_dims = 0; + + if (ptr_level || !lex_peek(T_asterisk, NULL)) + error_at("Array compound literal needs a pointer declarator", + cur_token_loc()); + do { + lex_expect(T_asterisk); + tn.array_element_ptr_level++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } while (lex_peek(T_asterisk, NULL)); + lex_expect(T_open_square); + if (!lex_peek(T_close_square, NULL)) { + tn.array_size = read_const_expr(parent); + if (tn.array_size <= 0) + error_at("Array compound literal needs a positive bound", + cur_token_loc()); + } else { + tn.array_outer_unsized = true; + } + lex_expect(T_close_square); + lex_expect(T_close_bracket); + while (lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + if (pointee_dims >= 4) + error_at( + "Array declarators support at most four " + "dimensions", + cur_token_loc()); + if (bound <= 0) + error_at("Array size must be positive", cur_token_loc()); + if (pointee_dims == 0) + tn.pointee_array_size = bound; + else { + if (pointee_dims == 1) + tn.pointee_array_dim2 = bound; + else if (pointee_dims == 2) + tn.pointee_array_dim3 = bound; + else + tn.pointee_array_dim4 = bound; + tn.pointee_array_size *= bound; + } + lex_expect(T_close_square); + pointee_dims++; + } + if (!pointee_dims) + error_at("Array compound literal needs a row bound", + cur_token_loc()); + is_array = true; + tn.array_dims = 1; + tn.parenthesized_array = true; + } + + /* Parse every array bound in a compound-literal type name. `var_t` + * records the complete flattened element count plus up to three + * trailing dimensions, the same representation ordinary declarators use + * for `int a[2][3]`. + */ + while (!tn.parenthesized_array && lex_accept(T_open_square)) { + int bound = 0; + + is_array = true; + if (tn.array_dims >= 4) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + if (lex_peek(T_numeric, NULL)) { + /* The constant expression reader keeps a literal too wide for + * an int from wrapping into a small bound. + */ + bound = read_const_expr(parent); + if (bound <= 0) + error_at("Array compound literal needs a positive bound", + next_token_loc()); + } + if (!bound && tn.array_dims) + error_at("Only the outer array bound may be omitted", + cur_token_loc()); + if (!tn.array_dims) + tn.array_size = bound; + else { + if (tn.array_size) + tn.array_size *= bound; + else + tn.array_size = bound; + if (tn.array_dims == 1) + tn.array_dim2 = bound; + else if (tn.array_dims == 2) + tn.array_dim3 = bound; + else + tn.array_dim4 = bound; + } + if (!tn.array_dims && !bound) + tn.array_outer_unsized = true; + tn.array_dims++; + lex_expect(T_close_square); + } + if (!is_array && type->array_size) { + is_array = true; + tn.array_size = type->array_size; + tn.array_dim2 = type->array_dim2; + tn.array_dim3 = type->array_dim3; + tn.array_dim4 = type->array_dim4; + tn.array_dims = + 1 + !!tn.array_dim2 + !!tn.array_dim3 + !!tn.array_dim4; + } + + /* Check what follows the closing ) */ + if (lex_accept(T_close_bracket)) { + if (lex_peek(T_open_curly, NULL)) { + /* (type){...} - compound literal */ + is_compound_literal = true; + tn.type = type; + tn.ptr_level = ptr_level; + tn.const_qualified = has_const_type || type->is_const_qualified; + + /* Store is_array flag in tn.ptr_level if it's an array */ + if (is_array) { + /* Special marker for array compound literal */ + tn.ptr_level = -1; + } + } else { + if (is_array) + error_at("Cast cannot specify an array type", + cur_token_loc()); + /* (type)expr - cast expression */ + is_cast = true; + tn.type = type; + tn.ptr_level = ptr_level; + tn.const_qualified = has_const_type || type->is_const_qualified; + } + } else { + /* Not a cast or compound literal - backtrack */ + cur_token = saved_token; + } + } else { + cur_token = saved_token; + } + + if (is_cast) { + read_cast_operand(parent, bb, &tn); + } else if (is_compound_literal) { + read_compound_literal_operand(parent, bb, &tn); + } else { + read_grouped_operand(parent, bb); + } +} + +/* Replace the operand on top of the stack with its negation, the unary minus of + * C99 6.5.3.3, folding a constant. + */ +static void negate_operand(block_t *parent, basic_block_t **bb) +{ + var_t *rs1 = opstack_pop(); + var_t *vd; + + reject_record_operand(rs1); + if (is_pointer_like_value(rs1) || rs1->is_func) + error_at("unary minus requires an arithmetic operand", cur_token_loc()); + rs1 = integer_promote_operand(parent, bb, rs1); + + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = rs1->type; + opstack_push(vd); + if (rs1->is_const && !rs1->ptr_level && !rs1->is_global) { + vd->is_const = true; + vd->init_val = -rs1->init_val; + vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); + + /* The high word of an int result follows its type, as for a cast: "-9U" + * is 0xfffffff7 with a zero high word. + */ + fold_integer_constant_cast(vd, vd->init_val, vd->init_val_hi); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + } else + add_insn(parent, *bb, OP_negate, vd, rs1, NULL, 0, NULL); +} + +static void read_expr_operand_body(block_t *parent, basic_block_t **bb) +{ + var_t *vd, *rs1; + bool is_neg = false; + + if (grouped_scalar_pointee_row_postfix_starts(parent)) { + handle_grouped_scalar_pointee_row_postfix(parent, bb); + return; + } + + if ((lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) && + grouped_scalar_pointee_row_prefix_starts(parent)) { + handle_grouped_scalar_pointee_row_prefix(parent, bb); + return; + } + + bool prefix_increment = lex_peek(T_increment, NULL); + bool prefix_decrement = lex_peek(T_decrement, NULL); + if ((prefix_increment || prefix_decrement) && cur_token->next->next && + (cur_token->next->next->kind == T_open_bracket || + cur_token->next->next->kind == T_asterisk || + cur_token->next->next->kind == T_numeric)) { + /* The operand is a unary expression rather than an identifier: `++*p`, + * `--*p++` or `++(*q).m`. It has recorded the address of the object it + * was loaded from, or is itself a named or compound-literal object. + */ + opcode_t op = prefix_increment ? OP_add : OP_sub; + var_t *object; + var_t *one; + + if (prefix_increment) + lex_expect(T_increment); + else + lex_expect(T_decrement); + read_expr_operand(parent, bb); + object = opstack_pop(); + if (object->is_compound_literal_reference) { + opstack_push(lower_reference_update(object, op, parent, bb)); + return; + } + if (object->array_size && is_named_object(object, parent)) + error_at("assignment to expression with array type", + cur_token_loc()); + if ((!object->is_compound_literal && + !is_named_object(object, parent)) || + object->array_size || is_record_type(object->type) || + object->is_func) + error_at("Prefix update requires a scalar modifiable lvalue", + cur_token_loc()); + if (object->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + one = require_typed_var(parent, TY_int); + one->var_name = gen_name(); + one->init_val = 1; + add_insn(parent, *bb, OP_load_constant, one, NULL, NULL, 0, NULL); + if (is_pointer_operation(op, object, one)) { + handle_pointer_arithmetic(parent, bb, op, object, one); + vd = opstack_pop(); + } else { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = integer_binary_result_type(op, object, one); + add_insn(parent, *bb, op, vd, object, one, 0, NULL); + } + vd = resize_var(parent, bb, vd, object); + mark_var_mutated(object); + add_insn(parent, *bb, OP_assign, object, vd, NULL, 0, NULL); + opstack_push(vd); + return; + } + + if (lex_accept(T_plus)) { + read_expr_operand(parent, bb); + rs1 = opstack_pop(); + reject_record_operand(rs1); + if (is_pointer_like_value(rs1) || rs1->is_func) + error_at("unary plus requires an arithmetic operand", + cur_token_loc()); + rs1 = integer_promote_operand(parent, bb, rs1); + + /* Unary plus performs the integer promotions, producing an ordinary + * arithmetic expression rather than a bit-field designator. + */ + rs1->is_bitfield = false; + opstack_push(rs1); + return; + } + + if (lex_accept(T_minus)) { + /* A numeric literal takes the sign into its value below. Any other + * operand is a whole unary expression (C99 6.5.3), another unary + * operator included: `- -x`, `-~x`, `-!x`, `-*p` and `-'a'`. + */ + if (!lex_peek(T_numeric, NULL) || + (cur_token->next->next && + cur_token->next->next->kind == T_open_square)) { + read_expr_operand(parent, bb); + negate_operand(parent, bb); + return; + } + is_neg = true; + } + + if (lex_peek(T_string, NULL) || lex_peek(T_wstring, NULL)) { + if (lex_peek(T_string, NULL)) + read_literal_param(parent, *bb); + else + read_wstring_param(parent, *bb); + + /* A string literal is an array lvalue, so it takes a subscript like any + * other array operand (C99 6.5.2.1): `"ab"[1]` is 'b'. + */ + if (lex_peek(T_open_square, NULL)) + lower_postfix_operators(parent, bb); + } else if (lex_peek(T_char, NULL)) + read_char_param(parent, *bb); + else if (lex_peek(T_wchar, NULL)) + read_wchar_param(parent, *bb); + + else if (lex_peek(T_floating, NULL)) + error_at("Floating point literals are not yet supported", + cur_token_loc()); + + else if (lex_peek(T_numeric, NULL) && cur_token->next->next && + cur_token->next->next->kind == T_open_square) { + /* E1[E2] is (*((E1)+(E2))), so the integer may come first: `2[arr]`. + * The subscript binds tighter than a unary minus, which is applied to + * the element below. + */ + read_numeric_param(parent, *bb, false); + lower_postfix_operators(parent, bb); + } else if (lex_peek(T_numeric, NULL)) { + read_numeric_param(parent, *bb, is_neg); + } else if (lex_accept(T_log_not)) { + read_expr_operand(parent, bb); + + rs1 = opstack_pop(); + reject_record_operand(rs1); + rs1 = materialize_function_designator(parent, bb, rs1); + + /* Constant folding for logical NOT */ + if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = TY_int; + vd->is_const = true; + vd->init_val = !(rs1->init_val || rs1->init_val_hi); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + } else { + vd = require_var(parent); + vd->var_name = gen_name(); + + /* C99 6.5.3.3: logical negation always yields int. Preserving the + * operand's type made !pointer inherit the pointed-to record size, + * so a later comparison attempted an invalid truncation on 32-bit + * targets. + */ + vd->type = TY_int; + vd->ptr_level = 0; + opstack_push(vd); + add_insn(parent, *bb, OP_log_not, vd, rs1, NULL, 0, NULL); + } + } else if (lex_accept(T_bit_not)) { + read_expr_operand(parent, bb); + + rs1 = opstack_pop(); + reject_record_operand(rs1); + if (is_pointer_like_value(rs1) || rs1->is_func) + error_at("bitwise complement requires an integer operand", + cur_token_loc()); + rs1 = integer_promote_operand(parent, bb, rs1); + + /* Constant folding for bitwise NOT */ + if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = rs1->type; + vd->is_const = true; + vd->init_val = ~rs1->init_val; + vd->init_val_hi = ~rs1->init_val_hi; + + /* "(unsigned long long) ~4294967294U" is 1, not 0xffffffff00000001. + */ + fold_integer_constant_cast(vd, vd->init_val, vd->init_val_hi); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + } else { + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = rs1->type; + opstack_push(vd); + add_insn(parent, *bb, OP_bit_not, vd, rs1, NULL, 0, NULL); + } + } else if (lex_accept(T_ampersand)) { + handle_address_of_operator(parent, bb); + } else if (lex_accept(T_asterisk)) { + /* dereference */ + if (lex_peek(T_asterisk, NULL)) { + handle_multiple_dereference(parent, bb); + } else { + handle_single_dereference(parent, bb); + } + } else if (lex_accept(T_open_bracket)) { + read_parenthesized_operand(parent, bb); + } else if (lex_accept(T_sizeof)) { + handle_sizeof_operator(parent, bb); + } else { + /* function call, constant or variable - read token and determine */ + opcode_t prefix_op = OP_generic; + char token[MAX_ID_LEN]; + + if (lex_accept(T_increment)) + prefix_op = OP_add; + else if (lex_accept(T_decrement)) + prefix_op = OP_sub; + + lex_peek(T_identifier, token); + + /* is a constant or variable? */ + const constant_t *con = find_scoped_constant(token, parent); + var_t *var = find_var(token, parent); + func_t *func = find_visible_func(token, parent); + + /* A block-scope function prototype shadows an automatic object, but its + * designator is the file-scope function rather than an indirect call + * through that object. + */ + if (var && var->is_extern_function_alias) + var = NULL; + + if (!strcmp(token, "__builtin_offsetof")) { + read_builtin_offsetof(parent, bb); + } else if (!strcmp(token, "__builtin_va_arg")) { + read_builtin_va_arg(parent, bb); + } else if (!strcmp(token, "__func__")) { + if (!parent->func || !parent->func->return_def.var_name[0]) + error_at("__func__ is only defined inside a function", + next_token_loc()); + lex_expect(T_identifier); + vd = require_typed_ptr_var(parent, TY_char, true); + vd->var_name = gen_name(); + vd->init_val = write_symbol(parent->func->return_def.var_name); + vd->is_string_literal = true; + opstack_push(vd); + add_insn(parent, *bb, OP_load_rodata_address, vd, NULL, NULL, 0, + NULL); + if (lex_accept(T_open_square)) { + var_t *base = opstack_pop(); + var_t *index; + var_t *address; + + read_expr(parent, bb); + read_ternary_operation(parent, bb); + index = opstack_pop(); + lex_expect(T_close_square); + address = require_typed_ptr_var(parent, TY_char, true); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); + vd = require_typed_var(parent, TY_char); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, 1, NULL); + } + } else if (con) { + /* An enumeration constant is an integer constant, so one that is + * zero is a null pointer constant (C99 6.3.2.3p3). + */ + vd = require_var(parent); + vd->init_val = con->value; + vd->is_const = true; + vd->var_name = gen_name(); + opstack_push(vd); + lex_expect(T_identifier); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + } else if (var && is_swapped_subscript_base(var)) { + /* An integer object subscripting an array: `i[arr]`. */ + lex_expect(T_identifier); + opstack_push(var); + lower_postfix_operators(parent, bb); + lower_call_result_prefix_update(&vd, prefix_op, parent, bb); + } else if (var) { + /* evalue lvalue expression */ + lvalue_t lvalue; + bool deferred_call_prefix = + prefix_op != OP_generic && cur_token->next->next && + cur_token->next->next->kind == T_open_bracket; + + read_lvalue(&lvalue, var, parent, bb, true, + deferred_call_prefix ? OP_generic : prefix_op); + + /* is it an indirect call with function pointer? */ + if (lex_peek(T_open_bracket, NULL)) { + var_t *callee = operand_stack[operand_stack_idx - 1]; + func_t *signature = get_func_signature(lvalue.decl); + + /* An indexed callback typedef has a reference to the selected + * element on the operand stack. Its declaration is the array + * (whose direct-call signature is intentionally absent), but + * the selected element's type retains the callback prototype. + */ + if (!signature && lvalue.is_reference && lvalue.type && + !lvalue.value_ptr_level) + signature = lvalue.type->func_signature; + + /* An element of a pointer to function pointers was loaded with + * the pointee's prototype. + */ + if (!signature && lvalue.is_reference && + !lvalue.value_ptr_level) + signature = callee->func_signature; + + if (!signature) + error_at("Called object is not a function pointer", + cur_token_loc()); + + /* A standalone function-pointer object is an lvalue, so the + * operand stack still holds its storage location here. Calls + * need the stored code address instead. Member pointers have + * already been read by read_lvalue() because they are + * references; only materialize the load for an object itself. + */ + if (!lvalue.is_reference && lvalue.decl->func_signature) { + var_t *object = opstack_pop(); + opstack_push( + load_function_pointer_object(parent, bb, object)); + callee = operand_stack[operand_stack_idx - 1]; + } else if (lvalue.is_reference) { + /* The element/member load already produced the pointer + * value. Carry its prototype and pointer-valued IR shape + * into indirect-call lowering. + */ + callee->func_signature = signature; + callee->ptr_level = 1; + } + vd = emit_indirect_call_result(callee, signature, true, parent, + bb); + lower_postfix_operators(parent, bb); + lower_call_result_prefix_update( + &vd, deferred_call_prefix ? prefix_op : OP_generic, parent, + bb); + } + } else if (func) { + if (parent->func && parent->func->is_inline && + !parent->func->is_static && func->is_static) + error_at( + "external inline definition references internal-linkage " + "function", + next_token_loc()); + lex_expect(T_identifier); + + if (lex_peek(T_open_bracket, NULL)) { + vd = emit_direct_call_result(func, true, parent, bb); + lower_postfix_operators(parent, bb); + lower_call_result_prefix_update(&vd, prefix_op, parent, bb); + } else { + /* indirective function pointer assignment */ + vd = require_func_symbol_var(parent); + vd->is_func = true; + vd->var_name = intern_string(token); + opstack_push(vd); + } + } else if (lex_accept(T_open_curly)) { + parse_array_literal_expr(parent, bb); + } else { + /* unknown expression */ + error_at("Unrecognized expression token", next_token_loc()); + } + } +} + +/* Nesting counter for read_expr_operand(). A unary operator, a cast or sizeof + * reads its operand by recursing here without passing through read_expr(), so + * `*&*&...x` needs a bound of its own to fail with a diagnostic rather than + * overflow the machine stack. + */ +int operand_depth = 0; + +void read_expr_operand(block_t *parent, basic_block_t **bb) +{ + operand_depth++; + if (operand_depth > MAX_EXPR_DEPTH) + error_at("Expression nesting too deep", cur_token_loc()); + read_expr_operand_body(parent, bb); + operand_depth--; +} + +void finalize_logical(opcode_t op, + block_t *parent, + basic_block_t **bb, + basic_block_t *shared_bb); + +bool is_logical(opcode_t op) +{ + return op == OP_log_and || op == OP_log_or; +} + +/* Consume a compound-assignment operator ("+=", "-=", ...) and report the + * arithmetic it applies. + * + * Returns false and consumes nothing when the next token is not one, so it can + * sit in an else-if chain beside the other statement forms. + */ +bool accept_compound_assign_op(opcode_t *op) +{ + if (lex_accept(T_pluseq)) + op[0] = OP_add; + else if (lex_accept(T_minuseq)) + op[0] = OP_sub; + else if (lex_accept(T_asteriskeq)) + op[0] = OP_mul; + else if (lex_accept(T_divideeq)) + op[0] = OP_div; + else if (lex_accept(T_modeq)) + op[0] = OP_mod; + else if (lex_accept(T_lshifteq)) + op[0] = OP_lshift; + else if (lex_accept(T_rshifteq)) + op[0] = OP_rshift; + else if (lex_accept(T_xoreq)) + op[0] = OP_bit_xor; + else if (lex_accept(T_oreq)) + op[0] = OP_bit_or; + else if (lex_accept(T_andeq)) + op[0] = OP_bit_and; + else + return false; + return true; +} + +bool lvalue_write_follows(opcode_t prefix_op) +{ + return prefix_op != OP_generic || lex_peek(T_assign, NULL) || + lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL) || + lex_peek(T_pluseq, NULL) || lex_peek(T_minuseq, NULL) || + lex_peek(T_asteriskeq, NULL) || lex_peek(T_divideeq, NULL) || + lex_peek(T_modeq, NULL) || lex_peek(T_lshifteq, NULL) || + lex_peek(T_rshifteq, NULL) || lex_peek(T_xoreq, NULL) || + lex_peek(T_oreq, NULL) || lex_peek(T_andeq, NULL); +} + +int get_pointer_element_size(var_t *ptr_var) +{ + int pointer_depth; + + if (!ptr_var || !ptr_var->type) + return PTR_SIZE; /* Default to pointer size */ + + pointer_depth = effective_pointer_depth(ptr_var); + + /* A pointer to function pointers steps over pointer objects. */ + if (ptr_var->pointee_func_signature && pointer_depth == 1 && + !ptr_var->array_size && !ptr_var->has_unsized_array) + return PTR_SIZE; + + /* An array of pointers decays to a pointer-to-pointer. The declaration + * records its element's indirection level, which may be held in a typedef. + * Account for the decay before deriving the pointed-to object size. + */ + if ((ptr_var->array_size || ptr_var->has_unsized_array) && pointer_depth) + return PTR_SIZE; + + /* Direct pointer with type info. + * + * Only a single level of indirection points at the base type. For deeper + * pointers (int **, char ***, ...) the element is itself a pointer, so the + * step is PTR_SIZE. Returning the base type size there makes "q + 1" + * advance by 4 instead of 8 on LP64 and drops a level of type information + * from the result. + */ + if (pointer_depth) { + if (pointer_depth > 1) + return PTR_SIZE; + + /* A single typedef-hidden pointer still advances by its underlying + * pointee, not by the alias object's pointer-sized representation. + */ + return pointer_typedef_pointee_size( + ptr_var->type, pointee_type_from_pointer_typedef(ptr_var->type)); + } + + /* Typedef pointer or array-derived pointer */ + switch (ptr_var->type->base_type) { + case TYPE_char: + return TY_char->size; + case TYPE_short: + return TY_short->size; + case TYPE_int: + return TY_int->size; + case TYPE_void: + return 1; + default: + break; + } + + return ptr_var->type->size ? ptr_var->type->size : PTR_SIZE; +} + +/* A direct void pointer has no complete pointed-to object type. A pointer to + * void pointer (void **) is different: its elements are pointer objects and + * therefore have a known size. + */ +bool is_direct_void_pointer(const var_t *var) +{ + if (!var || !var->type || var->type->base_type != TYPE_void) + return false; + + /* An array of void pointers decays to void ** before arithmetic. Its + * elements are complete pointer objects, even though the declared base type + * and direct declarator depth otherwise resemble void *. + */ + if (var->array_size || var->has_unsized_array) + return false; + return var->ptr_level == 1 || + (var->ptr_level == 0 && var->type->ptr_level == 1); +} + +bool is_direct_void_pointer_type(const type_t *type, int ptr_level) +{ + return type && type->base_type == TYPE_void && + (ptr_level == 1 || (ptr_level == 0 && type->ptr_level == 1)); +} + +/* Describe in @decayed the pointer to its first row that the multidimensional + * array @var decays to, as pointer arithmetic on it yields. + * + * Returns false, and leaves @decayed unset, for any other operand. + */ +static bool decay_fixed_array_rows(var_t *decayed, const var_t *var) +{ + fixed_array_shape_t shape; + + if (!is_array_declarator(var) || !var->array_dim2 || + has_effective_pointer(var) || var->is_func) + return false; + memset(decayed, 0, sizeof(var_t)); + decayed->type = var->type; + decayed->ptr_level = 1; + shape = fixed_array_shape_from_var(var); + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_pointee_var(decayed, &shape); + return true; +} + +/* The pointer to its first element that an array of function pointers @var + * decays to, or @var itself for any other operand. That pointer points to + * pointer objects, so arithmetic on it is valid. + */ +static var_t *decay_function_pointer_array(block_t *parent, + basic_block_t **bb, + var_t *var) +{ + var_t *pointer; + + if (!var || !var->is_func || !var->func_signature || var->array_dim2 || + !(var->array_size || var->has_unsized_array)) + return var; + pointer = require_typed_ptr_var(parent, var->type, 1); + pointer->var_name = gen_name(); + pointer->pointee_func_signature = var->func_signature; + add_insn(parent, *bb, OP_assign, pointer, var, NULL, 0, NULL); + return pointer; +} + +/* Helper function to handle pointer arithmetic (add/sub with scaling) */ +void handle_pointer_arithmetic(block_t *parent, + basic_block_t **bb, + opcode_t op, + var_t *rs1, + var_t *rs2) +{ + var_t *ptr_var = NULL; + var_t *int_var = NULL; + int element_size = 0; + + rs1 = decay_function_pointer_array(parent, bb, rs1); + rs2 = decay_function_pointer_array(parent, bb, rs2); + + /* Functions are not objects, so no form of C99 pointer arithmetic may use a + * function pointer. Keep this before the add/sub split below: only the + * subtraction path performs the more specific compatible-pointee check. + */ + if ((rs1 && rs1->is_func) || (rs2 && rs2->is_func)) + error_at("Pointer arithmetic requires object pointers", + cur_token_loc()); + + if (is_direct_void_pointer(rs1) || is_direct_void_pointer(rs2)) + error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); + + /* Pointer arithmetic: differences (char*, int*, struct*, etc.), + * addition/increment with scaling, and array indexing. + */ + + /* Check if both operands are pointers (pointer difference) */ + if (op == OP_sub) { + /* If both are variables (not temporaries), look them up */ + var_t *orig_rs1 = rs1, *orig_rs2 = rs2; + + /* If they have names, they might be variable references - look them up + */ + if (rs1->var_name[0]) { + var_t *found = find_var(rs1->var_name, parent); + if (found) + orig_rs1 = found; + } + if (rs2->var_name[0]) { + var_t *found = find_var(rs2->var_name, parent); + if (found) + orig_rs2 = found; + } + + /* Check if both have ptr_level or typedef pointer type */ + bool rs1_is_ptr = is_pointer_like_value(orig_rs1) || orig_rs1->is_func; + bool rs2_is_ptr = is_pointer_like_value(orig_rs2) || orig_rs2->is_func; + + /* If variable lookup failed, check the passed variables directly */ + if (!rs1_is_ptr) + rs1_is_ptr = is_pointer_like_value(rs1) || rs1->is_func; + if (!rs2_is_ptr) + rs2_is_ptr = is_pointer_like_value(rs2) || rs2->is_func; + + if (rs1_is_ptr && rs2_is_ptr) { + /* Both are pointers - this is pointer difference Determine element + * size C99 6.5.6 confines pointer subtraction to pointers to + * complete object types. A function pointer is pointer-like for + * calls and comparisons, but it has no object elements to count. + */ + if (orig_rs1->is_func || orig_rs2->is_func) + error_at("Pointer subtraction requires object pointers", + cur_token_loc()); + type_t *left_pointee = + pointee_type_from_pointer_typedef(orig_rs1->type); + type_t *right_pointee = + pointee_type_from_pointer_typedef(orig_rs2->type); + + /* An array operand has decayed to a pointer to its first element + * (C99 6.3.2.1), one level deeper than its declaration; the element + * of a deeper array is a row. + */ + var_t left_decayed, right_decayed; + + if (decay_fixed_array_rows(&left_decayed, orig_rs1)) + orig_rs1 = &left_decayed; + if (decay_fixed_array_rows(&right_decayed, orig_rs2)) + orig_rs2 = &right_decayed; + int left_depth = orig_rs1->ptr_level + orig_rs1->type->ptr_level + + !!is_array_declarator(orig_rs1); + int right_depth = orig_rs2->ptr_level + orig_rs2->type->ptr_level + + !!is_array_declarator(orig_rs2); + bool left_row = is_pointee_array_pointer(orig_rs1) && + !is_array_declarator(orig_rs1); + bool right_row = is_pointee_array_pointer(orig_rs2) && + !is_array_declarator(orig_rs2); + + if ((left_row || right_row) && + !pointee_array_shapes_compatible(orig_rs1, orig_rs2)) + error_at( + "Pointer subtraction requires compatible pointed-to types", + cur_token_loc()); + if (!compatible_decl_type(left_pointee, right_pointee) || + left_depth != right_depth) + error_at( + "Pointer subtraction requires compatible pointed-to types", + cur_token_loc()); + + element_size = PTR_SIZE; /* Default */ + + element_size = left_row ? pointee_array_row_stride(orig_rs1) + : get_pointer_element_size(orig_rs1); + + /* Perform subtraction first */ + var_t *diff = require_var(parent); + diff->var_name = gen_name(); + add_insn(parent, *bb, OP_sub, diff, rs1, rs2, 0, NULL); + + /* Then divide by element size if needed */ + if (element_size > 1) { + var_t *size_const = require_var(parent); + size_const->var_name = gen_name(); + size_const->init_val = element_size; + add_insn(parent, *bb, OP_load_constant, size_const, NULL, NULL, + 0, NULL); + + var_t *result = require_var(parent); + result->var_name = gen_name(); + add_insn(parent, *bb, OP_div, result, diff, size_const, 0, + NULL); + opstack_push(result); + } else { + opstack_push(diff); + } + return; + } + } + /* Determine which operand is the pointer for regular pointer arithmetic */ + if (is_pointer_like_value(rs1)) { + ptr_var = rs1; + int_var = rs2; + element_size = + is_pointee_array_pointer(rs1) && !is_array_declarator(rs1) + ? pointee_array_row_stride(rs1) + : (is_array_declarator(rs1) && rs1->array_dim2 && + !has_effective_pointer(rs1) && !rs1->is_func + ? fixed_array_decay_stride(rs1) + : get_pointer_element_size(rs1)); + } else if (is_pointer_like_value(rs2)) { + /* Only for addition (p + n == n + p) */ + if (op == OP_add) { + ptr_var = rs2; + int_var = rs1; + element_size = + is_pointee_array_pointer(rs2) && !is_array_declarator(rs2) + ? pointee_array_row_stride(rs2) + : (is_array_declarator(rs2) && rs2->array_dim2 && + !has_effective_pointer(rs2) && !rs2->is_func + ? fixed_array_decay_stride(rs2) + : get_pointer_element_size(rs2)); + /* Swap operands so pointer is rs1 */ + rs1 = ptr_var; + rs2 = int_var; + } + } + + /* If we need to scale the integer operand */ + if (ptr_var && element_size > 1) { + /* Create multiplication by element size */ + var_t *size_const = require_var(parent); + size_const->var_name = gen_name(); + size_const->init_val = element_size; + add_insn(parent, *bb, OP_load_constant, size_const, NULL, NULL, 0, + NULL); + + var_t *scaled = require_var(parent); + scaled->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, scaled, int_var, size_const, 0, NULL); + + /* Use scaled value as rs2 */ + rs2 = scaled; + } + + /* Perform the operation */ + var_t *vd = require_var(parent); + /* Preserve pointer type metadata on results of pointer arithmetic */ + if (ptr_var) { + vd->type = ptr_var->type; + + /* An array operand decays to a pointer before arithmetic. Retain that + * extra level on the result so `slots + 0` for an array of pointer + * typedefs cannot be mistaken for a direct callback value. + */ + vd->ptr_level = ptr_var->ptr_level + !!ptr_var->array_size; + vd->pointee_func_signature = ptr_var->pointee_func_signature; + + /* A pointer to const objects, `const int *` or a const record's decayed + * array member, still points to const objects after arithmetic. + */ + if (vd->ptr_level == 1 && !vd->type->ptr_level) { + vd->is_const_qualified = ptr_var->is_const_qualified; + vd->is_string_literal = ptr_var->is_string_literal; + } + + /* A pointer to such pointers, or an array of them, steps by a pointer + * and keeps the row shape one level further in. + */ + if (ptr_var->pointee_array_size) + copy_pointee_array_shape(vd, ptr_var); + else if (is_array_declarator(ptr_var) && ptr_var->array_dim2 && + !has_effective_pointer(ptr_var) && !ptr_var->is_func) { + fixed_array_shape_t shape = fixed_array_shape_from_var(ptr_var); + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_pointee_var(vd, &shape); + } + } + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); +} + +/* Helper function to check if pointer arithmetic is needed */ +bool is_pointer_operation(opcode_t op, var_t *rs1, var_t *rs2) +{ + if (op != OP_add && op != OP_sub) + return false; + + return is_pointer_like_value(rs1) || is_pointer_like_value(rs2) || + (rs1 && rs1->is_func) || (rs2 && rs2->is_func); +} + +/* The first unsigned slice has the existing int/short/char widths. Narrow + * unsigned operands promote to int because int represents their full range; an + * unsigned int operand gives the arithmetic result unsigned int. + */ +bool unsigned_int_operand(const var_t *var) +{ + return var && !var->ptr_level && var->type && var->type->is_unsigned && + var->type->size >= TY_int->size; +} + +/* A source-level write invalidates the parser's constant-propagation cache. + * This is distinct from const qualification, which controls write legality. + */ +void mark_var_mutated(var_t *var) +{ + if (var && !unevaluated_expression_depth) + var->is_const = false; +} + +/* The integer ranks currently represented by shecc are int, long (both 32-bit), + * and long long (64-bit). Equal representation widths do not merge int and + * long: C99 still gives long the higher rank. + */ +type_t *integer_common_type(const var_t *left, const var_t *right) +{ + /* The current type lattice has a 32-bit int/long tier and a distinct 64-bit + * long-long tier. A 64-bit signed operand can represent every 32-bit + * unsigned value; an unsigned 64-bit operand wins at its rank. + */ + if ((left && left->type && left->type->size > TY_int->size) || + (right && right->type && right->type->size > TY_int->size)) { + if ((left && left->type && left->type->size > TY_int->size && + left->type->is_unsigned) || + (right && right->type && right->type->size > TY_int->size && + right->type->is_unsigned)) + return TY_ulong_long; + return TY_long_long; + } + + if ((left && (left->type == TY_long || left->type == TY_ulong)) || + (right && (right->type == TY_long || right->type == TY_ulong))) { + if ((left && left->type && left->type->is_unsigned) || + (right && right->type && right->type->is_unsigned)) + return TY_ulong; + return TY_long; + } + + if (unsigned_int_operand(left) || unsigned_int_operand(right)) + return TY_uint; + return TY_int; +} + +type_t *integer_binary_result_type(opcode_t op, + const var_t *left, + const var_t *right) +{ + if (op == OP_eq || op == OP_neq || op == OP_lt || op == OP_leq || + op == OP_gt || op == OP_geq) + return TY_int; + + /* Shift counts are promoted, but do not participate in the usual arithmetic + * conversions; the result has the promoted left type. + */ + if (op == OP_lshift || op == OP_rshift) + return left->type; + return integer_common_type(left, right); +} + +var_t *integer_promote_operand(block_t *parent, basic_block_t **bb, var_t *var) +{ + /* An array operand carries its element type, so a char array would look + * like a char here and be sign-extended from the low byte of its address. + * The value is an address, which C99 6.3.2.1p3 converts to a pointer to the + * first element rather than promoting, so leave it alone as for a pointer. + */ + if (is_pointer_like_value(var)) + return var; + if (!var || !var->type || var->type->size >= TY_int->size) + return var; + return promote_unchecked(parent, bb, var, TY_int, 0); +} + +/* Apply C99's usual arithmetic conversions after the individual integer + * promotions. In particular this must materialize a zero-extension for an + * unsigned int that meets a signed long long, and a sign-extension for a + * negative int that meets unsigned long long. Merely giving the result the + * common type leaves the machine operation to consume stale upper bits. + */ +void normalize_integer_binary_operands(block_t *parent, + basic_block_t **bb, + opcode_t op, + var_t **left, + var_t **right) +{ + type_t *common; + + if (op == OP_lshift || op == OP_rshift) + return; + + /* Equality and relational operators also reach this helper. Their pointer + * cases keep address semantics and are not usual arithmetic conversions. + */ + if (is_pointer_like_value(left[0]) || is_pointer_like_value(right[0])) + return; + + /* The ABI-visible 64-bit rank is distinct today. int and long are both + * 32-bit in the current type model, so normalizing their signed/unsigned + * combinations here would add conversions throughout the self-hosted + * compiler without yet representing a distinct long rank. + */ + if (get_size(left[0]) <= TY_int->size && get_size(right[0]) <= TY_int->size) + return; + + common = integer_common_type(*left, *right); + + /* Do not manufacture no-op conversions. Besides bloating every unsigned + * expression, doing so makes stage1's self-hosting input prohibitively + * large. A conversion is observable here only across a width boundary, or + * when a signed value is reinterpreted at an unsigned common rank. + */ + if (get_size(left[0]) != common->size || + (!left[0]->type->is_unsigned && common->is_unsigned)) + left[0] = resize_to(parent, bb, left[0], common, 0); + if (get_size(right[0]) != common->size || + (!right[0]->type->is_unsigned && common->is_unsigned)) + right[0] = resize_to(parent, bb, right[0], common, 0); +} + +bool emit_wide_global_word_arithmetic(block_t *parent, + basic_block_t *bb, + var_t *result, + opcode_t op, + var_t *left, + var_t *right); + +/* The int folder in read_expr_body() leaves unsigned and long long operands + * alone. Fold those in two-word form before the usual arithmetic conversions + * emit IR that hides the constants, so an unsigned expression such as sizeof(x) + * - sizeof(x) stays an integer constant expression and, when zero, a null + * pointer constant. Division by zero and a shift outside the operand width are + * left to the target, as the int folder leaves them. + */ +static bool fold_wide_constant_binary(block_t *parent, + basic_block_t **bb, + opcode_t op, + var_t *rs1, + var_t *rs2) +{ + var_t *vd; + + if (!rs1 || !rs2 || !rs1->is_const || !rs2->is_const || rs1->is_global || + rs2->is_global || !rs1->type || !rs2->type || + is_pointer_like_value(rs1) || is_pointer_like_value(rs2) || + rs1->is_func || rs2->is_func) + return false; + if (!unsigned_int_operand(rs1) && !unsigned_int_operand(rs2) && + rs1->type->size <= TY_int->size && rs2->type->size <= TY_int->size) + return false; + + switch (op) { + case OP_div: + case OP_mod: + if (!rs2->init_val && + (rs2->type->size <= TY_int->size || !rs2->init_val_hi)) + return false; + break; + case OP_lshift: + case OP_rshift: + if ((rs2->type->size > TY_int->size && rs2->init_val_hi) || + rs2->init_val < 0 || rs2->init_val >= rs1->type->size * 8) + return false; + break; + case OP_add: + case OP_sub: + case OP_mul: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + break; + default: + return false; + } + + vd = require_typed_var(parent, integer_binary_result_type(op, rs1, rs2)); + vd->var_name = gen_name(); + emit_wide_global_word_arithmetic(parent, *bb, vd, op, rs1, rs2); + opstack_push(vd); + return true; +} + +void read_expr_body(block_t *parent, basic_block_t **bb) +{ + var_t *vd, *rs1, *rs2; + opcode_t oper_stack[MAX_OPERATOR_STACK_SIZE]; + int oper_stack_idx = 0; + + /* These variables used for parsing logical-and/or operation. + * + * For the logical-and operation, the false condition code path for testing + * each operand uses the same code snippet (basic block). + * + * Likewise, when testing each operand for the logical-or operation, all of + * them share a unified code path for the true condition. + */ + bool has_prev_log_op = false; + opcode_t prev_log_op = 0, pprev_log_op = 0; + basic_block_t *log_and_shared_bb = bb_create(parent), + *log_or_shared_bb = bb_create(parent); + + read_expr_operand(parent, bb); + + opcode_t op = get_operator(); + if (op == OP_generic || op == OP_ternary) + return; + if (is_logical(op)) { + bb_connect(*bb, op == OP_log_and ? log_and_shared_bb : log_or_shared_bb, + op == OP_log_and ? ELSE : THEN); + read_logical(op, parent, bb); + has_prev_log_op = true; + prev_log_op = op; + } else { + if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) + fatal("Expression too complex: operator stack exhausted"); + oper_stack[oper_stack_idx++] = op; + } + read_expr_operand(parent, bb); + op = get_operator(); + + while (op != OP_generic && op != OP_ternary) { + if (oper_stack_idx > 0) { + int same = 0; + do { + opcode_t top_op = oper_stack[oper_stack_idx - 1]; + if (get_operator_prio(top_op) >= get_operator_prio(op)) { + rs2 = opstack_pop(); + rs1 = opstack_pop(); + reject_record_operand(rs1); + reject_record_operand(rs2); + + /* As in the final reduction below, equality compares a + * function designator as its pointer. Reduced here, before + * a following operator such as ||, a raw symbol compared + * as garbage. + */ + if (top_op == OP_eq || top_op == OP_neq) { + rs1 = materialize_function_designator(parent, bb, rs1); + rs2 = materialize_function_designator(parent, bb, rs2); + } + + /* Handle pointer arithmetic for addition and subtraction */ + if (is_pointer_operation(top_op, rs1, rs2)) { + /* handle_pointer_arithmetic handles both pointer + * differences and regular pointer arithmetic internally + */ + handle_pointer_arithmetic(parent, bb, top_op, rs1, rs2); + oper_stack_idx--; + continue; + } + + rs1 = integer_promote_operand(parent, bb, rs1); + rs2 = integer_promote_operand(parent, bb, rs2); + normalize_integer_binary_operands(parent, bb, top_op, &rs1, + &rs2); + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = integer_binary_result_type(top_op, rs1, rs2); + opstack_push(vd); + add_insn(parent, *bb, top_op, vd, rs1, rs2, 0, NULL); + + oper_stack_idx--; + } else + same = 1; + } while (oper_stack_idx > 0 && same == 0); + } + if (is_logical(op)) { + if (prev_log_op == 0 || prev_log_op == op) { + bb_connect( + *bb, + op == OP_log_and ? log_and_shared_bb : log_or_shared_bb, + op == OP_log_and ? ELSE : THEN); + read_logical(op, parent, bb); + prev_log_op = op; + has_prev_log_op = true; + } else if (prev_log_op == OP_log_and) { + /* For example: a && b || c + * previous opcode: prev_log_op == OP_log_and current opcode: op + * == OP_log_or current operand: b + * + * Finalize the logical-and operation and test the operand for + * the following logical-or operation. + */ + finalize_logical(prev_log_op, parent, bb, log_and_shared_bb); + log_and_shared_bb = bb_create(parent); + bb_connect(*bb, log_or_shared_bb, THEN); + read_logical(op, parent, bb); + + /* Here are two cases to illustrate the following assignments + * after finalizing the logical-and operation and testing the + * operand for the following logical-or operation. + * + * 1. a && b || c + * pprev opcode: pprev_log_op == 0 (no opcode) + * previous opcode: prev_log_op == OP_log_and + * current opcode: op == OP_log_or + * current operand: b + * + * The current opcode should become the previous opcode, + * and the pprev opcode remains 0. + * + * 2. a || b && c || d + * pprev opcode: pprev_log_op == OP_log_or + * previous opcode: prev_log_op == OP_log_and + * current opcode: op == OP_log_or + * current operand: b + * + * The previous opcode should inherit the pprev opcode, which + * is equivalent to inheriting the current opcode because both + * of pprev opcode and current opcode are logical-or operator. + * + * Thus, pprev opcode is considered used and is cleared to 0. + * + * Eventually, the current opcode becomes the previous opcode + * and pprev opcode is set to 0. + */ + prev_log_op = op; + pprev_log_op = 0; + } else { + /* For example: a || b && c + * previous opcode: prev_log_op == OP_log_or current opcode: op + * == OP_log_and current operand: b + * + * Using the logical-and operation to test the current operand + * instead of using the logical-or operation. + * + * Then, the previous opcode becomes pprev opcode and the + * current opcode becomes the previous opcode. + */ + bb_connect(*bb, log_and_shared_bb, ELSE); + read_logical(op, parent, bb); + pprev_log_op = prev_log_op; + prev_log_op = op; + } + } else { + while (has_prev_log_op && + (get_operator_prio(op) < get_operator_prio(prev_log_op))) { + /* When encountering an operator with lower priority, conclude + * the current logical-and/or and create a new basic block for + * next logical-and/or operator. + */ + finalize_logical(prev_log_op, parent, bb, + prev_log_op == OP_log_and ? log_and_shared_bb + : log_or_shared_bb); + if (prev_log_op == OP_log_and) + log_and_shared_bb = bb_create(parent); + else + log_or_shared_bb = bb_create(parent); + + /* After finalizing the previous logical-and/or operation, the + * prev_log_op should inherit pprev_log_op and continue to check + * whether to finalize a logical-and/or operation. + */ + prev_log_op = pprev_log_op; + has_prev_log_op = prev_log_op != 0; + pprev_log_op = 0; + } + } + read_expr_operand(parent, bb); + if (!is_logical(op)) { + if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) + fatal("Expression too complex: operator stack exhausted"); + oper_stack[oper_stack_idx++] = op; + } + op = get_operator(); + } + + while (oper_stack_idx > 0) { + opcode_t top_op = oper_stack[--oper_stack_idx]; + rs2 = opstack_pop(); + rs1 = opstack_pop(); + reject_record_operand(rs1); + reject_record_operand(rs2); + + /* Equality compares function pointers after the C99 function-to- + * pointer conversion. A raw symbol has no SSA value and otherwise looks + * like zero to the backend, making `function == 0` spuriously true. + * Other arithmetic operations retain their function-pointer constraint + * diagnostics below. + */ + if (top_op == OP_eq || top_op == OP_neq) { + rs1 = materialize_function_designator(parent, bb, rs1); + rs2 = materialize_function_designator(parent, bb, rs2); + } + + /* An array of function pointers decays to an object pointer. */ + if ((top_op == OP_lt || top_op == OP_leq || top_op == OP_gt || + top_op == OP_geq) && + ((rs1 && !is_array_declarator(rs1) && + (rs1->is_func || get_func_signature(rs1))) || + (rs2 && !is_array_declarator(rs2) && + (rs2->is_func || get_func_signature(rs2))))) + error_at("Relational comparison requires object pointers", + cur_token_loc()); + + if (top_op == OP_eq || top_op == OP_neq) { + func_t *left_signature = get_func_signature(rs1); + func_t *right_signature = get_func_signature(rs2); + + if (left_signature || right_signature) { + var_t *other = left_signature ? rs2 : rs1; + + if ((left_signature && right_signature && + !compatible_function_signature(left_signature, + right_signature)) || + (!get_func_signature(other) && + !is_null_pointer_constant(other))) + error_at( + "Function pointer comparison requires compatible " + "pointers or null", + cur_token_loc()); + } + } + + bool rs1_is_placeholder = is_array_literal_placeholder(rs1); + bool rs2_is_placeholder = is_array_literal_placeholder(rs2); + bool rs1_is_ptr_like = + is_pointer_like_value(rs1) || (rs1 && rs1->is_func); + bool rs2_is_ptr_like = + is_pointer_like_value(rs2) || (rs2 && rs2->is_func); + bool pointer_context = (rs1_is_ptr_like && !rs1_is_placeholder) || + (rs2_is_ptr_like && !rs2_is_placeholder); + + /* Pointer arithmetic handling */ + if (pointer_context && is_pointer_operation(top_op, rs1, rs2)) { + handle_pointer_arithmetic(parent, bb, top_op, rs1, rs2); + continue; /* skip normal processing */ + } + + if ((top_op == OP_eq || top_op == OP_neq || top_op == OP_lt || + top_op == OP_leq || top_op == OP_gt || top_op == OP_geq) && + incompatible_character_pointer_conversion(rs1, rs2)) + error_at("incompatible character pointer types in comparison", + cur_token_loc()); + + if (rs1_is_placeholder && rs2_is_placeholder) { + rs1 = scalarize_array_literal(parent, bb, rs1, NULL); + rs2 = scalarize_array_literal(parent, bb, rs2, NULL); + } else { + if (rs1_is_placeholder && !rs2_is_ptr_like) + rs1 = scalarize_array_literal( + parent, bb, rs1, rs2 && rs2->type ? rs2->type : NULL); + + if (rs2_is_placeholder && !rs1_is_ptr_like) + rs2 = scalarize_array_literal( + parent, bb, rs2, rs1 && rs1->type ? rs1->type : NULL); + } + rs1 = integer_promote_operand(parent, bb, rs1); + rs2 = integer_promote_operand(parent, bb, rs2); + if (fold_wide_constant_binary(parent, bb, top_op, rs1, rs2)) + continue; + normalize_integer_binary_operands(parent, bb, top_op, &rs1, &rs2); + type_t *result_type = integer_binary_result_type(top_op, rs1, rs2); + /* Constant folding for binary operations */ + if (rs1 && rs2 && rs1->is_const && !rs1->ptr_level && !rs1->is_global && + rs2->is_const && !rs2->ptr_level && !rs2->is_global && + !unsigned_int_operand(rs1) && !unsigned_int_operand(rs2) && + rs1->type->size <= TY_int->size && + rs2->type->size <= TY_int->size) { + /* Both operands are compile-time constants */ + int result = 0; + bool folded = true; + + switch (top_op) { + case OP_add: + result = rs1->init_val + rs2->init_val; + break; + case OP_sub: + result = rs1->init_val - rs2->init_val; + break; + case OP_mul: + result = rs1->init_val * rs2->init_val; + break; + case OP_div: + if (rs2->init_val != 0) + result = rs1->init_val / rs2->init_val; + else + folded = false; /* Division by zero */ + break; + case OP_mod: + if (rs2->init_val != 0) + result = rs1->init_val % rs2->init_val; + else + folded = false; /* Modulo by zero */ + break; + case OP_bit_and: + result = rs1->init_val & rs2->init_val; + break; + case OP_bit_or: + result = rs1->init_val | rs2->init_val; + break; + case OP_bit_xor: + result = rs1->init_val ^ rs2->init_val; + break; + + /* A shift count outside the promoted int width has no defined + * result, and the compiler must not compute one in the host either: + * leave such a shift to the target. Otherwise shift the unsigned + * bit pattern, so a bit reaching the sign is not host undefined + * behavior. + */ + case OP_lshift: + if (rs2->init_val < 0 || rs2->init_val >= 32) + folded = false; + else + result = + (int) ((unsigned int) rs1->init_val << rs2->init_val); + break; + case OP_rshift: + if (rs2->init_val < 0 || rs2->init_val >= 32) + folded = false; + else + result = rs1->init_val >> rs2->init_val; + break; + case OP_eq: + result = rs1->init_val == rs2->init_val; + break; + case OP_neq: + result = rs1->init_val != rs2->init_val; + break; + case OP_lt: + result = rs1->init_val < rs2->init_val; + break; + case OP_leq: + result = rs1->init_val <= rs2->init_val; + break; + case OP_gt: + result = rs1->init_val > rs2->init_val; + break; + case OP_geq: + result = rs1->init_val >= rs2->init_val; + break; + default: + folded = false; + break; + } + + if (folded) { + /* Create constant result */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = result_type; + vd->is_const = true; + vd->init_val = result; + + /* The high word is the int result extended by its type, as a + * literal or a folded negation keeps it: a cast to long long + * copies it, and "(long long) (0 - 42)" came out positive. + */ + vd->init_val_hi = + result < 0 && !result_type->is_unsigned ? -1 : 0; + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + } else { + /* Normal operation - folding failed or not supported */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = result_type; + opstack_push(vd); + add_insn(parent, *bb, top_op, vd, rs1, rs2, 0, NULL); + } + } else { + /* Normal operation */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = result_type; + opstack_push(vd); + add_insn(parent, *bb, top_op, vd, rs1, rs2, 0, NULL); + } + } + while (has_prev_log_op) { + finalize_logical( + prev_log_op, parent, bb, + prev_log_op == OP_log_and ? log_and_shared_bb : log_or_shared_bb); + + prev_log_op = pprev_log_op; + has_prev_log_op = prev_log_op != 0; + pprev_log_op = 0; + } +} + +/* Nesting counter for read_expr(). The expression grammar descends recursively + * through read_expr_operand(), so input nested deeply enough would run out of + * machine stack before any diagnostic could be printed. + */ +int expr_depth = 0; + +void read_expr(block_t *parent, basic_block_t **bb) +{ + expr_depth++; + if (expr_depth > MAX_EXPR_DEPTH) + error_at("Expression nesting too deep", cur_token_loc()); + read_expr_body(parent, bb); + expr_depth--; +} + + +/* Whether @lvalue designates an integer object wider than int. The step and + * result of ++ and -- on it must have its type: an int temporary kept only the + * low word, which a 32-bit target stores back without a high word. + */ +static bool lvalue_is_wide_integer(const lvalue_t *lvalue) +{ + int level = + lvalue->is_reference ? lvalue->value_ptr_level : lvalue->ptr_level; + + return !level && !lvalue->is_func && lvalue->type && + !lvalue->type->ptr_level && !is_record_type(lvalue->type) && + lvalue->type->size > TY_int->size; +} + +/* Whether ++ and -- on @lvalue update a _Bool object. */ +static bool lvalue_is_bool(const lvalue_t *lvalue) +{ + int level = + lvalue->is_reference ? lvalue->value_ptr_level : lvalue->ptr_level; + + return !lvalue->is_func && is_bool_scalar(lvalue->type, level); +} + +/* The step of ++ and -- on @lvalue, read from @var: the pointee size for a + * pointer and 1 otherwise (C99 6.5.2.4, 6.5.3.1). A selected element or member + * has the pointer depth of its value, not of the declaration: p[0] of an int *p + * steps by 1, and h.p of an int *member by sizeof(int). Using the declaration's + * depth for both made one of them step by the wrong size. + */ +static int lvalue_step_size(const lvalue_t *lvalue, var_t *var) +{ + if (lvalue->is_reference) { + if (lvalue->value_ptr_level > 1) + return PTR_SIZE; + if (!lvalue->value_ptr_level || !lvalue->type) + return 1; + + /* A member pointing to function pointers steps over pointer objects. */ + if (lvalue->decl && lvalue->decl->pointee_func_signature && + !lvalue->subscript_depth && !is_array_declarator(lvalue->decl)) + return PTR_SIZE; + + /* An element or member pointing to an array, as in `int (*rows[2])[3]`, + * steps by a whole row. + */ + if (lvalue->decl && lvalue->decl->pointee_array_size && + !lvalue->pointee_func_signature && + lvalue->subscript_depth == + (is_array_declarator(lvalue->decl) ? 1 : 0)) + return pointee_array_row_stride(lvalue->decl); + if (lvalue->type->ptr_level) + return pointer_typedef_pointee_size( + lvalue->type, pointee_type_from_pointer_typedef(lvalue->type)); + return lvalue->type->size; + } + if (is_pointee_array_pointer(var)) + return pointee_array_row_stride(var); + if (lvalue->ptr_level > 1 || + (lvalue->ptr_level == 1 && var->pointee_func_signature)) + return PTR_SIZE; + if (lvalue->ptr_level) + return lvalue->type->size; + + /* A typedef pointer steps by its pointee: the alias's own size is a + * pointer's, whether it points at a record or at another pointer. + */ + if (lvalue->type && lvalue->type->ptr_level) + return get_pointer_element_size(var); + return 1; +} + +/* What follows a completed lvalue: an update or read of the object it names. + * read_lvalue() finishes with this. + */ +static void lower_lvalue_tail(lvalue_t *lvalue, + var_t *var, + block_t *parent, + basic_block_t **bb, + opcode_t prefix_op, + type_t *pointer_row_element_type) +{ + var_t *vd, *rs1, *rs2; + + /* Set and read only under 'is_reference'; the initializer says so to a + * compiler that cannot correlate the two tests. + */ + var_t *t = NULL; + + /* If operand is a reference, read the value and push to stack for the + * incoming addition/subtraction. Otherwise, use the top element of stack as + * the one of operands and the destination. + */ + if (lvalue->is_reference) { + rs1 = operand_stack[operand_stack_idx - 1]; + t = require_var(parent); + t->var_name = gen_name(); + t->type = pointer_row_element_type + ? pointer_row_element_type + : pointee_type_from_pointer_typedef(lvalue->type); + t->ptr_level = lvalue->value_ptr_level; + if (pointer_row_element_type) + t->is_const_qualified = lvalue->type->is_const_qualified; + + /* A record read through a pointer to const, `ps[0]`, stays const for a + * member selection that follows a grouping. + */ + if (!t->ptr_level && is_record_type(t->type) && + lvalue->is_const_qualified) + t->is_const_qualified = true; + + /* A loaded pointer-to-array member, as in `*s.rows`, still points to a + * whole row, and so does an element of an array of such pointers or of + * a pointer to one, as in `*pas[0]` or `*pp[1]`. + */ + if (lvalue->decl && lvalue->decl->pointee_array_size && + !lvalue->pointee_func_signature && + (is_array_declarator(lvalue->decl) + ? lvalue->decl->ptr_level == 1 && lvalue->subscript_depth == 1 + : !lvalue->subscript_depth || + lvalue->subscript_depth == lvalue->decl->ptr_level - 1)) + copy_pointee_array_shape(t, lvalue->decl); + + /* Retain a callback prototype even through a selected slot. A direct + * loaded callback is callable, while unary `*` restores this marker + * after consuming an extra object-pointer level. + */ + t->func_signature = lvalue->type->func_signature; + + /* `fpp[i]` of an `int (**fpp)(int)` loads a callable function pointer, + * and so do `fps[i]` of an `int (*fps[N])(int)` and a member `s.fp`. + */ + if (!t->ptr_level && lvalue->subscript_depth == 1 && lvalue->decl && + lvalue->decl->pointee_func_signature && + lvalue->decl->ptr_level == 1 && !lvalue->decl->array_size && + !lvalue->pointee_func_signature) { + t->ptr_level = 1; + t->func_signature = lvalue->decl->pointee_func_signature; + } else if (!t->ptr_level && lvalue->is_func && lvalue->decl && + lvalue->decl->is_func && lvalue->decl->func_signature && + !lvalue->decl->array_dim2 && + lvalue->subscript_depth == + (lvalue->decl->array_size ? 1 : 0)) { + t->ptr_level = 1; + t->func_signature = lvalue->decl->func_signature; + } + if (lvalue->pointee_func_signature) { + /* `slots[i]` loads a callback slot, not a callable callback. + * Preserve the element's prototype until one unary `*` consumes + * this outer object-pointer level. + */ + t->ptr_level = 1; + t->func_signature = NULL; + t->pointee_func_signature = lvalue->pointee_func_signature; + t->is_const_pointer = var->type->array_element_is_const_pointer; + t->pointer_const_mask = t->is_const_pointer ? 1U : 0; + t->is_volatile = var->type->array_element_is_volatile; + } + if (is_bitfield(lvalue->decl)) { + /* Bit-fields are values, never independently addressable storage: + * extract their slice from the containing unit. + */ + t = read_bitfield_value(parent, bb, rs1, lvalue->decl); + opstack_push(t); + mark_value_reference(t, rs1, lvalue->decl); + } else if (lvalue->is_func) { + /* A function pointer typed by its record return type is a code + * address, loaded like any other pointer. + */ + add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, NULL); + opstack_push(t); + mark_value_reference(t, rs1, NULL); + } else { + /* A register holds no more than a pointer's worth of a selected + * record, which push_object_at() keeps in place. + */ + push_object_at(parent, bb, t, rs1, lvalue->size); + } + } + if (prefix_op != OP_generic) { + if ((prefix_op == OP_add || prefix_op == OP_sub) && + is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) + error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); + vd = require_var(parent); + vd->var_name = gen_name(); + + vd->init_val = lvalue_step_size(lvalue, var); + if (lvalue_is_wide_integer(lvalue)) + vd->type = lvalue->type; + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + + rs2 = opstack_pop(); + if (lvalue->is_reference) + rs1 = opstack_pop(); + else + rs1 = operand_stack[operand_stack_idx - 1]; + vd = require_var(parent); + vd->var_name = gen_name(); + if (lvalue_is_wide_integer(lvalue)) + vd->type = lvalue->type; + add_insn(parent, *bb, prefix_op, vd, rs1, rs2, 0, NULL); + + if (lvalue->is_reference) { + rs1 = vd; + vd = opstack_pop(); + + /* A bit-field is updated inside its allocation unit, and the + * expression has the value the field then holds; a plain store of + * the sum overwrote the neighbouring fields. + */ + if (is_bitfield(lvalue->decl)) { + write_bitfield_value(parent, bb, vd, rs1, lvalue->decl); + rs1 = read_bitfield_value(parent, bb, vd, lvalue->decl); + rs1->is_bitfield = false; + } else { + rs1 = convert_stored_value(parent, bb, rs1, t->type, + t->ptr_level); + + /* The column of arguments of the new insn of 'OP_write' is + * different from 'ph1_ir' + */ + add_insn(parent, *bb, OP_write, NULL, vd, rs1, lvalue->size, + NULL); + } + /* Push the new value onto the operand stack */ + opstack_push(rs1); + } else { + rs1 = vd; + vd = operand_stack[operand_stack_idx - 1]; + rs1 = resize_var(parent, bb, rs1, vd); + mark_var_mutated(vd); + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + } + } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + if (is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) + error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); + + /* Deferred postfix stores are plain OP_write instructions. A bit-field + * instead needs a masked read-modify-write of its containing allocation + * unit, so lower it here while retaining the extracted pre-update value + * as the expression result. + */ + if (lvalue->is_reference && is_bitfield(lvalue->decl)) { + /* Consume the '--' as well: testing for '++' alone left it in the + * stream, so `bits.field--` failed to parse. + */ + opcode_t postfix_op = OP_sub; + if (lex_accept(T_increment)) + postfix_op = OP_add; + else + lex_expect(T_decrement); + var_t *old = opstack_pop(); + var_t *address = opstack_pop(); + var_t *one = bitfield_constant(parent, bb, 1); + var_t *updated = require_var(parent); + + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(postfix_op, old, one); + add_insn(parent, *bb, postfix_op, updated, old, one, 0, NULL); + write_bitfield_value(parent, bb, address, updated, lvalue->decl); + old->is_bitfield = false; + opstack_push(old); + return; + } + + /* Lower the update where the operand is read. C99 6.5.2.4 only requires + * it before the next sequence point, and that point may lie inside the + * full expression: after the first operand of &&, || and ?:, at a comma + * operator, and before a call (C99 6.5.2.2p10). Deferring it to the end + * of the statement also ran updates in a skipped operand. + */ + vd = require_var(parent); + vd->var_name = gen_name(); + + vd->init_val = lvalue_step_size(lvalue, var); + if (lvalue_is_wide_integer(lvalue)) + vd->type = lvalue->type; + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + rs2 = vd; + + /* Consume whichever operator is actually there. Testing only for '++' + * picks the right opcode but leaves a '--' in the stream, so postfix + * decrement parsed only where the leftover token happened to be + * harmless -- "i--;" as a statement worked, "a = i--" did not. + */ + opcode_t postfix_op = OP_sub; + if (lex_accept(T_increment)) + postfix_op = OP_add; + else + lex_expect(T_decrement); + + /* The expression has the value the object held before the update. A + * reference was already loaded into a temporary; a variable is itself + * the operand, so copy it before it is assigned, and step the copy: a + * volatile object is then read once. + */ + if (lvalue->is_reference) { + rs1 = opstack_pop(); + } else { + rs1 = operand_stack[operand_stack_idx - 1]; + t = require_var(parent); + t->var_name = gen_name(); + t->type = rs1->type; + t->ptr_level = rs1->ptr_level; + t->func_signature = rs1->func_signature; + t->pointee_func_signature = rs1->pointee_func_signature; + t->pointer_const_mask = rs1->pointer_const_mask; + if (is_pointee_array_pointer(rs1)) + copy_pointee_array_shape(t, rs1); + add_insn(parent, *bb, OP_assign, t, rs1, NULL, 0, NULL); + } + vd = require_var(parent); + vd->var_name = gen_name(); + if (!lvalue->is_reference && is_pointee_array_pointer(var)) { + vd->type = lvalue->type; + vd->ptr_level = var->ptr_level; + copy_pointee_array_shape(vd, var); + } else if (lvalue_is_wide_integer(lvalue)) + vd->type = lvalue->type; + add_insn(parent, *bb, postfix_op, vd, lvalue->is_reference ? rs1 : t, + rs2, 0, NULL); + + /* The update converts back to the operand type, which for _Bool is a + * comparison with zero: b++ leaves 1 and b-- on 0 leaves 1 (C99 + * 6.5.2.4). + */ + if (lvalue_is_bool(lvalue)) + vd = normalize_bool(parent, bb, vd); + + if (lvalue->is_reference) { + add_insn(parent, *bb, OP_write, NULL, opstack_pop(), vd, + lvalue->size, NULL); + } else { + mark_var_mutated(rs1); + add_insn(parent, *bb, OP_assign, rs1, vd, NULL, 0, NULL); + opstack_pop(); + } + opstack_push(t); + } else { + if (lvalue->is_reference) { + /* pop the address and keep the read value */ + t = opstack_pop(); + opstack_pop(); + opstack_push(t); + } + } +} + +/* Whether @subscripts on the pointer-to-array @var, as in `r[0][1]` for an `int + * *(*r)[2]`, reach an element that is itself a pointer. + */ +static bool pointee_row_pointer_element(const var_t *var, int subscripts) +{ + int element = var->pointee_array_element_ptr_level; + + return var->pointee_array_size && element > 0 && element <= 32 && + !is_array_declarator(var) && + subscripts == 1 + fixed_array_shape_from_pointee_var(var).rank; +} + +/* Whether that pointer element is const, as for `int *const (*r)[2]`. */ +static bool const_pointee_row_element(const var_t *var, int subscripts) +{ + if (!pointee_row_pointer_element(var, subscripts)) + return false; + return effective_pointer_const_mask(var) & + (1U << (var->pointee_array_element_ptr_level - 1)); +} + +/* Whether @var is a parameter of the function being parsed. A parameter + * declared with an array declarator is adjusted to a pointer (C99 6.7.5.3), so + * unlike an array it may be assigned. + */ +static bool is_function_parameter(const var_t *var, block_t *parent) +{ + if (!parent || !parent->func) + return false; + for (int i = 0; i < parent->func->num_params; i++) { + var_t *param = &parent->func->param_defs[i]; + + if (var == param || var->base == param) + return true; + } + return false; +} + +/* Return the address that an expression points to, or evaluate its value. + * x =; + * x[] =; + * x[expr].field =; + * x[expr]->field =; + * + * A following `+ expr` is not part of the lvalue: the binary operator reducer + * scales pointer arithmetic, with the precedence the operators have. + */ +void read_lvalue(lvalue_t *lvalue, + var_t *var, + block_t *parent, + basic_block_t **bb, + bool eval, + opcode_t prefix_op) +{ + var_t *vd, *rs1, *rs2; + var_t *loaded_scalar_row = NULL; + type_t *pointer_row_element_type = NULL; + bool pending_scalar_row_pointer_slot = false; + bool pending_fixed_array_pointer_slot = false; + bool is_address_got = false; + bool is_member = false; + int subscript_depth = 0; + + /* The decayed address of an array row selected by the latest subscript, + * such as `m[1]` of `int m[2][3]`. NULL once the lvalue designates an + * element or a member instead. + */ + var_t *array_row = NULL; + + /* Whether the record holding the current member is const. A member, and an + * element of an array member, is part of that record and shares its + * qualification; the object a pointer member points to does not. + */ + bool record_is_const = false; + + /* Callers pass a find_var() result, which is NULL for a name that was never + * declared. + */ + if (!var) + error_at("Undeclared identifier", next_token_loc()); + + /* C99 6.7.4 forbids an external-linkage inline definition from referencing + * an identifier with internal linkage. File-scope `static` objects have + * GLOBAL_BLOCK as their lexical owner; a static local has a function block + * instead and is diagnosed at its definition below. + */ + if (parent && parent->func && parent->func->is_inline && + !parent->func->is_static && var->is_static && + var->scope == GLOBAL_BLOCK) + error_at( + "external inline definition references internal-linkage object", + next_token_loc()); + + /* already peeked and have the variable */ + lex_expect(T_identifier); + + lvalue->type = var->type; + lvalue->decl = var; + lvalue->size = get_size(var); + lvalue->ptr_level = var->ptr_level; + lvalue->value_ptr_level = var->ptr_level + var->type->ptr_level; + lvalue->is_func = var->is_func; + lvalue->is_reference = false; + lvalue->pointee_func_signature = NULL; + + /* A pointer hidden in a typedef keeps its depth on the type rather than the + * declarator. Its outer const still makes the pointer object read-only, + * just as for an explicitly spelled `T * const`. + */ + lvalue->is_const_qualified = + (var->ptr_level || (var->type && var->type->ptr_level) || + var->is_func || var->array_size || var->has_unsized_array) + ? var->is_const_pointer + : var->is_const_qualified; + lvalue->pointer_const_mask = var->pointer_const_mask; + + opstack_push(var); + if (var->is_volatile) + unread_volatile_object = var; + + if (lex_peek(T_open_square, NULL) || lex_peek(T_arrow, NULL) || + lex_peek(T_dot, NULL)) + lvalue->is_reference = true; + + while (lex_peek(T_open_square, NULL) || lex_peek(T_arrow, NULL) || + lex_peek(T_dot, NULL)) { + array_row = NULL; + if (lex_accept(T_open_square)) { + int indexed_ptr_level; + fixed_array_shape_t row_shape; + bool indexes_fixed_array_pointer_slot; + bool indexes_direct_pointee_array; + bool indexes_scalar_pointee_row; + bool indexes_loaded_scalar_pointee_row; + bool indexes_pointee_row; + + /* if subscripted member's is not yet resolved, dereference to + * resolve base address. e.g., dereference of "->" in "data->raw[0]" + * would be performed here. The value depth counts the stars a + * typedef hides, as in a `ptr_t p` member or `pp_t pp; pp[0][1]`. + */ + if (lvalue->is_reference && + (lvalue->value_ptr_level || pending_fixed_array_pointer_slot) && + is_member) { + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + if (pending_scalar_row_pointer_slot) { + vd->type = var->type; + vd->ptr_level = 1; + copy_pointee_array_shape(vd, var); + loaded_scalar_row = vd; + pending_scalar_row_pointer_slot = false; + } else if (pending_fixed_array_pointer_slot) { + /* A direct `int (*planes[])[rows][columns]` array selects a + * pointer slot before it selects a row. Load that slot once + * and retain its fixed pointee shape. + */ + vd->type = var->type->pointee_array_element_type + ? var->type->pointee_array_element_type + : var->type; + vd->ptr_level = 1; + copy_pointee_array_shape(vd, var); + loaded_scalar_row = vd; + pending_fixed_array_pointer_slot = false; + } + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); + + /* The loaded value is the base for this index. A chain such as + * `int **p` still advances by pointer slots until its last + * indirection; a pointer-to-row then advances by base elements + * (or its preserved row extent below). An array of typedef + * pointers, as in `ptr_t rows[2]`, reaches here still holding + * the pointer alias, so step by its pointee instead. + */ + if (lvalue->value_ptr_level > 1) { + lvalue->size = PTR_SIZE; + } else if (lvalue->type->ptr_level && !loaded_scalar_row && + !lvalue->type->pointee_array_size) { + type_t *pointee = + pointee_type_from_pointer_typedef(lvalue->type); + + lvalue->size = + pointer_typedef_pointee_size(lvalue->type, pointee); + lvalue->type = pointee; + } else { + lvalue->size = lvalue->type->size; + } + } + + /* var must be either a pointer or an array of some type For typedef + * pointers, check the type's ptr_level + */ + bool is_typedef_pointer = (var->type && var->type->ptr_level > 0); + if (var->ptr_level == 0 && !is_array_declarator(var) && + !is_typedef_pointer) + error_at("Cannot apply square operator to non-pointer", + cur_token_loc()); + + /* The selected value has one less indirection than the expression + * being indexed. An array first decays to a pointer to its element; + * after an earlier subscript, use that selected value as the next + * indexing source. + */ + if (subscript_depth) + indexed_ptr_level = lvalue->value_ptr_level; + else + indexed_ptr_level = var->ptr_level + var->type->ptr_level + + !!is_array_declarator(var); + indexes_fixed_array_pointer_slot = + !subscript_depth && is_direct_fixed_array_pointer_slot(var); + + /* In `str (*p)[3]` for a char pointer typedef str, the element + * pointer is hidden in the base type rather than counted in the + * element depth. + */ + indexes_direct_pointee_array = + !subscript_depth && var->pointee_array_size > 0 && + ((var->pointee_array_element_ptr_level > 0 && + indexed_ptr_level == + var->pointee_array_element_ptr_level + 1) || + (!var->pointee_array_element_ptr_level && + !var->type->pointee_array_size && var->type->ptr_level && + var->ptr_level == 1 && !is_array_declarator(var))); + + /* `int (*p)[N]` selects an array row on its first subscript even + * though that row's scalar elements have no pointer depth. Keep + * this distinct from the pointer-element row case above: the row + * decays to `int *` for exactly the following scalar subscript. + */ + indexes_scalar_pointee_row = + !subscript_depth && !is_array_declarator(var) && + var->pointee_array_size > 0 && + var->pointee_array_element_ptr_level == 0 && + effective_pointer_depth(var) == 1; + indexes_loaded_scalar_pointee_row = + loaded_scalar_row && subscript_depth == 1; + indexes_pointee_row = indexes_direct_pointee_array || + indexes_scalar_pointee_row || + indexes_loaded_scalar_pointee_row; + + /* Selecting a row designates an array, which immediately decays + * back to a pointer to its first element for a following postfix + * subscript. Keep that element pointer depth instead of consuming + * an indirection as though the row itself were a pointer object. + */ + lvalue->value_ptr_level = + indexes_pointee_row + ? indexed_ptr_level + : (indexed_ptr_level ? indexed_ptr_level - 1 : 0); + + /* if nested pointer, still pointer Also handle typedef pointers + * which have ptr_level == 0 + */ + if (indexes_direct_pointee_array) { + /* Preserve the row's base element separately. The row itself + * must retain its existing address lowering, but a following + * subscript reads a pointer element whose value cannot carry a + * lexical alias's outer row-pointer descriptor. + */ + pointer_row_element_type = + var->type->pointee_array_element_type + ? var->type->pointee_array_element_type + : var->type; + } else if (!subscript_depth && is_array_declarator(var) && + var->type->array_element_ptr_level > 0) { + pointer_row_element_type = var->type->array_element_type + ? var->type->array_element_type + : var->type; + } else if (indexes_scalar_pointee_row || + indexes_loaded_scalar_pointee_row) { + var_t *row_source = + indexes_loaded_scalar_pointee_row ? loaded_scalar_row : var; + lvalue->type = + row_source->type->pointee_array_element_type + ? row_source->type->pointee_array_element_type + : row_source->type; + lvalue->size = lvalue->type->size; + } else if ((var->ptr_level <= 1 || is_typedef_pointer) && + !is_array_declarator(var)) { + /* For typedef pointers, get the size of the base type that the + * pointer points to + */ + if (lvalue->type->ptr_level > 0) { + type_t *pointee = + pointee_type_from_pointer_typedef(lvalue->type); + + /* void pointers retain the existing byte-stride extension; + * every other typedef pointer advances by its actual + * pointee, including a tagged record reached through a + * typedef alias. + */ + lvalue->size = + pointer_typedef_pointee_size(lvalue->type, pointee); + lvalue->type = pointee; + } else { + lvalue->size = lvalue->type->size; + } + + /* The pointee type above has every typedef star stripped. When + * the selected element is itself a pointer, as for `typedef int + * **ipp_t`, it still occupies a pointer slot, and so does the + * function pointer an `int (**fpp)(int)` selects. + */ + if (lvalue->value_ptr_level > 0 || + (var->pointee_func_signature && !subscript_depth && + var->ptr_level == 1)) + lvalue->size = PTR_SIZE; + } + + read_expr(parent, bb); + + /* Multiply by the remaining element extent. */ + int multiplier = lvalue->size; + + if (subscript_depth == 0 && var->array_dim2 > 0) { + multiplier = fixed_array_subscript_stride(var, subscript_depth, + lvalue->size); + } else if (subscript_depth == 0 && var->pointee_array_size > 0 && + indexes_pointee_row) { + multiplier = fixed_pointee_array_subscript_stride( + var, subscript_depth, lvalue->size); + } else if (indexes_loaded_scalar_pointee_row) { + multiplier = fixed_pointee_array_subscript_stride( + loaded_scalar_row, 0, lvalue->size); + } else if (loaded_scalar_row) { + multiplier = fixed_pointee_array_subscript_stride( + loaded_scalar_row, subscript_depth - 1, lvalue->size); + } else if (subscript_depth >= 1 && var->pointee_array_size > 0) { + multiplier = fixed_pointee_array_subscript_stride( + var, subscript_depth, lvalue->size); + } else if ((subscript_depth == 1 && var->array_dim3 > 0) || + (subscript_depth == 2 && var->array_dim4 > 0)) { + multiplier = fixed_array_subscript_stride(var, subscript_depth, + lvalue->size); + } + + if (multiplier != 1) { + vd = require_var(parent); + vd->init_val = multiplier; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + + rs2 = opstack_pop(); + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_mul, vd, rs1, rs2, 0, NULL); + } + + rs2 = opstack_pop(); + rs1 = opstack_pop(); + vd = require_var(parent); + + /* A subscript expression computes the address of its selected + * element. Preserve that pointer provenance: unary '&' leaves an + * already-addressable subscript alone, and pointer subtraction must + * still know whether this is an int or struct element. + */ + vd->type = lvalue->type; + + /* The computed address points at the selected element. If that + * element is itself a pointer (for example, `int *items[]`), its + * value indirection remains inside the address type: `&items[i]` is + * `int **`, not `int *`. + */ + vd->ptr_level = lvalue->ptr_level + 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); + + lex_expect(T_close_square); + is_address_got = true; + + /* Fewer subscripts than the array's rank select a row, an array + * that decays to a pointer to its first element (C99 6.3.2.1) + * rather than an object to load. The computed address is already + * that pointer; a row of rows keeps its remaining bounds as the + * pointee shape, so `m[1]` of `int m[2][2][3]` is `int (*)[3]`. + */ + row_shape = fixed_array_shape_from_var(var); + if (is_array_declarator(var) && !indexes_pointee_row && + !loaded_scalar_row && subscript_depth + 1 < row_shape.rank) { + for (int i = 0; i < subscript_depth + 2; i++) + fixed_array_shape_drop_outer(&row_shape); + if (row_shape.rank) { + fixed_array_shape_to_pointee_var(vd, &row_shape); + vd->pointee_array_element_ptr_level = var->ptr_level; + } + vd->is_const_qualified = var->is_const_qualified; + lvalue->value_ptr_level = indexed_ptr_level; + array_row = vd; + } else if (loaded_scalar_row || (!is_array_declarator(var) && + is_pointee_array_pointer(var))) { + /* So do the later subscripts of a pointer to an array that + * leave some of its bounds: `p[0][1]` of `int (*p)[2][3]` is a + * row of three, not an element to load. A pointer loaded from a + * slot, as `pp[0]` is for `int (**pp)[2][3]`, counts its + * subscripts from there. + */ + var_t *row_source = loaded_scalar_row ? loaded_scalar_row : var; + int row_depth = subscript_depth - !!loaded_scalar_row; + + row_shape = fixed_array_shape_from_pointee_var(row_source); + if (row_depth > 0 && row_depth < row_shape.rank) { + for (int i = 0; i <= row_depth; i++) + fixed_array_shape_drop_outer(&row_shape); + vd->ptr_level = + row_source->pointee_array_element_ptr_level + 1; + if (row_shape.rank) { + fixed_array_shape_to_pointee_var(vd, &row_shape); + vd->pointee_array_element_ptr_level = + row_source->pointee_array_element_ptr_level; + } + vd->is_const_qualified = var->is_const_qualified; + lvalue->value_ptr_level = indexed_ptr_level; + array_row = vd; + } + } + + /* A following subscript loads only when this selected element is + * itself a pointer (for example, `int **p; p[0][1]`). A + * pointer-to-array selects a row, not a pointer object, so treating + * every subscript as a member would dereference row data on its + * next index. + */ + is_member = !indexes_pointee_row && !array_row && + lvalue->value_ptr_level > 0; + subscript_depth++; + lvalue->is_reference = !indexes_pointee_row && !array_row; + if (subscript_depth == 1 && var->pointee_array_size > 0 && + var->pointee_array_element_ptr_level == 0 && + effective_pointer_depth(var) == 2) + pending_scalar_row_pointer_slot = true; + if (indexes_fixed_array_pointer_slot) + pending_fixed_array_pointer_slot = true; + if (loaded_scalar_row) { + fixed_array_shape_t loaded_shape = + fixed_array_shape_from_pointee_var(loaded_scalar_row); + + if (subscript_depth >= 1 + loaded_shape.rank) + loaded_scalar_row = NULL; + } + + /* A multidimensional slot array carries the slot descriptor on the + * whole array. Only its final subscript selects an element; earlier + * subscripts select rows that decay for the next index. + */ + if (var->type->array_element_pointee_func_signature) { + int array_dims = 1 + !!var->array_dim2 + !!var->array_dim3 + + !!var->array_dim4; + + if (subscript_depth == array_dims) + lvalue->pointee_func_signature = + var->type->array_element_pointee_func_signature; + } + + /* A subscript designates the pointee, whose qualification is the + * declaration's base qualification rather than `* const`. A pointer + * element of a row selects the pointer object, which is const only + * through its own qualifier, not its pointee's. + */ + if (pointee_row_pointer_element(var, subscript_depth)) + lvalue->is_const_qualified = + const_pointee_row_element(var, subscript_depth); + else + lvalue->is_const_qualified = + var->is_const_qualified || + (lvalue->pointee_func_signature && + var->type->array_element_is_const_pointer) || + (record_is_const && is_array_declarator(var)) || + (subscript_depth == 1 && points_to_const_callback(var)); + } else { + char token[MAX_ID_LEN]; + + if (lex_accept(T_arrow)) { + /* The record is the pointee, qualified by the base of the + * pointer's declaration however the pointer itself is. + */ + record_is_const = var->is_const_qualified; + + /* resolve where the pointer points at from the calculated + * address in a structure. + */ + if (is_member) { + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, + NULL); + } + } else { + lex_expect(T_dot); + record_is_const = lvalue->is_const_qualified; + + if (!is_address_got) { + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, + NULL); + + is_address_got = true; + } + } + + lex_ident(T_identifier, token); + + /* change type currently pointed to */ + var = find_member(token, lvalue->type); + if (!var) + error_at("Unknown struct or union member", next_token_loc()); + lvalue->type = var->type; + lvalue->decl = var; + + /* The member, not the pointer row element it was selected from, now + * types the value that is read. + */ + pointer_row_element_type = NULL; + lvalue->ptr_level = var->ptr_level; + lvalue->value_ptr_level = var->ptr_level + var->type->ptr_level; + lvalue->is_func = var->is_func; + lvalue->size = get_size(var); + + /* As for a declared object, a pointer or array member is itself + * const only through its outer qualifier. + */ + lvalue->is_const_qualified = + record_is_const || + ((var->ptr_level || var->type->ptr_level || var->is_func || + var->array_size || var->has_unsized_array) + ? var->is_const_pointer + : var->is_const_qualified); + subscript_depth = 0; + + /* if it is an array, get the address of first element instead of + * its value. Any other member is an object to read, even when the + * base was a row that decayed to its address, as in `rows[1]->x`. + */ + lvalue->is_reference = !is_array_declarator(var); + + /* move pointer to offset of structure */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = var->offset; + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + + rs2 = opstack_pop(); + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + + if (is_array_declarator(var)) { + /* A one-dimensional array member designates its first element's + * address. Type it as that pointer, so the value can still be + * subscripted after a grouping: `(record.items)[1]`. + */ + if (!var->array_dim2) { + vd->type = var->type; + vd->ptr_level = var->ptr_level + 1; + } + + /* The decayed address points to the member's elements, which a + * const record makes const as well (C99 6.3.2.1p1). + */ + vd->is_const_qualified = + record_is_const || var->is_const_qualified; + } + opstack_push(vd); + add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); + + is_address_got = true; + is_member = true; + } + } + + lvalue->subscript_depth = subscript_depth; + lvalue->is_array = + array_row || + (!lvalue->is_reference && is_array_declarator(lvalue->decl) && + !lvalue->decl->is_func && + !is_function_parameter(lvalue->decl, parent)); + if (lvalue->is_array && lvalue_write_follows(prefix_op)) + error_at("assignment to expression with array type", next_token_loc()); + if (!lvalue->is_array && !lvalue->value_ptr_level && + is_record_type(lvalue->type) && + (prefix_op != OP_generic || lex_peek(T_increment, NULL) || + lex_peek(T_decrement, NULL))) + error_at("Operand of record type requires a scalar value", + next_token_loc()); + + if (!eval) + return; + + if (lvalue->is_const_qualified && + (prefix_op != OP_generic || lex_peek(T_increment, NULL) || + lex_peek(T_decrement, NULL))) + error_at("assignment of read-only location", next_token_loc()); + + lower_lvalue_tail(lvalue, array_row ? array_row : var, parent, bb, + prefix_op, pointer_row_element_type); +} + +void read_logical(opcode_t op, block_t *parent, basic_block_t **bb) +{ + var_t *vd; + + if (op != OP_log_and && op != OP_log_or) + error_at("encounter an invalid logical opcode in read_logical()", + cur_token_loc()); + + /* Test the operand before the logical-and/or operator */ + vd = opstack_pop(); + reject_record_operand(vd); + vd = materialize_function_designator(parent, bb, vd); + add_insn(parent, *bb, OP_branch, NULL, vd, NULL, 0, NULL); + + /* Create a proper branch label for the operand of the logical-and/or + * operation. + */ + basic_block_t *new_bb = bb_create(parent); + bb_connect(*bb, new_bb, op == OP_log_and ? THEN : ELSE); + + bb[0] = new_bb; +} + +void finalize_logical(opcode_t op, + block_t *parent, + basic_block_t **bb, + basic_block_t *shared_bb) +{ + basic_block_t *then, *then_next, *else_if, *else_bb; + basic_block_t *end = bb_create(parent); + var_t *vd, *log_op_res; + + if (op == OP_log_and) { + /* For example: a && b + * + * If handling the expression, the basic blocks will connect to each + * other as the following illustration: + * + * bb1 bb2 bb3 + * +-----------+ +-----------+ +---------+ + * | teq a, #0 | True | teq b, #0 | True | ldr 1 | + * | bne bb2 | ----> | bne bb3 | ----> | b bb5 | + * | b bb4 | | b bb4 | +---------+ + * +-----------+ +-----------+ | + * | | | + * | False | False | + * | | | + * | +---------+ +--------+ + * -------------> | ldr 0 | ------> | | + * | b bb5 | | | + * +---------+ +--------+ + * bb4 bb5 + * + * In this case, finalize_logical() should add some instructions to bb2 + * ~ bb5 and properly connect them to each other. + * + * Notice that + * - bb1 has been handled by read_logical(). + * - bb2 is equivalent to '*bb'. + * - bb3 needs to be created. + * - bb4 is 'shared_bb'. + * - bb5 needs to be created. + * + * Thus, here uses 'then', 'then_next', 'else_bb' and 'end' to + * respectively point to bb2 ~ bb5. Subsequently, perform the mentioned + * operations for finalizing. + */ + then = *bb; + then_next = bb_create(parent); + else_bb = shared_bb; + bb_connect(then, then_next, THEN); + bb_connect(then, else_bb, ELSE); + bb_connect(then_next, end, NEXT); + } else if (op == OP_log_or) { + /* For example: a || b + * + * Similar to handling logical-and operations, it should add some + * instructions to the basic blocks and connect them to each other for + * logical-or operations as in the figure: + * + * bb1 bb2 bb3 + * +-----------+ +-----------+ +---------+ + * | teq a, #0 | False | teq b, #0 | False | ldr 0 | + * | bne bb4 | ----> | bne bb4 | ----> | b bb5 | + * | b bb2 | | b bb3 | +---------+ + * +-----------+ +-----------+ | + * | | | + * | True | True | + * | | | + * | +---------+ +--------+ + * -------------> | ldr 1 | ------> | | + * | b bb5 | | | + * +---------+ +--------+ + * bb4 bb5 + * + * Similarly, here uses 'else_if', 'else_bb', 'then' and 'end' to + * respectively point to bb2 ~ bb5, and then finishes the finalization. + */ + then = shared_bb; + else_if = *bb; + else_bb = bb_create(parent); + bb_connect(else_if, then, THEN); + bb_connect(else_if, else_bb, ELSE); + bb_connect(then, end, NEXT); + } else + error_at("encounter an invalid logical opcode in finalize_logical()", + cur_token_loc()); + bb_connect(else_bb, end, NEXT); + + /* Create the branch instruction for final logical-and/or operand */ + vd = opstack_pop(); + reject_record_operand(vd); + add_insn(parent, op == OP_log_and ? then : else_if, OP_branch, NULL, vd, + NULL, 0, NULL); + + /* If handling logical-and operation, here creates a true branch for the + * logical-and operation and assigns a true value. + * + * Otherwise, create a false branch and assign a false value for logical-or + * operation. + */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = op == OP_log_and; + add_insn(parent, op == OP_log_and ? then_next : else_bb, OP_load_constant, + vd, NULL, NULL, 0, NULL); + + log_op_res = require_var(parent); + log_op_res->var_name = gen_name(); + add_insn(parent, op == OP_log_and ? then_next : else_bb, OP_assign, + log_op_res, vd, NULL, 0, NULL); + + /* After assigning a value, go to the final basic block, this is done by BB + * fallthrough. + */ + + /* Create the shared branch and assign the other value for the other + * condition of a logical-and/or operation. + * + * If handing a logical-and operation, assign a false value. else, assign a + * true value for a logical-or operation. + */ + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = op != OP_log_and; + add_insn(parent, op == OP_log_and ? else_bb : then, OP_load_constant, vd, + NULL, NULL, 0, NULL); + + add_insn(parent, op == OP_log_and ? else_bb : then, OP_assign, log_op_res, + vd, NULL, 0, NULL); + + log_op_res->is_logical_ret = true; + opstack_push(log_op_res); + + bb[0] = end; +} + +/* Parse the full expression grammar used by control statements. This keeps + * comma sequencing and conditional expressions consistent across if, loops, and + * switch while argument and initializer lists retain their own delimiter rules. + */ +void read_control_expression(block_t *parent, basic_block_t **bb) +{ + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + + while (lex_accept(T_comma)) { + discard_operand(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + } + + /* A function designator in a controlling expression decays to its code + * pointer before OP_branch tests it. Raw symbols intentionally have no + * allocated value, so branching on one directly can observe zero or an + * unrelated register instead of C99's non-null function address. + */ + var_t *result = opstack_pop(); + result = materialize_function_designator(parent, bb, result); + opstack_push(result); +} + +/* Expression statements use the same full-expression grammar as controls: parse + * an assignment or conditional expression, sequence top-level commas, then + * discard only the final value after its side effects have been emitted. + */ +basic_block_t *read_full_expression_statement(block_t *parent, + basic_block_t *bb) +{ + read_control_expression(parent, &bb); + discard_operand(parent, bb); + lex_expect(T_semicolon); + return bb; +} + +/* This is deliberately narrower than expression assignment recognition: a + * grouped pointer-to-row store has no general lvalue representation yet. + */ +static bool grouped_scalar_pointee_row_store_starts(block_t *parent) +{ + token_t *token = cur_token ? cur_token->next : NULL; + int depth = 0; + + if (!token || token->kind != T_open_bracket || !(token = token->next) || + token->kind != T_asterisk || !(token = token->next) || + token->kind != T_identifier || + !names_scalar_row_pointer(token, parent) || !(token = token->next) || + token->kind != T_close_bracket || !(token = token->next) || + token->kind != T_open_square) + return false; + for (; token; token = token->next) { + if (token->kind == T_open_square || token->kind == T_open_bracket || + token->kind == T_open_curly) + depth++; + else if (token->kind == T_close_square || + token->kind == T_close_bracket || + token->kind == T_close_curly) { + if (!--depth) + break; + } + } + if (!token || !(token = token->next) || + (token->kind != T_assign && token->kind != T_pluseq && + token->kind != T_minuseq)) + return false; + for (depth = 0, token = token->next; token; token = token->next) { + if (token->kind == T_open_square || token->kind == T_open_bracket || + token->kind == T_open_curly) + depth++; + else if (token->kind == T_close_square || + token->kind == T_close_bracket || token->kind == T_close_curly) + depth--; + else if (!depth && token->kind == T_comma) + return false; + else if (!depth && token->kind == T_semicolon) + return true; + } + return false; +} + +static basic_block_t *handle_grouped_scalar_pointee_row_store(block_t *parent, + basic_block_t *bb) +{ + char name[MAX_VAR_LEN]; + var_t *source, *row, *index, *address, *value; + + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_ident(T_identifier, name); + source = find_var(name, parent); + if (!source) + error_at("Undeclared identifier", cur_token_loc()); + if (!lower_pointee_array_dereference(source, parent, &bb)) + error_at("Grouped pointer-to-array store requires a scalar fixed row", + cur_token_loc()); + row = opstack_pop(); + if (row->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + lex_expect(T_close_bracket); + lex_expect(T_open_square); + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } + index = opstack_pop(); + lex_expect(T_close_square); + if (row->type->size != 1) { + var_t *scale = require_var(parent); + var_t *scaled = require_var(parent); + scale->var_name = gen_name(); + scale->init_val = row->type->size; + add_insn(parent, bb, OP_load_constant, scale, NULL, NULL, 0, NULL); + scaled->var_name = gen_name(); + add_insn(parent, bb, OP_mul, scaled, index, scale, 0, NULL); + index = scaled; + } + address = require_typed_ptr_var(parent, row->type, 1); + address->var_name = gen_name(); + add_insn(parent, bb, OP_add, address, row, index, 0, NULL); + opcode_t compound_op = OP_generic; + if (lex_accept(T_pluseq)) + compound_op = OP_add; + else if (lex_accept(T_minuseq)) + compound_op = OP_sub; + else + lex_expect(T_assign); + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } + value = opstack_pop(); + if (compound_op != OP_generic) { + if (value->is_func || is_pointer_like_value(value) || + is_record_type(value->type)) + error_at("Invalid compound assignment operand", cur_token_loc()); + var_t *current = require_typed_var(parent, row->type); + current->var_name = gen_name(); + add_insn(parent, bb, OP_read, current, address, NULL, row->type->size, + NULL); + current = integer_promote_operand(parent, &bb, current); + value = integer_promote_operand(parent, &bb, value); + normalize_integer_binary_operands(parent, &bb, compound_op, ¤t, + &value); + var_t *updated = require_var(parent); + updated->var_name = gen_name(); + updated->type = integer_binary_result_type(compound_op, current, value); + add_insn(parent, bb, compound_op, updated, current, value, 0, NULL); + value = resize_to(parent, &bb, updated, row->type, 0); + } + value = convert_stored_value(parent, &bb, value, row->type, 0); + add_insn(parent, bb, OP_write, NULL, address, value, row->type->size, NULL); + lex_expect(T_semicolon); + return bb; +} + +/* The middle operand of ?: is an expression, rather than merely an + * assignment-expression, so top-level commas belong to the selected true + * branch. Keep this small sequencing layer here until all full-expression entry + * points share the core comma grammar. + */ +void read_conditional_true_expression(block_t *parent, basic_block_t **bb) +{ + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + + while (lex_accept(T_comma)) { + discard_operand(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + } +} + +/* An integer constant expression with value zero is the sole scalar that can + * form a conditional pointer expression. Keep this small predicate local to + * `?:`: ordinary pointer conversions have their own qualifier diagnostics. + */ +bool is_null_pointer_constant(var_t *value) +{ + return value && value->is_const && !is_pointer_like_value(value) && + !value->is_func && !value->init_val && !value->init_val_hi; +} + +void read_ternary_operation(block_t *parent, basic_block_t **bb) +{ + var_t *vd; + + if (!lex_accept(T_question)) + return; + + /* ternary-operator */ + vd = opstack_pop(); + reject_record_operand(vd); + add_insn(parent, *bb, OP_branch, NULL, vd, NULL, 0, NULL); + + basic_block_t *then_ = bb_create(parent); + basic_block_t *else_ = bb_create(parent); + basic_block_t *end_ternary = bb_create(parent); + basic_block_t *then_entry = then_; + basic_block_t *else_entry = else_; + + /* true branch */ + read_conditional_true_expression(parent, &then_); + bb_connect(*bb, then_entry, THEN); + + if (!lex_accept(T_colon)) { + /* ternary operator in standard C needs three operands */ + error_at("Expected ':' in conditional expression", next_token_loc()); + } + + var_t *true_val = opstack_pop(); + + /* false branch */ + if (!read_assignment_expression(parent, &else_)) + read_expr(parent, &else_); + + /* The third operand has conditional-expression grammar, making nested + * conditionals right-associative: a ? b : c ? d : e. + */ + read_ternary_operation(parent, &else_); + bb_connect(*bb, else_entry, ELSE); + var_t *false_val = opstack_pop(); + + /* A function designator decays to a pointer in each conditional operand. + * Raw function symbols have no storage/defining IR, so materialize both + * branch values before the join assigns the selected callback. This is the + * same representation used by function-pointer assignment, casts, and + * returns; delaying it until after the join can leave OP_assign carrying a + * symbol with no allocated value and make a later indirect call jump to + * garbage. + */ + true_val = materialize_function_designator(parent, &then_, true_val); + false_val = materialize_function_designator(parent, &else_, false_val); + func_t *true_signature = get_func_signature(true_val); + func_t *false_signature = get_func_signature(false_val); + func_t *true_pointee_signature = true_val->pointee_func_signature; + func_t *false_pointee_signature = false_val->pointee_func_signature; + bool true_array = is_array_literal_placeholder(true_val); + bool false_array = is_array_literal_placeholder(false_val); + bool true_ptr_like = is_pointer_like_value(true_val); + bool false_ptr_like = is_pointer_like_value(false_val); + + /* The ternary result must look like whichever side is pointer-like. If the + * "true" expression is still a raw array literal but the "false" side is a + * plain scalar, materialize the literal now so both branches produce + * comparable scalar SSA values. + */ + true_val = scalarize_array_literal_if_needed( + parent, &then_, true_val, false_val ? false_val->type : NULL, + true_array && !false_ptr_like); + + /* Apply the same conversion symmetrically when only the false branch is a + * literal array. This prevents OP_assign from trying to move array storage + * into a scalar destination later in code generation. + */ + false_val = scalarize_array_literal_if_needed( + parent, &else_, false_val, true_val ? true_val->type : NULL, + false_array && !true_ptr_like); + + if (is_record_object(true_val) || is_record_object(false_val)) { + var_t *true_addr; + var_t *false_addr; + var_t *selected; + + if (!is_record_object(true_val) || !is_record_object(false_val) || + size_var(true_val) != size_var(false_val)) + error_at("Conditional record operands must have the same type", + cur_token_loc()); + + /* A record operand is an object, not a register value, so joining the + * two operands as scalars would keep only a truncated prefix. Select + * the operand's address instead and copy the chosen object into a + * temporary after the join, which dominates every later use. + */ + true_addr = record_value_address(parent, then_, true_val); + false_addr = record_value_address(parent, else_, false_val); + selected = require_ref_var(parent, true_val->type, 0); + selected->var_name = gen_name(); + add_insn(parent, then_, OP_assign, selected, true_addr, NULL, 0, NULL); + add_insn(parent, else_, OP_assign, selected, false_addr, NULL, 0, NULL); + bb_connect(then_, end_ternary, NEXT); + bb_connect(else_, end_ternary, NEXT); + + vd = require_typed_var(parent, true_val->type); + vd->var_name = gen_name(); + add_insn(parent, end_ternary, OP_allocat, vd, NULL, NULL, 0, NULL); + emit_record_copy_from_address(parent, &end_ternary, vd, selected); + opstack_push(vd); + bb[0] = end_ternary; + return; + } + + vd = require_var(parent); + vd->var_name = gen_name(); + if (true_pointee_signature || false_pointee_signature) { + func_t *signature = true_pointee_signature ? true_pointee_signature + : false_pointee_signature; + var_t *pointer_value = true_pointee_signature ? true_val : false_val; + var_t *other_value = true_pointee_signature ? false_val : true_val; + + if ((true_pointee_signature && false_pointee_signature && + !compatible_function_signature(true_pointee_signature, + false_pointee_signature)) || + (!other_value->pointee_func_signature && + !is_null_pointer_constant(other_value))) + error_at("Conditional callback slots must be compatible or null", + cur_token_loc()); + + /* A matching conditional slot remains non-callable until dereferenced, + * so preserve its pointee signature rather than presenting the outer + * pointer as a function pointer. + */ + vd->type = pointer_value->type; + vd->ptr_level = pointer_value->ptr_level; + vd->pointee_func_signature = signature; + } else if (true_signature || false_signature) { + func_t *signature = true_signature ? true_signature : false_signature; + var_t *pointer_value = true_signature ? true_val : false_val; + var_t *other_value = true_signature ? false_val : true_val; + + if ((true_signature && false_signature && + !compatible_function_signature(true_signature, false_signature)) || + (!get_func_signature(other_value) && + !is_null_pointer_constant(other_value))) + error_at("Conditional function pointers must be compatible or null", + cur_token_loc()); + + /* The branch join is itself a function-pointer value: retain the + * selected callback's pointer representation and prototype so a postfix + * call on `(condition ? first : second)` lowers indirectly. + */ + vd->type = pointer_value->type; + vd->ptr_level = 1; + vd->func_signature = signature; + } else if (!true_ptr_like && !false_ptr_like && !true_val->ptr_level && + !false_val->ptr_level) { + true_val = integer_promote_operand(parent, &then_, true_val); + false_val = integer_promote_operand(parent, &else_, false_val); + vd->type = integer_binary_result_type(OP_add, true_val, false_val); + true_val = resize_to(parent, &then_, true_val, vd->type, 0); + false_val = resize_to(parent, &else_, false_val, vd->type, 0); + } else if (!true_array && !false_array) { + /* With a pointer operand the result has that pointer's type, whether + * the other operand is a pointer or a null pointer constant (C99 + * 6.5.15p6). An array operand converts to a pointer to its element. + */ + var_t *pointer_value = true_ptr_like ? true_val : false_val; + + vd->type = pointer_value->type; + vd->ptr_level = pointer_value->ptr_level + !!pointer_value->array_size; + } + add_insn(parent, then_, OP_assign, vd, true_val, NULL, 0, NULL); + add_insn(parent, else_, OP_assign, vd, false_val, NULL, 0, NULL); + + /* Recursive conditionals advance then_/else_ to their completed branch + * tails. Join those tails, not the original entries, to the outer end. + */ + bb_connect(then_, end_ternary, NEXT); + bb_connect(else_, end_ternary, NEXT); + + var_t *array_ref = NULL; + if (is_array_literal_placeholder(true_val)) + array_ref = true_val; + else if (is_array_literal_placeholder(false_val)) + array_ref = false_val; + + if (array_ref) { + vd->array_size = array_ref->array_size; + vd->init_val = array_ref->init_val; + vd->type = array_ref->type; + } + + vd->is_ternary_ret = true; + opstack_push(vd); + bb[0] = end_ternary; +} + +/* Lower the assignment whose left operand starts with the identifier ahead, + * declared by @var. There is nothing to lower when @var is NULL. + */ +bool read_body_assignment(var_t *var, + block_t *parent, + opcode_t prefix_op, + basic_block_t **bb, + var_t **assignment_result, + int lvalue_paren_depth) +{ + var_t *vd, *rs1, *rs2, *t; + if (assignment_result) + assignment_result[0] = NULL; + + if (var) { + int one = 0; + opcode_t op = OP_generic; + lvalue_t lvalue; + int size = 0; + + /* has memory address that we want to set */ + read_lvalue(&lvalue, var, parent, bb, false, OP_generic); + while (lvalue_paren_depth-- > 0) + lex_expect(T_close_bracket); + size = lvalue.size; + + if (lvalue.is_array && lvalue_write_follows(prefix_op)) + error_at("assignment to expression with array type", + next_token_loc()); + if (lvalue.is_const_qualified && lvalue_write_follows(prefix_op)) + error_at(lvalue.is_reference ? "assignment of read-only location" + : "assignment of read-only variable", + next_token_loc()); + + if (lex_accept(T_increment)) { + op = OP_add; + one = 1; + } else if (lex_accept(T_decrement)) { + op = OP_sub; + one = 1; + } else if (accept_compound_assign_op(&op)) { + /* op now holds the arithmetic the operator applies */ + } else if (lex_peek(T_open_bracket, NULL)) { + /* Dereference lvalue first if lvalue is a member access; otherwise, + * pass the function pointer value on the stack to + * read_indirect_call. + */ + if (lvalue.is_reference) { + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); + } + + emit_indirect_call_result(lvalue.decl, + get_func_signature(lvalue.decl), false, + parent, bb); + return true; + } else if (prefix_op == OP_generic) { + lex_expect(T_assign); + } else { + op = prefix_op; + one = 1; + } + + if (op != OP_generic) { + int increment_size = 1; + + if ((op == OP_add || op == OP_sub) && + is_direct_void_pointer_type(lvalue.type, lvalue.ptr_level)) + error_at("Pointer arithmetic on void* is invalid", + cur_token_loc()); + + /* A pointer is shifted by its element size, as ++ and -- step it, + * including a pointer selected by a subscript or member. + */ + if (op == OP_add || op == OP_sub) + increment_size = lvalue_step_size(&lvalue, var); + + /* If operand is a reference, read the value and push to stack for + * the incoming addition/subtraction. Otherwise, use the top element + * of stack as the one of operands and the destination. + */ + if (one == 1) { + if (lvalue.is_reference) { + t = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = pointee_type_from_pointer_typedef(lvalue.type); + vd->ptr_level = lvalue.value_ptr_level; + opstack_push(vd); + if (is_bitfield(lvalue.decl)) { + opstack_pop(); + vd = read_bitfield_value(parent, bb, t, lvalue.decl); + opstack_push(vd); + } else { + add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, + NULL); + } + } else + t = operand_stack[operand_stack_idx - 1]; + + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = increment_size; + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + + rs2 = vd; + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); + + if (lvalue.is_reference) { + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, t, vd, lvalue.decl); + else { + vd = convert_stored_value(parent, bb, vd, lvalue.type, + lvalue.value_ptr_level); + add_insn(parent, *bb, OP_write, NULL, t, vd, size, + NULL); + } + } else { + vd = resize_var(parent, bb, vd, t); + mark_var_mutated(t); + add_insn(parent, *bb, OP_assign, t, vd, NULL, 0, NULL); + } + if (assignment_result) { + if (!lvalue.is_reference) { + assignment_result[0] = vd; + } else if (is_bitfield(lvalue.decl)) { + var_t *stored = reload_assigned_bitfield(parent, bb, t, + lvalue.decl); + assignment_result[0] = stored; + } else { + var_t *stored = require_typed_var(parent, lvalue.type); + stored->ptr_level = lvalue.value_ptr_level; + stored->var_name = gen_name(); + stored->is_assignment_reload = true; + add_insn(parent, *bb, OP_read, stored, t, NULL, + lvalue.size, NULL); + assignment_result[0] = stored; + } + } + } else { + if (lvalue.is_reference) { + t = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = pointee_type_from_pointer_typedef(lvalue.type); + vd->ptr_level = lvalue.value_ptr_level; + opstack_push(vd); + if (is_bitfield(lvalue.decl)) { + opstack_pop(); + vd = read_bitfield_value(parent, bb, t, lvalue.decl); + opstack_push(vd); + } else { + add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, + NULL); + } + } else + t = operand_stack[operand_stack_idx - 1]; + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + + /* read_expr stops before `?`; compound assignment has the + * same conditional-expression RHS grammar as ordinary + * assignment. + */ + read_ternary_operation(parent, bb); + } + + var_t *rhs_val = opstack_pop(); + reject_record_operand(rhs_val); + rhs_val = scalarize_array_literal_if_needed( + parent, bb, rhs_val, lvalue.type, + !lvalue.ptr_level && + !(lvalue.type && lvalue.type->ptr_level) && + !lvalue.is_reference); + + /* Promote before scaling by the element size: the scaled + * product is typed int, so an unsigned char or unsigned short + * that is still in its narrow representation would otherwise + * lose its zero extension, and `sum += c` with c = 200 would + * add -56 to a wider left operand. + */ + rhs_val = integer_promote_operand(parent, bb, rhs_val); + opstack_push(rhs_val); + vd = require_var(parent); + vd->init_val = increment_size; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + + rs2 = opstack_pop(); + rs1 = opstack_pop(); + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = integer_binary_result_type(OP_mul, rs1, rs2); + opstack_push(vd); + add_insn(parent, *bb, OP_mul, vd, rs1, rs2, 0, NULL); + + rs2 = opstack_pop(); + rs1 = opstack_pop(); + + /* Compound assignment performs the same integer promotions and + * usual arithmetic conversions as the corresponding binary + * operator, then converts the result back to the lvalue type. + * In particular, unsigned char and unsigned short must be + * promoted before unsigned division, rather than being consumed + * as sign-extended byte/halfword values by the backend. + */ + if (!is_pointer_operation(op, rs1, rs2)) { + rs1 = integer_promote_operand(parent, bb, rs1); + rs2 = integer_promote_operand(parent, bb, rs2); + normalize_integer_binary_operands(parent, bb, op, &rs1, + &rs2); + } + vd = require_var(parent); + vd->var_name = gen_name(); + vd->type = integer_binary_result_type(op, rs1, rs2); + add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); + + if (lvalue.is_reference) { + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, t, vd, lvalue.decl); + else { + /* Convert back to the object type before the store, as + * the direct-variable path does. The store alone + * narrows the bytes, but the expression value is this + * register, and a _Bool object must receive 0 or 1 + * rather than the low byte of the sum. + */ + vd = resize_to(parent, bb, vd, lvalue.type, + lvalue.value_ptr_level); + add_insn(parent, *bb, OP_write, NULL, t, vd, + lvalue.size, NULL); + } + } else { + vd = resize_var(parent, bb, vd, t); + add_insn(parent, *bb, OP_assign, t, vd, NULL, 0, NULL); + } + if (assignment_result) { + /* Compound assignment has the value retained by its left + * operand. A bit-field store can truncate or sign-extend + * the arithmetic result, so its expression value must be + * reloaded just like an ordinary bit-field assignment. + */ + if (is_bitfield(lvalue.decl)) { + var_t *stored = reload_assigned_bitfield(parent, bb, t, + lvalue.decl); + assignment_result[0] = stored; + } else { + assignment_result[0] = vd; + } + } + } + } else { + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + + if (lvalue.is_func) { + rs2 = opstack_pop(); + rs1 = opstack_pop(); + if (!lvalue.subscript_depth) + diagnose_function_pointer_conversion(rs2, lvalue.decl); + + /* is_func labels both function symbols and function-pointer + * variables. A variable on the RHS must contribute its stored + * pointer value, rather than its identifier being lowered as a + * function address. + */ + if (rs2->is_func && find_var(rs2->var_name, parent) == rs2) { + t = require_ref_var(parent, rs2->type, rs2->ptr_level); + t->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, t, rs2, NULL, 0, NULL); + + vd = require_var(parent); + vd->var_name = gen_name(); + add_insn(parent, *bb, OP_read, vd, t, NULL, PTR_SIZE, NULL); + rs2 = vd; + } + + /* Acquire destination address of lvalue if lvalue is a local + * variable. + */ + if (!lvalue.is_reference) { + var_t *addr = + require_ref_var(parent, lvalue.type, lvalue.ptr_level); + addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, addr, rs1, NULL, 0, + NULL); + rs1 = addr; + } + + add_insn(parent, *bb, OP_write, NULL, rs1, rs2, PTR_SIZE, NULL); + if (assignment_result) + assignment_result[0] = rs2; + } else if (lvalue.is_reference) { + rs2 = opstack_pop(); + rs1 = opstack_pop(); + if (!lvalue.subscript_depth) + diagnose_function_pointer_conversion(rs2, lvalue.decl); + if (lvalue.pointee_func_signature) { + /* `slots[index]` denotes a callback slot whose descriptor + * is carried by the array, not by its scalar base type. + * Reconstruct that element target before writing so an + * ordinary indexed assignment observes the same prototype + * rule as a brace initializer. + */ + var_t element_target = {0}; + + element_target.type = lvalue.decl->type; + element_target.ptr_level = + lvalue.decl->type->array_element_ptr_level; + element_target.pointee_func_signature = + lvalue.pointee_func_signature; + if (incompatible_pointee_callback_conversion( + rs2, &element_target)) + error_at( + "incompatible callback slot types in array " + "assignment", + cur_token_loc()); + } + bool record_store = is_record_type(lvalue.type) && + !lvalue.value_ptr_level && + is_record_object(rs2); + + /* A record and a scalar do not convert to each other. */ + if (!record_store && is_record_object(rs2)) + error_at("incompatible types in assignment", + cur_token_loc()); + + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); + else if (record_store) + emit_record_copy_to_address(parent, bb, rs1, rs2); + else { + rs2 = convert_stored_value(parent, bb, rs2, lvalue.type, + lvalue.value_ptr_level); + add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); + } + if (assignment_result && record_store) { + /* The stored record is a copy of the right operand. */ + assignment_result[0] = rs2; + } else if (assignment_result) { + /* The value of an assignment is the value retained by its + * left operand. A bit-field store may mask or sign-extend + * the source, so reload its extracted value instead of + * leaking the unconverted right operand. + */ + if (is_bitfield(lvalue.decl)) { + var_t *stored = reload_assigned_bitfield( + parent, bb, rs1, lvalue.decl); + assignment_result[0] = stored; + } else { + var_t *stored = require_typed_var(parent, lvalue.type); + stored->ptr_level = lvalue.value_ptr_level; + stored->var_name = gen_name(); + stored->is_assignment_reload = true; + add_insn(parent, *bb, OP_read, stored, rs1, NULL, size, + NULL); + assignment_result[0] = stored; + } + } + } else { + rs1 = opstack_pop(); + vd = opstack_pop(); + rs1 = materialize_function_designator(parent, bb, rs1); + if (is_record_object(vd) != is_record_object(rs1) && + !rs1->is_func) + error_at("incompatible types in assignment", + cur_token_loc()); + if (is_record_object(vd) && is_record_object(rs1)) { + emit_record_copy(parent, bb, vd, rs1); + + /* C99 assignment expressions have the assigned value, + * including struct and union copies. The source record is + * that value after the copy and can feed a grouped or + * larger expression without re-lowering the assignment. + */ + if (assignment_result) + assignment_result[0] = rs1; + } else if (incompatible_character_pointer_conversion(rs1, vd)) { + error_at( + "incompatible character pointer types in assignment", + cur_token_loc()); + } else if (incompatible_pointee_callback_conversion(rs1, vd)) { + error_at("incompatible callback slot types in assignment", + cur_token_loc()); + } else { + /* A string literal only warns here, and its assignment + * still takes place. + */ + diagnose_const_pointer_conversion(rs1, vd); + diagnose_integer_to_pointer_conversion(rs1, vd, false); + diagnose_function_pointer_conversion(rs1, vd); + rs1 = resize_var(parent, bb, rs1, vd); + mark_var_mutated(vd); + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + if (assignment_result) + assignment_result[0] = rs1; + } + } + } + return true; + } + + return false; +} + +/* Assignment is right-associative and binds more weakly than the expression + * parser's binary operators. Existing statement parsing has an lvalue-aware + * lowering path; use a side-effect-free token scan to select that path before + * parsing an expression, rather than attempting to rewind emitted IR. + */ +bool assignment_expression_follows(void) +{ + int depth = 0; + + for (token_t *t = cur_token->next; t; t = t->next) { + switch (t->kind) { + case T_open_bracket: + case T_open_square: + case T_open_curly: + depth++; + break; + case T_close_bracket: + case T_close_square: + case T_close_curly: + if (!depth) + return false; + depth--; + break; + case T_assign: + case T_pluseq: + case T_minuseq: + case T_asteriskeq: + case T_divideeq: + case T_modeq: + case T_lshifteq: + case T_rshifteq: + case T_andeq: + case T_oreq: + case T_xoreq: + if (!depth) + return true; + break; + case T_question: + /* `?:` has lower precedence than assignment. An assignment in an + * arm belongs to that conditional expression, not to the leading + * lvalue that this scanner is classifying. + */ + if (!depth) + return false; + break; + case T_comma: + case T_semicolon: + case T_eof: + if (!depth) + return false; + break; + default: + break; + } + } + return false; +} + +/* A compound literal begins with a parenthesized type name followed by `{`. Its + * initializer may itself contain `=`, and the compound-literal lowering owns + * the assignment which follows the closing brace. + */ +bool compound_literal_starts_expression(void) +{ + token_t *t = cur_token->next; + int depth = 0; + + if (!t || t->kind != T_open_bracket) + return false; + for (; t; t = t->next) { + if (t->kind == T_open_bracket) + depth++; + else if (t->kind == T_close_bracket && --depth == 0) + return t->next && t->next->kind == T_open_curly; + } + return false; +} + +/* Whether the assignment ahead has a parenthesized left operand followed by + * postfix operators, `(*q).items[1] = v` or `(q.slots[1])->value = v`, that the + * dedicated dereference and identifier paths below cannot follow. A plain + * member chain on a dereference, `(*q).a.b = v`, stays with the dereference + * path. + */ +static bool grouped_postfix_lvalue_follows(void) +{ + token_t *token = cur_token->next; + token_t *inner; + int depth = 0; + + if (!token || token->kind != T_open_bracket) + return false; + inner = token->next; + for (; token; token = token->next) { + if (token->kind == T_open_bracket) + depth++; + else if (token->kind == T_close_bracket && !--depth) + break; + } + if (!token || !(token = token->next) || + (token->kind != T_dot && token->kind != T_arrow && + token->kind != T_open_square)) + return false; + if (inner && inner->kind == T_asterisk) { + while (token && token->kind == T_dot && token->next && + token->next->kind == T_identifier) + token = token->next->next; + if (token && token->kind != T_dot && token->kind != T_arrow && + token->kind != T_open_square) + return false; + } + return true; +} + +/* Whether the next token assigns to what precedes it. */ +static bool assignment_operator_follows(void) +{ + token_t *token = cur_token->next; + + if (!token) + return false; + switch (token->kind) { + case T_assign: + case T_pluseq: + case T_minuseq: + case T_asteriskeq: + case T_divideeq: + case T_modeq: + case T_lshifteq: + case T_rshifteq: + case T_xoreq: + case T_oreq: + case T_andeq: + return true; + default: + return false; + } +} + +/* Whether the assignment ahead has a call result as the root of its left + * operand, `one()->y = v` or `rows()[0][1].x = v`. No declaration describes + * such an operand, so the lvalue parser cannot follow it, but the postfix + * lowering records the address of what it reaches. + */ +static bool call_postfix_lvalue_follows(void) +{ + token_t *token = cur_token->next; + int depth = 0; + + if (!token || token->kind != T_identifier) + return false; + token = token->next; + if (!token || token->kind != T_open_bracket) + return false; + for (; token; token = token->next) { + if (token->kind == T_open_bracket) + depth++; + else if (token->kind == T_close_bracket && !--depth) + break; + } + if (!token || !(token = token->next)) + return false; + return token->kind == T_dot || token->kind == T_arrow || + token->kind == T_open_square; +} + +/* Assign through a left operand that the expression parser lowers as a value, + * using the address that value records. + */ +static void read_grouped_postfix_assignment(block_t *parent, basic_block_t **bb) +{ + var_t *target; + var_t *address; + var_t *value; + var_t *result; + opcode_t compound_op = OP_generic; + bool pointer_object; + int size; + + read_expr_operand(parent, bb); + target = opstack_pop(); + + /* A subscript of a call result owns the assignment that follows it, so the + * operand above has already stored the value this expression has. + */ + if (!assignment_operator_follows()) { + opstack_push(target); + return; + } + if (!target->is_compound_literal_reference || target->array_size || + target->is_func) + error_at("Assignment requires a modifiable lvalue", cur_token_loc()); + pointer_object = effective_pointer_depth(target) > 0; + if (pointer_object ? target->is_const_pointer : target->is_const_qualified) + error_at("assignment of read-only location", cur_token_loc()); + address = target->compound_literal_address; + size = pointer_object || target->func_signature ? PTR_SIZE + : target->type->size; + if (!lex_accept(T_assign) && !accept_compound_assign_op(&compound_op)) + error_at("Expected assignment operator", cur_token_loc()); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + value = opstack_pop(); + + if (is_record_object(target)) { + if (compound_op != OP_generic || !is_record_object(value)) + error_at("Invalid record assignment", cur_token_loc()); + emit_record_copy_to_address(parent, bb, address, value); + opstack_push(value); + return; + } + + /* A scalar object takes an arithmetic operand, and a pointer object only + * steps by an integer. + */ + if (is_record_object(value) || + (compound_op != OP_generic && + (value->is_func || is_pointer_like_value(value) || + (pointer_object && compound_op != OP_add && compound_op != OP_sub)))) + error_at("Invalid assignment operand", cur_token_loc()); + + if (compound_op != OP_generic) { + var_t *current; + var_t *combined; + + if (target->compound_literal_bitfield) + current = read_bitfield_value(parent, bb, address, + target->compound_literal_bitfield); + else { + current = + require_typed_ptr_var(parent, target->type, target->ptr_level); + current->var_name = gen_name(); + add_insn(parent, *bb, OP_read, current, address, NULL, size, NULL); + } + if (is_pointer_operation(compound_op, current, value)) { + handle_pointer_arithmetic(parent, bb, compound_op, current, value); + combined = opstack_pop(); + } else { + current = integer_promote_operand(parent, bb, current); + value = integer_promote_operand(parent, bb, value); + normalize_integer_binary_operands(parent, bb, compound_op, ¤t, + &value); + combined = require_var(parent); + combined->var_name = gen_name(); + combined->type = + integer_binary_result_type(compound_op, current, value); + add_insn(parent, *bb, compound_op, combined, current, value, 0, + NULL); + } + value = combined; + } + + if (target->compound_literal_bitfield) { + write_bitfield_value(parent, bb, address, value, + target->compound_literal_bitfield); + result = reload_assigned_bitfield(parent, bb, address, + target->compound_literal_bitfield); + opstack_push(result); + return; + } + if (!value->is_func) + value = resize_to(parent, bb, value, target->type, target->ptr_level); + add_insn(parent, *bb, OP_write, NULL, address, value, size, NULL); + + /* The assignment has the value the object then holds. */ + result = require_typed_ptr_var(parent, target->type, target->ptr_level); + result->var_name = gen_name(); + result->func_signature = target->func_signature; + add_insn(parent, *bb, OP_read, result, address, NULL, size, NULL); + opstack_push(result); +} + +/* The array named after the unary stars at the next tokens when an assignment + * operator follows the name, as in `*values = v` or `**rows += v`: the store + * goes to an element. @stars receives the number of stars before the name, the + * one already consumed not counted. + * + * Returns NULL for any other operand. + */ +static var_t *dereferenced_array_store_base(block_t *parent, int *stars) +{ + token_t *token = cur_token->next; + var_t *array; + + *stars = 0; + while (token && token->kind == T_asterisk) { + stars[0]++; + token = token->next; + } + if (!token || token->kind != T_identifier || !token->next) + return NULL; + array = find_var(token->literal, parent); + if (!array || !is_array_declarator(array) || array->is_func || + array->func_signature || array->pointee_func_signature) + return NULL; + switch (token->next->kind) { + case T_assign: + case T_pluseq: + case T_minuseq: + case T_asteriskeq: + case T_divideeq: + case T_modeq: + case T_lshifteq: + case T_rshifteq: + case T_xoreq: + case T_oreq: + case T_andeq: + return array; + default: + return NULL; + } +} + +bool read_assignment_expression(block_t *parent, basic_block_t **bb) +{ + char token[MAX_ID_LEN]; + var_t *var = NULL; + var_t *result; + int lvalue_paren_depth = 0; + + if (!assignment_expression_follows()) + return false; + if (compound_literal_starts_expression()) + return false; + + /* An identifier-rooted left operand needs its declaration either way, so + * look it up once for both the swapped-subscript test and the assignment. + */ + if (lex_peek(T_identifier, token)) + var = find_var(token, parent); + if ((var && is_swapped_subscript_base(var)) || + (lex_peek(T_numeric, NULL) && cur_token->next->next && + cur_token->next->next->kind == T_open_square) || + grouped_postfix_lvalue_follows() || call_postfix_lvalue_follows()) { + read_grouped_postfix_assignment(parent, bb); + return true; + } + + /* An assignment operator after a balanced parenthesized prefix means the + * parentheses wrap the left operand: `(item) = value`, unlike `(item = + * value)`, whose assignment is nested and therefore not seen by the + * top-level scan above. + */ + while (lex_accept(T_open_bracket)) + lvalue_paren_depth++; + + if (lex_peek(T_asterisk, NULL)) { + var_t *address; + var_t *object_address; + var_t *value; + var_t *field = NULL; + opcode_t compound_op = OP_generic; + int store_size; + type_t *value_type; + int value_ptr_level; + + /* Consume one dereference. read_expr() then evaluates its operand as an + * address: this also retains the established **pp and *(p + n) + * statement semantics. An array name directly before the assignment + * operator is read here instead, since read_lvalue() takes that + * spelling for an assignment to the array itself. + */ + lex_expect(T_asterisk); + int stars = 0; + var_t *array = dereferenced_array_store_base(parent, &stars); + + if (array) { + for (int i = 0; i < stars; i++) + lex_expect(T_asterisk); + lex_expect(T_identifier); + address = array; + for (int i = 0; i < stars; i++) { + push_dereference(parent, bb, address); + address = opstack_pop(); + } + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + address = opstack_pop(); + } + + /* An array operand stores through the pointer it decays to, unless its + * element is itself an array. + */ + if (is_array_declarator(address) && !address->is_func && + !address->func_signature && !address->pointee_func_signature) { + if (address->array_dim2) + error_at("assignment to expression with array type", + cur_token_loc()); + address = decay_dereferenced_array(parent, bb, address); + } + object_address = address; + while (lvalue_paren_depth-- > 0) + lex_expect(T_close_bracket); + + value_type = pointee_type_from_pointer_typedef(address->type); + value_ptr_level = address->ptr_level > 1 ? address->ptr_level - 1 : 0; + store_size = get_pointer_element_size(address); + + /* Parenthesized dereference may be followed by ordinary record-member + * postfixes: `(*pointer).member = value`. Resolve their address before + * selecting the shared scalar/bit-field store path. + */ + bool enclosing_const = false; + + if (lex_accept(T_dot)) { + type_t *record_type = value_type; + char field_name[MAX_ID_LEN]; + do { + lex_ident(T_identifier, field_name); + field = find_member(field_name, record_type); + if (!field) + error_at("Unknown record member", cur_token_loc()); + address = compute_field_address(parent, bb, address, field); + if (!lex_accept(T_dot)) + break; + if (!is_record_type(field->type) || field->ptr_level || + field->array_size) + error_at("Member access requires a record", + cur_token_loc()); + enclosing_const = enclosing_const || + field->is_const_qualified || + field->type->is_const_qualified; + record_type = field->type; + } while (true); + value_type = field->type; + value_ptr_level = field->ptr_level; + store_size = field->is_bitfield ? field->bit_storage_size + : field->type->size; + if (is_array_declarator(field)) + error_at("assignment to expression with array type", + cur_token_loc()); + } + + int address_depth = effective_pointer_depth(object_address); + unsigned int address_mask = + effective_pointer_const_mask(object_address); + + /* The member is read-only through its own qualifier, a pointer member's + * being on the pointer, or through a const record that encloses it (C99 + * 6.3.2.1p1). + */ + if ((field && (field->ptr_level || field->type->ptr_level + ? field->is_const_pointer + : field->is_const_qualified)) || + (field && + (object_address->is_const_qualified || enclosing_const)) || + address->is_const_qualified || + (address_depth > 1 && address_depth <= 32 && + (address_mask & (1U << (address_depth - 2)))) || + (!field && points_to_const_callback(object_address))) + error_at("assignment of read-only location", cur_token_loc()); + if (!lex_accept(T_assign) && !accept_compound_assign_op(&compound_op)) + error_at("Expected assignment after pointer dereference", + cur_token_loc()); + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + value = opstack_pop(); + + if (compound_op != OP_generic) { + var_t *current; + if (field && is_bitfield(field)) { + current = read_bitfield_value(parent, bb, address, field); + } else { + current = require_typed_var(parent, value_type); + current->ptr_level = value_ptr_level; + current->var_name = gen_name(); + add_insn(parent, *bb, OP_read, current, address, NULL, + store_size, NULL); + } + /* A pointer object steps by its element size. */ + if (is_pointer_operation(compound_op, current, value)) { + handle_pointer_arithmetic(parent, bb, compound_op, current, + value); + value = opstack_pop(); + } else { + current = integer_promote_operand(parent, bb, current); + value = integer_promote_operand(parent, bb, value); + normalize_integer_binary_operands(parent, bb, compound_op, + ¤t, &value); + var_t *combined = require_var(parent); + combined->var_name = gen_name(); + combined->type = + integer_binary_result_type(compound_op, current, value); + add_insn(parent, *bb, compound_op, combined, current, value, 0, + NULL); + value = combined; + } + } + + if (field && is_bitfield(field)) + write_bitfield_value(parent, bb, address, value, field); + else if (is_record_type(value_type) && !value_ptr_level) { + if (compound_op != OP_generic || !is_record_object(value)) + error_at("Invalid record assignment", cur_token_loc()); + emit_record_copy_to_address(parent, bb, address, value); + opstack_push(value); + return true; + } else { + value = convert_stored_value(parent, bb, value, value_type, + value_ptr_level); + add_insn(parent, *bb, OP_write, NULL, address, value, store_size, + NULL); + } + + /* Reload after the store: assignment expressions observe the stored + * object representation, including narrow integer conversion. + */ + if (field && is_bitfield(field)) { + result = reload_assigned_bitfield(parent, bb, address, field); + } else { + result = require_typed_var(parent, value_type); + result->ptr_level = value_ptr_level; + result->var_name = gen_name(); + result->is_assignment_reload = true; + add_insn(parent, *bb, OP_read, result, address, NULL, store_size, + NULL); + } + if (is_bool_scalar(result->type, result->ptr_level)) + result = normalize_bool(parent, bb, result); + opstack_push(result); + return true; + } + + /* Identifier-rooted lvalues include direct objects, member access, and + * subscripts through the shared lvalue parser. + */ + if (!lex_peek(T_identifier, token)) + return false; + if (lvalue_paren_depth) + var = find_var(token, parent); + + if (!read_body_assignment(var, parent, OP_generic, bb, &result, + lvalue_paren_depth)) + return false; + if (!result) + error_at("Assignment expression does not yield a value", + cur_token_loc()); + opstack_push(result); + return true; +} diff --git a/src/parser-global.c b/src/parser-global.c new file mode 100644 index 00000000..3580ed99 --- /dev/null +++ b/src/parser-global.c @@ -0,0 +1,1827 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Function definitions, file-scope declarations, and the parser entry point. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +/* Emit the optional initializer of a global declarator. Arrays and pointers + * written with a brace list go through the array initializer; everything else + * is a scalar constant. + */ +void read_global_init_var(var_t *var, block_t *block) +{ + if (!lex_accept(T_assign)) + return; + + var->has_initializer = true; + + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) + parse_array_init(var, block, &GLOBAL_FUNC->bbs); + else if (global_compound_literal_starts_here() && + !(var->ptr_level || var->type->ptr_level) && + is_record_type(var->type)) + parse_global_compound_record_init(var, block); + else if (global_compound_literal_starts_here() && + (var->ptr_level || var->type->ptr_level)) + parse_global_compound_array_init(var, block); + else if (global_compound_literal_starts_here()) + parse_global_compound_scalar_init(var, block); + else if (lex_peek(T_open_curly, NULL) && is_record_type(var->type)) + parse_global_record_init(var, block); + else + read_global_assignment_var(var); +} + +/* The declaration list of an old-style definition, `int f(x, y) int x; char *y; + * {`, which gives each identifier of the list its type (C99 6.9.1p6). Only + * register may be named as storage class, and C99 has no implicit int for a + * parameter the list leaves undeclared. + */ +static void read_identifier_list_declarations(func_t *func) +{ + if (!lex_peek(T_open_curly, NULL) && !lex_peek(T_identifier, NULL) && + !lex_peek(T_register, NULL) && !lex_peek(T_const, NULL) && + !lex_peek(T_volatile, NULL) && !lex_peek(T_signed, NULL) && + !lex_peek(T_unsigned, NULL) && !lex_peek(T_long, NULL) && + !lex_peek(T_struct, NULL) && !lex_peek(T_union, NULL) && + !lex_peek(T_enum, NULL)) + error_at("identifier list requires a function definition", + next_token_loc()); + + while (!lex_peek(T_open_curly, NULL)) { + var_t base = {0}; + bool is_register = lex_accept(T_register); + + base.scope = func->return_def.scope; + read_full_var_decl(&base, false, true, false); + for (var_t *decl = &base;;) { + var_t *param = NULL; + + for (int i = 0; i < func->num_params; i++) + if (!strcmp(func->param_defs[i].var_name, decl->var_name)) + param = &func->param_defs[i]; + if (!param) + error_at("declaration of a name not in the identifier list", + cur_token_loc()); + if (param->type) + error_at("duplicate parameter declaration", cur_token_loc()); + memcpy(param, decl, sizeof(var_t)); + param->is_register = is_register; + param->is_aggregate_param = + is_record_type(param->type) && !param->ptr_level; + if (!lex_accept(T_comma)) + break; + decl = &base; + memset(decl, 0, sizeof(var_t)); + decl->scope = func->return_def.scope; + decl->type = param->type; + read_inner_var_decl(decl, false, true, false); + } + lex_expect(T_semicolon); + } + for (int i = 0; i < func->num_params; i++) + if (!func->param_defs[i].type) + error_at("parameter type defaults to int, which C99 removed", + next_token_loc()); +} + +/* Whether the old-style parameter @defined agrees with the prototype parameter + * @declared: the prototype has the type the default argument promotions give + * the old-style one (C99 6.7.5.3p15). + */ +static bool identifier_list_param_matches(const var_t *defined, + const var_t *declared) +{ + if (parameter_changes_under_default_promotion(defined)) + return !declared->ptr_level && !declared->type->ptr_level && + !declared->is_func && declared->type == TY_int; + return compatible_function_param_decl(defined, declared); +} + +/* A declarator's base type is already known when this runs. Keeping function + * completion independent of how that type was spelled lets enum, record, and + * ordinary scalar declarations share linkage and redeclaration checks. + */ +bool read_global_function_declarator(block_t *block, + var_t *var, + bool is_static, + bool allow_definition) +{ + /* Functions and objects share C's ordinary identifier namespace at file + * scope. `var` is the provisional declarator for this function, so a + * different matching global object is a conflict rather than a function + * redeclaration. Without this check the back end emitted colliding labels + * and the resulting program could jump through object storage. + */ + var_t *object = find_global_var(var->var_name); + + if (object && object != var) + error_at("function declaration conflicts with global object", + next_token_loc()); + + func_t *func = find_func(var->var_name); + func_t func_tmp; + bool check_decl = false; + bool inherited_direct_function_type = + var->is_func && var->type->is_direct_function_type; + func_t *inherited_signature = var->func_signature; + + if (func) { + memcpy(&func_tmp, func, sizeof(func_t)); + check_decl = true; + } else { + func = add_func(var->var_name, false); + } + if (!var->is_block_scope_function_declaration) + func->is_block_scope_only_declaration = false; + else if (!check_decl) + func->is_block_scope_only_declaration = true; + + if (inherited_direct_function_type) { + memcpy(&func->return_def, &inherited_signature->return_def, + sizeof(var_t)); + func->return_def.var_name = var->var_name; + } else { + memcpy(&func->return_def, var, sizeof(var_t)); + } + func->returns_aggregate = is_record_type(func->return_def.type) && + !has_effective_pointer(&func->return_def); + + /* A typedef can hide an array return type: `typedef int row[2]; row + * f(void);` has no array suffix after the function declarator, but C99 + * still forbids a function from returning an array. Count pointer depth + * carried by both the declarator and its typedef, so a typedef-hidden + * pointer to that array remains a legal return type. + */ + if (!effective_pointer_depth(&func->return_def) && func->return_def.type && + func->return_def.type->array_size) + error_at("function cannot return an array type", next_token_loc()); + if (check_decl && !func_tmp.is_static && is_static) + error_at("static declaration follows non-static declaration", + next_token_loc()); + func->is_static = check_decl && func_tmp.is_static ? true : is_static; + func->is_inline = var->is_inline; + var_reset_subscripts(&func->return_def); + block->locals.size--; + + /* Parse this declarator independently of any earlier declaration. A later + * `f()` must not inherit stale parameter slots while a later typed list may + * legitimately refine an earlier unprototyped declaration. A block direct + * function typedef has already parsed its prototype, so copy that exact + * syntax-only signature instead of trying to consume another suffix. + */ + func->num_params = 0; + func->va_args = 0; + func->has_prototype = false; + memset(func->param_defs, 0, sizeof(func->param_defs)); + if (inherited_direct_function_type) { + func->num_params = inherited_signature->num_params; + func->va_args = inherited_signature->va_args; + func->has_prototype = inherited_signature->has_prototype; + memcpy(func->param_defs, inherited_signature->param_defs, + sizeof(func->param_defs)); + } else { + /* Parameter identifiers are optional in declarations. Definitions check + * for them below before body lowering makes parameter symbols visible. + */ + read_parameter_list_decl(func, true); + if (!func->has_prototype && func->num_params) + read_identifier_list_declarations(func); + } + + if (check_decl) { + if (!compatible_decl_type(func->return_def.type, + func_tmp.return_def.type) || + func->return_def.ptr_level != func_tmp.return_def.ptr_level || + func->return_def.is_const_qualified != + func_tmp.return_def.is_const_qualified) + error_at("conflicting types for function declaration", + next_token_loc()); + if (func->has_prototype && func_tmp.has_prototype) { + if (func->num_params != func_tmp.num_params || + func->va_args != func_tmp.va_args) + error_at("conflicting types for function declaration", + next_token_loc()); + for (int i = 0; i < func->num_params; i++) { + const var_t *now = &func->param_defs[i]; + const var_t *before = &func_tmp.param_defs[i]; + if (!compatible_function_param_decl(now, before)) + error_at("conflicting types for function declaration", + next_token_loc()); + } + } else if (func->has_prototype && !func_tmp.has_prototype && + func_tmp.num_params) { + /* A prototype after an old-style definition agrees with its + * promoted parameter types. + */ + if (func->num_params != func_tmp.num_params || func->va_args) + error_at("conflicting types for function declaration", + next_token_loc()); + for (int i = 0; i < func->num_params; i++) + if (!identifier_list_param_matches(&func_tmp.param_defs[i], + &func->param_defs[i])) + error_at("conflicting types for function declaration", + next_token_loc()); + } else if (strict_c99 && func->has_prototype && + !func_tmp.has_prototype) { + /* A variadic prototype and parameters promoted from char, short, or + * _Bool cannot be compatible with an earlier empty parameter list + * declaration. Keep the historical extension outside strict C99 + * mode, where existing old-style sources rely on it. + */ + if (func->va_args) + error_at("conflicting types for function declaration", + next_token_loc()); + for (int i = 0; i < func->num_params; i++) + if (parameter_changes_under_default_promotion( + &func->param_defs[i])) + error_at("conflicting types for function declaration", + next_token_loc()); + } else if (!func->has_prototype && func->num_params && + func_tmp.has_prototype) { + /* An old-style definition of a function declared with a prototype + * keeps its own parameter names and that prototype. + */ + if (func->num_params != func_tmp.num_params || func_tmp.va_args) + error_at("conflicting types for function declaration", + next_token_loc()); + for (int i = 0; i < func->num_params; i++) + if (!identifier_list_param_matches(&func->param_defs[i], + &func_tmp.param_defs[i])) + error_at("conflicting types for function declaration", + next_token_loc()); + func->has_prototype = true; + } else if (!func->has_prototype && func_tmp.has_prototype) { + /* An empty-list definition has no named parameters. It cannot + * define a function previously declared with fixed parameters or an + * ellipsis, even though a non-defining `f()` declaration may + * coexist with that prototype. + */ + if (lex_peek(T_open_curly, NULL) && + (func_tmp.num_params || func_tmp.va_args)) + error_at("conflicting types for function declaration", + next_token_loc()); + + /* A prior prototype remains visible after a compatible `f()` + * declaration and still constrains subsequent calls. + */ + memcpy(func->param_defs, func_tmp.param_defs, + sizeof(func->param_defs)); + func->num_params = func_tmp.num_params; + func->va_args = func_tmp.va_args; + func->has_prototype = true; + } + } + + if (lex_peek(T_open_curly, NULL)) { + /* C99 6.9.1 admits function definitions only as external declarations. + * Lowering one here would nest its body inside the enclosing function. + */ + if (var->is_block_scope_function_declaration) + error_at("function definition is not allowed at block scope", + next_token_loc()); + if (!allow_definition) + error_at("function definition must be the only declarator", + next_token_loc()); + if (inherited_direct_function_type && + !var->is_direct_function_declarator) + error_at("function definition cannot take its type from a typedef", + next_token_loc()); + if (check_decl && func_tmp.bbs) + error_at("redefinition of function", next_token_loc()); + if (is_incomplete_record_object(&func->return_def)) + error_at("function definition cannot return incomplete record type", + next_token_loc()); + for (int i = 0; i < func->num_params; i++) + if (!func->param_defs[i].var_name || + !func->param_defs[i].var_name[0]) + error_at("function definition parameter requires an identifier", + next_token_loc()); + else if (!func->param_defs[i].array_size && + !func->param_defs[i].has_unsized_array && + is_incomplete_record_object(&func->param_defs[i])) + error_at("Incomplete struct/union type cannot define an object", + next_token_loc()); + read_func_body(func); + return true; + } + + /* A prototype may be followed by further declarators of either kind, as in + * "int f(void), g(int), value;". Leave the comma to the list reader. + */ + if (lex_peek(T_comma, NULL)) + return false; + if (!lex_accept(T_semicolon)) + error_at("Syntax error in global declaration", next_token_loc()); + return true; +} + +/* A compatible repeated file-scope declaration names the same object. The + * parser creates a provisional var_t while reading its declarator, so discard + * that entry before emitting allocation or initializer IR and keep the first + * declaration's storage. + */ +var_t *resolve_global_declarator(block_t *block, + var_t *var, + bool is_static, + bool *is_redeclaration) +{ + var_t *previous = NULL; + + *is_redeclaration = false; + + /* The ordinary identifier namespace is shared with functions. This is + * intentionally before object redeclaration handling: a function is not a + * compatible tentative definition of an object, even when both happen to + * have the same declared scalar type. + */ + if (find_func(var->var_name)) + error_at("global object declaration conflicts with function", + next_token_loc()); + + for (int i = 0; i + 1 < block->locals.size; i++) { + var_t *candidate = block->locals.elements[i]; + if (!strcmp(candidate->var_name, var->var_name)) { + previous = candidate; + break; + } + } + if (!previous) + return var; + + *is_redeclaration = true; + + /* An array whose outer bound is omitted is compatible with any bound on the + * same element type (C99 6.7.5.2p6). Inner bounds must still agree; the + * outer product then follows from them, so compare it only when both + * declarations spell it. + */ + bool previous_is_array = + previous->array_size > 0 || previous->has_unsized_array; + bool var_is_array = var->array_size > 0 || var->has_unsized_array; + bool either_unsized = previous->has_unsized_array || var->has_unsized_array; + + if (!compatible_decl_type(previous->type, var->type) || + (!!previous->pointee_func_signature != !!var->pointee_func_signature) || + (previous->pointee_func_signature && + !compatible_function_signature(previous->pointee_func_signature, + var->pointee_func_signature)) || + previous->ptr_level != var->ptr_level || + previous_is_array != var_is_array || + (!either_unsized && previous->array_size != var->array_size) || + previous->array_dim2 != var->array_dim2 || + previous->array_dim3 != var->array_dim3 || + previous->array_dim4 != var->array_dim4 || + previous->pointee_array_size != var->pointee_array_size || + previous->pointee_array_dim2 != var->pointee_array_dim2 || + previous->pointee_array_dim3 != var->pointee_array_dim3 || + previous->pointee_array_dim4 != var->pointee_array_dim4 || + previous->is_const_qualified != var->is_const_qualified || + previous->is_volatile != var->is_volatile) + error_at("conflicting types for global declaration", next_token_loc()); + if (!previous->is_static && is_static) + error_at("static declaration follows non-static declaration", + next_token_loc()); + if (lex_peek(T_assign, NULL) && previous->has_initializer) + error_at("redefinition of global variable", next_token_loc()); + + /* A later bound completes the shared object's type. */ + if (previous->has_unsized_array && !var->has_unsized_array) { + previous->array_size = var->array_size; + previous->has_unsized_array = false; + } + + /* Scalar declarators were placed on the operand stack by + * read_inner_var_decl(). Its later initializer lowering pops that entry, so + * point it at the shared object rather than the discarded declaration. + */ + if (operand_stack_idx && operand_stack[operand_stack_idx - 1] == var) + operand_stack[operand_stack_idx - 1] = previous; + block->locals.size--; + return previous; +} + +/* Read one declarator after the first in a global declaration. Each shares the + * declaration's base type: "int a = 1, b, c = 3;". + */ +bool read_global_declarator(block_t *block, + type_t *decl_type, + bool is_const, + bool is_static, + bool is_volatile, + bool is_extern, + bool allow_definition) +{ + bool is_redeclaration; + var_t *nv = require_typed_var(block, decl_type); + nv->is_global = true; + nv->is_static = is_static; + nv->is_const_qualified = is_const; + nv->is_volatile = is_volatile; + read_inner_var_decl(nv, false, false, false); + nv->is_extern = is_extern && !lex_peek(T_assign, NULL); + if (lex_peek(T_open_bracket, NULL) || + (nv->is_func && nv->type->is_direct_function_type)) + return read_global_function_declarator(block, nv, is_static, + allow_definition); + bool is_definition = !nv->is_extern; + if (is_definition && is_incomplete_record_object(nv)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + nv = resolve_global_declarator(block, nv, is_static, &is_redeclaration); + if (is_definition && (!is_redeclaration || nv->is_extern)) { + nv->is_extern = false; + add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, nv, NULL, NULL, 0, NULL); + } + read_global_init_var(nv, block); + discard_global_declarator_operand(nv); + return false; +} + +void consume_global_compound_literal(void); + +/* Lower a scalar record initializer into the global initializer block. Global + * array elements already use parse_struct_field_init(); scalar records need the + * same field-address writes rather than merely consuming their braces. + */ +void parse_global_record_init(var_t *var, block_t *block) +{ + type_t *record_type = var->type; + block_t *saved_initializer_scope = global_constant_initializer_scope; + if (var->scope) + global_constant_initializer_scope = var->scope; + if (record_type->base_type == TYPE_typedef && record_type->base_struct) + record_type = record_type->base_struct; + + lex_expect(T_open_curly); + parse_struct_field_init(block, &GLOBAL_FUNC->bbs, record_type, var); + lex_expect(T_close_curly); + global_constant_initializer_scope = saved_initializer_scope; +} + +/* At file scope a compound literal has static storage duration. The target + * object is already global, so a record compound literal can use its normal + * constant aggregate lowering after consuming the spelled type name. + */ +void parse_global_compound_record_init(var_t *var, block_t *block) +{ + char type_name[MAX_ID_LEN]; + type_t *compound_type, *target_type; + + lex_expect(T_open_bracket); + base_type_t record_kind = accept_record_keyword(); + lex_ident(T_identifier, type_name); + lex_expect(T_close_bracket); + + compound_type = record_kind + ? find_record_tag(type_name, var->scope, record_kind) + : find_type(type_name, 1); + target_type = var->type; + if (target_type->base_type == TYPE_typedef && target_type->base_struct) + target_type = target_type->base_struct; + if (compound_type && compound_type->base_type == TYPE_typedef && + compound_type->base_struct) + compound_type = compound_type->base_struct; + if (!compound_type || !is_record_type(compound_type) || + compound_type != target_type) + error_at("Incompatible record compound literal", cur_token_loc()); + + if (!lex_peek(T_open_curly, NULL)) + error_at("Record compound literal needs an initializer", + next_token_loc()); + parse_global_record_init(var, block); +} + +/* A scalar compound literal at file scope also has static storage duration. Its + * sole initializer is the target object's constant initializer, so no temporary + * storage is necessary after validating the spelled scalar type. + */ +void parse_global_compound_scalar_init(var_t *var, block_t *block) +{ + type_t *compound_type; + + UNUSED(block); + + /* The type name may be spelled with keywords, as in `(unsigned long)`. */ + lex_expect(T_open_bracket); + compound_type = + read_type_name_specifiers(var->scope ? var->scope : GLOBAL_BLOCK); + lex_expect(T_close_bracket); + + if (!compound_type || is_record_type(compound_type) || var->ptr_level || + compound_type != var->type) + error_at("Incompatible scalar compound literal", cur_token_loc()); + + lex_expect(T_open_curly); + if (lex_peek(T_close_curly, NULL)) + error_at("Scalar compound literal needs an initializer", + next_token_loc()); + read_global_assignment_var(var); + if (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in scalar compound literal", + next_token_loc()); + lex_expect(T_close_curly); +} + +/* An array compound literal at file scope is an unnamed static array. Keep that + * array in the global initializer block so its backing storage survives for the + * full program, then initialize the declared pointer with its base. + */ +void parse_global_compound_array_init(var_t *var, block_t *block) +{ + type_t *element_type; + var_t *array; + int element_ptr_level = 0; + + /* The element type may be qualified or spelled with several keywords, as in + * `(const unsigned char[])`. + */ + lex_expect(T_open_bracket); + element_type = + read_type_name_specifiers(var->scope ? var->scope : GLOBAL_BLOCK); + while (lex_accept(T_asterisk)) { + element_ptr_level++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + + /* `(int (*[])[2]){...}` is an array whose elements are pointers to rows. + * The inner suffix supplies the backing array bound; the suffixes after `)` + * describe each pointer element's pointee. + */ + if (lex_accept(T_open_bracket)) { + int pointee_dims = 0; + + if (element_ptr_level || !lex_peek(T_asterisk, NULL)) + error_at("Array compound literal needs a pointer declarator", + cur_token_loc()); + do { + lex_expect(T_asterisk); + element_ptr_level++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } while (lex_peek(T_asterisk, NULL)); + lex_expect(T_open_square); + + array = require_typed_var(GLOBAL_BLOCK, element_type); + array->var_name = gen_name(); + array->is_global = true; + array->ptr_level = element_ptr_level; + if (!lex_peek(T_close_square, NULL)) { + array->array_size = read_const_expr(GLOBAL_BLOCK); + if (array->array_size <= 0) + error_at("Array compound literal needs a positive bound", + cur_token_loc()); + } else { + array->has_unsized_array = true; + } + lex_expect(T_close_square); + lex_expect(T_close_bracket); + while (lex_accept(T_open_square)) { + int bound = read_const_expr(GLOBAL_BLOCK); + + if (pointee_dims >= 4) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + if (bound <= 0) + error_at("Array size must be positive", cur_token_loc()); + if (pointee_dims == 0) + array->pointee_array_size = bound; + else { + if (pointee_dims == 1) + array->pointee_array_dim2 = bound; + else if (pointee_dims == 2) + array->pointee_array_dim3 = bound; + else + array->pointee_array_dim4 = bound; + array->pointee_array_size *= bound; + } + lex_expect(T_close_square); + pointee_dims++; + } + lex_expect(T_close_bracket); + + if (!element_type || element_type != var->type || + var->ptr_level != element_ptr_level + 1 || + var->pointee_array_size != array->pointee_array_size || + var->pointee_array_dim2 != array->pointee_array_dim2 || + var->pointee_array_dim3 != array->pointee_array_dim3 || + var->pointee_array_dim4 != array->pointee_array_dim4) + error_at("Incompatible array compound literal", cur_token_loc()); + if (!lex_peek(T_open_curly, NULL)) + error_at("Array compound literal needs an initializer", + next_token_loc()); + add_insn(GLOBAL_BLOCK, GLOBAL_FUNC->bbs, OP_allocat, array, NULL, NULL, + 0, NULL); + parse_array_init(array, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); + add_insn(block, GLOBAL_FUNC->bbs, OP_assign, var, array, NULL, 0, NULL); + return; + } + array = require_typed_var(GLOBAL_BLOCK, element_type); + array->var_name = gen_name(); + array->is_global = true; + array->ptr_level = element_ptr_level; + for (int dim = 0; lex_accept(T_open_square); dim++) { + int bound; + + if (dim >= 4) + error_at("Array compound literal supports at most four dimensions", + cur_token_loc()); + if (lex_peek(T_close_square, NULL)) { + if (dim) + error_at("Only the outer array bound may be inferred", + cur_token_loc()); + array->has_unsized_array = true; + lex_expect(T_close_square); + continue; + } + bound = read_const_expr(GLOBAL_BLOCK); + if (bound <= 0) + error_at("Array compound literal needs a positive bound", + cur_token_loc()); + if (!dim) + array->array_size = bound; + else { + if (dim == 1) + array->array_dim2 = bound; + else if (dim == 2) + array->array_dim3 = bound; + else + array->array_dim4 = bound; + array->array_size *= bound; + } + lex_expect(T_close_square); + } + lex_expect(T_close_bracket); + + /* The backing array has an outer compound-literal bound and the typedef + * element's inner row shape. parse_array_init() keeps that shape on var_t + * rather than type_t. + */ + if (element_type && element_type->array_size) { + int trailing = 1; + + if (element_type->array_dim2) + trailing *= element_type->array_dim2; + if (element_type->array_dim3) + trailing *= element_type->array_dim3; + if (element_type->array_dim4) + trailing *= element_type->array_dim4; + if (element_type->array_dim4) + error_at("Array compound literal supports at most four dimensions", + cur_token_loc()); + array->array_dim2 = element_type->array_size / trailing; + array->array_dim3 = element_type->array_dim2; + array->array_dim4 = element_type->array_dim3; + if (array->array_size) + array->array_size *= element_type->array_size; + } + + if (!element_type || element_type != var->type || + element_ptr_level + 1 != var->ptr_level || + var->pointee_array_size != array->array_dim2) + error_at("Incompatible array compound literal", cur_token_loc()); + if (!lex_peek(T_open_curly, NULL)) + error_at("Array compound literal needs an initializer", + next_token_loc()); + + add_insn(GLOBAL_BLOCK, GLOBAL_FUNC->bbs, OP_allocat, array, NULL, NULL, 0, + NULL); + parse_array_init(array, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); + add_insn(block, GLOBAL_FUNC->bbs, OP_assign, var, array, NULL, 0, NULL); +} + +/* Struct and union objects accept brace initializers, unlike scalar globals. + * Keep their continuation declarators on the same path as the first one so that + * linkage, qualifiers, and declarator-specific modifiers cannot diverge. + */ +bool read_global_record_declarator(block_t *block, + type_t *decl_type, + bool is_const, + bool is_static, + bool is_volatile, + bool is_extern, + bool allow_definition) +{ + bool is_redeclaration; + var_t *var = require_typed_var(block, decl_type); + var->is_global = true; + var->is_static = is_static; + var->is_const_qualified = is_const; + var->is_volatile = is_volatile; + read_inner_var_decl(var, false, false, false); + var->is_extern = is_extern && !lex_peek(T_assign, NULL); + if (lex_peek(T_open_bracket, NULL) || + (var->is_func && var->type->is_direct_function_type)) + return read_global_function_declarator(block, var, is_static, + allow_definition); + bool is_definition = !var->is_extern; + if (is_definition && is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + var = resolve_global_declarator(block, var, is_static, &is_redeclaration); + if (is_definition && (!is_redeclaration || var->is_extern)) { + var->is_extern = false; + add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); + } + + if (!lex_accept(T_assign)) { + discard_global_declarator_operand(var); + return false; + } + + var->has_initializer = true; + + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) { + parse_array_init(var, block, &GLOBAL_FUNC->bbs); + } else if (global_compound_literal_starts_here() && + (var->ptr_level || var->type->ptr_level)) { + parse_global_compound_array_init(var, block); + } else if (global_compound_literal_starts_here()) { + parse_global_compound_record_init(var, block); + } else if (lex_peek(T_open_curly, NULL)) { + parse_global_record_init(var, block); + } else { + read_global_assignment_var(var); + } + discard_global_declarator_operand(var); + return false; +} + +/* Read the declarators of a file-scope declaration through its terminating + * semicolon, or through the body of a function definition. Every declarator + * shares the base type, and object and function declarators may be mixed, as in + * "int f(void), g(int), value;"; a definition must stand alone. The caller has + * consumed a record or enum specifier, which qualifiers may still follow, as in + * "struct S volatile s;". + */ +void read_global_declarator_list(block_t *block, + type_t *decl_type, + bool is_const, + bool is_static, + bool is_volatile, + bool is_extern, + bool is_record) +{ + bool first = true; + + read_type_qualifiers(&is_const, &is_volatile, false); + do { + bool ended = + is_record + ? read_global_record_declarator(block, decl_type, is_const, + is_static, is_volatile, + is_extern, first) + : read_global_declarator(block, decl_type, is_const, is_static, + is_volatile, is_extern, first); + + if (ended) + return; + first = false; + } while (lex_accept(T_comma)); + lex_expect(T_semicolon); +} + +void read_global_decl(block_t *block, + bool is_const, + bool is_static, + bool is_extern, + bool is_inline, + bool is_volatile) +{ + bool is_redeclaration; + var_t *var = require_var(block); + var->is_global = true; + var->is_static = is_static; + var->is_inline = is_inline; + var->is_const_qualified = is_const; + var->is_volatile = is_volatile; + + /* new function, or variables under parent */ + read_full_var_decl(var, false, false, false); + var->is_extern = is_extern && !lex_peek(T_assign, NULL); + + /* `unary_t f;` with a function typedef declares the function f. */ + if (lex_peek(T_open_bracket, NULL) || + (var->is_func && var->type->is_direct_function_type)) { + if (read_global_function_declarator(block, var, is_static, true)) + return; + } else { + bool is_definition = !var->is_extern; + if (var->is_inline) + error_at("inline specifier requires a function declarator", + next_token_loc()); + if (is_definition && is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + var = + resolve_global_declarator(block, var, is_static, &is_redeclaration); + if (is_definition && (!is_redeclaration || var->is_extern)) { + var->is_extern = false; + add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, + NULL); + } + + /* is a variable */ + if (lex_peek(T_assign, NULL)) { + read_global_init_var(var, block); + } else if (lex_peek(T_semicolon, NULL)) { + } else if (!lex_peek(T_comma, NULL)) { + error_at("Syntax error in global declaration", next_token_loc()); + } + discard_global_declarator_operand(var); + } + + /* Continuation: "int a = 1, b, c = 3;" or "int f(void), g(int);". Every + * declarator after the first shares this declaration's base type and is + * handled exactly like the first, mirroring the struct-tagged global path. + */ + while (lex_accept(T_comma)) + if (read_global_declarator(block, var->type, var->is_const_qualified, + is_static, var->is_volatile, is_extern, + false)) + return; + + lex_expect(T_semicolon); +} + +void consume_global_compound_literal(void) +{ + lex_expect(T_open_curly); + + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + /* Just consume constant values for now */ + if (lex_peek(T_numeric, NULL)) { + lex_accept(T_numeric); + } else if (lex_peek(T_minus, NULL)) { + lex_accept(T_minus); + lex_accept(T_numeric); + } else if (lex_peek(T_string, NULL)) { + lex_accept(T_string); + } else if (lex_peek(T_char, NULL) || lex_peek(T_wchar, NULL)) { + lex_next(); + } else { + error_at( + "Global struct initialization requires constant values", + next_token_loc()); + } + + if (!lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + lex_expect(T_close_curly); +} + +void initialize_struct_field(var_t *nv, var_t *v, int offset) +{ + nv->type = v->type; + nv->var_name = ""; + nv->ptr_level = 0; + nv->is_func = false; + nv->is_global = false; + nv->is_const_qualified = false; + nv->array_size = 0; + nv->offset = offset; + nv->is_bitfield = false; + nv->bit_width = 0; + nv->bit_offset = 0; + nv->bit_storage_size = 0; + nv->init_val = 0; + nv->base = NULL; + nv->subscript = 0; + var_reset_subscripts(nv); + nv->is_compound_literal = false; +} + +/* The scalar or typedef-name specifier of a file-scope typedef, and any + * qualifiers mixed in among its keywords, which set @typedef_const and + * @typedef_volatile. + * + * Returns the specified type. + */ +static const type_t *read_global_typedef_base(bool *typedef_const, + bool *typedef_volatile) +{ + char base_type[MAX_ID_LEN]; + const type_t *base = + read_scalar_type_specifiers(typedef_const, typedef_volatile, NULL); + + if (!base) { + lex_ident(T_identifier, base_type); + base = find_type(base_type, true); + } + if (!base) + error_at("Unable to find base type", cur_token_loc()); + return base; +} + +/* One declarator of a file-scope typedef whose specifier resolved to @base, + * qualified by @typedef_const and @typedef_volatile: a scalar, pointer, array + * or function alias, or one of an enum or record. + */ +static void read_global_typedef_declarator(block_t *block, + bool typedef_const, + bool typedef_volatile, + const type_t *base) +{ + type_t *type = add_type(); + + type->base_type = base->base_type; + type->size = base->size; + + /* A typedef of a record typedef remains a record type. Sharing its + * immutable member table preserves ordinary `alias.member` and + * `pointer_alias->member` lookup instead of turning the alias into a scalar + * descriptor with no fields. + */ + type->fields = base->fields; + type->num_fields = base->num_fields; + type->base_struct = base->base_struct; + if (base->base_type == TYPE_typedef && base->num_fields && + !type->base_struct) + type->base_struct = (type_t *) base; + type->alignment = base->alignment; + type->is_union = base->is_union; + type->has_flexible_array_member = base->has_flexible_array_member; + type->ptr_level = base->ptr_level; + type->pointer_const_mask = base->pointer_const_mask; + type->is_const_qualified = typedef_const || base->is_const_qualified; + type->is_volatile_qualified = + typedef_volatile || base->is_volatile_qualified; + + /* `const ptr_t` qualifies the pointer that the base typedef hides, not its + * pointee. + */ + if (typedef_const && base->ptr_level && base->ptr_level <= 32 && + !base->func_signature && !base->pointee_func_signature) { + type->is_const_qualified = base->is_const_qualified; + type->pointer_const_mask |= 1U << (base->ptr_level - 1); + } + type->is_unsigned = base->is_unsigned; + type->is_floating = base->is_floating; + type->is_signed_char = base->is_signed_char; + type->is_bool = base->is_bool; + type->array_size = base->array_size; + type->array_dim2 = base->array_dim2; + type->array_dim3 = base->array_dim3; + type->array_dim4 = base->array_dim4; + type->array_element_ptr_level = base->array_element_ptr_level; + type->array_element_type = base->array_element_type; + type->func_signature = base->func_signature; + + /* A tag is not a typedef name. As for `typedef struct S alias`, the alias + * reaches the record through base_struct, which also sees a later + * completion of the tag. + */ + if (base->base_type == TYPE_struct || base->base_type == TYPE_union) { + type->base_type = TYPE_typedef; + type->base_struct = (type_t *) base; + type->is_union = base->base_type == TYPE_union; + } + + /* Handle pointer types in typedef: typedef char *string; */ + unsigned int star_const_mask = 0; + + while (lex_accept(T_asterisk)) { + type->ptr_level++; + type->size = PTR_SIZE; + while (true) { + if (lex_accept(T_const)) { + if (type->ptr_level <= 32) { + type->pointer_const_mask |= 1U << (type->ptr_level - 1); + star_const_mask |= 1U << (type->ptr_level - 1); + } + } else if (lex_accept(T_volatile)) { + /* As for an object declarator, a volatile pointer marks the + * whole declaration volatile. + */ + type->is_volatile_qualified = true; + } else if (lex_accept(T_restrict)) { + ; + } else + break; + } + } + + if (type->ptr_level == 1) + alias_callback_array_pointer(type, base, star_const_mask); + + /* A parenthesized declarator is the function-pointer form: `typedef int + * (*callback_t)(int)`. Parse it through the normal declarator reader so its + * prototype has exactly the same shape as an object declaration, then + * retain that syntax-only signature on the alias for each later object or + * parameter declaration. + */ + if (lex_peek(T_open_bracket, NULL)) { + var_t declarator = {0}; + bool saved_sizeof_signature = parsing_sizeof_function_signature; + + declarator.type = (type_t *) base; + declarator.scope = block; + declarator.ptr_level = type->ptr_level; + + /* A callback typedef carries only a function signature. Its floating + * parameters do not materialize values until a call, which remains + * rejected by the ordinary floating gates. + */ + parsing_sizeof_function_signature = true; + read_inner_var_decl(&declarator, false, false, false); + parsing_sizeof_function_signature = saved_sizeof_signature; + + /* Without a parameter list or an array suffix the parentheses only + * group pointers: `typedef int (*int_ptr)` is `typedef int *int_ptr`. + */ + if (!declarator.is_func && !declarator.array_size && + !declarator.has_unsized_array && !declarator.pointee_array_size && + !base->func_signature && !base->array_size) { + strncpy(type->type_name, declarator.var_name, MAX_TYPE_LEN - 1); + type->type_name[MAX_TYPE_LEN - 1] = '\0'; + type->ptr_level = declarator.ptr_level; + type->size = PTR_SIZE; + type->pointer_const_mask |= declarator.pointer_const_mask; + if (declarator.is_volatile) + type->is_volatile_qualified = true; + + /* `typedef int (**slot_t)(int)` names a callback slot. */ + type->pointee_func_signature = declarator.pointee_func_signature; + return; + } + + /* `typedef int (*row_ptr)[2]` points to a whole row. As a block-scope + * alias does, keep the pointer-sized descriptor and carry the row + * bounds and element separately. The spelled `int (*(*rows_t)[2])(int)` + * reads its row against the unnamed callback type, which the declarator + * then carries. + */ + if (declarator.type && declarator.type->func_signature && + !declarator.type->is_direct_function_type) + base = declarator.type; + if (!declarator.is_func && + declarator.has_direct_pointee_array_declarator && + !declarator.array_size && !base->array_size && + (!base->func_signature || + (!base->is_direct_function_type && !base->ptr_level)) && + !base->pointee_func_signature && + declarator.ptr_level == + declarator.pointee_array_element_ptr_level + 1 && + declarator.pointee_array_element_ptr_level <= 1) { + strncpy(type->type_name, declarator.var_name, MAX_TYPE_LEN - 1); + type->type_name[MAX_TYPE_LEN - 1] = '\0'; + + /* A callback typedef base, `fn_t (*rows_t)[2]`, is the row's + * element with its prototype and qualifiers; the row pointer itself + * is no callback. + */ + if (base->func_signature) + alias_callback_row_pointer(type, base, 0); + + /* A pointer typedef base, `ptr_t (*rows_t)[2]`, is spelled out as + * `int *(*rows_t)[2]`: the declarator already counts its stars on + * the row's element, which then points to the base's pointee. + */ + type->ptr_level = declarator.ptr_level; + type->size = PTR_SIZE; + type->pointer_const_mask |= declarator.pointer_const_mask; + type->pointee_array_size = declarator.pointee_array_size; + type->pointee_array_dim2 = declarator.pointee_array_dim2; + type->pointee_array_dim3 = declarator.pointee_array_dim3; + type->pointee_array_dim4 = declarator.pointee_array_dim4; + type->pointee_array_element_ptr_level = + declarator.pointee_array_element_ptr_level; + type->pointee_array_element_type = + base->ptr_level + ? pointee_type_from_pointer_typedef((type_t *) base) + : (type_t *) base; + return; + } + if (!declarator.is_func) + error_at( + "Typedef parenthesized declarator must be a function " + "pointer", + cur_token_loc()); + + /* `typedef int (*get_t(void))(int)` names a function type whose return + * is a callback, which the declarator reader already built. + */ + if (declarator.is_direct_function_declarator) { + memcpy(type, declarator.type, sizeof(type_t)); + strncpy(type->type_name, declarator.var_name, MAX_TYPE_LEN - 1); + type->type_name[MAX_TYPE_LEN - 1] = '\0'; + return; + } + strncpy(type->type_name, declarator.var_name, MAX_TYPE_LEN - 1); + type->type_name[MAX_TYPE_LEN - 1] = '\0'; + type->size = PTR_SIZE; + type->alignment = PTR_SIZE; + type->func_signature = declarator.func_signature; + + /* `typedef int (*row[2])(int)` is an array typedef whose elements are + * callback pointers. The signature describes each element, while the + * bounds are needed later when a pointer-to-row is indexed (including + * after a call result). + */ + type->array_size = declarator.array_size; + type->array_dim2 = declarator.array_dim2; + type->array_dim3 = declarator.array_dim3; + type->array_dim4 = declarator.array_dim4; + type->array_element_ptr_level = declarator.array_size ? 1 : 0; + + /* Stars before the parenthesized callback declarator belong to the + * callback's return type. That depth is retained in its parsed + * signature; the typedef alias itself is the pointer-sized callback + * object, not a derived pointer alias. + */ + type->ptr_level = 0; + type->pointer_const_mask = declarator.pointer_const_mask; + type->is_volatile_qualified = declarator.is_volatile; + return; + } + + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + + /* `typedef int unary_t(int)` names a function type. Mirror the block-scope + * direct function alias: its scalar or void base is only the return type, + * and the prototype stays on the descriptor. + */ + if (lex_peek(T_open_bracket, NULL) && !base->ptr_level && + !base->array_size && !base->func_signature && !is_record_type(base) && + !base->is_floating) { + func_t *func = arena_alloc_func(); + + /* The stars of `char *name_t(void)` belong to the return type. */ + func->return_def.type = (type_t *) base; + func->return_def.ptr_level = type->ptr_level; + func->return_def.pointer_const_mask = type->pointer_const_mask; + func->return_def.scope = block; + type->ptr_level = 0; + type->pointer_const_mask = 0; + type->size = base->size; + read_parameter_list_decl(func, true); + type->base_type = TYPE_typedef; + type->func_signature = func; + type->is_direct_function_type = true; + return; + } + + /* A typedef declarator may wrap an existing array typedef: `typedef row + * matrix[2]`. Gather its leading bounds first, then prepend them to the + * base alias's bounds instead of overwriting the inner extent. + */ + fixed_array_shape_t base_shape = fixed_array_shape_from_type(type); + fixed_array_shape_t decl_shape = {0}; + while (lex_accept(T_open_square)) { + int bound; + + if (decl_shape.rank >= MAX_FIXED_ARRAY_RANK) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + if (lex_peek(T_close_square, NULL)) + error_at("Typedef array needs a positive bound", cur_token_loc()); + bound = read_const_expr(block); + if (bound <= 0) + error_at("Typedef array needs a positive bound", cur_token_loc()); + decl_shape.bounds[decl_shape.rank++] = bound; + lex_expect(T_close_square); + } + if (decl_shape.rank) { + fixed_array_shape_t shape = + fixed_array_shape_prepend(&decl_shape, &base_shape); + + if (!type->ptr_level && !type->size && type->base_struct && + !type->base_struct->size) + error_at("Typedef array element has incomplete record type", + cur_token_loc()); + + fixed_array_shape_to_type(type, &shape); + + /* At the point an array typedef is introduced, ptr_level describes each + * array element. A later alias may add a pointer to the whole array, so + * preserve this separately. + */ + type->array_element_ptr_level = type->ptr_level; + type->array_element_type = + base->array_element_type + ? base->array_element_type + : (base->ptr_level + ? pointee_type_from_pointer_typedef((type_t *) base) + : (type_t *) base); + } +} + +/* The declarator list of a file-scope typedef whose specifier resolved to + * @base, qualified by @typedef_const and @typedef_volatile, through its + * terminating semicolon. The specifier and its qualifiers apply to each + * declarator. + */ +static void read_global_typedef_declarators(block_t *block, + bool typedef_const, + bool typedef_volatile, + const type_t *base) +{ + do { + read_global_typedef_declarator(block, typedef_const, typedef_volatile, + base); + } while (lex_accept(T_comma)); + lex_expect(T_semicolon); +} + +/* The record that a later declarator of a file-scope record typedef derives + * from: the @first alias when it names the record itself, the record a pointer + * alias reaches through base_struct, or else a nameless copy of that alias + * without its pointer, @size bytes wide and aligned to @alignment. + */ +static const type_t *global_record_typedef_base(type_t *first, + int size, + int alignment) +{ + if (!first->ptr_level) + return first; + if (first->base_struct) + return first->base_struct; + + type_t *record = add_type(); + + memcpy(record, first, sizeof(type_t)); + record->type_name[0] = '\0'; + record->ptr_level = 0; + record->pointer_const_mask = 0; + record->size = size; + record->alignment = alignment; + return record; +} + +/* Whether the first declarator of a file-scope record typedef is only stars and + * a name, which the record branch below completes in place. Any other form, + * such as `typedef struct P rows[2]`, derives from the record through + * read_global_typedef_declarator() instead. + */ +static bool global_record_typedef_declarator_is_plain(void) +{ + token_t *token = cur_token->next; + + while (token && token->kind == T_asterisk) + token = token->next; + return token && token->kind == T_identifier && token->next && + token->next->kind != T_open_square && + token->next->kind != T_open_bracket; +} + +/* A file-scope typedef, after its keyword: record, enum, callback pointer and + * scalar aliases, each with a comma-separated declarator list. + */ +static void read_global_typedef(block_t *block) +{ + char token[MAX_ID_LEN]; + bool typedef_const = false; + bool typedef_volatile = false; + + /* Qualifiers may lead the type specifier or follow it. Each branch reads + * the trailing ones after its specifier and records both on the alias, + * which every declaration spelled with it then inherits. + */ + read_type_qualifiers(&typedef_const, &typedef_volatile, false); + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { + base_type_t kind = accept_record_keyword(); + bool has_tag = lex_peek(T_identifier, token); + type_t *type = add_type(); + type_t *tag = NULL; + type_t *record = NULL; + + if (has_tag) + lex_expect(T_identifier); + + /* A member list completes the tag, or an untagged record, which the + * first declarator then names; a bare tag may still be incomplete. + */ + if (lex_peek(T_open_curly, NULL)) + record = read_record_body(NULL, kind, has_tag, token); + else if (has_tag) + tag = local_record_tag(token, GLOBAL_BLOCK, kind); + + read_type_qualifiers(&typedef_const, &typedef_volatile, false); + bool is_plain = global_record_typedef_declarator_is_plain(); + while (is_plain && lex_accept(T_asterisk)) + type->ptr_level++; + if (is_plain) + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + + /* A defining alias carries the layout itself; a forward one reaches the + * tag through base_struct, which in 'find_type' also sees a later + * completion of it. + */ + int size = record ? record->size : 0; + int alignment = record ? record->alignment : 1; + + type->alignment = type->ptr_level ? PTR_SIZE : alignment; + type->size = type->ptr_level ? PTR_SIZE : size; + if (record) { + type->fields = record->fields; + type->num_fields = record->num_fields; + type->has_flexible_array_member = record->has_flexible_array_member; + if (has_tag) + tag = record; + + /* A pointer alias also reaches its record through base_struct, as a + * forward one does, so that a dereference such as sizeof(*p) yields + * the record even when it has no tag to be found by. + */ + if (type->ptr_level) + type->base_struct = record; + } else + type->base_struct = tag; + type->base_type = TYPE_typedef; + type->is_union = kind == TYPE_union; + + /* Only the alias is qualified; the tag must stay plain. */ + type->is_const_qualified = typedef_const; + type->is_volatile_qualified = typedef_volatile; + + if (!is_plain) { + read_global_typedef_declarators(block, typedef_const, + typedef_volatile, tag ? tag : type); + } else if (lex_accept(T_comma)) { + /* Later declarators derive from the record as a typedef of it + * would. + */ + read_global_typedef_declarators( + block, typedef_const, typedef_volatile, + global_record_typedef_base(type, size, alignment)); + } else + lex_expect(T_semicolon); + } else { + /* An enum alias may name an existing tag, or define a tagged or + * untagged enum, and derive a pointer or array from it like any other + * base. + */ + bool is_definition; + const type_t *base = + lex_peek(T_enum, NULL) + ? read_enum_specifier(NULL, &is_definition) + : read_global_typedef_base(&typedef_const, &typedef_volatile); + + /* `typedef int const ci_t;` and `typedef int volatile vi_t;` qualify + * the alias just as the leading spelling does. + */ + read_type_qualifiers(&typedef_const, &typedef_volatile, + base->ptr_level != 0); + read_global_typedef_declarators(block, typedef_const, typedef_volatile, + base); + } +} + +void read_global_statement(void) +{ + char token[MAX_ID_LEN]; + block_t *block = GLOBAL_BLOCK; /* global block */ + bool is_const = false; + bool is_static = false; + bool is_extern = false; + bool is_inline = false; + bool is_volatile = false; + + /* These specifiers may appear in either order, and a storage class may also + * follow the type. + */ + hoist_storage_class_specifiers(); + while (lex_peek(T_const, NULL) || lex_peek(T_static, NULL) || + lex_peek(T_extern, NULL) || lex_peek(T_inline, NULL) || + lex_peek(T_volatile, NULL)) { + if (lex_accept(T_const)) + is_const = true; + else if (lex_accept(T_volatile)) + is_volatile = true; + else if (lex_accept(T_static)) { + if (is_static) + error_at("duplicate static storage class specifier", + cur_token_loc()); + is_static = true; + } else if (lex_accept(T_inline)) { + if (is_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + is_inline = true; + } else { + lex_expect(T_extern); + if (is_extern) + error_at("duplicate extern storage class specifier", + cur_token_loc()); + is_extern = true; + } + } + if (is_static && is_extern) + error_at("static and extern storage classes cannot be combined", + cur_token_loc()); + + /* typedef is a storage-class specifier too (6.7.1p2), so it admits no other + * one. + */ + if ((is_static || is_extern) && lex_peek(T_typedef, NULL)) + error_at("typedef cannot be combined with another storage class", + next_token_loc()); + + if (floating_type_starts_here()) + error_at("Floating point types are not yet supported", cur_token_loc()); + + /* `inline` is a function specifier, not a general declaration qualifier. + * Scalar declarators are checked by read_global_decl(), but record, enum, + * and typedef declarations bypass that path entirely. Rejecting those forms + * here keeps every file-scope declaration category under the same C99 + * constraint. + */ + if (is_inline && (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL) || + lex_peek(T_enum, NULL) || lex_peek(T_typedef, NULL))) + error_at("inline specifier requires a function declarator", + cur_token_loc()); + + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { + base_type_t kind = accept_record_keyword(); + bool has_tag = lex_peek(T_identifier, token); + type_t *type; + + if (has_tag) + lex_expect(T_identifier); + else if (!lex_peek(T_open_curly, NULL)) + error_at("Expected struct or union tag or definition", + next_token_loc()); + + /* variable declaration using existing record tag? */ + if (!lex_peek(T_open_curly, NULL)) { + type = local_record_tag(token, GLOBAL_BLOCK, kind); + + /* A declaration with no declarator only declares the tag. At file + * scope a repeated one names the same type, so it is valid whether + * the tag is new, forward declared, or already complete. + */ + if (lex_accept(T_semicolon)) + return; + + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, true); + return; + } + + type = read_record_body(NULL, kind, has_tag, token); + + /* A record definition may be followed by its declarators, as in "struct + * pair { int x, y; } first, *second;". + */ + if (!lex_accept(T_semicolon)) + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, true); + } else if (lex_peek(T_enum, NULL)) { + bool is_definition; + type_t *type = read_enum_specifier(NULL, &is_definition); + + /* A definition may stand alone; a reference to a tag needs a + * declarator, since it can neither declare nor complete the tag. + */ + if (!is_definition && lex_peek(T_semicolon, NULL)) + error_at( + "enum declaration without an enumerator list declares " + "nothing", + next_token_loc()); + if (!is_definition || !lex_accept(T_semicolon)) + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, false); + } else if (lex_accept(T_typedef)) { + read_global_typedef(block); + } else if (lex_peek(T_identifier, NULL) || lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { + read_global_decl(block, is_const, is_static, is_extern, is_inline, + is_volatile); + } else + error_at("Syntax error in global statement", next_token_loc()); +} + +void parse_internal(void) +{ + /* set starting point of global stack manually */ + GLOBAL_FUNC = add_func("", true); + + /* The first global slot retains the synthetic global-frame pointer. It must + * occupy a full target pointer, not the historic 32-bit word. + */ + GLOBAL_FUNC->stack_size = PTR_SIZE; + GLOBAL_FUNC->bbs = arena_calloc(BB_ARENA, 1, sizeof(basic_block_t)); + GLOBAL_FUNC->bbs->belong_to = GLOBAL_FUNC; /* Prevent nullptr deref in RA */ + GLOBAL_FUNC->bbs->elf_offset = -1; /* not yet emitted */ + + /* built-in types */ + TY_void = add_named_type("void"); + TY_void->base_type = TYPE_void; + TY_void->size = 0; + + TY_char = add_named_type("char"); + TY_char->base_type = TYPE_char; + TY_char->size = 1; + + TY_schar = add_named_type("signed char"); + TY_schar->base_type = TYPE_char; + TY_schar->size = 1; + TY_schar->is_signed_char = true; + + TY_uchar = add_named_type("unsigned char"); + TY_uchar->base_type = TYPE_char; + TY_uchar->size = 1; + TY_uchar->is_unsigned = true; + + TY_int = add_named_type("int"); + TY_int->base_type = TYPE_int; + TY_int->size = 4; + + TY_uint = add_named_type("unsigned int"); + TY_uint->base_type = TYPE_int; + TY_uint->size = 4; + TY_uint->is_unsigned = true; + + /* Keep C99 floating scalar identities in the type table before their IR and + * ABI lowering are admitted. The LP64 targets use their ABI's 16-byte + * long-double object slot; the current 32-bit soft-float targets use an + * 8-byte long double until their target-specific representation is + * implemented. + */ + TY_float = add_named_type("float"); + TY_float->base_type = TYPE_float; + TY_float->size = 4; + TY_float->is_floating = true; + + TY_double = add_named_type("double"); + TY_double->base_type = TYPE_double; + TY_double->size = 8; + TY_double->is_floating = true; + + TY_long_double = add_named_type("long double"); + TY_long_double->base_type = TYPE_long_double; + TY_long_double->size = PTR_SIZE == 8 ? 16 : 8; + TY_long_double->is_floating = true; + + /* long has the same current ABI width as int, but it remains a distinct C + * type: redeclarations and the usual arithmetic conversions depend on rank, + * not just representation size. + */ + TY_long = add_named_type("long"); + TY_long->base_type = TYPE_long; + TY_long->size = 4; + + TY_ulong = add_named_type("unsigned long"); + TY_ulong->base_type = TYPE_long; + TY_ulong->size = 4; + TY_ulong->is_unsigned = true; + + TY_short = add_named_type("short"); + TY_short->base_type = TYPE_short; + TY_short->size = 2; + + TY_ushort = add_named_type("unsigned short"); + TY_ushort->base_type = TYPE_short; + TY_ushort->size = 2; + TY_ushort->is_unsigned = true; + + /* Unlike `long`, which deliberately shares the current 32-bit int ABI, long + * long has a distinct type and an eight-byte object representation. Parser + * admission and target lowering are staged separately so 32-bit backends + * never silently truncate it. + */ + TY_long_long = add_named_type("long long"); + TY_long_long->base_type = TYPE_long_long; + TY_long_long->size = 8; + + TY_ulong_long = add_named_type("unsigned long long"); + TY_ulong_long->base_type = TYPE_long_long; + TY_ulong_long->size = 8; + TY_ulong_long->is_unsigned = true; + + /* C99's names target-sized integer typedefs. Keep them in the + * builtin type table so declarations, casts, sizeof, and prototypes use the + * same pointer-width representation as the rest of the compiler. + */ + type_t *TY_size = add_named_type("size_t"); + TY_size->base_type = PTR_SIZE == 8 ? TYPE_long_long : TYPE_long; + TY_size->size = PTR_SIZE; + TY_size->is_unsigned = true; + + type_t *TY_ptrdiff = add_named_type("ptrdiff_t"); + TY_ptrdiff->base_type = PTR_SIZE == 8 ? TYPE_long_long : TYPE_long; + TY_ptrdiff->size = PTR_SIZE; + + /* belongs to C99's freestanding library. Its va_list is the + * compiler ABI's int-based cursor, so retain that scalar base while + * recording the pointer depth directly in the builtin typedef. + */ + type_t *TY_va_list = add_named_type("va_list"); + TY_va_list->base_type = TYPE_int; + TY_va_list->ptr_level = 1; + TY_va_list->size = PTR_SIZE; + + /* The execution wide-character type is int until wide literal lowering is + * implemented; declaring the C99 typedef remains useful independently. + */ + type_t *TY_wchar = add_named_type("wchar_t"); + TY_wchar->base_type = TYPE_int; + TY_wchar->size = TY_int->size; + + type_t *TY_sig_atomic = add_named_type("sig_atomic_t"); + TY_sig_atomic->base_type = TYPE_int; + TY_sig_atomic->size = TY_int->size; + + type_t *TY_wint = add_named_type("wint_t"); + TY_wint->base_type = TYPE_int; + TY_wint->size = TY_int->size; + TY_wint->is_unsigned = true; + + /* C99 aliases share the target scalar representations. Keep them + * named in the builtin table so declarations, casts, sizeof, and prototypes + * use the same ABI metadata as their underlying types. + */ + type_t *TY_int8 = add_named_type("int8_t"); + TY_int8->base_type = TYPE_char; + TY_int8->size = 1; + TY_int8->is_signed_char = true; + type_t *TY_uint8 = add_named_type("uint8_t"); + TY_uint8->base_type = TYPE_char; + TY_uint8->size = 1; + TY_uint8->is_unsigned = true; + type_t *TY_int16 = add_named_type("int16_t"); + TY_int16->base_type = TYPE_short; + TY_int16->size = 2; + type_t *TY_uint16 = add_named_type("uint16_t"); + TY_uint16->base_type = TYPE_short; + TY_uint16->size = 2; + TY_uint16->is_unsigned = true; + type_t *TY_int32 = add_named_type("int32_t"); + TY_int32->base_type = TYPE_int; + TY_int32->size = 4; + type_t *TY_uint32 = add_named_type("uint32_t"); + TY_uint32->base_type = TYPE_int; + TY_uint32->size = 4; + TY_uint32->is_unsigned = true; + type_t *TY_int64 = add_named_type("int64_t"); + TY_int64->base_type = TYPE_long_long; + TY_int64->size = 8; + type_t *TY_uint64 = add_named_type("uint64_t"); + TY_uint64->base_type = TYPE_long_long; + TY_uint64->size = 8; + TY_uint64->is_unsigned = true; + type_t *TY_intmax = add_named_type("intmax_t"); + TY_intmax->base_type = TYPE_long_long; + TY_intmax->size = 8; + type_t *TY_uintmax = add_named_type("uintmax_t"); + TY_uintmax->base_type = TYPE_long_long; + TY_uintmax->size = 8; + TY_uintmax->is_unsigned = true; + type_t *TY_intptr = add_named_type("intptr_t"); + TY_intptr->base_type = PTR_SIZE == 8 ? TYPE_long_long : TYPE_long; + TY_intptr->size = PTR_SIZE; + type_t *TY_uintptr = add_named_type("uintptr_t"); + TY_uintptr->base_type = PTR_SIZE == 8 ? TYPE_long_long : TYPE_long; + TY_uintptr->size = PTR_SIZE; + TY_uintptr->is_unsigned = true; + + type_t *TY_int_least8 = add_named_type("int_least8_t"); + TY_int_least8->base_type = TY_int8->base_type; + TY_int_least8->size = TY_int8->size; + TY_int_least8->is_signed_char = true; + type_t *TY_uint_least8 = add_named_type("uint_least8_t"); + TY_uint_least8->base_type = TY_uint8->base_type; + TY_uint_least8->size = TY_uint8->size; + TY_uint_least8->is_unsigned = true; + type_t *TY_int_least16 = add_named_type("int_least16_t"); + TY_int_least16->base_type = TYPE_short; + TY_int_least16->size = 2; + type_t *TY_uint_least16 = add_named_type("uint_least16_t"); + TY_uint_least16->base_type = TYPE_short; + TY_uint_least16->size = 2; + TY_uint_least16->is_unsigned = true; + type_t *TY_int_least32 = add_named_type("int_least32_t"); + TY_int_least32->base_type = TYPE_int; + TY_int_least32->size = 4; + type_t *TY_uint_least32 = add_named_type("uint_least32_t"); + TY_uint_least32->base_type = TYPE_int; + TY_uint_least32->size = 4; + TY_uint_least32->is_unsigned = true; + type_t *TY_int_least64 = add_named_type("int_least64_t"); + TY_int_least64->base_type = TYPE_long_long; + TY_int_least64->size = 8; + type_t *TY_uint_least64 = add_named_type("uint_least64_t"); + TY_uint_least64->base_type = TYPE_long_long; + TY_uint_least64->size = 8; + TY_uint_least64->is_unsigned = true; + type_t *TY_int_fast8 = add_named_type("int_fast8_t"); + TY_int_fast8->base_type = TYPE_int; + TY_int_fast8->size = 4; + type_t *TY_uint_fast8 = add_named_type("uint_fast8_t"); + TY_uint_fast8->base_type = TYPE_int; + TY_uint_fast8->size = 4; + TY_uint_fast8->is_unsigned = true; + type_t *TY_int_fast16 = add_named_type("int_fast16_t"); + TY_int_fast16->base_type = TYPE_int; + TY_int_fast16->size = 4; + type_t *TY_uint_fast16 = add_named_type("uint_fast16_t"); + TY_uint_fast16->base_type = TYPE_int; + TY_uint_fast16->size = 4; + TY_uint_fast16->is_unsigned = true; + type_t *TY_int_fast32 = add_named_type("int_fast32_t"); + TY_int_fast32->base_type = TYPE_int; + TY_int_fast32->size = 4; + type_t *TY_uint_fast32 = add_named_type("uint_fast32_t"); + TY_uint_fast32->base_type = TYPE_int; + TY_uint_fast32->size = 4; + TY_uint_fast32->is_unsigned = true; + type_t *TY_int_fast64 = add_named_type("int_fast64_t"); + TY_int_fast64->base_type = TYPE_long_long; + TY_int_fast64->size = 8; + type_t *TY_uint_fast64 = add_named_type("uint_fast64_t"); + TY_uint_fast64->base_type = TYPE_long_long; + TY_uint_fast64->size = 8; + TY_uint_fast64->is_unsigned = true; + + /* builtin type _Bool was introduced in C99 specification, it is more + * well-known as macro type bool, which is defined in (in + * shecc, it is defined in 'lib/c.c'). + */ + TY_bool = add_named_type("_Bool"); + TY_bool->base_type = TYPE_char; + TY_bool->size = 1; + TY_bool->is_bool = true; + + GLOBAL_BLOCK = add_block(NULL, NULL); /* global block */ + elf_add_symbol("", 0); /* undef symbol */ + + if (dynlink) { + /* In dynamic mode, __syscall won't be implemented. + * + * Simply declare a 'syscall' function as follows if the program needs + * to use 'syscall': + * + * int syscall(int number, ...); + * + * shecc will treat it as an external function, and the compiled program + * will eventually use the implementation provided by the external C + * library. + * + * If shecc supports the 'long' data type in the future, it would be + * better to declare syscall using its original prototype: + * + * long syscall(long number, ...); + */ + } else { + /* Linux syscall */ + func_t *func = add_func("__syscall", true); + func->return_def.type = TY_int; + func->num_params = 0; + func->va_args = 1; + func->bbs = NULL; + /* Otherwise, allocate a basic block to implement in static mode. */ + func->bbs = arena_calloc(BB_ARENA, 1, sizeof(basic_block_t)); + func->bbs->elf_offset = -1; /* not yet emitted */ + } + + /* Add a global object to the .data section. + * + * This object saves the global stack pointer, so it is written back as a + * pointer and must reserve a full one: on an LP64 target the historic + * 32-bit word left four bytes belonging to the next global. + */ + elf_write_ptr(elf_data, 0); + + /* lexer initialization */ + do { + read_global_statement(); + } while (!lex_accept(T_eof)); + + /* Aggregate returns use shecc's internal destination-pointer convention, + * not the platform ABI's aggregate classification. A direct call to a + * declaration-only function would otherwise quietly cross that boundary + * with incompatible arguments. Indirect calls separately require tracked + * provenance proving that their target is shecc-defined. + */ + for (func_t *func = FUNC_LIST.head; func; func = func->next) + if (func->aggregate_call_used && !func->bbs) + error_at("aggregate-return call requires a shecc-defined function", + cur_token_loc()); +} + +void parse(token_t *tk) +{ + token_t head; + head.kind = T_start; + head.next = tk; + cur_token = &head; + + parse_internal(); +} diff --git a/src/parser-init.c b/src/parser-init.c new file mode 100644 index 00000000..a7333cef --- /dev/null +++ b/src/parser-init.c @@ -0,0 +1,2900 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Addresses of elements and members, record copies, aggregate and string + * initializers, and compound literals. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +void read_parameter_list_decl(func_t *func, bool anon); +void read_indirect_call(var_t *callee, block_t *parent, basic_block_t **bb); +var_t *integer_promote_operand(block_t *parent, basic_block_t **bb, var_t *var); +var_t *resolve_global_declarator(block_t *block, + var_t *var, + bool is_static, + bool *is_redeclaration); +bool read_global_function_declarator(block_t *block, + var_t *var, + bool is_static, + bool allow_definition); +var_t *bind_block_extern_object(block_t *parent, var_t *var); +int read_const_expr(block_t *scope); +var_t *read_wide_global_literal_expression(block_t *parent, + basic_block_t *bb, + block_t *scope); +var_t *read_wide_global_literal_primary(block_t *parent, + basic_block_t *bb, + block_t *scope); +bool subscripted_string_literal_starts_here(void); +bool string_element_appears_before_initializer_end(token_t *token); +var_t *read_string_literal_element_address(block_t *parent, + basic_block_t *bb, + block_t *scope); +bool string_address_offset_starts_here(void); +var_t *read_string_address_offset(block_t *parent, + basic_block_t *bb, + block_t *scope); + +/* Forward declaration for ternary handling used by initializers */ +void read_ternary_operation(block_t *parent, basic_block_t **bb); + +/* Parse array initializer to determine size for implicit arrays and optionally + * emit initialization code. + */ +var_t *compute_element_address(block_t *parent, + basic_block_t **bb, + var_t *base_addr, + int index, + int elem_size) +{ + if (index == 0) + return base_addr; + + var_t *offset = require_var(parent); + offset->var_name = gen_name(); + offset->init_val = index * elem_size; + add_insn(parent, *bb, OP_load_constant, offset, NULL, NULL, 0, NULL); + + var_t *addr = require_var(parent); + addr->var_name = gen_name(); + add_insn(parent, *bb, OP_add, addr, base_addr, offset, 0, NULL); + return addr; +} + +var_t *compute_field_address(block_t *parent, + basic_block_t **bb, + var_t *struct_addr, + const var_t *field) +{ + if (field->offset == 0) + return struct_addr; + + var_t *offset = require_var(parent); + offset->var_name = gen_name(); + offset->init_val = field->offset; + add_insn(parent, *bb, OP_load_constant, offset, NULL, NULL, 0, NULL); + + var_t *addr = require_var(parent); + addr->var_name = gen_name(); + add_insn(parent, *bb, OP_add, addr, struct_addr, offset, 0, NULL); + return addr; +} + +/* A record assignment is a value copy, not the scalar OP_assign used for + * ordinary variables. Keep the lowering in phase 1 so every backend can use its + * existing 1-, 2-, and 4-byte indirect accesses. + */ +bool is_record_object(const var_t *var) +{ + return var && !effective_pointer_depth(var) && !var->array_size && + var->type && + (var->type->base_type == TYPE_struct || + var->type->base_type == TYPE_union || + (var->type->base_type == TYPE_typedef && + var->type->num_fields > 0)); +} + +/* Copy size bytes between two known record addresses in 4-, 2- and 1-byte + * slices, the widest indirect accesses every backend encodes. A record is an + * object, not an integer value, so one OP_write of its whole size would leave + * narrow backends with a store width they cannot emit. + */ +void emit_record_copy_between(block_t *parent, + basic_block_t **bb, + var_t *dest_addr, + var_t *src_addr, + int size) +{ + for (int offset = 0; offset < size;) { + int width = 1; + if (size - offset >= 4) + width = 4; + else if (size - offset >= 2) + width = 2; + var_t *src_part = + compute_element_address(parent, bb, src_addr, offset, 1); + var_t *dest_part = + compute_element_address(parent, bb, dest_addr, offset, 1); + var_t *value = require_var(parent); + + value->var_name = gen_name(); + add_insn(parent, *bb, OP_read, value, src_part, NULL, width, NULL); + add_insn(parent, *bb, OP_write, NULL, dest_part, value, width, NULL); + offset += width; + } +} + +/* The address of the bytes of record value @record. A record whose copy was + * deferred still has them at the object it was read from, and a copy out of it + * reads them there. + */ +var_t *record_value_address(block_t *parent, basic_block_t *bb, var_t *record) +{ + var_t *address; + + if (record->defers_record_copy) + return record->compound_literal_address; + address = require_ref_var(parent, record->type, 0); + address->var_name = gen_name(); + add_insn(parent, bb, OP_address_of, address, record, NULL, 0, NULL); + return address; +} + +/* Copy a record into an address which is already known, such as a nested record + * member. + */ +void emit_record_copy_to_address(block_t *parent, + basic_block_t **bb, + var_t *dest_addr, + var_t *src) +{ + var_t *src_addr = record_value_address(parent, *bb, src); + + emit_record_copy_between(parent, bb, dest_addr, src_addr, size_var(src)); +} + +void emit_record_copy(block_t *parent, + basic_block_t **bb, + var_t *dest, + var_t *src) +{ + var_t *dest_addr = require_ref_var(parent, dest->type, 0); + var_t *src_addr; + + dest_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); + src_addr = record_value_address(parent, *bb, src); + emit_record_copy_between(parent, bb, dest_addr, src_addr, size_var(dest)); +} + +void emit_object_assignment(block_t *parent, + basic_block_t **bb, + var_t *dest, + var_t *src) +{ + if (is_record_object(dest) && is_record_object(src)) { + emit_record_copy(parent, bb, dest, src); + } else if (dest->is_func && src->is_func && + find_var(src->var_name, parent) != src) { + /* Function symbols are not scalar values: materialize their final + * address through OP_write, which the backend patches after laying out + * all functions. This is also needed for a declaration initializer such + * as `int (*fn)(int) = target;`. + */ + var_t *dest_addr = require_ref_var(parent, dest->type, dest->ptr_level); + dest_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); + add_insn(parent, *bb, OP_write, NULL, dest_addr, src, PTR_SIZE, NULL); + } else { + src = resize_var(parent, bb, src, dest); + add_insn(parent, *bb, OP_assign, dest, src, NULL, 0, NULL); + } +} + +type_t *read_type_name_specifiers(block_t *scope); +int read_const_expr_operand(block_t *scope); +int read_global_address_offset(block_t *scope, + block_t *parent, + basic_block_t *bb); + +/* While the operand of a pointer cast in a static initializer is read, the size + * of what the cast pointer points to, and 0 otherwise. An offset that follows + * the operand advances the converted pointer, so "(char *) array + 1" is one + * byte past the array whatever its element type. + */ +int global_pointer_cast_stride = 0; + +/* While the operand of a cast to a function pointer type in a static + * initializer is read, the prototype that cast names, and NULL otherwise. The + * cast, not the designated function, is converted to the initialized object. + */ +func_t *global_function_cast_signature = NULL; + +bool abstract_function_pointer_follows(void); +func_t *read_abstract_function_pointer(type_t *type, + int ptr_level, + int *pointer_level); + +/* If a cast to a function pointer type, `(int (*)(int))` or `(callback_t)`, + * starts at the next token of a static initializer, consume it and return the + * prototype it names. Otherwise consume nothing and return NULL. + */ +func_t *read_global_function_pointer_cast(block_t *scope) +{ + token_t *start = cur_token; + func_t *signature = NULL; + type_t *type; + int stars = 0; + int pointer_level = 0; + + if (!lex_accept(T_open_bracket)) + return NULL; + type = read_type_name_specifiers(scope); + if (type) { + while (lex_accept(T_const) || lex_accept(T_volatile)) + ; + while (lex_accept(T_asterisk)) { + stars++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (abstract_function_pointer_follows()) { + signature = + read_abstract_function_pointer(type, stars, &pointer_level); + if (pointer_level != 1) + signature = NULL; + } else if (type->func_signature && !type->is_direct_function_type && + !stars) { + signature = type->func_signature; + } else if (type->is_direct_function_type && stars == 1) { + signature = type->func_signature; + } + } + if (!signature || !lex_accept(T_close_bracket)) { + cur_token = start; + return NULL; + } + return signature; +} + +static token_t *matching_close_bracket(token_t *open); + +/* Say whether cur_token->next opens a cast to an object pointer type in a + * static initializer. C99 6.6 lets an address constant and an integer constant + * be converted by such a cast. + */ +bool global_pointer_cast_starts_here(block_t *scope) +{ + token_t *token = cur_token->next; + token_kind_t previous = T_open_bracket; + bool has_type = false; + bool is_pointer = false; + bool function_type = false; + int depth = 0; + + if (!token || token->kind != T_open_bracket) + return false; + for (token = token->next; token && token->kind != T_close_bracket; + token = token->next) { + if (token->kind == T_asterisk) { + if (!has_type) + return false; + is_pointer = true; + depth++; + } else if (token->kind == T_open_bracket && has_type) { + /* A pointer to function pointers, `(int (**)(void))`, points to + * pointer objects: two or more stars, then a parameter list. + */ + int stars = 0; + + for (token = token->next; + token && + (token->kind == T_asterisk || token->kind == T_const || + token->kind == T_volatile || token->kind == T_restrict); + token = token->next) + stars += token->kind == T_asterisk; + if (stars < 2 || !token || token->kind != T_close_bracket || + !(token = token->next) || token->kind != T_open_bracket) + return false; + token = matching_close_bracket(token); + if (!token || !(token = token->next)) + return false; + return token->kind == T_close_bracket; + } else if (token->kind == T_identifier) { + bool is_tag = previous == T_struct || previous == T_union || + previous == T_enum; + type_t *type = + is_tag ? NULL : find_visible_type(token->literal, scope); + + if (!is_tag && !type) + return false; + if (type && type->is_direct_function_type) + function_type = true; + + /* A callback typedef takes a star to point to a pointer object. */ + if (type && type->ptr_level && !type->func_signature) + is_pointer = true; + has_type = true; + } else if (token->kind == T_struct || token->kind == T_union || + token->kind == T_enum || token->kind == T_signed || + token->kind == T_unsigned || token->kind == T_long || + token->kind == T_float || token->kind == T_double) { + has_type = true; + } else if (token->kind != T_const && token->kind != T_volatile && + token->kind != T_restrict) + return false; + previous = token->kind; + } + + /* Without a star, a callback typedef is the function pointer cast that + * read_global_function_pointer_cast() reads, and so is a function typedef + * with one; a second star, `(fnty **)`, points to a pointer object. + */ + return token && has_type && is_pointer && (!function_type || depth > 1); +} + +/* Consume a cast that global_pointer_cast_starts_here() recognized and return + * the size of what its pointer type points to. @slot_signature receives the + * prototype of the function pointers that type points to, as `(int (**)(void))` + * or `(callback_t *)` do, and NULL for any other pointer. + */ +int read_global_pointer_cast(block_t *scope, func_t **slot_signature) +{ + type_t *type; + int depth = 0; + int size; + + *slot_signature = NULL; + lex_expect(T_open_bracket); + type = read_type_name_specifiers(scope); + while (lex_accept(T_asterisk)) { + depth++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!type) + error_at("Unknown type in pointer cast", cur_token_loc()); + + /* A pointer to function pointers, spelled out or through a callback + * typedef, steps over pointer objects. + */ + if (abstract_function_pointer_follows()) { + int pointer_level; + func_t *signature = + read_abstract_function_pointer(type, depth, &pointer_level); + + lex_expect(T_close_bracket); + if (pointer_level == 2) + *slot_signature = signature; + return PTR_SIZE; + } + lex_expect(T_close_bracket); + if (type->func_signature && + (type->is_direct_function_type ? depth == 2 + : !type->ptr_level && depth == 1)) + *slot_signature = type->func_signature; + if (depth + type->ptr_level > 1 || type->func_signature) + return PTR_SIZE; + type = pointee_type_from_pointer_typedef(type); + if (type == TY_void) + return 1; + size = type->size; + if (!size && type->base_struct) + size = type->base_struct->size; + if (type->array_size) + size *= type->array_size; + return size; +} + +/* Say whether the operand of a pointer cast in a static initializer is itself + * an address constant, rather than an integer constant converted to a pointer. + */ +bool global_address_operand_starts_here(block_t *scope) +{ + char name[MAX_ID_LEN]; + + if (lex_peek(T_ampersand, NULL) || lex_peek(T_string, NULL) || + global_pointer_cast_starts_here(scope) || + grouped_global_function_designator_starts_here(false) || + global_function_address_dereference_starts_here()) + return true; + if (lex_peek(T_identifier, name)) { + var_t *object = find_var(name, scope); + + return find_visible_func(name, scope) || + (object && object->is_global && object->array_size); + } + return false; +} + +/* Read an integer constant operand of a pointer cast with @stride and + * @slot_signature, and any offset that follows it, as the address constant the + * cast produces. + */ +var_t *read_global_cast_integer_address(block_t *parent, + basic_block_t *bb, + block_t *scope, + int stride, + func_t *slot_signature) +{ + var_t *address = require_var(parent); + char name[MAX_ID_LEN]; + unsigned int lo; + unsigned int hi; + + /* A pointer is as wide as the target's: read the operand in two words so a + * 64-bit target keeps its high half, as (char *) 0x100000000 needs. Only an + * identifier other than an enumerator, such as offsetof, takes the + * word-sized reader. + */ + if (lex_peek(T_identifier, name) && !find_scoped_constant(name, scope)) { + lo = read_const_expr_operand(scope); + hi = lo & 0x80000000U ? ~0U : 0; + } else { + var_t *operand = read_wide_global_literal_primary(parent, bb, scope); + + lo = operand->init_val; + hi = operand->init_val_hi; + if (operand->type->size <= TY_int->size) + hi = operand->type->is_unsigned || !(lo & 0x80000000U) ? 0 : ~0U; + } + if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { + int offset = read_global_address_offset(scope, parent, bb) * stride; + unsigned int sum = lo + offset; + + hi += (offset < 0 ? ~0U : 0) + (sum < lo); + lo = sum; + } + address->var_name = gen_name(); + address->init_val = lo; + address->init_val_hi = PTR_SIZE == 8 ? hi : 0; + address->is_const = true; + + /* The cast's result is a pointer, not the integer it was spelled with, and + * a pointer to function pointers keeps their prototype. + */ + address->ptr_level = 1; + address->pointee_func_signature = slot_signature; + add_insn(parent, bb, OP_load_constant, address, NULL, NULL, 0, NULL); + return address; +} + +/* Lower the designator that follows "&object" in a static initializer: member + * selections and constant subscripts in any order, then an optional constant + * offset. @object_addr is the address of @object; the address of the designated + * subobject is returned and that subobject is left in @object. The scalar and + * the aggregate initializer readers both come through here, so the forms they + * accept, the scope names resolve in, and the diagnostics cannot drift apart. + * + * With @decays there is no '&': the designator must name an array row, such as + * `matrix[1]`, whose conversion to a pointer to its first element is the + * address constant, and an offset then steps over that row's elements. + */ +var_t *read_global_address_designator(block_t *scope, + block_t *parent, + basic_block_t **bb, + var_t **object, + var_t *object_addr, + bool decays) +{ + var_t *target = *object; + fixed_array_shape_t shape = fixed_array_shape_from_var(target); + int subscripts = 0; + + for (;;) { + if (lex_accept(T_dot)) { + char field_name[MAX_ID_LEN]; + var_t *field; + + lex_ident(T_identifier, field_name); + field = find_member(field_name, target->type); + if (!field) + error_at("Unknown struct or union member", cur_token_loc()); + object_addr = compute_field_address(parent, bb, object_addr, field); + target = field; + shape = fixed_array_shape_from_var(target); + subscripts = 0; + } else if (lex_accept(T_open_square)) { + int element_size = + target->ptr_level ? PTR_SIZE : target->type->size; + int index; + + if (!target->array_size || subscripts >= shape.rank) + error_at("Subscripted global address needs an array object", + cur_token_loc()); + index = read_const_expr(scope); + lex_expect(T_close_square); + object_addr = compute_element_address( + parent, bb, object_addr, index, + fixed_array_shape_stride(&shape, subscripts, element_size)); + subscripts++; + } else + break; + object_addr->ptr_level = target->ptr_level + 1; + object_addr->is_global_address = true; + } + + /* A trailing offset after a partial multidimensional subscript advances by + * the remaining row or plane, the complete pointed-to object, rather than + * by its scalar leaf. read_global_address_offset() takes the sign as part + * of the constant expression. + */ + if (decays && (!target->array_size || subscripts >= shape.rank)) + error_at("Global initializer requires a constant address", + cur_token_loc()); + int element_size = target->ptr_level ? PTR_SIZE : target->type->size; + int stride = global_pointer_cast_stride + ? global_pointer_cast_stride + : fixed_array_shape_stride(&shape, subscripts - !decays, + element_size); + + if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { + int index = read_global_address_offset(scope, parent, *bb); + + object_addr = + compute_element_address(parent, bb, object_addr, index, stride); + object_addr->ptr_level = target->ptr_level + 1; + object_addr->is_global_address = true; + } + object_addr->address_stride = stride; + *object = target; + return object_addr; +} + +/* The block that names in an initializer lowered into @parent resolve in. A + * block-scope static is lowered through GLOBAL_BLOCK, yet its designators and + * constants belong to the lexical scope of its declaration. + */ +block_t *initializer_name_scope(block_t *parent) +{ + if (parent == GLOBAL_BLOCK && global_constant_initializer_scope) + return global_constant_initializer_scope; + return parent; +} + +/* Nonzero while the constant expression reader has this function read one + * address operand of the expression it is already reading. + */ +int global_tested_operand_depth; + +/* Whether the element ahead is an address constant that an operator only + * tests, as in `"a" && 1`, `1 ? "a" : "b"` or `array || 0`. Such an element is + * a constant expression, not the address itself, so the whole expression + * belongs to the constant expression reader. + */ +static bool global_tested_operand_starts_here(block_t *scope) +{ + char name[MAX_ID_LEN]; + token_t *after; + + if (global_tested_operand_depth || !cur_token->next) + return false; + if (!lex_peek(T_string, NULL) && + !(lex_peek(T_identifier, name) && + (find_visible_func(name, scope) || find_var(name, scope)))) + return false; + after = cur_token->next->next; + return after && (after->kind == T_log_and || after->kind == T_log_or || + after->kind == T_question); +} + +var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) +{ + var_t *val = NULL; + block_t *scope = initializer_name_scope(parent); + bool address_dereference = + global_function_address_dereference_starts_here(); + token_t *address_dereference_identifier = NULL; + bool explicit_address; + bool grouped_function_designator = false; + + /* A cast to a function pointer type converts the function designator or + * null pointer constant after it, which then carries the cast's prototype. + */ + func_t *cast_signature = read_global_function_pointer_cast(scope); + + if (cast_signature) { + val = parse_global_constant_value(parent, bb); + if (val && val->is_func) + val->func_signature = cast_signature; + else if (val && !effective_pointer_depth(val)) { + /* An integer cast to a function pointer type is an explicit + * conversion: keep the cast's type on the value, so the element + * store does not take it for an implicit one. + */ + val->ptr_level = 1; + val->func_signature = cast_signature; + } + return val; + } + + if (address_dereference) { + address_dereference_identifier = + consume_global_function_address_dereference(); + if (!find_visible_func(address_dereference_identifier->literal, scope)) + error_at("Function address requires a visible declaration", + cur_token_loc()); + val = require_func_symbol_var(parent); + val->var_name = intern_string(address_dereference_identifier->literal); + val->is_func = true; + return val; + } + if (global_pointer_cast_starts_here(scope)) { + int saved_stride = global_pointer_cast_stride; + func_t *slot_signature; + int stride = read_global_pointer_cast(scope, &slot_signature); + + /* In a chain of casts the outermost one decides the stride. */ + if (saved_stride) + stride = saved_stride; + if (!global_address_operand_starts_here(scope)) + return read_global_cast_integer_address(parent, *bb, scope, stride, + slot_signature); + global_pointer_cast_stride = stride; + val = parse_global_constant_value(parent, bb); + global_pointer_cast_stride = saved_stride; + return val; + } + explicit_address = lex_accept(T_ampersand); + if (explicit_address && subscripted_string_literal_starts_here()) + return read_string_literal_element_address(parent, *bb, scope); + if (!explicit_address && string_address_offset_starts_here()) + return read_string_address_offset(parent, *bb, scope); + + if (grouped_global_function_designator_starts_here(false)) { + lex_expect(T_open_bracket); + grouped_function_designator = true; + } + + char constant_name[MAX_ID_LEN]; + + if (!explicit_address && !grouped_function_designator && + (lex_peek(T_numeric, NULL) || lex_peek(T_minus, NULL) || + lex_peek(T_plus, NULL) || lex_peek(T_bit_not, NULL) || + lex_peek(T_log_not, NULL) || lex_peek(T_open_bracket, NULL) || + lex_peek(T_sizeof, NULL) || lex_peek(T_char, NULL) || + lex_peek(T_wchar, NULL) || subscripted_string_literal_starts_here() || + global_tested_operand_starts_here(scope) || + (lex_peek(T_identifier, constant_name) && + find_scoped_constant(constant_name, scope)))) { + /* Any integer constant expression, including casts, sizeof and grouped + * subexpressions. The two-word reader keeps a wide value's high word. + */ + val = read_wide_global_literal_expression(parent, *bb, scope); + } else if (explicit_address || grouped_function_designator || + lex_peek(T_identifier, NULL)) { + char name[MAX_ID_LEN]; + + if (lex_peek(T_identifier, name)) { + func_t *func = find_visible_func(name, scope); + if (func) { + lex_expect(T_identifier); + if (grouped_function_designator) + lex_expect(T_close_bracket); + val = require_func_symbol_var(parent); + val->var_name = intern_string(name); + val->is_func = true; + return val; + } + if (explicit_address) { + /* A block-scope static initializer lowers through this global + * constant path, yet its names, subscripts, and offsets resolve + * in the declaration's lexical scope. The is_global test still + * rejects the address of an automatic object. + */ + var_t *object = find_var(name, scope); + + if (object && object->is_global) { + lex_expect(T_identifier); + val = require_ref_var(parent, object->type, + object->ptr_level); + val->var_name = gen_name(); + val->is_global_address = true; + add_insn(parent, *bb, OP_address_of, val, object, NULL, 0, + NULL); + return read_global_address_designator(scope, parent, bb, + &object, val, false); + } + } else { + var_t *object = find_var(name, scope); + bool subscripted = cur_token->next->next && + cur_token->next->next->kind == T_open_square; + + /* A static array decays to an address constant (C99 6.6p7), + * optionally offset by an integer constant, and a subscripted + * row of one decays just as the whole array does. + */ + if (object && object->is_global && + (subscripted ? object->array_dim2 : object->array_size)) { + lex_expect(T_identifier); + val = require_ref_var(parent, object->type, + object->ptr_level); + val->var_name = gen_name(); + val->is_global_address = true; + add_insn(parent, *bb, OP_address_of, val, object, NULL, 0, + NULL); + return read_global_address_designator(scope, parent, bb, + &object, val, true); + } + } + } + if (explicit_address) + error_at("Expected a global object or function after '&'", + cur_token_loc()); + error_at("Global aggregate initializer requires a constant value", + cur_token_loc()); + } else if (lex_peek(T_string, NULL)) { + /* A character-pointer member has the same constant-expression form as a + * standalone global pointer: retain the rodata address, including + * adjacent-literal concatenation, for the aggregate store. + */ + read_literal_param(parent, *bb); + val = opstack_pop(); + } else { + error_at("Global array initialization requires constant values", + next_token_loc()); + } + + return val; +} + +bool is_record_type(const type_t *type) +{ + /* A callback typedef whose function returns a record keeps that record's + * members for its prototype, but names a pointer, not a record. + */ + if (type && type->func_signature && !type->is_direct_function_type) + return false; + + /* A pointer typedef that defines its record, `typedef struct r {...} *rp`, + * copies the record's fields but names a pointer, not a record. + */ + return type && + (type->base_type == TYPE_struct || type->base_type == TYPE_union || + (type->base_type == TYPE_typedef && type->num_fields > 0 && + !type->ptr_level)); +} + +void parse_struct_field_init(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr); +bool parse_struct_field_values(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr, + bool elided, + bool cleared, + var_t *first_value); +bool parse_unbraced_record_init(block_t *parent, + basic_block_t **bb, + type_t *record_type, + var_t *addr, + bool cleared); +bool unbraced_record_starts_here(const var_t *elem); +bool string_row_starts_here(const var_t *array); +void parse_string_row_init(block_t *parent, + basic_block_t **bb, + const var_t *array, + var_t *target_addr, + int start); + +/* Store @zero, a loaded zero constant, into each of @size bytes at @addr. The + * stores are byte-wide so that a record or a wide element needs no store width + * a narrow backend cannot encode. + */ +void emit_zero_bytes(block_t *parent, + basic_block_t **bb, + var_t *addr, + int size, + var_t *zero) +{ + for (int offset = 0; offset < size; offset++) { + var_t *byte_addr = compute_element_address(parent, bb, addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } +} + +var_t *emit_zero_constant(block_t *parent, basic_block_t **bb) +{ + var_t *zero = require_var(parent); + + zero->var_name = gen_name(); + zero->init_val = 0; + add_insn(parent, *bb, OP_load_constant, zero, NULL, NULL, 0, NULL); + return zero; +} + +/* Store zero into every byte of elements [from, to) of the array at @base. */ +void emit_zero_elements(block_t *parent, + basic_block_t **bb, + var_t *base, + int from, + int to, + int elem_size) +{ + var_t *zero; + + if (from >= to) + return; + zero = emit_zero_constant(parent, bb); + for (int i = from; i < to; i++) + emit_zero_bytes(parent, bb, + compute_element_address(parent, bb, base, i, elem_size), + elem_size, zero); +} + +/* The width of one element of @array: a pointer for an array of pointers or of + * function designators, and the element type's size otherwise. + */ +int array_element_size(const var_t *array) +{ + return (array->ptr_level || array->is_func) ? PTR_SIZE : array->type->size; +} + +/* Read an array designator such as `[1]` or `[1][0]` for @array, if one is + * next. Each subscript is checked against its own bound and descends one level, + * until a scalar is reached or, when @max_depth is nonzero, that many + * subscripts have been read. An @unbounded outer bound is still being inferred + * from the initializer and is not checked. The caller consumes whatever + * follows. + * + * Returns the number of subscripts read. When there were any, *@flat is the + * row-major element offset of the designated subobject and @slice, if given, + * holds the shape of the array it is one element of. + */ +int read_array_designator(block_t *scope, + const var_t *array, + int max_depth, + bool unbounded, + int *flat, + var_t *slice) +{ + var_t element; + int depth = 0; + int offset = 0; + + memcpy(&element, array, sizeof(element)); + while ((!max_depth || depth < max_depth) && + (!depth || element.array_size) && lex_accept(T_open_square)) { + int stride = fixed_array_inner_count(&element); + int index = read_const_expr(scope); + + lex_expect(T_close_square); + if (index < 0 || + ((depth || !unbounded) && index >= element.array_size / stride)) + error_at("Array designator index is out of bounds", + cur_token_loc()); + offset += index * stride; + if (slice) + memcpy(slice, &element, sizeof(element)); + + /* An unbounded outer dimension has no extent to drop yet: descend from + * a single outer element, whose inner bounds are known. + */ + if (!depth && unbounded) + element.array_size = stride; + fixed_array_var_drop_outer(&element); + depth++; + } + if (depth) + *flat = offset; + return depth; +} + +/* Read an `[index] =` designator for one of @bound slots, if one is next. */ +bool accept_slot_designator(block_t *scope, int bound, int *slot) +{ + var_t slots = {0}; + + slots.array_size = bound; + if (!read_array_designator(scope, &slots, 1, false, slot, NULL)) + return false; + lex_expect(T_assign); + return true; +} + +/* Parse one row of a two-dimensional array. An unbraced row returns true when a + * brace-elided record element consumed the comma after the row's last + * initializer, so the enclosing list must not expect it again. Only a braced + * row owns designators; in an unbraced one they belong to the enclosing list. + * As in the plane and hyperplane helpers below, `filled` is one past the + * highest slot written, so after designators in any order the zero fill covers + * exactly the slots nothing wrote. + */ +bool parse_array_field_row_values(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool braced) +{ + int count = 0; + int filled = 0; + bool comma_consumed = false; + int elem_size = array_element_size(field); + + if (braced) { + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + } + + /* The whole row may be one string literal, optionally in its own braces. */ + if (string_row_starts_here(field)) { + parse_string_row_init(parent, bb, field, target_addr, start); + if (braced) { + lex_accept(T_comma); + lex_expect(T_close_curly); + } + return false; + } + while (!lex_peek(T_close_curly, NULL)) { + var_t *value = NULL; + var_t *elem_addr; + + if (braced) + accept_slot_designator(initializer_name_scope(parent), + field->array_dim2, &count); + if (count >= field->array_dim2) + error_at("Too many elements in array initializer", + next_token_loc()); + emit_zero_elements(parent, bb, target_addr, start + filled, + start + count, elem_size); + + elem_addr = compute_element_address(parent, bb, target_addr, + start + count, elem_size); + comma_consumed = false; + if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { + type_t *record_type = resolve_record_type(field->type); + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, record_type, elem_addr); + lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(field)) { + comma_consumed = parse_unbraced_record_init( + parent, bb, field->type, elem_addr, count < filled); + } else if (parent == GLOBAL_BLOCK) { + value = parse_global_constant_value(parent, bb); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + + if (value) { + var_t *stored = value->is_func + ? value + : resize_to(parent, bb, value, field->type, + field->ptr_level); + add_insn(parent, *bb, OP_write, NULL, elem_addr, stored, elem_size, + NULL); + } + + count++; + if (count > filled) + filled = count; + if (count == field->array_dim2) { + if (braced && (comma_consumed || lex_accept(T_comma)) && + !lex_peek(T_close_curly, NULL)) { + if (lex_peek(T_open_square, NULL)) + continue; + error_at("Too many elements in array initializer", + next_token_loc()); + } + break; + } + if (!comma_consumed && !lex_accept(T_comma)) + break; + comma_consumed = true; + } + if (braced) { + lex_expect(T_close_curly); + comma_consumed = false; + } + + emit_zero_elements(parent, bb, target_addr, start + filled, + start + field->array_dim2, elem_size); + return comma_consumed; +} + +void parse_array_field_row_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start) +{ + parse_array_field_row_values(parent, bb, field, target_addr, start, true); +} + +/* Parse one braced plane of a three-dimensional array. Rows reuse the + * two-dimensional helper, which also supplies C99's trailing zero fill. + */ +void parse_array_field_plane_values(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool braced) +{ + var_t row; + int rows = 0; + int filled = 0; + int row_width = field->array_dim3; + int elem_size = array_element_size(field); + + memcpy(&row, field, sizeof(row)); + row.array_dim2 = row_width; + row.array_dim3 = 0; + if (braced) { + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + } + while (!lex_peek(T_close_curly, NULL)) { + accept_slot_designator(initializer_name_scope(parent), + field->array_dim2, &rows); + if (rows >= field->array_dim2) + error_at("Too many rows in array initializer", next_token_loc()); + emit_zero_elements(parent, bb, target_addr, start + filled * row_width, + start + rows * row_width, elem_size); + bool row_comma = parse_array_field_row_values( + parent, bb, &row, target_addr, start + rows * row_width, + lex_peek(T_open_curly, NULL)); + rows++; + if (rows > filled) + filled = rows; + if (rows == field->array_dim2) { + if (braced && (row_comma || lex_accept(T_comma)) && + !lex_peek(T_close_curly, NULL)) { + if (lex_peek(T_open_square, NULL)) + continue; + error_at("Too many rows in array initializer", + next_token_loc()); + } + break; + } + if (!row_comma && !lex_accept(T_comma)) + break; + } + if (braced) + lex_expect(T_close_curly); + + emit_zero_elements(parent, bb, target_addr, start + filled * row_width, + start + field->array_dim2 * row_width, elem_size); +} + +void parse_array_field_plane_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start) +{ + parse_array_field_plane_values(parent, bb, field, target_addr, start, true); +} + +/* Parse one braced outer slice of a four-dimensional array. Each contained + * three-dimensional plane reuses the existing plane parser, so row bounds and + * trailing zero fill remain identical at every nesting level. + */ +void parse_array_field_hyperplane_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start) +{ + var_t plane; + int planes = 0; + int filled = 0; + int plane_size = field->array_dim3 * field->array_dim4; + int elem_size = array_element_size(field); + + memcpy(&plane, field, sizeof(plane)); + plane.array_size = plane_size; + plane.array_dim2 = field->array_dim3; + plane.array_dim3 = field->array_dim4; + plane.array_dim4 = 0; + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + while (!lex_peek(T_close_curly, NULL)) { + accept_slot_designator(initializer_name_scope(parent), + field->array_dim2, &planes); + if (planes >= field->array_dim2) + error_at("Too many planes in array initializer", next_token_loc()); + emit_zero_elements(parent, bb, target_addr, start + filled * plane_size, + start + planes * plane_size, elem_size); + parse_array_field_plane_values(parent, bb, &plane, target_addr, + start + planes * plane_size, + lex_peek(T_open_curly, NULL)); + planes++; + if (planes > filled) + filled = planes; + if (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + emit_zero_elements(parent, bb, target_addr, start + filled * plane_size, + start + field->array_dim2 * plane_size, elem_size); +} + +/* Read a string literal and every adjacent one after it, decoded and joined + * into @combined, which holds MAX_STRING_LEN bytes (C99 translation phase 6). + * Each piece is decoded in place at the end of what came before, so the + * capacity handed to the decoder and the buffer it writes are the same object. + * Returns the joined length in bytes as reported by the decoder, which counts + * an embedded null character; strlen() of @combined would stop at it. + */ +int read_concatenated_string(char *combined) +{ + char literal[MAX_STRING_LEN]; + int used; + + lex_ident(T_string, literal); + used = unescape_string(literal, combined, MAX_STRING_LEN); + if (used < 0) + error_at("Concatenated string literal too long", cur_token_loc()); + while (lex_peek(T_string, NULL)) { + int added; + + lex_ident(T_string, literal); + added = + unescape_string(literal, combined + used, MAX_STRING_LEN - used); + if (added < 0 || used + added >= MAX_STRING_LEN - 1) + error_at("Concatenated string literal too long", cur_token_loc()); + used += added; + } + return used; +} + +/* An array member can be initialized directly by a string literal just like a + * standalone character array. The member has no independent var_t storage, so + * write through its already-computed field address rather than routing this + * through parse_string_array_init(). + */ +void parse_string_field_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr) +{ + char combined[MAX_STRING_LEN]; + int len; + + /* The terminating null is dropped when only the characters fit (C99 + * 6.7.8p14). + */ + len = read_concatenated_string(combined) + 1; + if (len - 1 > field->array_size) + error_at("String initializer is too long for character array", + cur_token_loc()); + + for (int i = 0; i < field->array_size; i++) { + var_t *value = require_var(parent); + var_t *addr = compute_element_address(parent, bb, target_addr, i, 1); + + value->var_name = gen_name(); + value->init_val = i < len ? (unsigned char) combined[i] : 0; + value->is_const = true; + add_insn(parent, *bb, OP_load_constant, value, NULL, NULL, 0, NULL); + add_insn(parent, *bb, OP_write, NULL, addr, value, 1, NULL); + } +} + +bool is_char_array(const var_t *var) +{ + return var && !var->ptr_level && !var->array_dim2 && !var->array_dim3 && + !var->array_dim4 && + compatible_decl_type(var->type, find_type("char", true)); +} + +bool is_wchar_array(const var_t *var) +{ + return var && !var->ptr_level && !var->array_dim2 && !var->array_dim3 && + !var->array_dim4 && + compatible_decl_type(var->type, find_type("wchar_t", true)); +} + +/* C99 permits a string literal initializer only for an array of matching + * character units. Diagnose this before the ordinary assignment path, which + * treats a literal as a pointer and otherwise produces a misleading error. + */ +void validate_string_array_initializer(const var_t *var) +{ + if (!var || var->ptr_level || + !(var->array_size > 0 || var->has_unsized_array) || + !(lex_peek(T_string, NULL) || lex_peek(T_wstring, NULL))) + return; + if ((lex_peek(T_string, NULL) && !is_char_array(var)) || + (lex_peek(T_wstring, NULL) && !is_wchar_array(var))) + error_at( + "String literal initializer has incompatible array element type", + cur_token_loc()); +} + +void parse_wstring_field_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr) +{ + int values[MAX_STRING_LEN]; + int length; + + length = read_wstring_units(values, MAX_STRING_LEN); + if (length > field->array_size) + error_at("Wide string initializer is too long for array", + cur_token_loc()); + + for (int i = 0; i < field->array_size; i++) { + var_t *value = require_typed_var(parent, field->type); + var_t *addr = compute_element_address(parent, bb, target_addr, i, + field->type->size); + + value->var_name = gen_name(); + value->init_val = i < length ? values[i] : 0; + value->is_const = true; + add_insn(parent, *bb, OP_load_constant, value, NULL, NULL, 0, NULL); + add_insn(parent, *bb, OP_write, NULL, addr, value, field->type->size, + NULL); + } +} + +/* A string literal also initializes one innermost row of a multidimensional + * character array: `char names[2][4] = { "ab", "cd" }` fills each row of four + * bytes, zero-padded, rather than storing the literal's address into a char. + * Return whether the next initializer is such a string for @array. A literal + * that only begins a larger expression, as in `"ab"[0]`, remains a scalar. + */ +bool string_row_starts_here(const var_t *array) +{ + token_t *after = cur_token->next; + token_kind_t kind = after ? after->kind : T_eof; + + if (!array || has_effective_pointer(array) || array->is_func || + !array->array_dim2 || is_record_type(array->type) || + (kind != T_string && kind != T_wstring)) + return false; + while (after->next && after->next->kind == kind) + after = after->next; + if (!after->next || + (after->next->kind != T_comma && after->next->kind != T_close_curly)) + return false; + if ((kind == T_string && + !compatible_decl_type(array->type, find_type("char", true))) || + (kind == T_wstring && + !compatible_decl_type(array->type, find_type("wchar_t", true)))) + error_at( + "String literal initializer has incompatible array element type", + next_token_loc()); + return true; +} + +/* Elements in the innermost row of a multidimensional @array. */ +int string_row_width(const var_t *array) +{ + if (array->array_dim4) + return array->array_dim4; + return array->array_dim3 ? array->array_dim3 : array->array_dim2; +} + +/* Initialize the innermost row of @array that starts at flat element @start + * from the string literal string_row_starts_here() accepted. The row must begin + * on a row boundary; a string cannot initialize a single element. + */ +void parse_string_row_init(block_t *parent, + basic_block_t **bb, + const var_t *array, + var_t *target_addr, + int start) +{ + var_t row; + int width = string_row_width(array); + var_t *row_addr; + + if (start % width) + error_at("String literal cannot initialize a single array element", + next_token_loc()); + memcpy(&row, array, sizeof(row)); + row.array_size = width; + row.array_dim2 = row.array_dim3 = row.array_dim4 = 0; + row_addr = compute_element_address(parent, bb, target_addr, start, + array->type->size); + if (lex_peek(T_wstring, NULL)) + parse_wstring_field_init(parent, bb, &row, row_addr); + else + parse_string_field_init(parent, bb, &row, row_addr); +} + +void parse_array_field_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr) +{ + int count = 0; + + /* One past the highest element written so far. A designator can move + * backwards, so zero fill starts here rather than at `count`, which would + * clear rows that an earlier designator already stored. + */ + int filled = 0; + int elem_size = array_element_size(field); + + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + while (!lex_peek(T_close_curly, NULL)) { + var_t *value = NULL; + var_t slice; + bool comma_consumed = false; + + /* `slice` is the array whose one element the next initializer fills. + * Without a designator that is the member itself. Each subscript of a + * designator such as `[1] = { ... }` or `[1][0] = 3` descends one + * level, so the braced row, plane and scalar paths below see exactly + * the shape the designator names and reordered rows stay valid. + */ + memcpy(&slice, field, sizeof(slice)); + if (read_array_designator(initializer_name_scope(parent), field, 0, + false, &count, &slice)) + lex_expect(T_assign); + + if (count >= field->array_size) + error_at("Too many elements in array initializer", + next_token_loc()); + emit_zero_elements(parent, bb, target_addr, filled, count, elem_size); + + var_t *elem_addr = + compute_element_address(parent, bb, target_addr, count, elem_size); + if (slice.array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, &slice, target_addr, + count); + count += fixed_array_inner_count(&slice); + } else if (slice.array_dim3 && lex_peek(T_open_curly, NULL)) { + parse_array_field_plane_init(parent, bb, &slice, target_addr, + count); + count += fixed_array_inner_count(&slice); + } else if (slice.array_dim2 && lex_peek(T_open_curly, NULL)) { + parse_array_field_row_init(parent, bb, &slice, target_addr, count); + count += fixed_array_inner_count(&slice); + } else if (string_row_starts_here(&slice)) { + parse_string_row_init(parent, bb, &slice, target_addr, count); + count += string_row_width(&slice); + } else { + if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { + type_t *record_type = resolve_record_type(field->type); + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, record_type, elem_addr); + lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(field)) { + comma_consumed = parse_unbraced_record_init( + parent, bb, field->type, elem_addr, count < filled); + } else if (parent == GLOBAL_BLOCK) { + value = parse_global_constant_value(parent, bb); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + + if (value) { + var_t *stored = value->is_func + ? value + : resize_to(parent, bb, value, field->type, + field->ptr_level); + add_insn(parent, *bb, OP_write, NULL, elem_addr, stored, + elem_size, NULL); + } + count++; + } + + if (count > filled) + filled = count; + if (!comma_consumed && !lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + emit_zero_elements(parent, bb, target_addr, filled, field->array_size, + elem_size); +} + +/* Initialize the record at @addr from @value, an initializer already read that + * does not begin with a brace. An expression of the record's own type + * initializes the whole record. Anything else, or no value at all, starts a + * brace-elided list that takes one initializer per member from the enclosing + * list (C99 6.7.8p13 and p20). + * + * @cleared says the record's automatic storage is already zero: the object that + * holds it was cleared up front, and a designator may since have stored some of + * its members, which the elided list must keep (C99 6.7.8p19). + * + * Returns true when that list consumed the comma after its last initializer, + * which the enclosing loop must then not expect. + */ +bool parse_record_from_value(block_t *parent, + basic_block_t **bb, + type_t *record_type, + var_t *addr, + var_t *value, + bool cleared) +{ + record_type = resolve_record_type(record_type); + if (is_record_object(value) && + resolve_record_type(value->type) == record_type) { + emit_record_copy_to_address(parent, bb, addr, value); + return false; + } + return parse_struct_field_values(parent, bb, record_type, addr, true, + cleared, value); +} + +/* A record slot whose initializer does not begin with a brace. Reading a block + * scope expression first is the only way to tell a record value from the first + * scalar of an elided list. Static storage takes constants only, which are + * never records, and a string always belongs to a member, so neither is read + * ahead: the elided list then parses it in the member's own context. + */ +bool parse_unbraced_record_init(block_t *parent, + basic_block_t **bb, + type_t *record_type, + var_t *addr, + bool cleared) +{ + var_t *value = NULL; + + if (parent != GLOBAL_BLOCK && !lex_peek(T_string, NULL) && + !lex_peek(T_wstring, NULL)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + return parse_record_from_value(parent, bb, record_type, addr, value, + cleared); +} + +/* Whether an initializer starting at the next token for a slot of @elem's type + * is a brace-elided record, rather than a braced one or a designator that + * belongs to the enclosing list. + */ +bool unbraced_record_starts_here(const var_t *elem) +{ + return !elem->ptr_level && !elem->is_func && is_record_type(elem->type) && + !lex_peek(T_open_curly, NULL) && !lex_peek(T_dot, NULL) && + !lex_peek(T_open_square, NULL); +} + +/* The address an initializer of @member in the record at @record_addr stores + * through: @designated when a designator or a brace-elided array already + * selected the member or one of its elements, and the member itself otherwise. + */ +var_t *member_address(block_t *parent, + basic_block_t **bb, + var_t *record_addr, + const var_t *member, + var_t *designated) +{ + if (designated) + return designated; + return compute_field_address(parent, bb, record_addr, member); +} + +/* The index of the first member of @record from @index on that takes an + * initializer. An unnamed bit-field is padding rather than a member: it takes + * part in layout but consumes no positional initializer, so the second value of + * `{low, high}` reaches the named field after it. + */ +int skip_unnamed_bitfields(const type_t *record, int index) +{ + while (index < record->num_fields && is_bitfield(&record->fields[index]) && + !record->fields[index].var_name[0]) + index++; + return index; +} + +void parse_struct_field_init(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr) +{ + parse_struct_field_values(parent, bb, struct_type, target_addr, false, + false, NULL); +} + +/* The constant bit-field slices of a static record, one entry per allocation + * unit, keyed by the unit's byte offset in the record that owns the list. + */ +typedef struct { + var_t *unit[MAX_FIELDS]; + var_t *addr[MAX_FIELDS]; + int offset[MAX_FIELDS]; + unsigned value[MAX_FIELDS]; + int count; +} static_bitfield_units_t; + +/* Set just before a member designator's nested record list is parsed: that list + * adds its slices to these units, at this byte offset, so a later designator of + * the same unit keeps the bits an earlier one stored. + */ +static_bitfield_units_t *designated_bitfield_units; +int designated_bitfield_base; + +/* Parse the members of the record at @target_addr. A braced list runs to its + * closing brace, which the caller consumes. An @elided list has no braces of + * its own: it stops once every member has an initializer, or at a closing brace + * or designator that belongs to the enclosing list, and @first_value is an + * initializer the caller already read for the first scalar member. A @cleared + * record is not zeroed again; see parse_record_from_value(). + */ +bool parse_struct_field_values(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr, + bool elided, + bool cleared, + var_t *first_value) +{ + int field_idx = 0; + int initializer_count = 0; + bool comma_consumed = false; + bool is_union = + struct_type->base_type == TYPE_union || struct_type->is_union; + + if (!elided) + reject_empty_initializer_in_strict_c99(); + + /* Both of these back a "field" pointer that outlives the designator block + * they are filled in from, so they have to live as long as the loop that + * dereferences it rather than as long as that block. + */ + var_t array_element; + var_t pending_array_element; + var_t *pending_array_base = NULL; + int pending_array_index = 0; + int pending_array_count = 0; + int pending_array_elem_size = 0; + bool has_pending_array_element = false; + + /* A static aggregate is zero-initialized before its initializer runs. A + * bit-field store must nevertheless preserve earlier fields in its shared + * allocation unit. Collect constant slices here and emit one direct store + * per unit after the whole initializer has been parsed, avoiding a global + * setup read before the global-pointer frame is established. + */ + static_bitfield_units_t own_units; + static_bitfield_units_t *units = &own_units; + int units_base = 0; + + own_units.count = 0; + if (designated_bitfield_units) { + units = designated_bitfield_units; + units_base = designated_bitfield_base; + designated_bitfield_units = NULL; + } + + /* Zero the complete struct before processing fields. Positional + * initializers could defer this until the first omitted member, but a + * designator may skip forward or return to an earlier member. A union is + * cleared whole: its member arrays take positional and designated elements + * through the same pending sequence, which leaves the elements it does not + * reach alone. + */ + if (parent != GLOBAL_BLOCK && !cleared) { + var_t *zero = emit_zero_constant(parent, bb); + + if (is_union) + emit_zero_bytes(parent, bb, target_addr, struct_type->size, zero); + for (int i = 0; !is_union && i < struct_type->num_fields; i++) { + var_t *field = &struct_type->fields[i]; + + emit_zero_bytes( + parent, bb, + compute_field_address(parent, bb, target_addr, field), + size_var(field), zero); + } + } + + if (first_value || !lex_peek(T_close_curly, NULL)) { + for (;;) { + var_t *field_val_raw = NULL; + var_t *field = NULL; + var_t *field_addr = NULL; + bool consumed_pending_array_element = false; + bool nested_comma_consumed = false; + bool nested_designator = false; + bool designated = false; + + if (!first_value && lex_accept(T_dot)) { + char field_name[MAX_ID_LEN]; + + /* The row-major offset of the subobject an array designator + * selected, or -1 when the designator names a member only. + */ + int selected_index = -1; + + /* A new member designator ends any array element sequence + * started by an earlier `.a[i] =`, so the positional + * initializers after it follow the newly named member instead + * of resuming the stale array slots. + */ + has_pending_array_element = false; + designated = true; + + lex_ident(T_identifier, field_name); + for (int i = 0; i < struct_type->num_fields; i++) { + if (!strcmp(struct_type->fields[i].var_name, field_name)) { + field_idx = i; + field = &struct_type->fields[i]; + break; + } + } + if (!field) + error_at("Unknown field in record initializer", + cur_token_loc()); + + field_addr = + compute_field_address(parent, bb, target_addr, field); + designated_bitfield_base = units_base + field->offset; + if (lex_peek(T_open_square, NULL)) { + int linear_index = 0; + int total_element_count = field->array_size; + int elem_size = array_element_size(field); + var_t *array_base_addr = field_addr; + + if (!field->array_size) + error_at("Array designator requires an array member", + next_token_loc()); + + /* The subscripts leave the remaining inner array in + * `field`, so rows, planes, and a final scalar can all + * share the same bounds and continuation path. + */ + read_array_designator(initializer_name_scope(parent), field, + 0, false, &linear_index, + &array_element); + fixed_array_var_drop_outer(&array_element); + field = &array_element; + field_addr = compute_element_address( + parent, bb, field_addr, linear_index, elem_size); + designated_bitfield_base += linear_index * elem_size; + pending_array_base = array_base_addr; + pending_array_count = total_element_count; + pending_array_elem_size = elem_size; + selected_index = linear_index; + if (!field->array_size) { + memcpy(&pending_array_element, field, sizeof(var_t)); + pending_array_index = linear_index + 1; + has_pending_array_element = true; + } + } + + /* A further member designator, as in `.in.a[1] = 3` or + * `.arr[1].x = 1`, descends into the record just selected. That + * record's own list takes the rest of the designator and the + * positional values after it, as a brace-elided record does, so + * they fill its following members before this list resumes + * after it (C99 6.7.8p17 and p18). + */ + if (lex_peek(T_dot, NULL)) { + if (field->array_size || field->ptr_level || + !is_record_type(field->type)) + error_at("Nested designator requires a record member", + next_token_loc()); + designated_bitfield_units = units; + nested_comma_consumed = parse_struct_field_values( + parent, bb, resolve_record_type(field->type), + field_addr, true, true, NULL); + nested_designator = true; + } else { + lex_expect(T_assign); + } + + /* A designator that selects a row or plane, as in `.a[1] = 3`, + * names a subobject that is itself an array. Without braces its + * initializer is brace-elided exactly as for a positional array + * member: the value fills the first element of that subobject + * and the following ones continue in row-major order through + * the rest of the member. + */ + if (!nested_designator && selected_index >= 0 && + field->array_size && !lex_peek(T_open_curly, NULL) && + ((!field->ptr_level && is_record_type(field->type)) || + has_effective_pointer(field) || + (!lex_peek(T_string, NULL) && + !lex_peek(T_wstring, NULL)))) { + memcpy(&pending_array_element, field, sizeof(var_t)); + pending_array_element.array_size = 0; + pending_array_element.array_dim2 = 0; + pending_array_element.array_dim3 = 0; + pending_array_element.array_dim4 = 0; + field = &pending_array_element; + pending_array_index = selected_index; + has_pending_array_element = true; + consumed_pending_array_element = true; + } + } else if (has_pending_array_element) { + field = &pending_array_element; + field_addr = compute_element_address( + parent, bb, pending_array_base, pending_array_index, + pending_array_elem_size); + consumed_pending_array_element = true; + } + + if (!field && !has_pending_array_element) + field_idx = skip_unnamed_bitfields(struct_type, field_idx); + + /* A union takes one positional initializer, but every designator + * names a member again, the same one or another, and the last wins + * (C99 6.7.8p19): `{ .p.x = 1, .p.y = 2 }` sets both. + */ + if (field_idx >= struct_type->num_fields || + (is_union && initializer_count > 0 && !designated && + !consumed_pending_array_element)) + error_at("Too many elements in record initializer", + next_token_loc()); + + if (!field && field_idx < struct_type->num_fields) + field = &struct_type->fields[field_idx]; + + /* A scalar that meets an array member without braces initializes + * the member's first element, and the following scalars fill the + * rest before the next member (C99 6.7.8p20). Writing it at the + * member's aggregate size instead corrupts the elements and gives + * narrow backends a store width they cannot encode. + */ + if (field && !field_addr && !has_pending_array_element && + field->array_size && + (first_value || + (!lex_peek(T_open_curly, NULL) && + ((!field->ptr_level && is_record_type(field->type)) || + has_effective_pointer(field) || + (!lex_peek(T_string, NULL) && + !lex_peek(T_wstring, NULL)))))) { + pending_array_base = + compute_field_address(parent, bb, target_addr, field); + pending_array_elem_size = array_element_size(field); + pending_array_count = field->array_size; + pending_array_index = 0; + memcpy(&pending_array_element, field, sizeof(var_t)); + pending_array_element.array_size = 0; + pending_array_element.array_dim2 = 0; + pending_array_element.array_dim3 = 0; + pending_array_element.array_dim4 = 0; + field = &pending_array_element; + field_addr = compute_element_address( + parent, bb, pending_array_base, 0, pending_array_elem_size); + has_pending_array_element = true; + consumed_pending_array_element = true; + } + + if (!first_value && field && field->array_size && + !has_effective_pointer(field) && + (lex_peek(T_string, NULL) || lex_peek(T_wstring, NULL)) && + ((lex_peek(T_string, NULL) && !is_char_array(field)) || + (lex_peek(T_wstring, NULL) && !is_wchar_array(field)))) + error_at( + "String literal initializer has incompatible array element " + "type", + cur_token_loc()); + if (nested_designator) { + /* The selected record's list already took its initializers. */ + } else if (first_value) { + /* The caller read this value for the first scalar of an elided + * record. A record member takes it on to that record's own + * first member; see parse_record_from_value(). + */ + if (!field->ptr_level && is_record_type(field->type)) + nested_comma_consumed = parse_record_from_value( + parent, bb, field->type, + member_address(parent, bb, target_addr, field, + field_addr), + first_value, true); + else + field_val_raw = first_value; + first_value = NULL; + } else if (field && field->array_size && is_char_array(field) && + lex_peek(T_string, NULL)) { + parse_string_field_init( + parent, bb, field, + member_address(parent, bb, target_addr, field, field_addr)); + } else if (field && field->array_size && !field->ptr_level && + compatible_decl_type(field->type, + find_type("wchar_t", true)) && + lex_peek(T_wstring, NULL)) { + parse_wstring_field_init( + parent, bb, field, + member_address(parent, bb, target_addr, field, field_addr)); + } else if (field && lex_peek(T_open_curly, NULL) && + field->array_size) { + parse_array_field_init( + parent, bb, field, + member_address(parent, bb, target_addr, field, field_addr)); + } else if (field && lex_peek(T_open_curly, NULL) && + is_record_type(field->type)) { + type_t *nested_type = resolve_record_type(field->type); + lex_expect(T_open_curly); + parse_struct_field_init( + parent, bb, nested_type, + member_address(parent, bb, target_addr, field, field_addr)); + lex_expect(T_close_curly); + } else if (field && !field->array_size && + unbraced_record_starts_here(field)) { + nested_comma_consumed = parse_unbraced_record_init( + parent, bb, field->type, + member_address(parent, bb, target_addr, field, field_addr), + true); + } else if (parent == GLOBAL_BLOCK) { + field_val_raw = parse_global_constant_value(parent, bb); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + field_val_raw = opstack_pop(); + } + + if (field_val_raw) { + field_addr = + member_address(parent, bb, target_addr, field, field_addr); + diagnose_function_pointer_conversion(field_val_raw, field); + if (field_val_raw->pointee_func_signature) + diagnose_callback_slot_initializer(field_val_raw, field); + if (is_record_type(field->type) && + is_record_object(field_val_raw)) { + emit_record_copy_to_address(parent, bb, field_addr, + field_val_raw); + } else if (field_val_raw->is_func) { + /* Keep a function designator intact until global lowering + * can patch its final code address. Converting it through a + * scalar temporary loses that relocation provenance, and at + * block scope resize_to() sees neither the designator nor a + * function pointer member as pointer-sized, so on LP64 it + * truncated the code address to an int. + */ + add_insn(parent, *bb, OP_write, NULL, field_addr, + field_val_raw, PTR_SIZE, NULL); + } else { + var_t *field_val = resize_to(parent, bb, field_val_raw, + field->type, field->ptr_level); + int field_size = size_var(field); + if (is_bitfield(field) && parent == GLOBAL_BLOCK) { + int unit = -1; + for (int i = 0; i < units->count; i++) + if (units->offset[i] == + units_base + field->offset && + units->unit[i]->bit_storage_size == + field->bit_storage_size) { + unit = i; + break; + } + if (unit < 0) { + if (units->count >= MAX_FIELDS) + error_at("Too many bit-fields in initializer", + cur_token_loc()); + unit = units->count++; + units->unit[unit] = field; + units->addr[unit] = field_addr; + units->offset[unit] = units_base + field->offset; + units->value[unit] = 0; + } + unsigned mask = bitfield_mask(field) + << field->bit_offset; + unsigned init_val = + is_bool_type(field->type) + ? field_val_raw->init_val != 0 + : (unsigned) field_val_raw->init_val; + units->value[unit] = (units->value[unit] & ~mask) | + ((init_val & bitfield_mask(field)) + << field->bit_offset); + } else if (is_bitfield(field)) + write_bitfield_value(parent, bb, field_addr, field_val, + field); + else + add_insn(parent, *bb, OP_write, NULL, field_addr, + field_val, field_size, NULL); + } + } + + if (has_pending_array_element) { + if (consumed_pending_array_element) + pending_array_index++; + if (pending_array_index >= pending_array_count) { + has_pending_array_element = false; + field_idx++; + } + } else { + field_idx++; + } + initializer_count++; + + /* An elided record ends with its last member and leaves the comma + * that follows to the enclosing list. + */ + if (elided && !has_pending_array_element) { + field_idx = skip_unnamed_bitfields(struct_type, field_idx); + if (field_idx >= struct_type->num_fields || is_union) { + comma_consumed = nested_comma_consumed; + break; + } + } + if (!nested_comma_consumed && !lex_accept(T_comma)) + break; + comma_consumed = true; + if (lex_peek(T_close_curly, NULL)) + break; + if (elided && + (lex_peek(T_dot, NULL) || lex_peek(T_open_square, NULL))) + break; + comma_consumed = false; + } + } + + /* A nested designator's list leaves its units to the list it came from. */ + if (parent == GLOBAL_BLOCK && units == &own_units) { + for (int i = 0; i < own_units.count; i++) { + var_t *value = bitfield_constant(parent, bb, own_units.value[i]); + add_insn(parent, *bb, OP_write, NULL, own_units.addr[i], value, + own_units.unit[i]->bit_storage_size, NULL); + } + } + return comma_consumed; +} + +void parse_array_literal_expr(block_t *parent, basic_block_t **bb) +{ + var_t *array_var = require_var(parent); + array_var->var_name = gen_name(); + array_var->is_compound_literal = true; + + int element_count = 0; + var_t *first_element = NULL; + + if (!lex_peek(T_close_curly, NULL)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + first_element = opstack_pop(); + element_count = 1; + + while (lex_accept(T_comma)) { + if (lex_peek(T_close_curly, NULL)) + break; + + read_expr(parent, bb); + read_ternary_operation(parent, bb); + opstack_pop(); + element_count++; + } + } + + lex_expect(T_close_curly); + + array_var->array_size = element_count; + if (first_element) { + array_var->type = first_element->type; + array_var->init_val = first_element->init_val; + } else { + array_var->type = TY_int; + array_var->init_val = 0; + } + + opstack_push(array_var); + add_insn(parent, *bb, OP_load_constant, array_var, NULL, NULL, 0, NULL); +} + +basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) +{ + if (lex_accept(T_semicolon)) { + if (strict_c99 && parent->func->return_def.type && + (parent->func->return_def.type->base_type != TYPE_void || + has_effective_pointer(&parent->func->return_def))) + error_at("non-void function requires a return expression in C99", + cur_token_loc()); + add_insn(parent, bb, OP_return, NULL, NULL, NULL, 0, NULL); + bb_connect(bb, parent->func->exit, NEXT); + return NULL; + } + + if (strict_c99 && parent->func->return_def.type && + parent->func->return_def.type->base_type == TYPE_void && + !has_effective_pointer(&parent->func->return_def)) + error_at("void function cannot return an expression in C99", + cur_token_loc()); + + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } + while (lex_accept(T_comma)) { + discard_operand(parent, bb); + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } + } + lex_expect(T_semicolon); + + var_t *rs1 = opstack_pop(); + + /* Handle array compound literals in return context. Convert array compound + * literals to their first element value. + */ + if (is_array_literal_placeholder(rs1) && strict_c99 && + !has_effective_pointer(&parent->func->return_def)) + error_at("array compound literal cannot be used as a scalar in C99", + cur_token_loc()); + + if (rs1 && rs1->array_size > 0 && rs1->var_name[0] == '.' && !strict_c99) { + var_t *val = require_var(parent); + val->type = rs1->type; + val->init_val = rs1->init_val; + val->var_name = gen_name(); + add_insn(parent, bb, OP_load_constant, val, NULL, NULL, 0, NULL); + rs1 = val; + } + + /* Every ordinary use of a function designator converts it to a pointer. + * This includes scalar conversions such as `_Bool f(void) { return cb; }`, + * not only callback-pointer returns. + */ + rs1 = materialize_function_designator(parent, &bb, rs1); + diagnose_function_pointer_conversion(rs1, &parent->func->return_def); + if (parent->func->return_def.type->func_signature) { + rs1->func_signature = parent->func->return_def.type->func_signature; + } + + if (parent->func->returns_aggregate) { + if (!is_record_object(rs1) || + !compatible_decl_type(rs1->type, parent->func->return_def.type)) + error_at("incompatible record return expression", cur_token_loc()); + emit_record_copy_to_address(parent, &bb, &parent->func->sret_def, rs1); + add_insn(parent, bb, OP_return, NULL, NULL, NULL, 0, NULL); + bb_connect(bb, parent->func->exit, NEXT); + return NULL; + } + + /* A return expression is converted to the function's declared type just + * like an assignment. This is particularly important for _Bool: a pointer + * return value must become 0 or 1 before it crosses the ABI boundary, + * rather than leaving an address in the low return byte. + */ + diagnose_integer_to_pointer_conversion(rs1, &parent->func->return_def, + false); + if (!parent->func->return_def.type->func_signature) + rs1 = resize_to(parent, &bb, rs1, parent->func->return_def.type, + parent->func->return_def.ptr_level); + + add_insn(parent, bb, OP_return, NULL, rs1, NULL, 0, NULL); + bb_connect(bb, parent->func->exit, NEXT); + return NULL; +} + +basic_block_t *handle_if_statement(block_t *parent, basic_block_t *bb) +{ + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; + + lex_expect(T_open_bracket); + read_control_expression(parent, &bb); + lex_expect(T_close_bracket); + + var_t *vd = opstack_pop(); + reject_record_operand(vd); + add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); + + basic_block_t *then_ = bb_create(parent); + basic_block_t *else_ = bb_create(parent); + bb_connect(bb, then_, THEN); + bb_connect(bb, else_, ELSE); + + basic_block_t *then_body = read_body_statement(parent, then_); + basic_block_t *then_next_ = NULL; + if (then_body) { + then_next_ = bb_create(parent); + bb_connect(then_body, then_next_, NEXT); + } + + if (lex_accept(T_else)) { + basic_block_t *else_body = read_body_statement(parent, else_); + basic_block_t *else_next_ = NULL; + if (else_body) { + else_next_ = bb_create(parent); + bb_connect(else_body, else_next_, NEXT); + } + + if (then_next_ && else_next_) { + basic_block_t *next_ = bb_create(parent); + bb_connect(then_next_, next_, NEXT); + bb_connect(else_next_, next_, NEXT); + return next_; + } + + return then_next_ ? then_next_ : else_next_; + } else { + if (then_next_) { + bb_connect(else_, then_next_, NEXT); + return then_next_; + } + return else_; + } +} + +basic_block_t *handle_while_statement(block_t *parent, basic_block_t *bb) +{ + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; + + continue_bb_push(bb); + + basic_block_t *cond = bb; + lex_expect(T_open_bracket); + read_control_expression(parent, &bb); + lex_expect(T_close_bracket); + + var_t *vd = opstack_pop(); + reject_record_operand(vd); + add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); + + basic_block_t *then_ = bb_create(parent); + basic_block_t *else_ = bb_create(parent); + bb_connect(bb, then_, THEN); + bb_connect(bb, else_, ELSE); + break_bb_push(else_); + + basic_block_t *body_ = read_body_statement(parent, then_); + + continue_pos_idx--; + break_exit_idx--; + + if (body_) + bb_connect(body_, cond, NEXT); + + return else_; +} + +basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) +{ + /* Since a goto splits the current program into two basic blocks and makes + * the subsequent basic block unreachable, this causes problems for later + * CFG operations. Therefore, we create a fake if that always executes to + * wrap the goto, and connect the unreachable basic block to the else + * branch. Finally, return this else block. + * + * after: a = b + c; goto label; c *= d; + * + * before: a = b + c; if (1) + * goto label; + * c *= d; + */ + + char token[MAX_ID_LEN]; + if (!lex_peek(T_identifier, token)) + error_at("Expected identifier after 'goto'", next_token_loc()); + + lex_expect(T_identifier); + lex_expect(T_semicolon); + + basic_block_t *fake_if = bb_create(parent); + bb_connect(bb, fake_if, NEXT); + var_t *val = require_var(parent); + val->var_name = gen_name(); + val->init_val = 1; + add_insn(parent, fake_if, OP_load_constant, val, NULL, NULL, 0, NULL); + add_insn(parent, fake_if, OP_branch, NULL, val, NULL, 0, NULL); + + basic_block_t *then_ = bb_create(parent); + basic_block_t *else_ = bb_create(parent); + bb_connect(fake_if, then_, THEN); + bb_connect(fake_if, else_, ELSE); + + add_insn(parent, then_, OP_jump, NULL, NULL, NULL, 0, token); + label_t *label = find_label(token); + if (label) { + label->used = true; + bb_connect(then_, label->bb, NEXT); + return else_; + } + + if (backpatch_bb_idx > MAX_LABELS - 1) + error_at("Too many forward-referenced labels", cur_token_loc()); + + backpatch_bb[backpatch_bb_idx++] = then_; + return else_; +} + +int read_const_expr(block_t *scope); + +/* Whether @token starts a string literal that is the whole brace-enclosed + * initializer of the character array @var: `"ab"}` or `"ab",}`. + */ +static bool string_ends_braced_initializer(const var_t *var, token_t *token) +{ + token_kind_t kind; + + if (!token) + return false; + kind = token->kind; + if (!((kind == T_string && is_char_array(var)) || + (kind == T_wstring && is_wchar_array(var)))) + return false; + while (token->next && token->next->kind == kind) + token = token->next; + token = token->next; + if (token && token->kind == T_comma) + token = token->next; + return token && token->kind == T_close_curly; +} + +/* Reject a string literal that is the whole initializer of one element of the + * array @var, as in `char s[3] = {"ab", "c"}` or `int s[3] = {"ab"}`. A string + * literal initializes a character array only as that array's whole initializer + * (C99 6.7.8p14), and an element that is not a pointer cannot take its address + * either. A literal that only begins a larger expression, such as `"ab"[0]`, is + * an ordinary scalar. + */ +static void reject_string_element_initializer(const var_t *var) +{ + token_t *token = cur_token->next; + token_kind_t kind = token ? token->kind : T_eof; + + if (var->ptr_level || var->is_func || has_effective_pointer(var) || + (kind != T_string && kind != T_wstring)) + return; + while (token->next && token->next->kind == kind) + token = token->next; + if (!token->next || + (token->next->kind != T_comma && token->next->kind != T_close_curly)) + return; + if ((kind == T_string && + compatible_decl_type(var->type, find_type("char", true))) || + (kind == T_wstring && + compatible_decl_type(var->type, find_type("wchar_t", true)))) + error_at("String literal cannot initialize a single array element", + next_token_loc()); + error_at("String literal initializer has incompatible array element type", + next_token_loc()); +} + +void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) +{ + int count = 0; + int inferred_size = 0; + var_t *base_addr = NULL; + + /* An omitted outer bound of a multidimensional declaration already has an + * inner-dimension product in `array_size`. It is nevertheless inferred from + * the initializer, just like a one-dimensional `int a[]`. + */ + bool is_implicit = (var->array_size == 0 || var->has_unsized_array); + block_t *initializer_scope = parent; + + if (parent == GLOBAL_BLOCK && var->scope && var->scope != GLOBAL_BLOCK) + initializer_scope = var->scope; + block_t *saved_initializer_scope = global_constant_initializer_scope; + if (parent == GLOBAL_BLOCK) + global_constant_initializer_scope = initializer_scope; + + /* Elements of a pointer array are pointer-sized. Using the base type's + * width strided "char *a[2] = {...}" by one byte, so every element but the + * first got a bogus address. `ptr_level` describes the array element type + * even when its outer bound is inferred. + */ + int elem_size = array_element_size(var); + + /* A character array's string literal may be enclosed in braces (C99 + * 6.7.8p14): `char s[3] = {"ab"}` holds the characters, not the literal's + * address converted to a char. + */ + if (lex_peek(T_open_curly, NULL) && + string_ends_braced_initializer(var, cur_token->next->next)) { + lex_expect(T_open_curly); + if (lex_peek(T_wstring, NULL)) + parse_wstring_array_init(var, parent, bb); + else + parse_string_array_init(var, parent, bb); + lex_accept(T_comma); + lex_expect(T_close_curly); + global_constant_initializer_scope = saved_initializer_scope; + return; + } + + base_addr = var; + + /* Reordered array designators can leave holes both before and after a + * written element. Initialize the whole automatic array first, then let + * explicit elements overwrite their slots. Byte stores also cover record + * elements without relying on a backend-wide aggregate store. + */ + if (parent != GLOBAL_BLOCK && !is_implicit) + emit_zero_elements(parent, bb, base_addr, 0, var->array_size, + elem_size); + + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + var_t *val = NULL; + var_t designated_array; + var_t *initializer_var = var; + bool elided_comma = false; + + /* Whether a member designator follows the subscripts, as in `[1].k + * = 3`, naming a member of one record element. + */ + bool member_designator = false; + + /* A designator such as `[1][0]` moves to the row-major offset of + * the subobject it names, and the braced row, plane and string + * paths below then see the array that subobject is an element of, + * rather than the whole declaration. + */ + if (read_array_designator(initializer_scope, var, 0, is_implicit, + &count, &designated_array)) { + initializer_var = &designated_array; + member_designator = lex_peek(T_dot, NULL) && + !designated_array.array_dim2 && + !var->ptr_level && !var->is_func && + is_record_type(var->type); + if (!member_designator) + lex_expect(T_assign); + } + + if (!is_implicit && count >= var->array_size) + error_at("Too many elements in array initializer", + next_token_loc()); + + /* A forward designator leaves a gap. Explicit arrays were zeroed + * before parsing and global storage begins zeroed, but an inferred + * local array has no known bound yet: zero the elements skipped + * past the highest one written, whatever form the next initializer + * takes. Every element below `inferred_size` is then either zero or + * written, which a brace-elided record relies on below. + */ + if (is_implicit && parent != GLOBAL_BLOCK) + emit_zero_elements(parent, bb, base_addr, inferred_size, count, + elem_size); + + if (initializer_var->array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, initializer_var, + base_addr, count); + count += fixed_array_inner_count(initializer_var); + if (is_implicit && count > inferred_size) + inferred_size = count; + if (!lex_accept(T_comma)) + break; + continue; + } else if (initializer_var->array_dim3 && + lex_peek(T_open_curly, NULL)) { + parse_array_field_plane_init(parent, bb, initializer_var, + base_addr, count); + count += fixed_array_inner_count(initializer_var); + if (is_implicit && count > inferred_size) + inferred_size = count; + if (!lex_accept(T_comma)) + break; + continue; + } else if (initializer_var->array_dim2 && + lex_peek(T_open_curly, NULL)) { + parse_array_field_row_init(parent, bb, initializer_var, + base_addr, count); + count += fixed_array_inner_count(initializer_var); + if (is_implicit && count > inferred_size) + inferred_size = count; + if (!lex_accept(T_comma)) + break; + continue; + } else if (string_row_starts_here(initializer_var)) { + parse_string_row_init(parent, bb, initializer_var, base_addr, + count); + count += string_row_width(initializer_var); + if (is_implicit && count > inferred_size) + inferred_size = count; + if (!lex_accept(T_comma) || lex_peek(T_close_curly, NULL)) + break; + continue; + } else if (member_designator) { + /* The record element's own list starts at that designator and + * takes the positional values after it, as a brace-elided + * record does, up to the next designator of this array. The + * element keeps what an earlier `[1].fn = f` stored, so only an + * inferred-bound element not yet reached is cleared first. + */ + var_t *elem_addr = compute_element_address( + parent, bb, base_addr, count, elem_size); + + if (is_implicit && parent != GLOBAL_BLOCK && + count >= inferred_size) + emit_zero_elements(parent, bb, base_addr, count, count + 1, + elem_size); + elided_comma = parse_struct_field_values( + parent, bb, resolve_record_type(var->type), elem_addr, true, + true, NULL); + } else if (lex_peek(T_open_curly, NULL) && + is_record_type(var->type)) { + type_t *struct_type = resolve_record_type(var->type); + + var_t *elem_addr = compute_element_address( + parent, bb, base_addr, count, elem_size); + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, struct_type, elem_addr); + lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(var)) { + /* Without braces a record element takes one initializer per + * member, so `struct p a[2] = { 1, 2, 3, 4 }` fills a[0] from 1 + * and 2 before a[1] starts. The record parser stops after its + * last member and leaves the comma to this loop. + */ + var_t *elem_addr = compute_element_address( + parent, bb, base_addr, count, elem_size); + + /* An element already reached is zero or written, and may hold + * members a designator stored; a new one clears itself. + */ + elided_comma = parse_unbraced_record_init( + parent, bb, var->type, elem_addr, + !is_implicit || count < inferred_size); + } else { + /* A global initializer is restricted to simple constants, but + * it still has to be stored. Consuming the tokens and dropping + * the value left every global array zero-filled, while the same + * initializer on a local worked. + */ + reject_string_element_initializer(var); + char leading_name[MAX_ID_LEN]; + + /* An arithmetic element, such as 1 ? 2 : 3 or 1 && 2, is folded + * as a constant. The runtime expression reader would branch, + * moving the global setup entry block to the branch's join and + * dropping every store emitted before it. + */ + if (parent == GLOBAL_BLOCK && + (lex_peek(T_numeric, NULL) || lex_peek(T_minus, NULL) || + global_tested_operand_starts_here(initializer_scope) || + lex_peek(T_char, NULL) || lex_peek(T_wchar, NULL) || + (lex_peek(T_identifier, leading_name) && + find_scoped_constant(leading_name, initializer_scope)) || + lex_peek(T_ampersand, NULL) || + grouped_global_function_designator_starts_here(true) || + global_function_address_dereference_starts_here() || + lex_peek(T_open_bracket, NULL) || + lex_peek(T_sizeof, NULL) || lex_peek(T_plus, NULL) || + lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL) || + string_element_appears_before_initializer_end( + cur_token->next) || + string_address_offset_starts_here())) { + /* Addresses, casts, sizeof and grouped or unary constant + * expressions share the reader of aggregate members. + */ + val = parse_global_constant_value(parent, bb); + } else { + char initializer_name[MAX_ID_LEN]; + char global_token[MAX_ID_LEN]; + bool function_initializer = + lex_peek(T_identifier, initializer_name) && + find_visible_func(initializer_name, initializer_scope); + var_t *object_constant = NULL; + + if (parent == GLOBAL_BLOCK && + lex_peek(T_identifier, global_token)) + object_constant = + find_var(global_token, initializer_scope); + + /* A static array named by a block-scope static's element is + * an address constant (C99 6.6p7), not an integer one, and + * takes the array-to-pointer path below. + */ + if (parent == GLOBAL_BLOCK && + initializer_scope != GLOBAL_BLOCK && + !lex_peek(T_string, NULL) && !function_initializer && + !lex_peek(T_ampersand, NULL) && + !(object_constant && object_constant->is_global && + object_constant->array_size)) { + /* Storage for a block-scope static lives globally, + * while its initializer is an integer constant + * expression in the surrounding block. Resolve local + * enumerators before emitting the global setup-store + * value. + */ + if (typed_global_literal_appears_before_initializer_end( + cur_token->next) || + string_element_appears_before_initializer_end( + cur_token->next)) { + /* read_const_expr() evaluates in an int, which + * drops the high word of a wide element. + */ + val = read_wide_global_literal_expression( + GLOBAL_BLOCK, *bb, var->scope); + } else { + val = require_var(GLOBAL_BLOCK); + val->var_name = gen_name(); + val->init_val = read_const_expr(var->scope); + val->is_const = true; + add_insn(GLOBAL_BLOCK, *bb, OP_load_constant, val, + NULL, NULL, 0, NULL); + } + } else { + if (parent == GLOBAL_BLOCK) { + char token[MAX_ID_LEN]; + bool enum_constant = + lex_peek(T_identifier, token) && + find_scoped_constant(token, parent); + bool function_constant = + lex_peek(T_identifier, token) && + find_visible_func(token, initializer_scope); + + if (!lex_peek(T_numeric, NULL) && + !lex_peek(T_minus, NULL) && + !lex_peek(T_string, NULL) && + !lex_peek(T_char, NULL) && + !lex_peek(T_wchar, NULL) && !enum_constant && + !function_constant && + !lex_peek(T_ampersand, NULL) && + !(object_constant && + object_constant->is_global && + object_constant->array_size)) + error_at( + "Global array initialization requires " + "constant " + "values", + next_token_loc()); + } + + if (parent == GLOBAL_BLOCK && object_constant && + object_constant->is_global && + object_constant->array_size) { + int stride = array_element_size(object_constant); + + /* Array-to-pointer conversion is a permitted + * address constant in static aggregate + * initializers. Preserve its row stride here rather + * than reading an object value, which global setup + * cannot do before GP is established. + */ + val = require_ref_var(parent, object_constant->type, + object_constant->ptr_level); + val->var_name = gen_name(); + lex_ident(T_identifier, global_token); + add_insn(parent, *bb, OP_address_of, val, + object_constant, NULL, 0, NULL); + if (object_constant->array_dim2) + stride *= object_constant->array_dim2; + if (object_constant->array_dim3) + stride *= object_constant->array_dim3; + if (object_constant->array_dim4) + stride *= object_constant->array_dim4; + if (object_constant->array_dim2 && + lex_peek(T_open_square, NULL)) { + var_t *row = object_constant; + + val->is_global_address = true; + val = read_global_address_designator( + initializer_scope, parent, bb, &row, val, + true); + } else if (lex_accept(T_plus)) { + int index = read_const_expr(initializer_scope); + + val = compute_element_address(parent, bb, val, + index, stride); + } else if (lex_accept(T_minus)) { + int index = read_const_expr(initializer_scope); + + val = compute_element_address(parent, bb, val, + -index, stride); + } + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + val = opstack_pop(); + } + } + } + } + + if (is_implicit && count >= MAX_IMPLICIT_ARRAY) + error_at("Too many elements in array initializer", + next_token_loc()); + + /* An element of an array of callbacks is a callback object. */ + if (val && !var->type->array_element_pointee_func_signature && + !var->pointee_func_signature && + (var->func_signature || + (var->type->func_signature && + !var->type->is_direct_function_type && !var->ptr_level))) { + var_t element = {0}; + + element.type = var->type; + element.ptr_level = var->func_signature ? var->ptr_level : 0; + element.is_func = true; + element.func_signature = var->func_signature + ? var->func_signature + : var->type->func_signature; + diagnose_function_pointer_conversion(val, &element); + } else if (val && + (val->is_func || val->func_signature || + (val->type && val->type->func_signature)) && + effective_pointer_depth(var) && !var->is_func && + !var->func_signature && !var->pointee_func_signature && + !var->type->func_signature && + !var->type->array_element_pointee_func_signature) { + /* An element of an array of object pointers takes no function + * pointer, not even one an explicit cast produced. + */ + var_t element = {0}; + + element.type = var->type; + element.ptr_level = var->ptr_level; + diagnose_function_pointer_conversion(val, &element); + } + + /* A pointer to function pointers keeps its prototype through a + * cast, as `(int (**)(void)) 4` does, and an element compares it. + */ + func_t *element_slot = + var->type->array_element_pointee_func_signature; + + if (val && (element_slot || (val->pointee_func_signature && + var->pointee_func_signature))) { + /* The array descriptor is not an element descriptor. Build the + * slot type that this scalar initializer is about to store so + * callback prototype validation remains elementwise. + */ + var_t element_target = {0}; + + element_target.type = var->type; + element_target.ptr_level = + element_slot ? var->type->array_element_ptr_level + : var->ptr_level; + element_target.pointee_func_signature = + element_slot ? element_slot : var->pointee_func_signature; + if (incompatible_pointee_callback_conversion(val, + &element_target)) + error_at( + "incompatible callback slot types in array " + "initializer", + cur_token_loc()); + } + + if (val && (is_implicit || count < var->array_size)) { + /* Keep a function symbol intact until OP_address_of_func can + * emit its deferred relocation. Treating an array-of-callback + * element as an `int` conversion loses that provenance. + */ + var_t *v; + + if (val->is_func || var->ptr_level > 0) + v = val; + else + v = resize_to(parent, bb, val, var->type, 0); + + var_t *elem_addr = compute_element_address( + parent, bb, base_addr, count, elem_size); + + /* A direct long long element is wider than a pointer on a + * 32-bit target, where it is written as a register pair. + */ + if (elem_size <= PTR_SIZE || + (elem_size == 8 && !var->ptr_level && !var->is_func && + !is_record_type(var->type))) { + add_insn(parent, *bb, OP_write, NULL, elem_addr, v, + elem_size, NULL); + } else { + fatal("Unsupported: array element wider than a pointer"); + } + } + + count++; + if (is_implicit && count > inferred_size) + inferred_size = count; + if (!elided_comma && !lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + + lex_expect(T_close_curly); + + if (is_implicit) { + /* `count` walks scalars, so a brace-elided list that stops inside a + * row, as in `int m[][2] = { 1, 2, 3 }`, leaves inferred_size short of + * a whole outer element. The bound counts outer elements, so round up + * and zero the rest of that last element in automatic storage. + */ + int inner = fixed_array_inner_count(var); + int whole = (inferred_size + inner - 1) / inner * inner; + + if (parent != GLOBAL_BLOCK) + emit_zero_elements(parent, bb, base_addr, inferred_size, whole, + elem_size); + inferred_size = whole; + var->array_size = inferred_size; + var->has_unsized_array = false; + } + global_constant_initializer_scope = saved_initializer_scope; +} + +void parse_array_compound_literal(var_t *var, + block_t *parent, + basic_block_t **bb) +{ + int elem_size = var->type->size; + int count = 0; + + reject_empty_initializer_in_strict_c99(); + + /* A compound literal may spell either an inferred bound, ``int[]``, or an + * actual array type, ``int[4]``. The latter is not merely syntax: omitted + * members are zero-initialized and an excess initializer is a constraint + * violation. Keep the parsed bound until the initializer has been consumed; + * previously this routine reset it and silently turned every declared-bound + * literal into an inferred-size one. + */ + int declared_size = var->array_size; + int inferred_size = 0; + var->init_val = 0; + + /* The opening brace is already consumed. See parse_array_init(). */ + if (string_ends_braced_initializer(var, cur_token->next)) { + if (lex_peek(T_wstring, NULL)) + parse_wstring_array_init(var, parent, bb); + else + parse_string_array_init(var, parent, bb); + lex_accept(T_comma); + lex_expect(T_close_curly); + return; + } + + /* A designated element can leave holes before or after it, so initialize + * the declared object before parsing any explicit elements. + */ + emit_zero_elements(parent, bb, var, 0, declared_size, elem_size); + + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + bool elided_comma = false; + + if (read_array_designator(parent, var, 1, !declared_size, &count, + NULL)) + lex_expect(T_assign); + if (declared_size && count >= declared_size) + error_at("Too many elements in array compound literal", + next_token_loc()); + if (!declared_size && count >= MAX_IMPLICIT_ARRAY) + error_at("Too many elements in array compound literal", + next_token_loc()); + + /* An inferred-bound array gets its size only after the closing + * brace. Still zero every gap before storing a designator so the + * automatic object obeys C99's aggregate initialization rule. + * inferred_size is one past the highest initialized slot, so a + * later backward designator cannot make a forward gap overwrite an + * earlier explicit value. + */ + if (!declared_size) + emit_zero_elements(parent, bb, var, inferred_size, count, + elem_size); + + var_t *elem_addr = + compute_element_address(parent, bb, var, count, elem_size); + if (lex_peek(T_open_curly, NULL) && is_record_type(var->type)) { + /* The array compound literal owns a real aggregate object, just + * like an ordinary array initializer. A braced element must + * therefore be lowered through the shared record path; treating + * it as an expression rejected the opening brace and made + * (struct S[]){ { ... }, { ... } } unusable. + */ + type_t *record_type = resolve_record_type(var->type); + + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, record_type, elem_addr); + lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(var)) { + elided_comma = parse_unbraced_record_init( + parent, bb, var->type, elem_addr, + declared_size > 0 || count < inferred_size); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + var_t *value = opstack_pop(); + if (count == 0) + var->init_val = value->init_val; + + var_t *store_val = resize_to(parent, bb, value, var->type, 0); + add_insn(parent, *bb, OP_write, NULL, elem_addr, store_val, + elem_size, NULL); + } + + if (!declared_size) { + if (count + 1 > inferred_size) + inferred_size = count + 1; + } + count++; + if (!elided_comma && !lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + + lex_expect(T_close_curly); + + var->array_size = declared_size ? declared_size : inferred_size; +} + +/* Identify compiler-emitted temporaries that hold array compound literals. They + * keep array metadata without pointer indirection and are marked via + * is_compound_literal when synthesized. + */ +bool is_array_literal_placeholder(const var_t *var) +{ + return var && var->array_size > 0 && !var->ptr_level && + var->is_compound_literal; +} + +bool is_pointer_like_value(var_t *var) +{ + return var && (var->ptr_level || var->array_size || + (var->type && var->type->ptr_level > 0)); +} + +/* Lower a compiler-emitted array literal placeholder (marked via + * is_compound_literal) into a scalar temporary when later IR expects a plain + * value instead of addressable storage. This keeps SSA joins uniform when only + * one branch originates from an array literal. + */ +var_t *scalarize_array_literal(block_t *parent, + basic_block_t **bb, + var_t *array_var, + type_t *hint_type) +{ + if (!is_array_literal_placeholder(array_var)) + return array_var; + + if (strict_c99) + error_at("array compound literal cannot be used as a scalar in C99", + cur_token_loc()); + + /* Array literal placeholders carry the literal's natural type; default to + * int when the parser left the type unset. + */ + type_t *literal_type = array_var->type ? array_var->type : TY_int; + int literal_size = literal_type->size; + if (literal_size <= 0) + literal_size = TY_int->size; + + /* A caller-provided hint (e.g., assignment target) dictates the result type + * when available so we reuse wider/narrower scalar destinations. + */ + type_t *result_type = hint_type ? hint_type : literal_type; + if (!result_type) + result_type = TY_int; + + /* Create a new scalar temporary, giving it a unique name and copying over + * the literal data so downstream code can treat it like a normal value. + */ + var_t *scalar = require_typed_var(parent, result_type); + scalar->ptr_level = 0; + scalar->var_name = gen_name(); + scalar->init_val = array_var->init_val; + + /* Materialize the literal data into the scalar temporary via an OP_read. */ + add_insn(parent, *bb, OP_read, scalar, array_var, NULL, literal_size, NULL); + + return scalar; +} + +/* Centralized guard for lowering array literal placeholders when a scalar value + * is expected, keeping the scattered special cases consistent. + */ +var_t *scalarize_array_literal_if_needed(block_t *parent, + basic_block_t **bb, + var_t *value, + type_t *hint_type, + bool needs_scalar) +{ + if (!needs_scalar) + return value; + + return scalarize_array_literal(parent, bb, value, hint_type); +} diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c new file mode 100644 index 00000000..0d386d5c --- /dev/null +++ b/src/parser-sizeof.c @@ -0,0 +1,813 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* The sizeof operator and the type-only walks over its operands. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +void read_expr_operand(block_t *parent, basic_block_t **bb); + +/* Consume one adjacent-string-literal sequence and return the size of its C99 + * array object, including the terminating null byte. This is deliberately + * independent of expression lowering: a string literal decays in an ordinary + * expression, but not when it is the operand of sizeof. + */ +int read_sizeof_string_literal(void) +{ + char literal[MAX_STRING_LEN]; + char unescaped[MAX_STRING_LEN]; + int size = 1; + + do { + int length; + + /* Count the decoded bytes the decoder reports: strlen() of the buffer + * would stop at an embedded null character that is part of the array. + */ + lex_ident(T_string, literal); + length = unescape_string(literal, unescaped, MAX_STRING_LEN); + if (length < 0) + error_at("Invalid escape sequence", cur_token_loc()); + size += length; + } while (lex_peek(T_string, NULL)); + + return size; +} + +/* Starting at the first token inside the parenthesis of "sizeof (", count the + * grouping parentheses around an adjacent sequence of string literals of the + * given kind. + * + * Return -1 unless the operand is exactly such a grouped literal sequence + * closed by its groups and by sizeof's own parenthesis. + */ +int sizeof_grouped_literal_depth(token_t *token, token_kind_t kind) +{ + int depth = 0; + + while (token && token->kind == T_open_bracket) { + depth++; + token = token->next; + } + if (!token || token->kind != kind) + return -1; + while (token && token->kind == kind) + token = token->next; + for (int i = 0; i <= depth; i++) { + if (!token || token->kind != T_close_bracket) + return -1; + token = token->next; + } + return depth; +} + +/* Push the value of a sizeof expression. VLA is intentionally outside shecc's + * C99 scope, so every admitted sizeof result is an integer constant expression; + * mark it so constant folding and the null pointer constant test treat every + * operand form alike. Its type is size_t, so arithmetic and comparisons on the + * result follow the unsigned conversions, as the file-scope constant path does. + */ +static void push_sizeof_result(block_t *parent, basic_block_t *bb, int size) +{ + var_t *result = require_typed_var(parent, find_type("size_t", true)); + + result->init_val = size; + result->is_const = true; + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); +} + +int sizeof_array_object(const var_t *array) +{ + int element_size = array->type->size; + + if (array->ptr_level || array->type->ptr_level || array->is_func) + element_size = PTR_SIZE; + return array->array_size * element_size; +} + +/* What a type-only walk over a sizeof operand has seen. */ +typedef struct { + int operators; /* unary, postfix and cast operators applied */ + int members; /* record member selections */ + bool addressed; /* the operand so far is "&array" */ + bool designator; /* the operand so far designates a function */ + bool rvalue; /* the operand so far is a cast result, not an lvalue */ +} sizeof_walk_t; + +/* Callback typedefs keep their function types in metadata the walk does not + * rebuild; indirections through them stay with ordinary expression lowering. + */ +static bool sizeof_walk_function_type(const type_t *type) +{ + return type->func_signature || type->pointee_func_signature || + type->is_direct_function_type; +} + +/* Apply one indirection, from '*', '[]' or '->', to the walked operand: an + * array selects its first element, a pointer its pointee, and a callback object + * the function it designates. + * + * Return false for a form the walk does not model, and reject an operand that + * has no pointer or array type with @message. + */ +static bool sizeof_walk_indirect(var_t *object, + sizeof_walk_t *walk, + char *message, + token_t *op) +{ + bool addressed = walk->addressed; + int depth; + + walk->addressed = false; + walk->rvalue = false; + if (walk->designator) + return true; + if (object->array_size > 0) { + fixed_array_shape_t shape = fixed_array_shape_from_var(object); + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_var(object, &shape); + return true; + } + if (object->has_unsized_array || object->is_flexible_array_member) { + object->has_unsized_array = false; + object->is_flexible_array_member = false; + return true; + } + if (object->pointee_func_signature || + sizeof_walk_function_type(object->type)) + return false; + depth = effective_pointer_depth(object); + if (object->is_func && !depth) { + walk->designator = true; + return true; + } + if (depth <= 0) + error_at(message, &op->location); + if (is_pointee_array_pointer(object)) { + /* A pointer to an array designates the complete array, so the + * dereference restores its bounds rather than decaying them. The + * element depth excludes pointers hidden in the base typedef, as in + * `str (*p)[3]` for a char pointer typedef str. + */ + fixed_array_shape_t shape = fixed_array_shape_from_pointee_var(object); + + if (!addressed && object->type->pointee_array_element_type) + object->type = object->type->pointee_array_element_type; + fixed_array_shape_to_var(object, &shape); + object->ptr_level = object->pointee_array_element_ptr_level; + object->pointee_array_size = 0; + object->pointee_array_dim2 = 0; + object->pointee_array_dim3 = 0; + object->pointee_array_dim4 = 0; + object->pointee_array_element_ptr_level = 0; + } else { + object->type = pointee_type_from_pointer_typedef(object->type); + object->ptr_level = depth - 1; + } + return true; +} + +/* Apply '&' to the walked operand. */ +static void sizeof_walk_address(var_t *object, sizeof_walk_t *walk, token_t *op) +{ + if (walk->designator) { + /* The address of a function is a callback object. */ + walk->designator = false; + object->is_func = true; + return; + } + if (walk->rvalue) + error_at("lvalue required as unary '&' operand", &op->location); + if (is_bitfield(object)) + error_at("cannot take address of bit-field", &op->location); + if (object->is_register) + error_at("cannot take address of register object", &op->location); + object->has_unsized_array = false; + object->is_flexible_array_member = false; + walk->addressed = object->array_size > 0; + if (walk->addressed) { + fixed_array_shape_t shape = fixed_array_shape_from_var(object); + fixed_array_shape_t scalar = {0}; + + fixed_array_shape_to_pointee_var(object, &shape); + object->pointee_array_element_ptr_level = object->ptr_level; + fixed_array_shape_to_var(object, &scalar); + } + object->ptr_level++; +} + +/* Say whether the token after a '(' in a sizeof operand begins a type name, so + * that the parenthesis opens a cast rather than a grouping. + */ +static bool sizeof_cast_starts_at(block_t *scope, const token_t *token) +{ + if (!token) + return false; + if (token->kind == T_identifier) + return find_visible_type(token->literal, scope); + return token->kind == T_struct || token->kind == T_union || + token->kind == T_enum || token->kind == T_signed || + token->kind == T_unsigned || token->kind == T_long || + token->kind == T_const || token->kind == T_volatile || + token->kind == T_float || token->kind == T_double; +} + +/* Consume the type name and ')' of a cast whose '(' is already consumed. Only + * integer, pointer and void types are modeled; return NULL for any other. + */ +static type_t *read_sizeof_cast_type(block_t *scope, int *ptr_level) +{ + type_t *type = read_type_name_specifiers(scope); + int stars = 0; + + while (type && lex_accept(T_asterisk)) { + stars++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + *ptr_level = stars; + if (!type || type->is_floating || type->array_size || + sizeof_walk_function_type(type) || (!stars && is_record_type(type)) || + !lex_accept(T_close_bracket)) + return NULL; + return type; +} + +/* A sizeof operand needs the declared type of an lvalue, before ordinary + * expression lowering decays an array or evaluates a postfix operand. Keep this + * descriptor as a copy of the declaration metadata: no IR value is ever + * constructed while walking it. + * + * The walk covers identifiers, grouping, '*', '&', casts, member selections and + * subscripts. It returns false, without consuming anything, for an operand it + * does not model, which then keeps the ordinary unevaluated-expression path. + * @allow_cast is false for the operand of an unparenthesized sizeof: in "sizeof + * (T) x" the parenthesis holds a type name, and a cast expression is not a + * unary expression. + */ +static bool scan_sizeof_postfix_operand(block_t *scope, + token_t **cursor, + var_t *object, + sizeof_walk_t *walk, + bool allow_cast) +{ + token_t *token = *cursor; + token_t *op = token; + + if (!token) + return false; + if (token->kind == T_asterisk) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk, true) || + !sizeof_walk_indirect( + object, walk, "Cannot dereference non-pointer in sizeof", op)) + return false; + walk->operators++; + } else if (token->kind == T_ampersand) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk, true)) + return false; + sizeof_walk_address(object, walk, op); + walk->operators++; + } else if (token->kind == T_open_bracket && + sizeof_cast_starts_at(scope, token->next)) { + token_t *saved_token = cur_token; + var_t operand; + sizeof_walk_t operand_walk = {0}; + type_t *type; + int ptr_level; + + if (!allow_cast) + return false; + + /* Borrow the lexer to read the type name, then put it back. */ + cur_token = token; + type = read_sizeof_cast_type(scope, &ptr_level); + token = cur_token->next; + cur_token = saved_token; + if (!type) + return false; + + /* A literal operand, as in "(struct S *) 0", does not affect the type + * of the cast either. + */ + if (token && (token->kind == T_numeric || token->kind == T_char)) { + token = token->next; + if (token && (token->kind == T_dot || token->kind == T_arrow || + token->kind == T_open_square)) + return false; + } else if (!scan_sizeof_postfix_operand(scope, &token, &operand, + &operand_walk, true)) + return false; + memset(object, 0, sizeof(*object)); + object->type = type; + object->ptr_level = ptr_level; + walk->addressed = false; + walk->designator = false; + walk->rvalue = true; + walk->operators++; + } else if (token->kind == T_open_bracket) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk, true) || + !token || token->kind != T_close_bracket) + return false; + token = token->next; + } else if (token->kind == T_identifier) { + var_t *root = find_var(token->literal, scope); + func_t *target = find_visible_func(token->literal, scope); + func_t *func = scope->func; + + /* C99 6.7.4 constrains every reference an external inline definition + * makes, including one sizeof does not evaluate. + */ + bool external_inline = func && func->is_inline && !func->is_static; + + if (root && !root->is_extern_function_alias) { + if (root->is_direct_function_declarator || + (root->type->is_direct_function_type && + !effective_pointer_depth(root))) + return false; + if (external_inline && root->is_static && + root->scope == GLOBAL_BLOCK) + error_at( + "external inline definition references " + "internal-linkage object", + &token->location); + memcpy(object, root, sizeof(*object)); + + /* C99 6.7.5.3p7 adjusts a parameter declared as an array to a + * pointer to its element, so `sizeof a` of `int a[4]` is a + * pointer's size. The walk owns the adjusted operand like one with + * an operator applied. + */ + if (root->array_size > 0 && is_function_parameter(root, scope)) { + fixed_array_shape_t shape = fixed_array_shape_from_var(root); + fixed_array_shape_t scalar = {0}; + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_var(object, &scalar); + if (shape.rank) { + fixed_array_shape_to_pointee_var(object, &shape); + object->pointee_array_element_ptr_level = root->ptr_level; + } + object->ptr_level++; + walk->operators++; + } + } else if (target) { + if (external_inline && target->is_static) + error_at( + "external inline definition references " + "internal-linkage function", + &token->location); + memset(object, 0, sizeof(*object)); + object->type = TY_int; + object->is_func = true; + walk->designator = true; + } else + return false; + token = token->next; + } else + return false; + + while (token && (token->kind == T_dot || token->kind == T_arrow || + token->kind == T_open_square)) { + op = token; + walk->operators++; + if (walk->designator) + return false; + if (token->kind == T_dot || token->kind == T_arrow) { + bool rvalue = walk->rvalue; + var_t *field; + + token = token->next; + if (!token || token->kind != T_identifier) + return false; + if (op->kind == T_arrow) { + if (!sizeof_walk_indirect( + object, walk, + "Member reference through '->' requires a pointer", op)) + return false; + rvalue = false; + } + if (walk->designator || effective_pointer_depth(object) || + is_array_declarator(object) || object->has_unsized_array || + !is_record_type(object->type)) + error_at("Member reference base is not a struct or union", + &op->location); + field = find_member(token->literal, object->type); + if (!field) + error_at("Unknown struct or union member", &token->location); + memcpy(object, field, sizeof(*object)); + walk->members++; + walk->addressed = false; + walk->rvalue = rvalue; + token = token->next; + } else { + int depth = 0; + + /* The subscript itself is read, unevaluated, by the consuming pass; + * only its extent matters here. + */ + for (; token; token = token->next) { + if (token->kind == T_open_square) + depth++; + else if (token->kind == T_close_square && --depth == 0) { + token = token->next; + break; + } + } + if (depth) + return false; + if (object->is_func && !is_array_declarator(object) && + !effective_pointer_depth(object)) + return false; + if (!sizeof_walk_indirect( + object, walk, "Cannot apply square operator to non-pointer", + op)) + return false; + } + } + *cursor = token; + return true; +} + +/* The preceding scan proves the exact token shape and type constraints before + * this pass advances the lexer. Each subscript is read in a detached block so + * its side effects remain unevaluated. @open_consumed says the operand's + * opening parenthesis was already taken as the one following sizeof. + */ +static void consume_sizeof_postfix_operand(block_t *parent, + basic_block_t **bb, + bool open_consumed) +{ + if (open_consumed) { + consume_sizeof_postfix_operand(parent, bb, false); + lex_expect(T_close_bracket); + } else if (lex_accept(T_asterisk) || lex_accept(T_ampersand)) + consume_sizeof_postfix_operand(parent, bb, false); + else if (lex_peek(T_open_bracket, NULL) && + sizeof_cast_starts_at(parent, cur_token->next->next)) { + int ptr_level; + + lex_expect(T_open_bracket); + read_sizeof_cast_type(parent, &ptr_level); + if (!lex_accept(T_numeric) && !lex_accept(T_char)) + consume_sizeof_postfix_operand(parent, bb, false); + } else if (lex_accept(T_open_bracket)) { + consume_sizeof_postfix_operand(parent, bb, false); + lex_expect(T_close_bracket); + } else + lex_expect(T_identifier); + + while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL) || + lex_peek(T_open_square, NULL)) { + if (lex_accept(T_dot) || lex_accept(T_arrow)) + lex_expect(T_identifier); + else { + basic_block_t *unevaluated_bb; + + lex_expect(T_open_square); + unevaluated_bb = bb_create(parent); + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + opstack_pop(); + lex_expect(T_close_square); + } + } +} + +/* The operand must end where sizeof's operand ends: at the closing parenthesis + * of a parenthesized one, unless a postfix operator continues past it, and + * otherwise before anything that would keep it going as a call or an update. + */ +static bool sizeof_operand_tail_ends(const token_t *tail, bool parenthesized) +{ + if (!tail) + return false; + if (!parenthesized) + return tail->kind != T_open_bracket && tail->kind != T_increment && + tail->kind != T_decrement; + tail = tail->kind == T_close_bracket ? tail->next : NULL; + return tail && tail->kind != T_dot && tail->kind != T_arrow && + tail->kind != T_open_square && tail->kind != T_open_bracket && + tail->kind != T_increment && tail->kind != T_decrement; +} + +/* Walk a local sizeof operand from @start and say whether this walker owns it: + * any operand it models that applies an operator, or that still designates an + * array, possibly incomplete, or a callback object. Without a parenthesis it + * owns a bare scalar identifier too: ordinary lowering of an identifier takes a + * following "+ n" as pointer arithmetic, which made "sizeof p + 1" the size of + * "p + 1". Inside one, that identifier keeps the unevaluated-expression path. + */ +static bool walk_local_sizeof_operand(block_t *parent, + token_t *start, + bool parenthesized, + var_t *object, + sizeof_walk_t *walk) +{ + token_t *tail = start; + + if (!scan_sizeof_postfix_operand(parent, &tail, object, walk, + parenthesized) || + !sizeof_operand_tail_ends(tail, parenthesized)) + return false; + return !parenthesized || walk->operators > 0 || object->array_size > 0 || + object->has_unsized_array || (object->is_func && !walk->designator); +} + +/* Every sizeof operand made of an identifier, grouping, dereference, address, + * cast, member selections and constant or runtime subscripts goes through here: + * the result is the declared extent of what the operand designates, which + * ordinary expression lowering would decay or evaluate. + */ +static bool read_sizeof_postfix_operand(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + bool open_consumed = false; + var_t object; + sizeof_walk_t walk = {0}; + int size; + + if (!walk_local_sizeof_operand(parent, cur_token->next, parenthesized, + &object, &walk)) { + sizeof_walk_t regrouped = {0}; + + /* In "sizeof (*p).member" the parenthesis taken as sizeof's own opens a + * grouping inside a longer unary expression. Walk it again from that + * parenthesis as an operand without one. + */ + if (!parenthesized || cur_token->kind != T_open_bracket || + !walk_local_sizeof_operand(parent, cur_token, false, &object, + ®rouped)) + return false; + walk = regrouped; + open_consumed = true; + } + if (walk.designator) + error_at("sizeof(function) is invalid", cur_token_loc()); + if (object.is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + cur_token_loc()); + if (object.has_unsized_array) + error_at("sizeof cannot be applied to an incomplete array", + cur_token_loc()); + if (is_bitfield(&object)) + error_at("sizeof cannot be applied to a bit-field", cur_token_loc()); + + consume_sizeof_postfix_operand(parent, bb, open_consumed); + if (parenthesized && !open_consumed) + lex_expect(T_close_bracket); + if (object.array_size > 0) { + size = sizeof_array_object(&object); + } else if (object.ptr_level || object.type->ptr_level || object.is_func) { + size = PTR_SIZE; + } else { + if (object.type == TY_void) + error_at("sizeof(void) is invalid", cur_token_loc()); + if (is_incomplete_record_object(&object)) + error_at("sizeof cannot be applied to an incomplete record type", + cur_token_loc()); + size = object.type->size; + } + push_sizeof_result(parent, *bb, size); + return true; +} + +static bool is_direct_fixed_array_pointer_slot(const var_t *pointer) +{ + return pointer && is_array_declarator(pointer) && !pointer->array_dim2 && + pointer->pointee_array_size > 0 && + !pointer->pointee_array_element_ptr_level && + ((pointer->ptr_level == 1 && !pointer->type->ptr_level) || + (!pointer->ptr_level && pointer->type->ptr_level == 1 && + pointer->type->array_element_ptr_level == 1)); +} + +void handle_sizeof_operator(block_t *parent, basic_block_t **bb) +{ + char token[MAX_ID_LEN]; + int ptr_cnt = 0; + int array_size = 0; + token_t *sizeof_tk = cur_token; + type_t *type = NULL; + bool is_function = false; + + bool parenthesized = lex_accept(T_open_bracket); + + /* A string literal is an array, not a pointer, before the array-to-pointer + * conversion that ordinary expression lowering applies. Parenthesized + * sizeof may take the fast path only when the literal sequence, possibly + * grouped again, is the complete operand: neither sizeof's own parenthesis + * nor any grouping inside it is part of the array's extent. + */ + int string_groups = 0; + int wstring_groups = 0; + + if (parenthesized) { + string_groups = sizeof_grouped_literal_depth(cur_token->next, T_string); + wstring_groups = + sizeof_grouped_literal_depth(cur_token->next, T_wstring); + } else { + /* A subscript binds tighter than sizeof: `sizeof "ab"[1]` measures one + * element, which the expression path below reads. + */ + token_t *after = cur_token->next; + + while (after && (after->kind == T_string || after->kind == T_wstring)) + after = after->next; + if (after && after->kind == T_open_square) { + string_groups = -1; + wstring_groups = -1; + } + } + if ((lex_peek(T_string, NULL) || string_groups > 0) && string_groups >= 0) { + int size; + + for (int i = 0; i < string_groups; i++) + lex_expect(T_open_bracket); + size = read_sizeof_string_literal(); + for (int i = 0; i < string_groups; i++) + lex_expect(T_close_bracket); + if (parenthesized) + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, size); + return; + } + if ((lex_peek(T_wstring, NULL) || wstring_groups > 0) && + wstring_groups >= 0) { + int size; + + for (int i = 0; i < wstring_groups; i++) + lex_expect(T_open_bracket); + size = read_const_wstring_size(); + for (int i = 0; i < wstring_groups; i++) + lex_expect(T_close_bracket); + if (parenthesized) + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, size); + return; + } + + if (read_sizeof_postfix_operand(parent, bb, parenthesized)) + return; + + /* The type-name alternative requires parentheses, but C99 also permits a + * unary expression directly after sizeof. Keep direct array identifiers + * from decaying before their extent is observed. + */ + if (!parenthesized) { + if (parent->func && lex_peek(T_identifier, token) && + !strcmp(token, "__func__")) { + lex_expect(T_identifier); + push_sizeof_result(parent, *bb, + strlen(parent->func->return_def.var_name) + 1); + return; + } + + if (lex_peek(T_identifier, token)) { + var_t *array = find_var(token, parent); + + if (array && array->array_size > 0) { + lex_expect(T_identifier); + push_sizeof_result(parent, *bb, sizeof_array_object(array)); + return; + } + } + + basic_block_t *unevaluated_bb = bb_create(parent); + unevaluated_expression_depth++; + read_expr_operand(parent, &unevaluated_bb); + unevaluated_expression_depth--; + var_t *expr_var = opstack_pop(); + if (is_bitfield(expr_var)) + error_at("sizeof cannot be applied to a bit-field", + &sizeof_tk->location); + type = expr_var->type; + ptr_cnt = expr_var->ptr_level; + array_size = expr_var->array_size; + is_function = expr_var->is_func; + if (type == TY_void && ptr_cnt == 0) + error_at("sizeof(void) is invalid", &sizeof_tk->location); + if (is_function && ptr_cnt == 0) + error_at("sizeof(function) is invalid", &sizeof_tk->location); + + int size = type->size; + + if (ptr_cnt) + size = PTR_SIZE; + if (array_size > 0) + size *= array_size; + push_sizeof_result(parent, *bb, size); + return; + } + + /* C99 specifies __func__ as if each function contained a distinct `static + * const char []` initialized with its unadorned name. The normal expression + * lowering materializes its address, but sizeof must retain the array + * extent rather than observing that decayed pointer. + */ + if (parent->func && lex_peek(T_identifier, token) && + !strcmp(token, "__func__") && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_close_bracket) { + lex_expect(T_identifier); + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, + strlen(parent->func->return_def.var_name) + 1); + return; + } + + /* A bare array identifier is the one expression form that must retain its + * declared extent for sizeof; ordinary expression parsing intentionally + * decays it to a pointer. cur_token is the opening parenthesis here. + */ + if (lex_peek(T_identifier, token) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_close_bracket) { + var_t *array = find_var(token, parent); + + if (array && array->array_size > 0) { + lex_expect(T_identifier); + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, sizeof_array_object(array)); + return; + } + } + + /* Check if this is sizeof(type) or sizeof(expression). `sizeof` only + * consumes object representation metadata, so it can admit the C99 floating + * type names before value arithmetic and ABI lowering exist. + */ + token_t *type_name_start = cur_token; + + type = read_type_name_specifiers(parent); + if (!type && cur_token != type_name_start) + error_at("Unknown type name in sizeof", cur_token_loc()); + + /* A type name continues with its abstract declarator; only the derivation + * it applies last decides the object size. + */ + if (type) { + int elements; + sizeof_derivation_t derivation = + read_sizeof_abstract_declarator(parent, &elements); + int size = sizeof_type_name_size(type, derivation, elements); + + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, size); + return; + } + + /* sizeof(expression) - parse the expression and get its type */ + basic_block_t *unevaluated_bb = bb_create(parent); + unevaluated_expression_depth++; + if (!read_assignment_expression(parent, &unevaluated_bb)) { + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + } + unevaluated_expression_depth--; + var_t *expr_var = opstack_pop(); + if (is_bitfield(expr_var)) + error_at("sizeof cannot be applied to a bit-field", + &sizeof_tk->location); + type = expr_var->type; + ptr_cnt = expr_var->ptr_level; + array_size = expr_var->array_size; + is_function = expr_var->is_func; + if (is_incomplete_record_object(expr_var)) + error_at("sizeof cannot be applied to an incomplete record type", + &sizeof_tk->location); + + if (!type) + error_at("Unable to determine type in sizeof", &sizeof_tk->location); + if (type == TY_void && ptr_cnt == 0) + error_at("sizeof(void) is invalid", &sizeof_tk->location); + if ((is_function || type->is_direct_function_type) && ptr_cnt == 0) + error_at("sizeof(function) is invalid", &sizeof_tk->location); + + if (!array_size && !ptr_cnt && type->array_size) + array_size = type->array_size; + int size = type->size; + + /* An array result, as a call result's row or member is under sizeof, may + * have pointer elements: its extent counts pointer-sized slots. + */ + if (ptr_cnt) + size = PTR_SIZE; + if (array_size > 0) + size *= array_size; + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, size); +} diff --git a/src/parser-stmt.c b/src/parser-stmt.c new file mode 100644 index 00000000..e62da729 --- /dev/null +++ b/src/parser-stmt.c @@ -0,0 +1,1904 @@ +/* + * shecc - Self-Hosting and Educational C Compiler. + * + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +/* Statements, and declarations and record, enum and typedef definitions in + * block scope. + * + * A fragment of the parser: parser.c includes it in order, so it sees every + * definition that precedes it there and cannot be compiled on its own. + */ + +basic_block_t *read_code_block(func_t *func, + block_t *parent, + basic_block_t *bb); + +/* A switch dispatch is built while its labeled statements are read. Keep the + * deferred no-match edge separate from the source-order body chain: labels may + * occur after ordinary statements or inside nested compound statements. + */ +typedef struct switch_label_context { + block_t *dispatch_parent; + var_t *control; + basic_block_t *dispatch_tail; + basic_block_t *default_body; + switch_case_value_t *seen_cases; + bool has_default; +} switch_label_context_t; + +switch_label_context_t switch_label_contexts[MAX_NESTING]; +int switch_label_context_idx = 0; + +/* A label must precede a statement in C99. Resolve an identifier against the + * scope the label appears in, so a block typedef starts a declaration and an + * object that hides a file-scope typedef starts an expression statement. + */ +void reject_label_followed_by_declaration_in_strict_c99(block_t *parent) +{ + char name[MAX_ID_LEN]; + + if (!strict_c99) + return; + + if (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL) || + lex_peek(T_restrict, NULL) || lex_peek(T_static, NULL) || + lex_peek(T_extern, NULL) || lex_peek(T_register, NULL) || + lex_peek(T_auto, NULL) || lex_peek(T_typedef, NULL) || + lex_peek(T_inline, NULL) || lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL) || + lex_peek(T_struct, NULL) || lex_peek(T_union, NULL) || + lex_peek(T_enum, NULL) || + (lex_peek(T_identifier, name) && find_visible_type(name, parent))) + error_at("a C99 label must precede a statement, not a declaration", + cur_token_loc()); +} + +/* The word-sized constant evaluator narrows a constant expression to int. + * Select the typed two-word evaluator when a literal needing a wider or + * unsigned type appears in the expression that starts at @token: before the + * ',', ';' or brace ending it, the ')' or ']' closing an enclosing bracket, or + * a case label's colon. A colon closing a nested '?' is not the end. + */ +bool constant_expression_needs_typed_value(token_t *token) +{ + int bracket_depth = 0; + int pending_ternaries = 0; + + for (; token; token = token->next) { + if (token->kind == T_open_bracket || token->kind == T_open_square) + bracket_depth++; + else if (token->kind == T_close_bracket || + token->kind == T_close_square) { + if (bracket_depth == 0) + return false; + bracket_depth--; + } else if (token->kind == T_question) + pending_ternaries++; + else if (token->kind == T_colon) { + if (bracket_depth == 0 && pending_ternaries == 0) + return false; + if (pending_ternaries) + pending_ternaries--; + } else if (token->kind == T_semicolon || token->kind == T_open_curly || + token->kind == T_close_curly || + (bracket_depth == 0 && token->kind == T_comma)) + return false; + else if (token->kind == T_numeric && + (numeric_literal_needs_wide_path(token->literal) || + numeric_literal_needs_typed_global_path(token->literal))) + return true; + } + return false; +} + +basic_block_t *read_switch_label_statement(block_t *parent, basic_block_t *body) +{ + switch_label_context_t *context; + basic_block_t *label_body; + + if (!switch_label_context_idx) + error_at("case or default label outside switch", next_token_loc()); + context = &switch_label_contexts[switch_label_context_idx - 1]; + label_body = bb_create(parent); + if (body) + bb_connect(body, label_body, NEXT); + + if (lex_accept(T_default)) { + if (context->has_default) + error_at("duplicate default label in switch", cur_token_loc()); + context->has_default = true; + context->default_body = label_body; + } else { + unsigned int case_lo; + unsigned int case_hi; + var_t *constant; + var_t *comparison; + basic_block_t *next_dispatch; + + lex_expect(T_case); + if (constant_expression_needs_typed_value(cur_token->next)) { + pp_integer_t typed_value; + block_t *saved_scope = pp_integer_constant_scope; + + pp_integer_constant_scope = parent; + cur_token = + pp_read_constant_infix_expr(0, cur_token, &typed_value, true); + pp_integer_constant_scope = saved_scope; + pp_enum_normalize(&typed_value); + case_lo = typed_value.lo; + case_hi = typed_value.hi; + } else { + case_lo = read_const_expr(parent); + case_hi = case_lo & 0x80000000U ? ~0U : 0; + } + + /* C99 6.8.4.2p5 converts each case constant to the promoted type of the + * controlling expression. Compare and check duplicates at that width, + * so an int switch folds a wide label to its low word. + */ + if (context->control->type->size <= TY_int->size) + case_hi = + context->control->type->is_unsigned || !(case_lo & 0x80000000U) + ? 0 + : ~0U; + if (strict_c99) { + switch_case_value_t *seen; + + for (seen = context->seen_cases; seen; seen = seen->next) + if (seen->value == case_lo && seen->value_hi == case_hi) + error_at("duplicate case value in C99 switch", + cur_token_loc()); + seen = arena_alloc(GENERAL_ARENA, sizeof(*seen)); + seen->value = case_lo; + seen->value_hi = case_hi; + seen->next = context->seen_cases; + context->seen_cases = seen; + } + + constant = + require_typed_var(context->dispatch_parent, context->control->type); + constant->var_name = gen_name(); + constant->init_val = case_lo; + constant->init_val_hi = case_hi; + add_insn(context->dispatch_parent, context->dispatch_tail, + OP_load_constant, constant, NULL, NULL, 0, NULL); + + comparison = require_var(context->dispatch_parent); + comparison->var_name = gen_name(); + add_insn(context->dispatch_parent, context->dispatch_tail, OP_eq, + comparison, constant, context->control, 0, NULL); + add_insn(context->dispatch_parent, context->dispatch_tail, OP_branch, + NULL, comparison, NULL, 0, NULL); + bb_connect(context->dispatch_tail, label_body, THEN); + next_dispatch = bb_create(context->dispatch_parent); + bb_connect(context->dispatch_tail, next_dispatch, ELSE); + context->dispatch_tail = next_dispatch; + } + lex_expect(T_colon); + reject_label_followed_by_declaration_in_strict_c99(parent); + return label_body; +} + +/* A switch, its cases, and the block they break out of. */ +basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) +{ + basic_block_t *n = bb_create(parent); + basic_block_t *body; + basic_block_t *switch_end; + switch_label_context_t *context; + + bb_connect(bb, n, NEXT); + bb = n; + + lex_expect(T_open_bracket); + read_control_expression(parent, &bb); + lex_expect(T_close_bracket); + var_t *control = operand_stack[operand_stack_idx - 1]; + + /* C99 6.8.4.2 requires an integer controlling expression. After the + * ordinary expression reader produces the operand, enforce that constraint + * and apply integer promotions before deferred dispatch captures it. + */ + if (!control->type || control->type == TY_void || + is_pointer_like_value(control) || control->is_func || + is_record_type(control->type)) + error_at("switch controlling expression must have integer type", + cur_token_loc()); + control = integer_promote_operand(parent, &bb, control); + + /* create exit jump for breaks */ + switch_end = bb_create(parent); + break_bb_push(switch_end); + if (switch_label_context_idx >= MAX_NESTING) + fatal("Too many nested switch statements"); + context = &switch_label_contexts[switch_label_context_idx++]; + context->dispatch_parent = parent; + context->control = control; + context->dispatch_tail = bb; + context->default_body = NULL; + context->seen_cases = NULL; + context->has_default = false; + + /* Statements before the first label are legal but are not an entry point of + * the switch. A following label supplies the source-order fallthrough edge + * without making those statements reachable from dispatch. + * + * The braces open a block like any compound statement, so a declaration + * inside them hides an outer one of the same name instead of redeclaring + * it. + */ + block_t *blk = add_block(parent, parent->func); + body = bb_create(blk); + + lex_expect(T_open_curly); + while (!lex_accept(T_close_curly)) { + body = read_body_statement(blk, body); + } + + /* Complete the deferred no-match dispatch after every case comparison is + * known. This also supplies the natural exit edge for an empty switch. + */ + bb_connect(context->dispatch_tail, + context->has_default ? context->default_body : switch_end, NEXT); + + if (body) + /* if the last label has no explicit break, connect it to the end */ + bb_connect(body, switch_end, NEXT); + + break_exit_idx--; + switch_label_context_idx--; + /* remove the expression in switch() */ + opstack_pop(); + + int dangling = 1; + for (int i = 0; i < switch_end->prev_idx; i++) + if (switch_end->prev[i].bb) + dangling = 0; + + if (dangling) + return NULL; + + return switch_end; +} + +static void reject_ordinary_typedef_collision(block_t *block, var_t *var) +{ + if (find_block_typedef(block, var->var_name)) + error_at("ordinary identifier conflicts with typedef name", + cur_token_loc()); +} + +/* Reject @name when an enumeration constant or, in the outermost block of a + * function body, a parameter already declares it in @block's scope (6.2.1p4). + * Neither has linkage, so no second declaration may follow. + */ +static void reject_unlinked_scope_name(block_t *block, const char *name) +{ + ordinary_kind_t kind = find_block_ordinary( + block, name, ORDINARY_CONSTANT | ORDINARY_PARAMETER, NULL); + + if (kind == ORDINARY_CONSTANT) + error_at("identifier redeclared as a different kind of symbol", + cur_token_loc()); + if (kind == ORDINARY_PARAMETER) + error_at("redeclaration of parameter in the function body", + cur_token_loc()); +} + +/* C99 6.7p3 lets an identifier without linkage be declared only once in a + * scope. A block-scope extern object or function declaration has linkage + * (6.2.2p4-5), so it may repeat an earlier one of the same kind, and the file + * scope helpers then check the types agree. Any other pair in @block conflicts: + * an object and a function never denote the same entity. + * + * @var is the declarator being bound, still listed in @block's locals when it + * declares an object. @is_function and @has_linkage describe its binding. + */ +static void reject_block_redeclaration(block_t *block, + var_t *var, + bool is_function, + bool has_linkage) +{ + const char *name = var->var_name; + int pos = 0; + + if (!name[0]) + return; + for (var_t *prior = find_block_local(block, name, &pos); prior; + prior = find_block_local(block, name, &pos)) { + bool prior_is_function; + + if (prior == var) + continue; + prior_is_function = prior->is_extern_function_alias; + if (prior_is_function != is_function) + error_at("identifier redeclared as a different kind of symbol", + cur_token_loc()); + + /* A prior extern object is the file-scope entry itself; a local static + * object shares its name but is a different record. + */ + if (!has_linkage || + !(prior_is_function || find_global_var(prior->var_name) == prior)) + error_at("redeclaration of identifier with no linkage", + cur_token_loc()); + } + reject_unlinked_scope_name(block, name); +} + +/* The storage-class specifiers and qualifiers that lead a block-scope + * declaration. + */ +typedef struct { + bool is_const; + bool is_static; + bool is_extern; + bool is_register; + bool is_auto; + bool is_volatile; +} block_decl_specifiers_t; + +static basic_block_t *read_block_declarators( + block_t *parent, + basic_block_t *bb, + type_t *type, + const block_decl_specifiers_t *spec, + bool has_base_type); + +/* A for loop: setup, condition, body and increment. */ +basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + type_t *type; + var_t *vd; + bool is_const = false; + bool is_static = false; + bool is_extern = false; + bool is_register = false; + bool is_auto = false; + bool is_volatile = false; + bool saw_decl_specifier = false; + bool for_decl_semicolon_consumed = false; + + lex_expect(T_open_bracket); + + /* synthesize for loop block */ + block_t *blk = add_block(parent, parent->func); + + /* setup - execute once */ + basic_block_t *setup = bb_create(blk); + bb_connect(bb, setup, NEXT); + + hoist_storage_class_specifiers(); + if (lex_peek(T_typedef, NULL)) { + if (strict_c99) + error_at("C99 for initializer cannot declare a typedef", + next_token_loc()); + + /* The default-mode typedef extension owns the loop's synthetic block + * scope. The typedef parser consumes its terminating semicolon and + * emits no setup IR. + */ + handle_block_typedef_statement(blk, setup); + for_decl_semicolon_consumed = true; + } else if (!lex_accept(T_semicolon)) { + while (lex_peek(T_static, NULL) || lex_peek(T_extern, NULL) || + lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL) || + lex_peek(T_register, NULL) || lex_peek(T_auto, NULL)) { + saw_decl_specifier = true; + if (lex_accept(T_static)) { + if (is_static) + error_at("duplicate static storage class specifier", + cur_token_loc()); + is_static = true; + } else if (lex_accept(T_extern)) { + if (is_extern) + error_at("duplicate extern storage class specifier", + cur_token_loc()); + is_extern = true; + } else if (lex_accept(T_register)) { + if (is_register) + error_at("duplicate register storage class specifier", + cur_token_loc()); + is_register = true; + } else if (lex_accept(T_auto)) { + if (is_auto) + error_at("duplicate auto storage class specifier", + cur_token_loc()); + is_auto = true; + } else if (lex_accept(T_volatile)) { + is_volatile = true; + } else { + lex_expect(T_const); + is_const = true; + } + } + if ((is_static && (is_register || is_extern || is_auto)) || + (is_register && (is_extern || is_auto)) || (is_extern && is_auto)) + error_at("incompatible storage class specifiers", cur_token_loc()); + + bool has_builtin_type = lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL); + bool has_identifier = lex_peek(T_identifier, token); + bool has_record_type = lex_peek(T_struct, NULL) || + lex_peek(T_union, NULL) || + lex_peek(T_enum, NULL); + bool has_enum_type = lex_peek(T_enum, NULL); + bool has_tagged_record = + lex_peek(T_struct, NULL) || lex_peek(T_union, NULL); + if (has_enum_type) { + /* read_full_var_decl() owns consuming and resolving `enum TAG`. Use + * a scalar placeholder only to select its declaration path. + */ + type = TY_int; + } else if (has_tagged_record) { + /* read_full_var_decl() owns consuming and resolving the tag. */ + type = TY_int; + } else { + type = has_builtin_type ? TY_int + : has_identifier || has_record_type + ? find_visible_type(token, blk) + : NULL; + } + if (!type && saw_decl_specifier) + error_at("declaration specifier requires a type", cur_token_loc()); + if (type) { + block_decl_specifiers_t spec = {0}; + + /* C99 6.8.5.3 admits only automatic or register object declarations + * here. The default mode retains its historical block-scope + * static/extern extension. + */ + if (strict_c99 && (is_static || is_extern)) + error_at( + "C99 for initializer permits only auto or register objects", + cur_token_loc()); + spec.is_const = is_const; + spec.is_static = is_static; + spec.is_extern = is_extern; + spec.is_register = is_register; + spec.is_auto = is_auto; + spec.is_volatile = is_volatile; + + /* The loop scope takes the ordinary block declaration lowering, + * which also consumes the terminating semicolon. + */ + parsing_for_initializer_declaration = true; + setup = read_block_declarators(blk, setup, type, &spec, false); + for_decl_semicolon_consumed = true; + } else { + read_control_expression(blk, &setup); + discard_operand(blk, setup); + } + + if (!for_decl_semicolon_consumed) + lex_expect(T_semicolon); + } + + basic_block_t *cond_ = bb_create(blk); + basic_block_t *for_end = bb_create(parent); + basic_block_t *cond_start = cond_; + break_bb_push(for_end); + bb_connect(setup, cond_, NEXT); + + /* condition - check before the loop */ + if (!lex_accept(T_semicolon)) { + read_control_expression(blk, &cond_); + lex_expect(T_semicolon); + } else { + /* always true */ + vd = require_var(blk); + vd->init_val = 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(blk, cond_, OP_load_constant, vd, NULL, NULL, 0, NULL); + } + bb_connect(cond_, for_end, ELSE); + + vd = opstack_pop(); + reject_record_operand(vd); + add_insn(blk, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + + basic_block_t *inc_ = bb_create(blk); + continue_bb_push(inc_); + + /* increment after each loop */ + if (!lex_accept(T_close_bracket)) { + read_control_expression(blk, &inc_); + discard_operand(blk, inc_); + lex_expect(T_close_bracket); + } + + /* loop body */ + basic_block_t *body_ = bb_create(blk); + bb_connect(cond_, body_, THEN); + body_ = read_body_statement(blk, body_); + + /* Normal fallthrough from the loop body goes through the increment block. A + * continue statement may already have connected another predecessor to + * inc_. + */ + if (body_) + bb_connect(body_, inc_, NEXT); + + /* An empty increment block still needs its back-edge when it is reachable + * through normal fallthrough or continue. + * + * Do not connect a completely unreachable increment block, such as: + * + * for (;;) { + * break; + * } + */ + bool has_pred = false; + for (int i = 0; i < inc_->prev_idx; i++) { + if (inc_->prev[i].bb) { + has_pred = true; + break; + } + } + if (has_pred) + bb_connect(inc_, cond_start, NEXT); + + /* jump to increment */ + continue_pos_idx--; + break_exit_idx--; + return for_end; +} + +/* A do-while loop, whose condition is tested after the body. */ +basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) +{ + var_t *vd; + + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; + + basic_block_t *cond_ = bb_create(parent); + basic_block_t *do_while_end = bb_create(parent); + + continue_bb_push(cond_); + break_bb_push(do_while_end); + + basic_block_t *do_body = read_body_statement(parent, bb); + if (do_body) + bb_connect(do_body, cond_, NEXT); + + lex_expect(T_while); + lex_expect(T_open_bracket); + read_control_expression(parent, &cond_); + lex_expect(T_close_bracket); + + vd = opstack_pop(); + reject_record_operand(vd); + add_insn(parent, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + + lex_expect(T_semicolon); + + for (int i = 0; i < cond_->prev_idx; i++) { + if (cond_->prev[i].bb) { + bb_connect(cond_, bb, THEN); + bb_connect(cond_, do_while_end, ELSE); + break; + } + /* if breaking out of loop, skip condition block */ + } + + continue_pos_idx--; + break_exit_idx--; + return do_while_end; +} + +/* A local struct or union declaration. */ +basic_block_t *handle_record_statement(block_t *parent, + basic_block_t *bb, + const block_decl_specifiers_t *spec) +{ + char token[MAX_ID_LEN]; + type_t *type = NULL; + + /* The caller has seen struct or union ahead. */ + base_type_t kind = accept_record_keyword(); + bool has_tag = lex_peek(T_identifier, token); + + if (has_tag) + lex_expect(T_identifier); + else if (!lex_peek(T_open_curly, NULL)) + error_at("Expected struct or union tag or definition", + next_token_loc()); + if (lex_peek(T_open_curly, NULL)) { + type = read_record_body(parent, kind, has_tag, token); + if (lex_accept(T_semicolon)) + return bb; + } + if (!type && lex_accept(T_semicolon)) { + /* A block-scope `struct tag;` or `union tag;` introduces an incomplete + * tag in this block, or repeats one it declared. Its pointer + * declarators become valid immediately; the later complete definition + * reuses this type record. + */ + local_record_tag(token, parent, kind); + return bb; + } + if (!type) + type = reference_record_tag(token, parent, kind); + + /* The declarators share the ordinary block-scope lowering, which owns + * storage classes, qualifiers and typedef name collisions. + */ + return read_block_declarators(parent, bb, type, spec, true); +} + +/* C99 6.7.2.2 constrains every enumerator value to int range, while leaving the + * compatible integer type of an enum implementation-defined. shecc deliberately + * selects int for every target, so enum objects, parameters, returns, arrays, + * and record fields use the ordinary int ABI consistently. + */ +void initialize_enum_type(type_t *type) +{ + type->base_type = TYPE_int; + type->size = TY_int->size; +} + +/* Advance an implicitly numbered enumerator without wrapping past C99's + * required int domain. Callers invoke this only for an actual implicit value. + */ +int next_enum_value(int value) +{ + if (value == INT_MAX) + error_at("Enumerator value exceeds int range", cur_token_loc()); + return value + 1; +} + +int read_enum_constant(block_t *scope) +{ + bool saved_checking = checking_enum_constant; + int value; + + if (typed_global_literal_appears_before_initializer_end(cur_token->next)) { + pp_integer_t typed_value; + block_t *saved_scope = pp_integer_constant_scope; + token_t *last; + + pp_integer_constant_scope = scope; + last = pp_read_constant_infix_expr(0, cur_token, &typed_value, true); + pp_integer_constant_scope = saved_scope; + cur_token = last; + if ((typed_value.is_unsigned && + (typed_value.hi || typed_value.lo > 0x7fffffffU)) || + (!typed_value.is_unsigned && + typed_value.hi != (typed_value.lo & 0x80000000U ? ~0U : 0))) + error_at("Enumerator value exceeds int range", cur_token_loc()); + return typed_value.lo; + } + + checking_enum_constant = true; + value = read_const_expr(scope); + checking_enum_constant = saved_checking; + return value; +} + +/* The enum specifier that starts at the next token, in block @parent or at file + * scope when @parent is NULL: a reference to a visible tag, or a tagged or + * untagged definition. An enum definition is a declaration in its own right; it + * need not introduce a typedef. Its enumerators are integer constants of the + * scope that may use the same integer constant expressions accepted for array + * bounds and case labels. @is_definition reports whether a body was read. + * + * Returns the enum type. + */ +type_t *read_enum_specifier(block_t *parent, bool *is_definition) +{ + char token[MAX_ID_LEN]; + int val = 0; + type_t *type = NULL; + bool has_tag = false; + block_t *scope = parent ? parent : GLOBAL_BLOCK; + + lex_expect(T_enum); + if (lex_peek(T_identifier, token)) { + lex_expect(T_identifier); + type = local_enum_tag(token, scope); + has_tag = true; + } + *is_definition = lex_peek(T_open_curly, NULL); + if (!*is_definition) { + if (!has_tag) + error_at("Unknown enum type", next_token_loc()); + return type ? type : reference_enum_tag(token, parent); + } + + /* Only a definition creates an enum tag, so one already declared in this + * scope would be defined twice (C99 6.7.2.3p1). + */ + if (type) + error_at("redefinition of enum tag", cur_token_loc()); + type = add_type(); + initialize_enum_type(type); + if (has_tag) { + set_type_name(type, token); + add_type_tag(scope, token, type); + } + lex_expect(T_open_curly); + bool first = true; + do { + lex_ident(T_identifier, token); + if (!first && !lex_peek(T_assign, NULL)) + val = next_enum_value(val); + if (lex_accept(T_assign)) { + val = read_enum_constant(scope); + } + first = false; + if (!parent) { + add_constant(token, val); + continue; + } + + /* An enumeration constant has no linkage, so no other ordinary + * identifier of this block, object or typedef name, may share its name. + */ + if (find_block_ordinary(parent, token, + ORDINARY_VARIABLE | ORDINARY_TYPEDEF, NULL)) + error_at("identifier redeclared as a different kind of symbol", + cur_token_loc()); + reject_unlinked_scope_name(parent, token); + add_scoped_constant(parent, token, val); + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); + lex_expect(T_close_curly); + return type; +} + +/* A block-scope enum declaration. Like a record definition, an enum definition + * may stand alone or introduce declarators after the closing brace. + */ +basic_block_t *handle_enum_statement(block_t *parent, + basic_block_t *bb, + const block_decl_specifiers_t *spec) +{ + bool is_definition; + type_t *type = read_enum_specifier(parent, &is_definition); + + if (is_definition && lex_accept(T_semicolon)) + return bb; + if (lex_peek(T_semicolon, NULL)) + error_at("enum declaration without an enumerator list declares nothing", + next_token_loc()); + return read_block_declarators(parent, bb, type, spec, true); +} + +/* Bind a block-scope extern declaration to the file-scope declaration table. + * + * The provisional declarator was added to @parent while its syntax was read. It + * must not become an automatic object: an extern declaration has no local + * storage. Move it to GLOBAL_BLOCK so the ordinary file-scope redeclaration + * checks and eventual definition share one var_t, then leave a scope alias in + * the current block. The alias matters when the declaration hides an outer + * automatic object of the same name. + */ +var_t *bind_block_extern_object(block_t *parent, var_t *var) +{ + bool is_redeclaration; + + reject_block_redeclaration(parent, var, false, true); + parent->locals.size--; + var->is_global = true; + var->is_static = false; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; + var = + resolve_global_declarator(GLOBAL_BLOCK, var, false, &is_redeclaration); + if (!is_redeclaration) + add_insn(GLOBAL_BLOCK, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, + NULL); + + parent->locals.elements[parent->locals.size++] = var; + return var; +} + +/* Bind a block-scope function declarator whose parameter list comes next. C99 + * 6.2.2p5 gives it external linkage whether or not it is spelled extern, so + * both spellings share this path. The global helper owns redeclaration checks + * and parameter parsing and keeps the translation-unit entry for a later + * definition, while the lexical alias left in @parent limits the name to this + * block and hides an outer automatic object of the same name. + * + * Returns true when the declaration ended at its semicolon, false when a comma + * leaves further declarators to the caller. + */ +static bool read_block_function_declarator(block_t *parent, var_t *var) +{ + bool ended; + var_t *alias; + + reject_block_redeclaration(parent, var, true, true); + parent->locals.size--; + var->is_block_scope_function_declaration = true; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; + ended = read_global_function_declarator(GLOBAL_BLOCK, var, false, false); + alias = require_var(parent); + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + return ended; +} + +/* Convert the value @expr_result of a block declarator's scalar initializer to + * @var and store it, with the diagnostics every declarator of a declaration + * shares. + */ +static void emit_scalar_initializer(block_t *parent, + basic_block_t **bb, + var_t *var, + var_t *expr_result) +{ + /* Keep direct function-pointer initializers on their relocation path, but + * every other initializer consumes a function designator as its converted + * pointer value. + */ + if (!var->is_func) + expr_result = materialize_function_designator(parent, bb, expr_result); + + if (strict_c99 && is_array_literal_placeholder(expr_result) && + !has_effective_pointer(var) && var->array_size == 0) + error_at("array compound literal cannot be used as a scalar in C99", + cur_token_loc()); + + /* Handle array compound literal to scalar assignment */ + if (expr_result && expr_result->array_size > 0 && !var->ptr_level && + var->array_size == 0 && var->type && + (var->type->base_type == TYPE_int || + var->type->base_type == TYPE_short) && + expr_result->var_name[0] == '.') { + /* Extract first element from compound literal array */ + var_t *first_elem = require_var(parent); + first_elem->type = var->type; + first_elem->var_name = gen_name(); + + /* Read first element from array at offset 0 expr_result is the array + * itself, so we can read directly from it + */ + add_insn(parent, *bb, OP_read, first_elem, expr_result, NULL, + var->type->size, NULL); + expr_result = first_elem; + } + + diagnose_callback_slot_initializer(expr_result, var); + diagnose_const_pointer_conversion(expr_result, var); + diagnose_integer_to_pointer_conversion(expr_result, var, false); + diagnose_function_pointer_conversion(expr_result, var); + emit_object_assignment(parent, bb, var, expr_result); +} + +/* Define the object that the block declarator @var declares, as @spec says, and + * lower its initializer. Every declarator of a declaration shares this. + */ +static void read_block_declarator_storage(block_t *parent, + basic_block_t **bb, + const block_decl_specifiers_t *spec, + var_t *var) +{ + if (is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + reject_block_redeclaration(parent, var, false, false); + add_insn(spec->is_static ? GLOBAL_BLOCK : parent, + spec->is_static ? GLOBAL_FUNC->bbs : *bb, OP_allocat, var, NULL, + NULL, 0, NULL); + add_symbol(*bb, var); + if (lex_accept(T_assign)) { + validate_string_array_initializer(var); + if (spec->is_static) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + /* A block-scope static has global storage duration, so its + * brace initializer belongs to the same constant-data lowering + * as a file-scope array. + */ + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); + } else if (global_compound_literal_starts_here() && + !(var->ptr_level || var->type->ptr_level) && + is_record_type(var->type)) { + parse_global_compound_record_init(var, GLOBAL_BLOCK); + } else if (global_compound_literal_starts_here() && + (var->ptr_level || var->type->ptr_level)) { + parse_global_compound_array_init(var, GLOBAL_BLOCK); + } else if (global_compound_literal_starts_here()) { + parse_global_compound_scalar_init(var, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL)) { + parse_global_record_init(var, GLOBAL_BLOCK); + } else { + read_global_assignment_var(var); + } + } else if ((var->has_unsized_array || var->array_size > 0) && + is_char_array(var) && lex_peek(T_string, NULL)) { + parse_string_array_init(var, parent, bb); + } else if ((var->has_unsized_array || var->array_size > 0) && + is_wchar_array(var) && lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(var, parent, bb); + } else if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + /* Emit code for locals in functions */ + parse_array_init(var, parent, bb); + } else if (lex_peek(T_open_curly, NULL) && is_record_type(var->type)) { + type_t *struct_type = var->type; + if (struct_type->base_type == TYPE_typedef && + struct_type->base_struct) + struct_type = struct_type->base_struct; + + var_t *struct_addr = require_var(parent); + struct_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, struct_addr, var, NULL, 0, + NULL); + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, struct_type, struct_addr); + lex_expect(T_close_curly); + } else { + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + + emit_scalar_initializer(parent, bb, var, opstack_pop()); + } + } + if (spec->is_static) + discard_global_declarator_operand(var); +} + +/* A block-scope declaration whose type handle_declaration() has resolved: every + * declarator, its initializer, and its storage. @has_base_type says a struct, + * union or enum specifier has already consumed the base type, so only the + * declarators remain. + * + * Returns the block that follows. + */ +static basic_block_t *read_block_declarators( + block_t *parent, + basic_block_t *bb, + type_t *type, + const block_decl_specifiers_t *spec, + bool has_base_type) +{ + block_decl_specifiers_t qualified; + var_t *var; + + /* The flag below covers only the first declarator; keep whether this list + * is a for initializer for the ones after a comma. + */ + bool for_declaration = parsing_for_initializer_declaration; + + /* A record or enum specifier has been consumed, and qualifiers may follow + * it before the first declarator. They qualify every declarator in the + * list, like the leading ones in @spec. + */ + if (has_base_type) { + qualified = *spec; + read_type_qualifiers(&qualified.is_const, &qualified.is_volatile, + false); + spec = &qualified; + } + + var = require_typed_var(parent, type); + var->is_static = spec->is_static; + var->is_register = spec->is_register; + var->is_global = spec->is_static; + var->is_const_qualified = spec->is_const; + var->is_volatile = spec->is_volatile; + if (has_base_type) + read_partial_var_decl(var, NULL); + else + read_full_var_decl(var, false, false, false); + + /* A for initializer sets this for its first declarator only, so strict C99 + * can parse a function declarator there and reject it. A prototype leaves + * its parameter list unread, so the open parenthesis also marks one. + */ + if (parsing_for_initializer_declaration) { + parsing_for_initializer_declaration = false; + if (strict_c99 && (var->is_func || lex_peek(T_open_bracket, NULL))) + error_at("C99 for initializer cannot declare a function", + cur_token_loc()); + } + reject_ordinary_typedef_collision(parent, var); + + /* A declaration spelled with signed, unsigned or long arrives with int as a + * placeholder, and only the first declarator's specifiers resolve it. Every + * later declarator in the list shares that resolved base type, as at file + * scope; created from the placeholder, "unsigned int a, b;" gave b the type + * int. + */ + if (type == TY_int) + type = var->type; + + /* A direct function typedef used without a star declares a function, not an + * automatic object. Bind it through the file-scope function table and leave + * a lexical function alias so it also hides an outer local object of the + * same ordinary identifier. + */ + if (var->is_func && var->type->is_direct_function_type) { + for (;;) { + if (spec->is_static || spec->is_register || spec->is_auto) + error_at("invalid storage class for block function declaration", + cur_token_loc()); + if (spec->is_const || spec->is_volatile || + var->is_const_qualified || var->is_volatile) + error_at("function type cannot be qualified", cur_token_loc()); + if (read_block_function_declarator(parent, var)) + return bb; + lex_expect(T_comma); + + var = require_typed_var(parent, type); + var->is_const_qualified = spec->is_const; + var->is_volatile = spec->is_volatile; + var->func_signature = type->func_signature; + var->is_func = var->func_signature != NULL; + read_partial_var_decl(var, NULL); + reject_ordinary_typedef_collision(parent, var); + if (!(var->is_func && var->type->is_direct_function_type)) + + /* The comma list may continue with an object derived from the + * direct-function typedef, such as `unary_t declared, + * *callback`. The function declaration above has already been + * registered; hand this fully parsed object to the ordinary + * declaration lowering below. + */ + break; + } + } + if (var->is_inline) + error_at("inline specifier requires a function declarator", + next_token_loc()); + + /* Decide after the full declarator has been read: both `const int` and `int + * const`, and an outer `* const`, make the defined object non-modifiable. + */ + if (spec->is_static && parent->func && parent->func->is_inline && + !parent->func->is_static && !var->is_const_qualified && + !var->is_const_pointer) + error_at("external inline definition cannot define static object", + cur_token_loc()); + + /* Function declarators, and every declarator of an extern declaration, own + * no automatic storage. Read them here until the declaration ends or an + * ordinary object declarator follows, as in "int f(void), value = 1;", + * which then takes the object lowering below with the rest of the list. + */ + if (spec->is_extern || lex_peek(T_open_bracket, NULL)) { + for (;;) { + if (lex_peek(T_open_bracket, NULL) || + (spec->is_extern && var->is_func)) { + if (spec->is_static || spec->is_register || spec->is_auto) + error_at( + "invalid storage class for block function declaration", + cur_token_loc()); + if (read_block_function_declarator(parent, var)) + return bb; + } else if (spec->is_extern) { + var = bind_block_extern_object(parent, var); + if (lex_peek(T_assign, NULL)) + error_at("extern declaration cannot have an initializer", + next_token_loc()); + } else + break; + if (!lex_accept(T_comma)) { + lex_expect(T_semicolon); + return bb; + } + var = require_typed_var(parent, type); + var->is_static = spec->is_static; + var->is_register = spec->is_register; + var->is_global = spec->is_static; + var->is_const_qualified = spec->is_const; + var->is_volatile = spec->is_volatile; + read_partial_var_decl(var, NULL); + reject_ordinary_typedef_collision(parent, var); + } + } + read_block_declarator_storage(parent, &bb, spec, var); + while (lex_accept(T_comma)) { + var_t *nv; + + /* multiple (partial) declarations */ + nv = require_typed_var(parent, type); + nv->is_static = spec->is_static; + nv->is_register = spec->is_register; + nv->is_global = spec->is_static; + nv->is_const_qualified = var->is_const_qualified; + nv->is_volatile = var->is_volatile; + read_partial_var_decl(nv, var); /* partial */ + if (strict_c99 && for_declaration && + (nv->is_func || lex_peek(T_open_bracket, NULL))) + error_at("C99 for initializer cannot declare a function", + cur_token_loc()); + reject_ordinary_typedef_collision(parent, nv); + if (lex_peek(T_open_bracket, NULL)) { + if (spec->is_static || spec->is_register || spec->is_auto) + error_at("invalid storage class for block function declaration", + cur_token_loc()); + if (read_block_function_declarator(parent, nv)) + return bb; + continue; + } + read_block_declarator_storage(parent, &bb, spec, nv); + } + lex_expect(T_semicolon); + return bb; +} + +/* Everything a statement can still be: a declaration, an assignment, a call, or + * an expression evaluated for its effect. + */ +basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + func_t *func; + type_t *type; + block_decl_specifiers_t spec = {0}; + + while (lex_peek(T_static, NULL) || lex_peek(T_extern, NULL) || + lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL) || + lex_peek(T_register, NULL) || lex_peek(T_auto, NULL)) { + if (lex_accept(T_static)) { + if (spec.is_static) + error_at("duplicate static storage class specifier", + cur_token_loc()); + spec.is_static = true; + } else if (lex_accept(T_extern)) { + if (spec.is_extern) + error_at("duplicate extern storage class specifier", + cur_token_loc()); + spec.is_extern = true; + } else if (lex_accept(T_register)) { + if (spec.is_register) + error_at("duplicate register storage class specifier", + cur_token_loc()); + spec.is_register = true; + } else if (lex_accept(T_auto)) { + if (spec.is_auto) + error_at("duplicate auto storage class specifier", + cur_token_loc()); + spec.is_auto = true; + } else if (lex_accept(T_volatile)) { + spec.is_volatile = true; + } else { + lex_expect(T_const); + spec.is_const = true; + } + } + if ((spec.is_static && + (spec.is_register || spec.is_extern || spec.is_auto)) || + (spec.is_register && (spec.is_extern || spec.is_auto)) || + (spec.is_extern && spec.is_auto)) + error_at("incompatible storage class specifiers", cur_token_loc()); + + if (floating_type_starts_here()) + error_at("Floating point types are not yet supported", cur_token_loc()); + + if (lex_peek(T_enum, NULL)) + return handle_enum_statement(parent, bb, &spec); + + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) + return handle_record_statement(parent, bb, &spec); + + /* must be an identifier or asterisk (for pointer dereference) */ + bool has_asterisk = lex_peek(T_asterisk, NULL); + bool has_identifier = lex_peek(T_identifier, token); + bool has_record_keyword = + lex_peek(T_struct, NULL) || lex_peek(T_union, NULL); + bool has_signed_keyword = lex_peek(T_signed, NULL); + bool has_unsigned_keyword = lex_peek(T_unsigned, NULL); + bool has_long_keyword = lex_peek(T_long, NULL); + if (!spec.is_const && !has_identifier && !has_asterisk && + !has_record_keyword && !has_signed_keyword && !has_unsigned_keyword && + !has_long_keyword) + error_at("Unexpected token", next_token_loc()); + + /* is it a variable declaration? Special handling when statement starts with + * asterisk + */ + if (has_asterisk) { + /* For "*identifier", check if identifier is a type. If not, it's a + * dereference, not a declaration. + */ + token_t *saved_token = cur_token; + + /* Skip the asterisk to peek at the identifier */ + lex_accept(T_asterisk); + char next_ident[MAX_TOKEN_LEN]; + bool could_be_type = false; + + if (lex_peek(T_identifier, next_ident)) { + /* Check if it's a type name */ + type = find_visible_type(next_ident, parent); + if (type) + could_be_type = true; + } + + /* Restore position */ + cur_token = saved_token; + + /* If it's not a type, skip the declaration block */ + if (!could_be_type) + type = NULL; + } else { + /* Normal type checking without asterisk */ + if (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL)) { + type = TY_int; + } else { + /* struct and union declarations were handed to + * handle_record_statement() above. + */ + type = find_visible_type(token, parent); + } + } + + if ((spec.is_static || spec.is_extern) && !type) + error_at("Expected declaration after storage class specifier", + next_token_loc()); + + if (type) + return read_block_declarators(parent, bb, type, &spec, false); + + /* Keep the long-standing direct-call lowering only for a truly standalone + * `function(...);` statement. An identifier-led call that is followed by an + * operator or comma belongs to the full expression grammar below. The + * self-hosted compiler still exercises this short path heavily, while the + * bounded lookahead prevents it from stealing valid C99 expressions such as + * `first(), second();` or `function() + 1;`. + */ + var_t *local = find_local_var(token, parent); + if (!has_asterisk && (!local || local->is_extern_function_alias)) { + token_t *call = cur_token->next; + token_t *end = call && call->next ? call->next : NULL; + int depth = 0; + bool matched_call = false; + + if (end && end->kind == T_open_bracket) { + for (; end; end = end->next) { + if (end->kind == T_open_bracket) + depth++; + else if (end->kind == T_close_bracket && !--depth) { + end = end->next; + matched_call = true; + break; + } + } + } + if (matched_call && end && end->kind == T_semicolon && call) { + func = find_visible_func(token, parent); + if (func) { + lex_expect(T_identifier); + emit_direct_call_result(func, false, parent, &bb); + lex_expect(T_semicolon); + return bb; + } + } + } + + /* A declaration has already returned above. Resolve a label before routing + * every remaining non-declaration statement through the full C99 expression + * grammar. + */ + if (lex_peek(T_identifier, token) && cur_token->next->next && + cur_token->next->next->kind == T_colon) { + lex_accept(T_identifier); + token_t *id_tk = cur_token; + + lex_expect(T_colon); + const label_t *l = find_label(token); + if (l) + error_at("label redefinition", &id_tk->location); + reject_label_followed_by_declaration_in_strict_c99(parent); + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + add_label(token, n); + add_insn(parent, n, OP_label, NULL, NULL, NULL, 0, token); + return n; + } + return read_full_expression_statement(parent, bb); +} + +/* Whether the declaration specifiers starting at the next token include const + * and volatile, which set @is_const and @is_volatile. The scan skips a record + * or enum body and stops at the first declarator token, so a qualifier after a + * star, which belongs to one declarator, is not seen. + */ +static void peek_specifier_qualifiers(bool *is_const, bool *is_volatile) +{ + bool saw_type_name = false; + int depth = 0; + + for (token_t *tk = cur_token->next; tk; tk = tk->next) { + token_kind_t kind = tk->kind; + + if (depth) { + depth += (kind == T_open_curly) - (kind == T_close_curly); + continue; + } + if (kind == T_open_curly) + depth = 1; + else if (kind == T_const) + *is_const = true; + else if (kind == T_volatile) + *is_volatile = true; + else if (kind == T_identifier) { + if (saw_type_name) + return; + saw_type_name = true; + } else if (kind != T_struct && kind != T_union && kind != T_enum && + kind != T_signed && kind != T_unsigned && kind != T_long && + kind != T_float && kind != T_double && kind != T_restrict) + return; + } +} + +/* Lexical typedef aliases are declaration-only bindings on the current block, + * never ordinary objects or global type names. This bounded parser admits + * selected direct-function, callback-pointer, and fixed callback-array forms; + * remaining derived declarations await the shared declarator path. + */ +basic_block_t *handle_block_typedef_statement(block_t *parent, + basic_block_t *bb) +{ + var_t decl = {0}; + bool specifier_const = false; + bool specifier_volatile = false; + + lex_expect(T_typedef); + decl.scope = parent; + peek_specifier_qualifiers(&specifier_const, &specifier_volatile); + parsing_block_typedef_declarator = true; + + /* The shared specifier reader resolves an enum tag but cannot define one, + * so read an enum specifier here, then its trailing qualifiers and the + * declarators. A leading qualifier is still ahead of the enum keyword. + */ + read_type_qualifiers(&decl.is_const_qualified, &decl.is_volatile, false); + if (lex_peek(T_enum, NULL)) { + bool is_definition; + + decl.type = read_enum_specifier(parent, &is_definition); + read_type_qualifiers(&decl.is_const_qualified, &decl.is_volatile, + false); + read_inner_var_decl(&decl, false, false, false); + } else if ((lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) && + (cur_token->next->next->kind == T_open_curly || + (cur_token->next->next->kind == T_identifier && + cur_token->next->next->next->kind == T_open_curly))) { + /* A record body defines its type, and its tag in this block, before the + * declarators; the shared specifier reader only names a tag. + */ + char tag[MAX_ID_LEN]; + base_type_t kind = accept_record_keyword(); + bool has_tag = lex_peek(T_identifier, tag); + + if (has_tag) + lex_expect(T_identifier); + decl.type = read_record_body(parent, kind, has_tag, tag); + read_type_qualifiers(&decl.is_const_qualified, &decl.is_volatile, + false); + read_inner_var_decl(&decl, false, false, false); + } else + read_full_var_decl(&decl, false, false, false); + parsing_block_typedef_declarator = false; + do { + type_t *base = decl.type; + type_t *alias = add_type(); + func_t *direct_function_signature = decl.func_signature; + func_t *callback_signature = decl.func_signature; + func_t *callback_slot_signature = decl.pointee_func_signature; + bool direct_array = decl.has_direct_array_declarator; + bool direct_pointee_array = decl.has_direct_pointee_array_declarator; + + /* `fn_t (*rows_t)[2]`, or the spelled `int (*(*rows_t)[2])(int)`, + * points to a row of callbacks: the callback typedef is the element. + */ + bool callback_row_alias = + direct_pointee_array && base->func_signature && + !base->is_direct_function_type && !base->pointee_func_signature && + !decl.array_size && !decl.pointee_array_element_ptr_level; + bool inherited_pointee_array = base->pointee_array_size != 0; + bool direct_function_alias = + decl.is_direct_function_declarator && decl.is_func && + decl.func_signature && !decl.array_size && + !decl.pointee_array_size && !base->ptr_level && + !is_record_type(base) && !base->is_floating && + !direct_function_signature->returns_aggregate; + bool callback_pointer_alias = + decl.is_func && decl.func_signature && + decl.parenthesized_function_pointer_level == 1 && !decl.ptr_level && + !decl.array_size && !decl.pointee_array_size && !base->ptr_level && + !is_record_type(base) && !base->is_floating && + !callback_signature->va_args && + !callback_signature->returns_aggregate; + bool callback_pointer_realias = + decl.is_func && decl.func_signature && + !decl.parenthesized_function_pointer_level && !decl.ptr_level && + !decl.array_size && !decl.pointee_array_size && !base->ptr_level && + base->func_signature && !base->is_direct_function_type; + + /* `int (**slot_t)(int)` is a pointer-to-callback object, not a callable + * callback pointer. The shared declarator parser has already normalized + * the extra star into decl.ptr_level and retained the prototype as + * pointee metadata. Keep this first exact typedef form equally narrow: + * scalar/void, no arrays or qualifiers; each further star, as in `int + * (***slot_t)(int)`, is one more object pointer. + */ + bool callback_slot_alias = + callback_slot_signature && + decl.parenthesized_function_pointer_level >= 2 && + decl.ptr_level == decl.parenthesized_function_pointer_level - 1 && + !decl.array_size && !decl.pointee_array_size && !base->ptr_level && + !is_record_type(base) && !base->is_floating && + !decl.is_const_qualified && + !decl.parenthesized_function_pointer_inner_qualified && + !callback_slot_signature->va_args && + !callback_slot_signature->returns_aggregate; + bool callback_slot_realias = + decl.pointee_func_signature && + !decl.parenthesized_function_pointer_level && !decl.ptr_level && + !decl.array_size && !decl.pointee_array_size && + base->ptr_level >= 1 && base->pointee_func_signature && + !decl.is_const_qualified && + !decl.parenthesized_function_pointer_inner_qualified && + !decl.parenthesized_function_pointer_restrict; + bool callback_slot_array_alias = + callback_slot_signature && + decl.parenthesized_function_pointer_level == 2 && + decl.ptr_level == 1 && decl.array_size > 0 && + !decl.has_unsized_array && !decl.pointee_array_size && + !base->ptr_level && !base->array_size && !is_record_type(base) && + !base->is_floating && !decl.is_const_qualified && + (!decl.is_const_pointer || + decl.parenthesized_function_pointer_outer_const) && + (!decl.pointer_const_mask || + (decl.parenthesized_function_pointer_outer_const && + decl.pointer_const_mask == 1U)) && + (!decl.is_volatile || + decl.parenthesized_function_pointer_outer_volatile) && + !decl.parenthesized_function_pointer_inner_qualified && + (!decl.parenthesized_function_pointer_restrict || + decl.parenthesized_function_pointer_outer_restrict) && + !callback_slot_signature->va_args && + !callback_slot_signature->returns_aggregate; + bool callback_slot_array_realias = + !decl.parenthesized_function_pointer_level && !decl.ptr_level && + !decl.pointee_array_size && base->array_size > 0 && + base->array_element_ptr_level == 1 && + base->array_element_pointee_func_signature && + !decl.is_const_pointer && !decl.pointer_const_mask && + !decl.parenthesized_function_pointer_restrict; + bool callback_array_alias = + decl.is_func && decl.func_signature && + decl.parenthesized_function_pointer_level == 1 && + decl.array_size > 0 && !decl.ptr_level && + !decl.pointee_array_size && !base->ptr_level && + !is_record_type(base) && !base->is_floating && + !callback_signature->va_args && + !callback_signature->returns_aggregate; + bool callback_array_realias = + decl.is_func && decl.func_signature && + !decl.parenthesized_function_pointer_level && !direct_array && + !decl.ptr_level && !decl.pointee_array_size && + base->array_size > 0 && base->array_element_ptr_level == 1 && + base->func_signature && !base->is_direct_function_type; + + if (decl.parenthesized_function_pointer_level > 1 && + !callback_slot_alias && !callback_slot_array_alias) + error_at( + "deeper block callback-pointer typedef is not yet supported", + cur_token_loc()); + if (callback_pointer_alias && + decl.parenthesized_function_pointer_restrict) + error_at("restrict requires a pointer to an object type", + cur_token_loc()); + if (callback_array_alias && + (decl.is_const_pointer || (decl.pointer_const_mask & 1U) || + decl.parenthesized_function_pointer_const || decl.is_volatile || + decl.parenthesized_function_pointer_restrict)) + error_at( + "qualified block callback-array typedef is not yet supported", + cur_token_loc()); + if (direct_array && base->func_signature && + !base->is_direct_function_type && + (decl.is_const_qualified || decl.is_volatile)) + error_at( + "qualified block callback-array typedef is not yet supported", + cur_token_loc()); + if (direct_array && base->array_size && + base->array_element_ptr_level == 1 && base->func_signature && + !base->is_direct_function_type) + error_at( + "derived block callback-array typedef is not yet supported", + cur_token_loc()); + + /* Keep pointer-to-array aliases intentionally narrow until their + * composition rules share the full declarator path. The supported forms + * are exactly fixed scalar rows and fixed one-pointer-element rows, + * plus a plain re-alias of a completed type. + */ + if (decl.has_unsized_array || + ((decl.is_func || decl.func_signature) && !direct_function_alias && + !callback_row_alias && !callback_pointer_alias && + !callback_pointer_realias && !callback_array_alias && + !callback_array_realias && !callback_slot_alias && + !callback_slot_realias && !callback_slot_array_alias && + !callback_slot_array_realias) || + (base->pointee_func_signature && !callback_slot_realias) || + (base->array_element_pointee_func_signature && + !callback_slot_array_realias) || + (direct_array && + ((base->ptr_level && !inherited_pointee_array) || + (inherited_pointee_array && decl.array_dim2) || + (decl.ptr_level && (decl.array_dim2 || decl.ptr_level > 1)))) || + (direct_pointee_array && + (base->ptr_level || base->array_size || + decl.ptr_level != decl.pointee_array_element_ptr_level + 1 || + decl.pointee_array_element_ptr_level > 1)) || + (inherited_pointee_array && + (decl.ptr_level || direct_pointee_array))) + error_at("block typedef derived declarator is not yet supported", + cur_token_loc()); + if (lex_peek(T_assign, NULL)) + error_at("typedef declaration cannot have an initializer", + next_token_loc()); + memcpy(alias, base, sizeof(*alias)); + + /* A record alias takes the typedef descriptor, which reaches the layout + * through base_struct, and so does a function alias, whose void or + * scalar base is only its return type. A data alias keeps its base's + * type, as a file-scope one does: with no base_struct to follow, + * dereferencing a pointer alias would otherwise yield the pointer type + * itself. + */ + bool is_function_alias = decl.is_func || decl.func_signature || + decl.pointee_func_signature || + base->func_signature || + base->pointee_func_signature || + base->array_element_pointee_func_signature; + + if (is_function_alias) + alias->base_type = TYPE_typedef; + if (is_record_type(base) && !base->ptr_level) { + alias->base_type = TYPE_typedef; + alias->base_struct = base; + + /* A pointer to the record is no record itself: like `typedef struct + * S *SP` at file scope it finds the members on its base. + */ + if (decl.ptr_level) { + alias->fields = NULL; + alias->num_fields = 0; + } + } + alias->ptr_level = base->ptr_level + decl.ptr_level; + alias->pointer_const_mask = + base->pointer_const_mask | + (decl.pointer_const_mask << base->ptr_level); + + /* `const ptr_t` qualifies the pointer that the base typedef hides, not + * its pointee, for every declarator of the list. + */ + if (specifier_const && base->ptr_level && base->ptr_level <= 32 && + !is_function_alias) + alias->pointer_const_mask |= 1U << (base->ptr_level - 1); + alias->is_const_qualified = decl.is_const_qualified; + if (callback_pointer_realias && decl.is_const_qualified) { + /* A callback alias itself is a pointer type, even though its + * compact descriptor stores no ordinary pointer depth. + */ + alias->pointer_const_mask |= 1U; + alias->is_const_qualified = false; + } + alias->is_volatile_qualified = + decl.is_volatile || base->is_volatile_qualified; + if (direct_function_alias) { + /* The stars of `char *name_t(void)` belong to the return type, + * which the signature already records. + */ + alias->ptr_level = base->ptr_level; + alias->pointer_const_mask = base->pointer_const_mask; + alias->func_signature = decl.func_signature; + alias->is_direct_function_type = true; + } + if (callback_pointer_alias) { + alias->size = PTR_SIZE; + alias->func_signature = decl.func_signature; + alias->is_direct_function_type = false; + } + if (callback_slot_alias) { + alias->size = PTR_SIZE; + alias->alignment = PTR_SIZE; + alias->func_signature = NULL; + alias->pointee_func_signature = callback_slot_signature; + alias->is_direct_function_type = false; + } + if (callback_slot_array_alias) { + alias->ptr_level = 0; + alias->size = PTR_SIZE; + alias->alignment = PTR_SIZE; + alias->func_signature = NULL; + alias->pointee_func_signature = NULL; + alias->is_direct_function_type = false; + alias->pointer_const_mask = 0; + alias->is_volatile_qualified = false; + compose_block_typedef_array(alias, base, &decl); + alias->array_element_ptr_level = 1; + alias->array_element_pointee_func_signature = + callback_slot_signature; + alias->array_element_is_const_pointer = + decl.parenthesized_function_pointer_outer_const; + alias->array_element_is_volatile = + decl.parenthesized_function_pointer_outer_volatile; + } + if (callback_slot_array_realias && direct_array) { + /* Wrapping a completed slot-array alias prepends bounds but must + * keep its element as a callback slot. compose_* records the + * declaration's zero direct pointer depth, so restore the completed + * element descriptor after composition. + */ + compose_block_typedef_array(alias, base, &decl); + alias->array_element_ptr_level = base->array_element_ptr_level; + alias->array_element_pointee_func_signature = + base->array_element_pointee_func_signature; + } + if (callback_slot_array_realias) { + /* Qualifying an array typedef qualifies its element type. Keep that + * fact on the final callback-slot lvalue rather than making the + * array object itself const or volatile. `restrict` has no runtime + * representation, but was validated while parsing. + */ + alias->is_const_qualified = false; + alias->is_volatile_qualified = false; + alias->array_element_is_const_pointer |= decl.is_const_qualified; + alias->array_element_is_volatile |= decl.is_volatile; + } + if (callback_slot_realias && decl.is_const_pointer) { + /* `slot_t const` qualifies the typedef-hidden outer slot pointer, + * so do not shift the declaration's compact bit past it. + */ + alias->pointer_const_mask |= 1U; + alias->is_const_qualified = false; + } + if (callback_array_alias) { + alias->size = PTR_SIZE; + alias->alignment = PTR_SIZE; + alias->func_signature = decl.func_signature; + alias->is_direct_function_type = false; + } + if (direct_array || callback_array_alias) { + compose_block_typedef_array(alias, base, &decl); + if (callback_array_alias) + alias->array_element_ptr_level = 1; + if (direct_array && inherited_pointee_array) { + /* A direct array suffix around `int (*row_t)[N]` stores + * pointer-to-row elements. Keep the row descriptor on the + * completed array and mark the selected element as that pointer + * slot so its later load retains the row stride. + */ + alias->array_element_ptr_level = base->ptr_level; + alias->array_element_type = + base->pointee_array_element_type + ? base->pointee_array_element_type + : base; + } + } + if (callback_row_alias) + alias_callback_row_pointer(alias, base, decl.pointer_const_mask); + + /* As at file scope, `typedef arr_t *rows_t` points to a row of the + * callbacks in the array typedef arr_t. + */ + if (decl.ptr_level == 1 && !direct_array && !direct_pointee_array) + alias_callback_array_pointer(alias, base, decl.pointer_const_mask); + if (direct_pointee_array) { + alias->pointee_array_size = decl.pointee_array_size; + alias->pointee_array_dim2 = decl.pointee_array_dim2; + alias->pointee_array_dim3 = decl.pointee_array_dim3; + alias->pointee_array_dim4 = decl.pointee_array_dim4; + alias->pointee_array_element_ptr_level = + decl.pointee_array_element_ptr_level; + alias->pointee_array_element_type = base; + } + if (alias->ptr_level) + alias->size = PTR_SIZE; + alias->type_name[0] = '\0'; + add_block_typedef(parent, decl.var_name, alias); + if (!lex_accept(T_comma)) + break; + decl = (var_t) {0}; + decl.scope = parent; + decl.type = base; + + /* The specifiers, and so their qualifiers, are shared by every + * declarator: `typedef const int ci_t, *cp_t;` points to const int. As + * read_full_var_decl() does, const on a pointer base qualifies that + * pointer instead. + */ + decl.is_const_qualified = + base->is_const_qualified || (specifier_const && !base->ptr_level); + decl.is_const_pointer = specifier_const && base->ptr_level; + decl.is_volatile = specifier_volatile || base->is_volatile_qualified; + + /* A comma continuation may itself be the bounded direct function + * typedef form, e.g. `typedef int *p_t, unary_t(int);`. + */ + parsing_block_typedef_declarator = true; + read_partial_var_decl(&decl, NULL); + parsing_block_typedef_declarator = false; + } while (true); + lex_expect(T_semicolon); + return bb; +} + +basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) +{ + /* statement can be: + * function call, variable declaration, assignment operation, + * keyword, block + */ + + if (lex_peek(T_case, NULL) || lex_peek(T_default, NULL)) + return read_switch_label_statement(parent, bb); + + if (!bb) + printf("Warning: unreachable code detected\n"); + + if (lex_peek(T_open_curly, NULL)) + return read_code_block(parent->func, parent, bb); + + if (lex_accept(T_return)) { + return handle_return_statement(parent, bb); + } + + if (lex_accept(T_if)) { + return handle_if_statement(parent, bb); + } + + if (lex_accept(T_while)) { + return handle_while_statement(parent, bb); + } + + if (lex_accept(T_switch)) + return handle_switch_statement(parent, bb); + + if (lex_accept(T_break)) { + if (!break_exit_idx) + error_at("'break' outside of a loop or switch", cur_token_loc()); + bb_connect(bb, break_bb[break_exit_idx - 1], NEXT); + lex_expect(T_semicolon); + return NULL; + } + + if (lex_accept(T_continue)) { + if (!continue_pos_idx) + error_at("'continue' outside of a loop", cur_token_loc()); + bb_connect(bb, continue_bb[continue_pos_idx - 1], NEXT); + lex_expect(T_semicolon); + return NULL; + } + + if (lex_accept(T_for)) + return handle_for_statement(parent, bb); + + if (lex_accept(T_do)) + return handle_do_statement(parent, bb); + + if (lex_accept(T_goto)) + return handle_goto_statement(parent, bb); + + hoist_storage_class_specifiers(); + if (lex_peek(T_typedef, NULL)) + return handle_block_typedef_statement(parent, bb); + + /* empty statement */ + if (lex_accept(T_semicolon)) + return bb; + + if (grouped_scalar_pointee_row_store_starts(parent)) + return handle_grouped_scalar_pointee_row_store(parent, bb); + + /* These cannot begin a declaration, so they are unambiguously expression + * statements. Identifiers remain delegated to handle_declaration(), which + * first checks whether they name a type. + */ + if (lex_peek(T_open_bracket, NULL) || lex_peek(T_numeric, NULL) || + lex_peek(T_char, NULL) || lex_peek(T_string, NULL) || + lex_peek(T_wstring, NULL) || lex_peek(T_ampersand, NULL) || + lex_peek(T_asterisk, NULL) || lex_peek(T_plus, NULL) || + lex_peek(T_minus, NULL) || lex_peek(T_increment, NULL) || + lex_peek(T_decrement, NULL) || lex_peek(T_log_not, NULL) || + lex_peek(T_bit_not, NULL)) + return read_full_expression_statement(parent, bb); + + /* Declarations, including struct, union and enum ones */ + return handle_declaration(parent, bb); +} + +/* Nesting counter for read_code_block(), which recurses through + * read_body_statement() for every nested block. + */ +int block_depth = 0; + +basic_block_t *read_code_block(func_t *func, block_t *parent, basic_block_t *bb) +{ + block_t *blk = add_block(parent, func); + + if (bb) + bb->scope = blk; + + block_depth++; + if (block_depth > MAX_BLOCK_DEPTH) + error_at("Block nesting too deep", cur_token_loc()); + + lex_expect(T_open_curly); + + while (!lex_accept(T_close_curly)) { + bb = read_body_statement(blk, bb); + } + + block_depth--; + return bb; +} + +void var_add_killed_bb(var_t *var, basic_block_t *bb); + +void read_func_body(func_t *func) +{ + block_t *blk = add_block(NULL, func); + func->bbs = bb_create(blk); + func->exit = bb_create(blk); + + if (func->returns_aggregate) { + func->sret_def.type = func->return_def.type; + func->sret_def.ptr_level = 1; + func->sret_def.var_name = "__shecc_sret"; + func->sret_def.base = &func->sret_def; + add_symbol(func->bbs, &func->sret_def); + var_add_killed_bb(&func->sret_def, func->bbs); + } + + for (int i = 0; i < func->num_params; i++) { + /* arguments */ + func->param_defs[i].is_aggregate_param = + is_record_type(func->param_defs[i].type) && + !func->param_defs[i].ptr_level; + /* A volatile parameter lives in its slot, as a volatile local does. */ + if (func->param_defs[i].is_volatile) + func->param_defs[i].address_taken = true; + add_symbol(func->bbs, &func->param_defs[i]); + func->param_defs[i].base = &func->param_defs[i]; + var_add_killed_bb(&func->param_defs[i], func->bbs); + } + basic_block_t *body = read_code_block(func, NULL, func->bbs); + if (body) + bb_connect(body, func->exit, NEXT); + + for (int i = 0; i < backpatch_bb_idx; i++) { + basic_block_t *bb = backpatch_bb[i]; + insn_t *g = bb->insn_list.tail; + label_t *label = find_label(g->str); + if (!label) + error_at("goto label undefined", cur_token_loc()); + + label->used = true; + bb_connect(bb, label->bb, NEXT); + } + + for (int i = 0; i < label_idx; i++) { + const label_t *label = &labels[i]; + if (label->used) + continue; + + printf("Warning: unused label %s\n", label->label_name); + } + + backpatch_bb_idx = 0; + label_idx = 0; +} diff --git a/src/parser.c b/src/parser.c index 1d710e1f..5fbb8329 100644 --- a/src/parser.c +++ b/src/parser.c @@ -16,10 +16,6 @@ /* C language syntactic analyzer */ int global_var_idx = 0; -/* Side effect instructions cache */ -insn_t side_effect[MAX_SIDE_EFFECT]; -int se_idx = 0; - /* Control flow utilities */ basic_block_t *break_bb[MAX_NESTING]; int break_exit_idx = 0; @@ -36,18 +32,535 @@ int backpatch_bb_idx = 0; var_t *operand_stack[MAX_OPERAND_STACK_SIZE]; int operand_stack_idx = 0; +/* sizeof parses expression operands for constraints and type information, but + * those operands are unevaluated and must not invalidate value metadata. + */ +int unevaluated_expression_depth = 0; + +/* A function prototype nested in a sizeof type-name describes only a pointer + * pointee. It never introduces a floating value into IR or an ABI boundary. + */ +bool parsing_sizeof_function_signature = false; +static bool parsing_block_typedef_declarator = false; +static bool parsing_for_initializer_declaration = false; +static block_t *global_constant_initializer_scope = NULL; + +/* Keep the current storage contract explicit while shape handling is migrated + * to helpers. Later rank expansion changes this one limit and its consumers, + * rather than duplicating bound ordering and products at every call site. + */ +#define MAX_FIXED_ARRAY_RANK 4 + +typedef struct fixed_array_shape { + int rank; + int bounds[MAX_FIXED_ARRAY_RANK]; +} fixed_array_shape_t; + /* Forward declarations */ source_location_t *cur_token_loc(void); source_location_t *next_token_loc(void); basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb); -void perform_side_effect(block_t *parent, basic_block_t *bb); -void read_inner_var_decl(var_t *vd, bool anon, bool is_param); +static fixed_array_shape_t fixed_array_shape_from_type(const type_t *type) +{ + fixed_array_shape_t shape = {0}; + int trailing = 1; + + if (!type->array_size) + return shape; + if (type->array_dim2) + trailing *= type->array_dim2; + if (type->array_dim3) + trailing *= type->array_dim3; + if (type->array_dim4) + trailing *= type->array_dim4; + shape.bounds[shape.rank++] = type->array_size / trailing; + if (type->array_dim2) + shape.bounds[shape.rank++] = type->array_dim2; + if (type->array_dim3) + shape.bounds[shape.rank++] = type->array_dim3; + if (type->array_dim4) + shape.bounds[shape.rank++] = type->array_dim4; + return shape; +} + +static fixed_array_shape_t fixed_array_shape_from_var(const var_t *var) +{ + type_t shape = {0}; + + shape.array_size = var->array_size; + shape.array_dim2 = var->array_dim2; + shape.array_dim3 = var->array_dim3; + shape.array_dim4 = var->array_dim4; + return fixed_array_shape_from_type(&shape); +} + +static fixed_array_shape_t fixed_array_shape_from_pointee_type( + const type_t *type) +{ + type_t shape = {0}; + + shape.array_size = type->pointee_array_size; + shape.array_dim2 = type->pointee_array_dim2; + shape.array_dim3 = type->pointee_array_dim3; + shape.array_dim4 = type->pointee_array_dim4; + return fixed_array_shape_from_type(&shape); +} + +static fixed_array_shape_t fixed_array_shape_from_pointee_var(const var_t *var) +{ + type_t shape = {0}; + + shape.array_size = var->pointee_array_size; + shape.array_dim2 = var->pointee_array_dim2; + shape.array_dim3 = var->pointee_array_dim3; + shape.array_dim4 = var->pointee_array_dim4; + return fixed_array_shape_from_type(&shape); +} + +static void fixed_array_shape_to_type(type_t *type, + const fixed_array_shape_t *shape) +{ + type->array_size = shape->rank ? shape->bounds[0] : 0; + type->array_dim2 = type->array_dim3 = type->array_dim4 = 0; + for (int i = 1; i < shape->rank; i++) { + type->array_size *= shape->bounds[i]; + if (i == 1) + type->array_dim2 = shape->bounds[i]; + else if (i == 2) + type->array_dim3 = shape->bounds[i]; + else + type->array_dim4 = shape->bounds[i]; + } +} + +static void fixed_array_shape_to_var(var_t *var, + const fixed_array_shape_t *shape) +{ + var->array_size = shape->rank ? shape->bounds[0] : 0; + var->array_dim2 = var->array_dim3 = var->array_dim4 = 0; + for (int i = 1; i < shape->rank; i++) { + var->array_size *= shape->bounds[i]; + if (i == 1) + var->array_dim2 = shape->bounds[i]; + else if (i == 2) + var->array_dim3 = shape->bounds[i]; + else + var->array_dim4 = shape->bounds[i]; + } +} + +static void fixed_array_shape_to_pointee_var(var_t *var, + const fixed_array_shape_t *shape) +{ + var->pointee_array_size = shape->rank ? shape->bounds[0] : 0; + var->pointee_array_dim2 = var->pointee_array_dim3 = + var->pointee_array_dim4 = 0; + for (int i = 1; i < shape->rank; i++) { + var->pointee_array_size *= shape->bounds[i]; + if (i == 1) + var->pointee_array_dim2 = shape->bounds[i]; + else if (i == 2) + var->pointee_array_dim3 = shape->bounds[i]; + else + var->pointee_array_dim4 = shape->bounds[i]; + } +} + +static fixed_array_shape_t fixed_array_shape_prepend( + const fixed_array_shape_t *outer, + const fixed_array_shape_t *inner) +{ + fixed_array_shape_t shape = {0}; + + if (outer->rank + inner->rank > MAX_FIXED_ARRAY_RANK) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + for (int i = 0; i < outer->rank; i++) + shape.bounds[shape.rank++] = outer->bounds[i]; + for (int i = 0; i < inner->rank; i++) + shape.bounds[shape.rank++] = inner->bounds[i]; + return shape; +} + +static int fixed_array_shape_stride(const fixed_array_shape_t *shape, + int subscript_depth, + int element_size); +static bool fixed_array_shape_drop_outer(fixed_array_shape_t *shape); + +/* Elements in one outer element of @var's direct array. Only the inner bounds + * matter, which is what keeps this right while an unsized outer bound is still + * being inferred from its initializer and array_size is not yet known. + */ +static int fixed_array_inner_count(const var_t *var) +{ + int count = 1; + + if (var->array_dim2) + count *= var->array_dim2; + if (var->array_dim3) + count *= var->array_dim3; + if (var->array_dim4) + count *= var->array_dim4; + return count; +} + +/* Rewrite @var's direct array bounds to those of one element of the array: a + * matrix becomes a row, a row becomes a scalar. + */ +static void fixed_array_var_drop_outer(var_t *var) +{ + fixed_array_shape_t shape = fixed_array_shape_from_var(var); + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_var(var, &shape); +} + +static void compose_block_typedef_array(type_t *alias, + type_t *base, + var_t *decl) +{ + fixed_array_shape_t base_shape = fixed_array_shape_from_type(base); + fixed_array_shape_t decl_shape = fixed_array_shape_from_var(decl); + fixed_array_shape_t shape; + + shape = fixed_array_shape_prepend(&decl_shape, &base_shape); + fixed_array_shape_to_type(alias, &shape); + alias->array_element_ptr_level = decl->ptr_level; + alias->array_element_type = base; +} + +/* Make @alias a pointer to a row of the callbacks @element, as `fn_t + * (*rows_t)[2]` is. The pointer itself carries no prototype and only the + * qualifiers in @pointer_const_mask; const callbacks, `cfn_t`, make the row it + * points to const. + */ +static void alias_callback_row_pointer(type_t *alias, + const type_t *element, + unsigned int pointer_const_mask) +{ + alias->func_signature = NULL; + alias->pointer_const_mask = pointer_const_mask; + if (element->pointer_const_mask & 1U) + alias->is_const_qualified = true; +} + +/* `typedef arr_t *rows_t` for `typedef fn_t arr_t[2]` points to a whole row of + * callbacks, as `fn_t (*rows_t)[2]` does. If @base is such an array typedef, + * make @alias, which has one star on it, carry the row as the pointee with the + * callback typedef as its element. + */ +static void alias_callback_array_pointer(type_t *alias, + const type_t *base, + unsigned int pointer_const_mask) +{ + type_t *element = base->array_element_type; + + if (base->ptr_level || !base->array_size || !element || + !element->func_signature || element->is_direct_function_type || + base->array_element_ptr_level) + return; + alias->pointee_array_size = base->array_size; + alias->pointee_array_dim2 = base->array_dim2; + alias->pointee_array_dim3 = base->array_dim3; + alias->pointee_array_dim4 = base->array_dim4; + alias->pointee_array_element_ptr_level = 0; + alias->pointee_array_element_type = element; + alias->array_size = 0; + alias->array_dim2 = alias->array_dim3 = alias->array_dim4 = 0; + alias->array_element_type = NULL; + alias_callback_row_pointer(alias, element, pointer_const_mask); +} + +basic_block_t *handle_block_typedef_statement(block_t *parent, + basic_block_t *bb); +bool read_assignment_expression(block_t *parent, basic_block_t **bb); +bool is_null_pointer_constant(var_t *value); +bool is_record_type(const type_t *type); +void read_control_expression(block_t *parent, basic_block_t **bb); +basic_block_t *read_full_expression_statement(block_t *parent, + basic_block_t *bb); +void read_expr_operand(block_t *parent, basic_block_t **bb); +bool accept_compound_assign_op(opcode_t *op); +bool is_pointer_operation(opcode_t op, var_t *rs1, var_t *rs2); +void handle_pointer_arithmetic(block_t *parent, + basic_block_t **bb, + opcode_t op, + var_t *rs1, + var_t *rs2); +type_t *integer_binary_result_type(opcode_t op, + const var_t *left, + const var_t *right); +void normalize_integer_binary_operands(block_t *parent, + basic_block_t **bb, + opcode_t op, + var_t **left, + var_t **right); +void read_inner_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member); void read_partial_var_decl(var_t *vd, var_t *template); -void parse_array_init(var_t *var, - block_t *parent, - basic_block_t **bb, - bool emit_code); +void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb); +void parse_string_array_init(var_t *var, block_t *parent, basic_block_t **bb); +void parse_wstring_array_init(var_t *var, block_t *parent, basic_block_t **bb); +void parse_global_record_init(var_t *var, block_t *block); +void parse_global_compound_record_init(var_t *var, block_t *block); +void parse_global_compound_scalar_init(var_t *var, block_t *block); +void parse_global_compound_array_init(var_t *var, block_t *block); +bool read_global_assignment_var(var_t *var); +void initialize_struct_field(var_t *nv, var_t *v, int offset); +bool is_array_literal_placeholder(const var_t *var); +bool is_incomplete_record_object(const var_t *var); +void copy_call_result_array_shape(var_t *result, const var_t *return_def); +void lower_call_result_array_postfix(var_t **value, + block_t *parent, + basic_block_t **bb); +void lower_call_result_prefix_update(var_t **value, + opcode_t op, + block_t *parent, + basic_block_t **bb); +var_t *read_bitfield_value(block_t *parent, + basic_block_t **bb, + var_t *address, + const var_t *field); +void mark_value_reference(var_t *value, var_t *address, var_t *bitfield); +void push_object_at(block_t *parent, + basic_block_t **bb, + var_t *value, + var_t *address, + int size); +var_t *lower_reference_update(var_t *object, + opcode_t op, + block_t *parent, + basic_block_t **bb); +void lower_member_postfix(var_t *address, + type_t *record_type, + bool arrow_first, + bool is_lvalue, + block_t *parent, + basic_block_t **bb); +void lower_postfix_operators(block_t *parent, basic_block_t **bb); +void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1); +bool is_swapped_subscript_base(const var_t *var); +void reject_record_operand(const var_t *var); +void mark_var_mutated(var_t *var); + +/* Pointer declarators can be carried by a typedef's type object rather than the + * variable's direct ptr_level. Scalarization decisions need that full effective + * depth, not only the spelling at the use site. + */ +bool has_effective_pointer(const var_t *var) +{ + return var && (var->ptr_level || (var->type && var->type->ptr_level)); +} + +int callback_slot_depth(const var_t *var); + +/* Whether the callback pointer that the pointer @var designates is const, for a + * slot declared `int (*const *p)(int)` or a pointer to a const callback typedef + * such as `typedef int (*const cfn_t)(int); cfn_t *p`. + */ +bool points_to_const_callback(const var_t *var) +{ + if (!var || !var->type) + return false; + if (var->pointee_func_signature) + return var->callback_is_const && callback_slot_depth(var) == 1; + return var->ptr_level == 1 && var->type->func_signature && + !var->type->is_direct_function_type && !var->type->ptr_level && + (var->type->pointer_const_mask & 1U); +} + +/* The object pointer levels from the callback slot value @var to the callback + * it finally reaches: 1 for `int (**)(int)`, 2 for `int (***)(int)`, whatever + * pointers the callback's own return type has. 0 for anything else. + */ +int callback_slot_depth(const var_t *var) +{ + const var_t *returned; + + if (!var || !var->pointee_func_signature) + return 0; + returned = &((func_t *) var->pointee_func_signature)->return_def; + + /* A pointer to a function typedef, `thunk_t *`, is itself the callback, and + * an array descriptor keeps its element's stars apart. + */ + return var->ptr_level + + (var->type && !var->type->array_size ? var->type->ptr_level : 0) - + (var->type && var->type->is_direct_function_type ? 1 : 0) - + returned->ptr_level - + (returned->type ? returned->type->ptr_level : 0); +} + +/* Float, double, and long double are reserved C99 type spellings. Their + * semantic representation is staged, but every grammar entry point must + * recognize the complete spelling before integer-specifier parsing consumes its + * leading `long`. + */ +bool floating_type_starts_here(void) +{ + return lex_peek(T_float, NULL) || lex_peek(T_double, NULL) || + (lex_peek(T_long, NULL) && cur_token->next->next && + cur_token->next->next->kind == T_double); +} + +/* Accept a struct or union keyword. + * + * Return the kind of record it names, or TYPE_void, which is zero, when neither + * keyword comes next. + */ +base_type_t accept_record_keyword(void) +{ + if (lex_accept(T_struct)) + return TYPE_struct; + return lex_accept(T_union) ? TYPE_union : TYPE_void; +} + +bool function_signature_has_floating(const func_t *signature) +{ + if (!signature) + return false; + if (signature->return_def.type && signature->return_def.type->is_floating) + return true; + for (int i = 0; i < signature->num_params; i++) { + const var_t *param = &signature->param_defs[i]; + + if ((param->type && param->type->is_floating) || + function_signature_has_floating(param->func_signature)) + return true; + } + return false; +} + +bool global_compound_literal_starts_here(void) +{ + token_t *next; + + if (!lex_peek(T_open_bracket, NULL)) + return false; + next = cur_token->next->next; + if (!next || + !((next->kind == T_identifier && find_type(next->literal, true)) || + next->kind == T_struct || next->kind == T_union || + next->kind == T_enum || next->kind == T_const || + next->kind == T_volatile || next->kind == T_signed || + next->kind == T_unsigned || next->kind == T_long)) + return false; + + /* Only a brace after the type name makes a compound literal; otherwise the + * parenthesized type name is a cast. + */ + for (int depth = 1; next; next = next->next) { + if (next->kind == T_open_bracket) + depth++; + else if (next->kind == T_close_bracket && --depth == 0) + return next->next && next->next->kind == T_open_curly; + } + return false; +} + +/* A grouped function designator remains a C99 address constant in a global + * initializer. Callers may have consumed an optional leading `&` already, or + * ask this predicate to recognize it as part of the spelling. + */ +bool grouped_global_function_designator_starts_here(bool allow_address) +{ + token_t *token = cur_token->next; + + if (allow_address && token && token->kind == T_ampersand) + token = token->next; + return token && token->kind == T_open_bracket && token->next && + token->next->kind == T_identifier && token->next->next && + token->next->next->kind == T_close_bracket && + find_func(token->next->literal); +} + +/* `&*function` and `*&function` are both the original function designator. + * Preserve balanced grouping around either that pair or its function operand, + * while recognizing only declared functions so object expressions with the same + * unary spelling follow their ordinary initializer rules. + */ +bool global_function_designator_tokens(token_t *token, + token_t **after, + token_t **identifier) +{ + token_t *inner_after; + + if (!token) + return false; + if (token->kind == T_identifier && find_func(token->literal)) { + *after = token->next; + *identifier = token; + return true; + } + if (token->kind != T_open_bracket || + !global_function_designator_tokens(token->next, &inner_after, + identifier) || + !inner_after || inner_after->kind != T_close_bracket) + return false; + *after = inner_after->next; + return true; +} + +bool global_function_address_dereference_tokens(token_t *token, + token_t **after, + token_t **identifier) +{ + token_t *operand_after; + token_t *inner_after; + + if (!token) + return false; + if ((token->kind == T_ampersand && token->next && + token->next->kind == T_asterisk) || + (token->kind == T_asterisk && token->next && + token->next->kind == T_ampersand)) + return global_function_designator_tokens(token->next->next, after, + identifier); + if ((token->kind == T_ampersand || token->kind == T_asterisk) && + token->next && token->next->kind == T_open_bracket && + token->next->next && + token->next->next->kind == + (token->kind == T_ampersand ? T_asterisk : T_ampersand) && + global_function_designator_tokens(token->next->next->next, &inner_after, + identifier) && + inner_after && inner_after->kind == T_close_bracket) { + *after = inner_after->next; + return true; + } + if (token->kind != T_open_bracket || + !global_function_address_dereference_tokens(token->next, &inner_after, + identifier) || + !inner_after || inner_after->kind != T_close_bracket) + return false; + operand_after = inner_after->next; + *after = operand_after; + return true; +} + +bool global_function_address_dereference_starts_here(void) +{ + token_t *after; + token_t *identifier; + + return global_function_address_dereference_tokens(cur_token->next, &after, + &identifier); +} + +token_t *consume_global_function_address_dereference(void) +{ + token_t *after; + token_t *identifier; + + if (!global_function_address_dereference_tokens(cur_token->next, &after, + &identifier)) + fatal("expected grouped function address dereference"); + while (cur_token->next != after) + lex_next(); + return identifier; +} label_t *find_label(const char *name) { @@ -118,11 +631,13 @@ var_t *require_var(block_t *blk) var->var_name = ""; var->consumed = -1; var->phys_reg = -1; + var->phys_reg_hi = -1; var->first_use = -1; var->last_use = -1; var->use_count = 0; var->base = var; var->type = TY_int; + var->scope = blk; var->space_is_allocated = false; var->has_backing_storage = false; var->ofs_based_on_stack_top = false; @@ -158,6 +673,8 @@ var_t *require_typed_ptr_var(block_t *blk, type_t *type, int ptr) return var; } +type_t *pointee_type_from_pointer_typedef(type_t *type); + var_t *require_ref_var(block_t *blk, type_t *type, int ptr) { if (!type) @@ -173,5531 +690,1310 @@ var_t *require_deref_var(block_t *blk, type_t *type, int ptr) if (!type) error_at("Cannot dereference variable from NULL type", cur_token_loc()); + int effective_ptr = ptr + type->ptr_level; + /* Allowing integer dereferencing */ - if (!ptr && type->base_type != TYPE_struct && + if (!effective_ptr && type->base_type != TYPE_struct && type->base_type != TYPE_typedef) return require_var(blk); - if (!ptr) + if (!effective_ptr) error_at("Cannot dereference from non-pointer typed variable", cur_token_loc()); - var_t *var = require_typed_var(blk, type); - var->ptr_level = ptr - 1; + var_t *var = + require_typed_var(blk, pointee_type_from_pointer_typedef(type)); + var->ptr_level = effective_ptr - 1; return var; } -/* The next free field slot of @type. - * - * Struct and union bodies fill these in from two places -- one field per - * declaration, and one more per comma in a multiple declarator -- so the bound - * belongs here rather than being repeated, and missed, at each of them. +/* A typedef keeps its stars (and their qualifiers) on type_t while ordinary + * declarators keep them on var_t. Expression results are ordinary vars, so + * dereferencing a typedef pointer must move the surviving qualifier bits into + * that representation instead of silently losing them. */ -var_t *type_add_field(type_t *type, int *idx) +int effective_pointer_depth(const var_t *var) { - int i = *idx; - - if (i >= MAX_FIELDS) - error_at("Too many fields in struct or union", cur_token_loc()); - type_ensure_fields(type); - *idx = i + 1; - return &type->fields[i]; + return var && var->type ? var->ptr_level + var->type->ptr_level : 0; } -void opstack_push(var_t *var) +/* A typedef can conceal an incomplete record tag. Object declarations must + * still reject that type by value, while pointers to it remain valid. + */ +bool is_incomplete_record_object(const var_t *var) { - if (operand_stack_idx >= MAX_OPERAND_STACK_SIZE) - fatal("Expression too complex: operand stack exhausted"); - operand_stack[operand_stack_idx++] = var; + const type_t *type; + + if (!var || effective_pointer_depth(var)) + return false; + type = var->type; + if (type && type->base_type == TYPE_typedef && type->base_struct) + type = type->base_struct; + return type && + (type->base_type == TYPE_struct || type->base_type == TYPE_union) && + !type->size; } -/* The break/continue targets form a stack indexed by loop and switch nesting. - * Pushing through these keeps the depth check in one place instead of at each - * of the sites that open a new nesting level. - */ -void break_bb_push(basic_block_t *bb) +unsigned int effective_pointer_const_mask(const var_t *var) { - if (break_exit_idx >= MAX_NESTING) - fatal("Too many nested loops or switch statements"); - break_bb[break_exit_idx++] = bb; + if (!var || !var->type) + return 0; + if (var->type->ptr_level >= 32) + return var->type->pointer_const_mask; + + /* The const of a callback typedef, `typedef int (*const cfn_t)(int)`, + * belongs to the callback, which a `cfn_t *` counts as no pointer level of + * its own; points_to_const_callback() reports it instead. + */ + if (var->ptr_level && var->type->func_signature && + !var->type->is_direct_function_type && !var->type->ptr_level) + return var->pointer_const_mask; + return var->type->pointer_const_mask | + (var->pointer_const_mask << var->type->ptr_level); } -void continue_bb_push(basic_block_t *bb) +unsigned int dereferenced_pointer_const_mask(const var_t *var) { - if (continue_pos_idx >= MAX_NESTING) - fatal("Too many nested loops"); - continue_bb[continue_pos_idx++] = bb; + int depth = effective_pointer_depth(var); + unsigned int mask = effective_pointer_const_mask(var); + + /* The outermost pointer object was consumed by the dereference. */ + if (depth > 0 && depth <= 32) + mask &= ~(1U << (depth - 1)); + return mask; } -var_t *opstack_pop(void) +/* A pointer typedef stores its pointer depth in type_t rather than var_t. After + * indexing through it, use the underlying scalar type for the loaded value and + * carry any remaining pointer depth in var_t. Otherwise a `typedef unsigned + * char *P; P p; p[0]` read looks like a pointer-sized alias instead of an + * unsigned byte to the backend. + */ +type_t *pointee_type_from_pointer_typedef(type_t *type) { - return operand_stack[--operand_stack_idx]; + if (!type || !type->ptr_level) + return type; + + if (type->base_type == TYPE_typedef && type->base_struct) + return type->base_struct; + + /* A block callback slot alias, `typedef int (***slot_t)(int)`, reaches its + * scalar through the callback's return type. + */ + if (type->base_type == TYPE_typedef && type->pointee_func_signature) + return ((func_t *) type->pointee_func_signature)->return_def.type; + + switch (type->base_type) { + case TYPE_void: + return TY_void; + case TYPE_char: + return type->is_unsigned ? TY_uchar + : (type->is_signed_char ? TY_schar : TY_char); + case TYPE_short: + return type->is_unsigned ? TY_ushort : TY_short; + case TYPE_int: + return type->is_unsigned ? TY_uint : TY_int; + case TYPE_long: + return type->is_unsigned ? TY_ulong : TY_long; + case TYPE_long_long: + return type->is_unsigned ? TY_ulong_long : TY_long_long; + case TYPE_float: + return TY_float; + case TYPE_double: + return TY_double; + case TYPE_long_double: + return TY_long_double; + default: + return type; + } } -void read_expr(block_t *parent, basic_block_t **bb); +/* Scalar typedefs have their own descriptor, but C declaration compatibility is + * based on the represented type. Records retain nominal identity. + */ +bool compatible_function_signature(const func_t *left, const func_t *right); +func_t *find_visible_func(char *name, block_t *scope); +var_t *materialize_function_designator(block_t *parent, + basic_block_t **bb, + var_t *value); +var_t *emit_direct_call_result(func_t *func, + bool want_value, + block_t *parent, + basic_block_t **bb); +var_t *emit_indirect_call_result(var_t *callee, + func_t *signature, + bool want_value, + block_t *parent, + basic_block_t **bb); -int write_symbol(const char *data) +static int callback_pointer_indirection(const var_t *var) { - /* Write string literals to .rodata section */ - const int start_len = elf_rodata->size; - elf_write_str(elf_rodata, data); - elf_write_byte(elf_rodata, 0); - return start_len; + if (!var || !var->is_func) + return 0; + if (var->parenthesized_function_pointer_level) + return var->parenthesized_function_pointer_level; + + /* A non-direct function typedef denotes the established one-pointer + * callback object representation. + */ + if (var->type && var->type->func_signature && + !var->type->is_direct_function_type) + return 1; + return 0; } -int get_size(var_t *var) +/* The record a typedef names through base_struct, or @type itself. Unlike + * is_plain_record_alias() below this does not look at the typedef's own + * declarator, so callers apply it once they know they have a record object. + */ +type_t *resolve_record_type(type_t *type) { - if (var->ptr_level || var->is_func) - return PTR_SIZE; - return var->type->size; + if (type->base_type == TYPE_typedef && type->base_struct) + return type->base_struct; + return type; } -int get_operator_prio(opcode_t op) +/* A typedef naming a struct or union type with no derived declarator of its + * own, as in "typedef volatile struct S vs_t;". + */ +static bool is_plain_record_alias(const type_t *type) { - /* https://www.cs.uic.edu/~i109/Notes/COperatorPrecedenceTable.pdf */ - switch (op) { - case OP_ternary: - return 3; - case OP_log_or: - return 4; - case OP_log_and: - return 5; - case OP_bit_or: - return 6; - case OP_bit_xor: - return 7; - case OP_bit_and: - return 8; - case OP_eq: - case OP_neq: - return 9; - case OP_lt: - case OP_leq: - case OP_gt: - case OP_geq: - return 10; - case OP_lshift: - case OP_rshift: - return 11; - case OP_add: - case OP_sub: - return 12; - case OP_mul: - case OP_div: - case OP_mod: - return 13; - default: - return 0; - } + return type && type->base_type == TYPE_typedef && type->base_struct && + !type->ptr_level && !type->array_size && !type->pointee_array_size && + !type->func_signature && !type->pointee_func_signature; } -opcode_t get_operator(void) -{ - opcode_t op = OP_generic; - if (lex_accept(T_plus)) - op = OP_add; - else if (lex_accept(T_minus)) - op = OP_sub; - else if (lex_accept(T_asterisk)) - op = OP_mul; - else if (lex_accept(T_divide)) - op = OP_div; - else if (lex_accept(T_mod)) - op = OP_mod; - else if (lex_accept(T_lshift)) - op = OP_lshift; - else if (lex_accept(T_rshift)) - op = OP_rshift; - else if (lex_accept(T_log_and)) - op = OP_log_and; - else if (lex_accept(T_log_or)) - op = OP_log_or; - else if (lex_accept(T_eq)) - op = OP_eq; - else if (lex_accept(T_noteq)) - op = OP_neq; - else if (lex_accept(T_lt)) - op = OP_lt; - else if (lex_accept(T_le)) - op = OP_leq; - else if (lex_accept(T_gt)) - op = OP_gt; - else if (lex_accept(T_ge)) - op = OP_geq; - else if (lex_accept(T_ampersand)) - op = OP_bit_and; - else if (lex_accept(T_bit_or)) - op = OP_bit_or; - else if (lex_accept(T_bit_xor)) - op = OP_bit_xor; - else if (lex_peek(T_question, NULL)) - op = OP_ternary; - return op; -} - -var_t *promote_unchecked(block_t *block, - basic_block_t **bb, - var_t *var, - type_t *target_type, - int target_ptr) +bool compatible_decl_type(const type_t *left, const type_t *right) { - var_t *rd = require_typed_ptr_var(block, target_type, target_ptr); - rd->var_name = gen_name(); - - /* Encode both source and target sizes in src1: Lower 16 bits: target size - * Upper 16 bits: source size This allows codegen to distinguish between - * different promotion types without changing IR semantics. + /* A typedef of a struct or union tag denotes the tag's type. Its own + * qualifiers were copied into each declarator, which the callers compare. */ - int encoded_size = ((var->type->size) << 16); - if (target_ptr) - encoded_size |= PTR_SIZE; - else - encoded_size |= target_type->size; - add_insn(block, *bb, OP_sign_ext, rd, var, NULL, encoded_size, NULL); - return rd; -} - -var_t *promote(block_t *block, - basic_block_t **bb, - var_t *var, - type_t *target_type, - int target_ptr) -{ - /* Effectively checking whether var has size of int */ - if (var->type->size == target_type->size || var->ptr_level || - var->array_size) - return var; - - if (var->type->size > TY_int->size && !var->ptr_level) { - printf("Warning: Suspicious type promotion %s\n", var->type->type_name); - return var; - } - - return promote_unchecked(block, bb, var, target_type, target_ptr); -} - -var_t *truncate_unchecked(block_t *block, - basic_block_t **bb, - var_t *var, - type_t *target_type, - int target_ptr) -{ - var_t *rd = require_typed_ptr_var(block, target_type, target_ptr); - rd->var_name = gen_name(); - add_insn(block, *bb, OP_trunc, rd, var, NULL, - target_ptr ? PTR_SIZE : target_type->size, NULL); - return rd; -} - -var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) -{ - bool is_from_ptr = from->ptr_level || from->array_size, - is_to_ptr = to->ptr_level || to->array_size || - (to->type && to->type->ptr_level > 0); - - if (is_from_ptr && is_to_ptr) - return from; - - int from_size = get_size(from), to_size = get_size(to); - - if (from_size > to_size) { - /* Truncation */ - return truncate_unchecked(block, bb, from, to->type, to->ptr_level); - } - - if (from_size < to_size) { - /* Widening into a pointer needs no conversion instruction. Values - * already occupy a full register and integer loads sign-extend, so the - * pointer's bits are the value's bits. Emitting the conversion here - * also placed it ahead of the instructions computing its own operand, - * which produced a garbage pointer. - * - * On the 32-bit targets PTR_SIZE equals an int, so this case cannot - * arise there and behaviour is unchanged. - */ - if (is_to_ptr) - return from; - - /* Sign extend */ - return promote_unchecked(block, bb, from, to->type, to->ptr_level); - } - - return from; -} - -/* Convert @val to @type at @ptr_level levels of indirection. - * - * resize_var() takes its target as a var_t, and every caller builds that the - * same way: a zeroed local carrying only those two fields. Building it here - * keeps the zeroing in one place -- shecc miscompiles "var_t t = {0};", so it - * has to be a memset, and nine copies of that were nine chances to leave one - * out. - */ -var_t *resize_to(block_t *block, - basic_block_t **bb, - var_t *val, - type_t *type, - int ptr_level) -{ - var_t target; + while (is_plain_record_alias(left)) + left = left->base_struct; + while (is_plain_record_alias(right)) + right = right->base_struct; + if (left == right) + return true; - memset(&target, 0, sizeof(var_t)); - target.type = type; - target.ptr_level = ptr_level; - return resize_var(block, bb, val, &target); + /* A callback-pointer typedef may use a distinct descriptor at block and + * file scope while still denoting the same pointer-to-function type. + */ + if (left && right && left->func_signature && right->func_signature && + !left->is_direct_function_type && !right->is_direct_function_type) + return left->size == right->size && + left->ptr_level == right->ptr_level && + left->pointer_const_mask == right->pointer_const_mask && + left->is_const_qualified == right->is_const_qualified && + left->is_volatile_qualified == right->is_volatile_qualified && + compatible_function_signature(left->func_signature, + right->func_signature); + + /* So may a callback slot typedef, `int (**slot_t)(int)`. */ + if (left && right && left->pointee_func_signature && + right->pointee_func_signature) + return left->ptr_level == right->ptr_level && + left->pointer_const_mask == right->pointer_const_mask && + left->is_const_qualified == right->is_const_qualified && + left->is_volatile_qualified == right->is_volatile_qualified && + compatible_function_signature(left->pointee_func_signature, + right->pointee_func_signature); + if (!left || !right || left->base_type != right->base_type || + left->size != right->size || left->ptr_level != right->ptr_level || + left->is_unsigned != right->is_unsigned || + left->is_signed_char != right->is_signed_char || + left->is_bool != right->is_bool) + return false; + if (!!left->func_signature != !!right->func_signature) + return false; + if (left->func_signature && + !compatible_function_signature(left->func_signature, + right->func_signature)) + return false; + if (left->base_type == TYPE_struct || left->base_type == TYPE_union) + return false; + if (left->base_type == TYPE_typedef && + (left->num_fields || right->num_fields)) + return left->base_struct == right->base_struct; + return true; } -void read_parameter_list_decl(func_t *func, bool anon); - -/* Forward declaration for ternary handling used by initializers */ -void read_ternary_operation(block_t *parent, basic_block_t **bb); - -/* Parse array initializer to determine size for implicit arrays and optionally - * emit initialization code. +/* Function-pointer declarators carry their pointee function type separately + * from the pointer-sized var_t representation. Redeclarations must compare that + * syntax-only signature as well: comparing just the outer storage shape accepts + * incompatible callbacks such as `int (*)(int)` and `int (*)(long)`. When one + * side has no prototype, retain the existing old-style policy. */ -var_t *compute_element_address(block_t *parent, - basic_block_t **bb, - var_t *base_addr, - int index, - int elem_size) +bool compatible_function_param_decl(const var_t *left, const var_t *right) { - if (index == 0) - return base_addr; - - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = index * elem_size; - add_insn(parent, *bb, OP_load_constant, offset, NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, *bb, OP_add, addr, base_addr, offset, 0, NULL); - return addr; -} + bool left_points_to_value; + bool right_points_to_value; -var_t *compute_field_address(block_t *parent, - basic_block_t **bb, - var_t *struct_addr, - const var_t *field) -{ - if (field->offset == 0) - return struct_addr; - - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, *bb, OP_load_constant, offset, NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, *bb, OP_add, addr, struct_addr, offset, 0, NULL); - return addr; -} - -var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) -{ - var_t *val = NULL; - - if (lex_peek(T_numeric, NULL) || lex_peek(T_minus, NULL)) { - bool is_neg = false; - if (lex_accept(T_minus)) - is_neg = true; - char numtok[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, numtok, MAX_TOKEN_LEN); - int num_val = parse_numeric_constant(numtok); - if (is_neg) - num_val = -num_val; - - val = require_var(parent); - val->var_name = gen_name(); - val->init_val = num_val; - add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); - } else if (lex_peek(T_char, NULL)) { - char chtok[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; - lex_ident(T_char, chtok); - unescape_string(chtok, unescaped, MAX_TOKEN_LEN); - - val = require_typed_var(parent, TY_char); - val->var_name = gen_name(); - val->init_val = unescaped[0]; - add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); - } else if (lex_peek(T_string, NULL)) { - lex_accept(T_string); - - /* TODO: String fields in structs not yet supported - requires proper - * handling of string literals as initializers - */ - } else { - error_at("Global array initialization requires constant values", - next_token_loc()); - } - - return val; -} - -void consume_global_constant_syntax(void) -{ - if (lex_peek(T_numeric, NULL)) { - lex_accept(T_numeric); - } else if (lex_peek(T_minus, NULL)) { - lex_accept(T_minus); - lex_accept(T_numeric); - } else if (lex_peek(T_string, NULL)) { - lex_accept(T_string); - } else if (lex_peek(T_char, NULL)) { - lex_accept(T_char); - } else { - error_at("Global array initialization requires constant values", - next_token_loc()); - } -} - -void parse_struct_field_init(block_t *parent, - basic_block_t **bb, - type_t *struct_type, - var_t *target_addr, - bool emit_code) -{ - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - var_t *field_val_raw = NULL; - - if (parent == GLOBAL_BLOCK) { - if (emit_code) { - field_val_raw = parse_global_constant_value(parent, bb); - } else { - consume_global_constant_syntax(); - } - } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - field_val_raw = opstack_pop(); - } - - if (field_val_raw && field_idx < struct_type->num_fields) { - var_t *field = &struct_type->fields[field_idx]; - - var_t *field_val = resize_to(parent, bb, field_val_raw, - field->type, field->ptr_level); - - var_t *field_addr = - compute_field_address(parent, bb, target_addr, field); - - int field_size = size_var(field); - add_insn(parent, *bb, OP_write, NULL, field_addr, field_val, - field_size, NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } -} - -void parse_array_literal_expr(block_t *parent, basic_block_t **bb) -{ - var_t *array_var = require_var(parent); - array_var->var_name = gen_name(); - array_var->is_compound_literal = true; - - int element_count = 0; - var_t *first_element = NULL; - - if (!lex_peek(T_close_curly, NULL)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - first_element = opstack_pop(); - element_count = 1; - - while (lex_accept(T_comma)) { - if (lex_peek(T_close_curly, NULL)) - break; - - read_expr(parent, bb); - read_ternary_operation(parent, bb); - opstack_pop(); - element_count++; - } - } - - lex_expect(T_close_curly); - - array_var->array_size = element_count; - if (first_element) { - array_var->type = first_element->type; - array_var->init_val = first_element->init_val; - } else { - array_var->type = TY_int; - array_var->init_val = 0; - } - - opstack_push(array_var); - add_insn(parent, *bb, OP_load_constant, array_var, NULL, NULL, 0, NULL); -} - -basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) -{ - if (lex_accept(T_semicolon)) { - add_insn(parent, bb, OP_return, NULL, NULL, NULL, 0, NULL); - bb_connect(bb, parent->func->exit, NEXT); - return NULL; - } - - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - lex_expect(T_semicolon); - - var_t *rs1 = opstack_pop(); - - /* Handle array compound literals in return context. Convert array compound - * literals to their first element value. - */ - if (rs1 && rs1->array_size > 0 && rs1->var_name[0] == '.') { - var_t *val = require_var(parent); - val->type = rs1->type; - val->init_val = rs1->init_val; - val->var_name = gen_name(); - add_insn(parent, bb, OP_load_constant, val, NULL, NULL, 0, NULL); - rs1 = val; - } - - /* "return i++" yields the value i held before the increment, yet the - * increment still has to happen before the function leaves. Applying the - * pending side effects before the value was read returned the modified - * variable instead, so take a copy first and return that. - */ - if (se_idx > 0 && rs1) { - var_t *snapshot = require_var(parent); - snapshot->type = rs1->type; - snapshot->ptr_level = rs1->ptr_level; - snapshot->var_name = gen_name(); - add_insn(parent, bb, OP_assign, snapshot, rs1, NULL, 0, NULL); - rs1 = snapshot; - } - perform_side_effect(parent, bb); - - add_insn(parent, bb, OP_return, NULL, rs1, NULL, 0, NULL); - bb_connect(bb, parent->func->exit, NEXT); - return NULL; -} + if (!left || !right || left->is_func != right->is_func) + return false; -basic_block_t *handle_if_statement(block_t *parent, basic_block_t *bb) -{ - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; - - lex_expect(T_open_bracket); - read_expr(parent, &bb); - lex_expect(T_close_bracket); - - var_t *vd = opstack_pop(); - add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); - - basic_block_t *then_ = bb_create(parent); - basic_block_t *else_ = bb_create(parent); - bb_connect(bb, then_, THEN); - bb_connect(bb, else_, ELSE); - - basic_block_t *then_body = read_body_statement(parent, then_); - basic_block_t *then_next_ = NULL; - if (then_body) { - then_next_ = bb_create(parent); - bb_connect(then_body, then_next_, NEXT); + if (left->is_func) { + if (!left->func_signature || !right->func_signature) + return false; + return callback_pointer_indirection(left) == + callback_pointer_indirection(right) && + compatible_function_signature(left->func_signature, + right->func_signature); } - if (lex_accept(T_else)) { - basic_block_t *else_body = read_body_statement(parent, else_); - basic_block_t *else_next_ = NULL; - if (else_body) { - else_next_ = bb_create(parent); - bb_connect(else_body, else_next_, NEXT); - } - - if (then_next_ && else_next_) { - basic_block_t *next_ = bb_create(parent); - bb_connect(then_next_, next_, NEXT); - bb_connect(else_next_, next_, NEXT); - return next_; - } - - return then_next_ ? then_next_ : else_next_; - } else { - if (then_next_) { - bb_connect(else_, then_next_, NEXT); - return then_next_; - } - return else_; + if (left->pointee_func_signature || right->pointee_func_signature) { + if (!left->pointee_func_signature || !right->pointee_func_signature || + !compatible_function_signature(left->pointee_func_signature, + right->pointee_func_signature)) + return false; } -} - -basic_block_t *handle_while_statement(block_t *parent, basic_block_t *bb) -{ - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; - - continue_bb_push(bb); - - basic_block_t *cond = bb; - lex_expect(T_open_bracket); - read_expr(parent, &bb); - lex_expect(T_close_bracket); - - var_t *vd = opstack_pop(); - add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); - - basic_block_t *then_ = bb_create(parent); - basic_block_t *else_ = bb_create(parent); - bb_connect(bb, then_, THEN); - bb_connect(bb, else_, ELSE); - break_bb_push(else_); - - basic_block_t *body_ = read_body_statement(parent, then_); - continue_pos_idx--; - break_exit_idx--; - - if (body_) - bb_connect(body_, cond, NEXT); + if (!compatible_decl_type(left->type, right->type) || + left->ptr_level != right->ptr_level) + return false; - return else_; -} + left_points_to_value = left->ptr_level || left->type->ptr_level; + right_points_to_value = right->ptr_level || right->type->ptr_level; -basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) -{ - /* Since a goto splits the current program into two basic blocks and makes - * the subsequent basic block unreachable, this causes problems for later - * CFG operations. Therefore, we create a fake if that always executes to - * wrap the goto, and connect the unreachable basic block to the else - * branch. Finally, return this else block. - * - * after: a = b + c; goto label; c *= d; - * - * before: a = b + c; if (1) - * goto label; - * c *= d; + /* C99 ignores top-level parameter qualifiers, but qualifiers on the reached + * object remain part of a pointer parameter's type. */ - - char token[MAX_ID_LEN]; - if (!lex_peek(T_identifier, token)) - error_at("Expected identifier after 'goto'", next_token_loc()); - - lex_expect(T_identifier); - lex_expect(T_semicolon); - - basic_block_t *fake_if = bb_create(parent); - bb_connect(bb, fake_if, NEXT); - var_t *val = require_var(parent); - val->var_name = gen_name(); - val->init_val = 1; - add_insn(parent, fake_if, OP_load_constant, val, NULL, NULL, 0, NULL); - add_insn(parent, fake_if, OP_branch, NULL, val, NULL, 0, NULL); - - basic_block_t *then_ = bb_create(parent); - basic_block_t *else_ = bb_create(parent); - bb_connect(fake_if, then_, THEN); - bb_connect(fake_if, else_, ELSE); - - add_insn(parent, then_, OP_jump, NULL, NULL, NULL, 0, token); - label_t *label = find_label(token); - if (label) { - label->used = true; - bb_connect(then_, label->bb, NEXT); - return else_; - } - - if (backpatch_bb_idx > MAX_LABELS - 1) - error_at("Too many forward-referenced labels", cur_token_loc()); - - backpatch_bb[backpatch_bb_idx++] = then_; - return else_; + return !((left_points_to_value || right_points_to_value) && + (left->is_const_qualified != right->is_const_qualified || + left->is_volatile != right->is_volatile)); } -void parse_array_init(var_t *var, - block_t *parent, - basic_block_t **bb, - bool emit_code) +/* C99 6.7.5.3 requires a prototype following an earlier `f()` declaration to + * use only parameter types unchanged by the default argument promotions. Array + * and function parameters have already adjusted to pointers here, so every + * pointer-shaped declaration is stable. + */ +bool parameter_changes_under_default_promotion(const var_t *param) { - int count = 0; - var_t *base_addr = NULL; - var_t *stored_vals[MAX_IMPLICIT_ARRAY]; - bool is_implicit = (var->array_size == 0); - - /* Elements of a pointer array are pointer-sized. Using the base type's - * width strided "char *a[2] = {...}" by one byte, so every element but the - * first got a bogus address. An implicit-size array reaches this with - * ptr_level set as a marker rather than as a real pointer type, so only an - * explicitly sized array is treated this way. - */ - int elem_size = var->type->size; - if (!is_implicit && var->ptr_level > 0) - elem_size = PTR_SIZE; - - if (emit_code) - base_addr = var; - - lex_expect(T_open_curly); - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - var_t *val = NULL; - - if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_typedef)) { - type_t *struct_type = var->type; - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - if (emit_code) { - var_t *elem_addr = compute_element_address( - parent, bb, base_addr, count, elem_size); - lex_expect(T_open_curly); - parse_struct_field_init(parent, bb, struct_type, elem_addr, - emit_code); - lex_expect(T_close_curly); - val = NULL; - } else { - lex_expect(T_open_curly); - while (!lex_peek(T_close_curly, NULL)) { - if (parent == GLOBAL_BLOCK) { - consume_global_constant_syntax(); - } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - opstack_pop(); - } - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - lex_expect(T_close_curly); - val = NULL; - } - } else { - /* A global initializer is restricted to simple constants, but - * it still has to be stored. Consuming the tokens and dropping - * the value left every global array zero-filled, while the same - * initializer on a local worked. - */ - if (parent == GLOBAL_BLOCK && !lex_peek(T_numeric, NULL) && - !lex_peek(T_minus, NULL) && !lex_peek(T_string, NULL) && - !lex_peek(T_char, NULL)) - error_at( - "Global array initialization requires constant values", - next_token_loc()); - - read_expr(parent, bb); - read_ternary_operation(parent, bb); - val = opstack_pop(); - } - - if (is_implicit && emit_code) { - /* Truncating would declare array_size == count while storing - * fewer elements, so refuse. - */ - if (count >= MAX_IMPLICIT_ARRAY) - error_at("Too many elements in array initializer", - next_token_loc()); - stored_vals[count] = val; - } - - if (val && emit_code && !is_implicit && count < var->array_size) { - var_t *v = resize_to(parent, bb, val, var->type, 0); - - var_t *elem_addr = compute_element_address( - parent, bb, base_addr, count, elem_size); - - if (elem_size <= PTR_SIZE) { - add_insn(parent, *bb, OP_write, NULL, elem_addr, v, - elem_size, NULL); - } else { - fatal("Unsupported: array element wider than a pointer"); - } - } - - count++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - - if (parent != GLOBAL_BLOCK && emit_code && !is_implicit) { - /* e.g.: - * - * 1. - * int main() - * { - * int a[5] = {}; - * return a[0] + a[1] + a[2] + a[3] + a[4]; - * } - * - * 2. - * int main() - * { - * int a[5] = {5, 10} - * return a[0] + a[1] + a[2] + a[3] + a[4]; - * } - * - * The initializer should set the value of the first elements, and - * initialize other elements without explicit assignments to 0. - * - * Therefore, the first and second cases return 0 and 15, respectively. - */ - for (; count < var->array_size; count++) { - var_t *val = require_var(parent); - val->var_name = gen_name(); - val->init_val = 0; - add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); - - var_t *v = resize_to(parent, bb, val, var->type, 0); - - var_t *elem_addr = compute_element_address(parent, bb, base_addr, - count, elem_size); - - if (elem_size <= PTR_SIZE) { - add_insn(parent, *bb, OP_write, NULL, elem_addr, v, elem_size, - NULL); - } else { - fatal("Unsupported: array element wider than a pointer"); - } - } - } - lex_expect(T_close_curly); - - if (is_implicit) { - if (var->ptr_level > 0) - var->ptr_level = 0; - var->array_size = count; - - if (emit_code && count > 0) { - base_addr = var; - - for (int i = 0; i < count; i++) { - if (!stored_vals[i]) - continue; - var_t *v = resize_to(parent, bb, stored_vals[i], var->type, 0); + type_t *type; - var_t *elem_addr = compute_element_address( - parent, bb, base_addr, i, elem_size); + if (!param || !(type = param->type)) + return true; + if (param->is_func || param->ptr_level || type->ptr_level || + param->array_size || param->has_unsized_array) + return false; + if (type->is_bool) + return true; - add_insn(parent, *bb, OP_write, NULL, elem_addr, v, elem_size, - NULL); - } - } + switch (type->base_type) { + case TYPE_char: + case TYPE_short: + case TYPE_float: + return true; + default: + return false; } } -void parse_array_compound_literal(var_t *var, - block_t *parent, - basic_block_t **bb) +bool compatible_function_signature(const func_t *left, const func_t *right) { - int elem_size = var->type->size; - int count = 0; - var->array_size = 0; - var->init_val = 0; - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - var_t *value = opstack_pop(); - if (count == 0) - var->init_val = value->init_val; - - var_t *store_val = resize_to(parent, bb, value, var->type, 0); - var_t *elem_addr = - compute_element_address(parent, bb, var, count, elem_size); - add_insn(parent, *bb, OP_write, NULL, elem_addr, store_val, - elem_size, NULL); - - count++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - - lex_expect(T_close_curly); - var->array_size = count; -} - -/* Identify compiler-emitted temporaries that hold array compound literals. They - * keep array metadata without pointer indirection and are marked via - * is_compound_literal when synthesized. - */ -bool is_array_literal_placeholder(const var_t *var) -{ - return var && var->array_size > 0 && !var->ptr_level && - var->is_compound_literal; -} - -bool is_pointer_like_value(var_t *var) -{ - return var && (var->ptr_level || var->array_size || - (var->type && var->type->ptr_level > 0)); -} - -/* Lower a compiler-emitted array literal placeholder (marked via - * is_compound_literal) into a scalar temporary when later IR expects a plain - * value instead of addressable storage. This keeps SSA joins uniform when only - * one branch originates from an array literal. - */ -var_t *scalarize_array_literal(block_t *parent, - basic_block_t **bb, - var_t *array_var, - type_t *hint_type) -{ - if (!is_array_literal_placeholder(array_var)) - return array_var; - - /* Array literal placeholders carry the literal's natural type; default to - * int when the parser left the type unset. - */ - type_t *literal_type = array_var->type ? array_var->type : TY_int; - int literal_size = literal_type->size; - if (literal_size <= 0) - literal_size = TY_int->size; - - /* A caller-provided hint (e.g., assignment target) dictates the result type - * when available so we reuse wider/narrower scalar destinations. - */ - type_t *result_type = hint_type ? hint_type : literal_type; - if (!result_type) - result_type = TY_int; - - /* Create a new scalar temporary, giving it a unique name and copying over - * the literal data so downstream code can treat it like a normal value. - */ - var_t *scalar = require_typed_var(parent, result_type); - scalar->ptr_level = 0; - scalar->var_name = gen_name(); - scalar->init_val = array_var->init_val; - - /* Materialize the literal data into the scalar temporary via an OP_read. */ - add_insn(parent, *bb, OP_read, scalar, array_var, NULL, literal_size, NULL); + if (!left || !right || + !compatible_decl_type(left->return_def.type, right->return_def.type) || + left->return_def.ptr_level != right->return_def.ptr_level || + left->return_def.is_const_qualified != + right->return_def.is_const_qualified) + return false; - return scalar; + if (!left->has_prototype || !right->has_prototype) + return true; + if (left->num_params != right->num_params || + left->va_args != right->va_args) + return false; + for (int i = 0; i < left->num_params; i++) + if (!compatible_function_param_decl(&left->param_defs[i], + &right->param_defs[i])) + return false; + return true; } -/* Centralized guard for lowering array literal placeholders when a scalar value - * is expected, keeping the scattered special cases consistent. +/* Function linkage and ordinary-identifier scope are separate in C. A prototype + * first introduced in a block stays in the translation-unit table for + * compatibility with a later definition, but expressions may name it only while + * its lexical function alias is visible. */ -var_t *scalarize_array_literal_if_needed(block_t *parent, - basic_block_t **bb, - var_t *value, - type_t *hint_type, - bool needs_scalar) +func_t *find_visible_func(char *name, block_t *scope) { - if (!needs_scalar) - return value; + func_t *func = find_func(name); + var_t *binding; - return scalarize_array_literal(parent, bb, value, hint_type); + if (!func || !func->is_block_scope_only_declaration) + return func; + binding = find_var(name, scope); + return binding && binding->is_extern_function_alias ? func : NULL; } -/* Integer constant-expression parser. - * - * Array dimensions, and other places C requires an integer constant expression, - * accept far more than a bare literal. These evaluate such an expression at - * parse time without emitting any IR, folding through the same precedence table - * (get_operator_prio()) and the same operator semantics (eval_expression_imm()) - * the rest of the parser already uses, so there is only one statement of what - * C's operators mean. +/* An inline record pointer typedef stores PTR_SIZE in type->size, while its + * fields still describe the pointee layout. Recover that layout for indexing; + * this differs on 32-bit targets whenever the record is wider than a pointer. + * The record the alias reaches through base_struct has the padded size, which + * the end of the last member falls short of. */ -#define MAX_CONST_EXPR_OPS 16 - -int eval_expression_imm(opcode_t op, int op1, int op2); -int read_const_expr(void); - -int read_const_expr_operand(void) +int pointer_typedef_pointee_size(type_t *type, type_t *pointee) { - char buffer[MAX_TOKEN_LEN]; + if (!type || !type->ptr_level || type->base_type != TYPE_typedef || + !type->num_fields) + return pointee == TY_void ? 1 : pointee->size; + if (type->ptr_level == 1 && type->base_struct) + return type->base_struct->size; - if (lex_accept(T_minus)) - return -read_const_expr_operand(); - if (lex_accept(T_plus)) - return read_const_expr_operand(); - if (lex_accept(T_bit_not)) - return ~read_const_expr_operand(); - if (lex_accept(T_log_not)) - return !read_const_expr_operand(); - - if (lex_accept(T_open_bracket)) { - int res = read_const_expr(); - lex_expect(T_close_bracket); - return res; - } - if (lex_peek(T_numeric, buffer)) { - lex_expect(T_numeric); - return parse_numeric_constant(buffer); - } - if (lex_peek(T_char, buffer)) { - char unescaped[MAX_TOKEN_LEN]; - lex_expect(T_char); - if (unescape_string(buffer, unescaped, MAX_TOKEN_LEN) < 0) - error_at("Invalid escape sequence", cur_token_loc()); - return unescaped[0]; - } - if (lex_peek(T_identifier, buffer)) { - lex_expect(T_identifier); - constant_t *con = find_constant(buffer); - if (con) - return con->value; - error_at("Identifier is not an integer constant", next_token_loc()); - } - error_at("Expected an integer constant expression", next_token_loc()); - return 0; -} - -int read_const_expr(void) -{ - opcode_t op_stack[MAX_CONST_EXPR_OPS]; - int val_stack[MAX_CONST_EXPR_OPS]; - int op_n = 0, val_n = 0; - - val_stack[val_n++] = read_const_expr_operand(); - - while (true) { - opcode_t op = get_operator(); - - if (op == OP_generic) - break; - - /* The conditional binds loosest, so everything folded so far is its - * condition, and its arms are whole constant expressions of their own. - */ - if (op == OP_ternary) { - while (op_n > 0) { - val_n--; - op_n--; - val_stack[val_n - 1] = eval_expression_imm( - op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); - } - lex_expect(T_question); - int then_val = read_const_expr(); - lex_expect(T_colon); - int else_val = read_const_expr(); - return val_stack[0] ? then_val : else_val; - } - - /* Everything at least as tight as the operator just read is complete, - * so fold it before pushing. - */ - int prio = get_operator_prio(op); - while (op_n > 0 && get_operator_prio(op_stack[op_n - 1]) >= prio) { - val_n--; - op_n--; - val_stack[val_n - 1] = eval_expression_imm( - op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); - } - if (op_n >= MAX_CONST_EXPR_OPS - 1) - error_at("Constant expression nests too deeply", next_token_loc()); - op_stack[op_n++] = op; - val_stack[val_n++] = read_const_expr_operand(); + int size = 0; + for (int i = 0; i < type->num_fields; i++) { + int field_size = size_var(&type->fields[i]); + int end = + type->is_union ? field_size : type->fields[i].offset + field_size; + if (end > size) + size = end; } - - while (op_n > 0) { - val_n--; - op_n--; - val_stack[val_n - 1] = eval_expression_imm( - op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); - } - return val_stack[0]; + return size; } -void read_inner_var_decl(var_t *vd, bool anon, bool is_param) -{ - /* Preserve typedef pointer level - don't reset if already inherited */ - vd->init_val = 0; - if (is_param) { - /* However, if the parsed variable is a function parameter, reset its - * pointer level to zero. - */ - vd->ptr_level = 0; - } - - while (lex_accept(T_asterisk)) { - vd->ptr_level++; - - /* Check for const after asterisk (e.g., int * const ptr). For now, we - * just consume const qualifiers after pointer. Full support would - * require tracking const-ness of the pointer itself vs the pointed-to - * data separately. - */ - while (lex_peek(T_const, NULL)) - lex_accept(T_const); - } - - /* is it function pointer declaration? */ - if (lex_accept(T_open_bracket)) { - func_t func; - char temp_name[MAX_VAR_LEN]; - lex_expect(T_asterisk); - lex_ident(T_identifier, temp_name); - vd->var_name = intern_string(temp_name); - lex_expect(T_close_bracket); - read_parameter_list_decl(&func, true); - vd->is_func = true; - } else { - if (!anon) { - char temp_name[MAX_VAR_LEN]; - lex_ident(T_identifier, temp_name); - vd->var_name = intern_string(temp_name); - if (!lex_peek(T_open_bracket, NULL) && !is_param) { - if (vd->is_global) { - opstack_push(vd); - } - } - } - if (!lex_peek(T_open_square, NULL)) { - vd->array_size = 0; - vd->array_dim2 = 0; - } - - /* Every dimension multiplies into array_size, so "int matrix[3][4]" - * becomes an array of 12 elements. The second dimension is kept - * separately as well, because indexing needs the row length; further - * dimensions only contribute to the total. A dimension left empty - * contributes no size. - */ - bool first_dim_empty = false; - int dims = 0; - - for (int dim = 0; lex_accept(T_open_square); dim++) { - if (lex_peek(T_close_square, NULL)) { - /* An omitted leading size is only a pointer when nothing - * follows it: "int a[]" is "int *", but "int a[][4]" points at - * rows of four and is indexed exactly like "int a[3][4]". - * Raising the pointer level for the latter would scale the row - * index by a pointer instead of by the row, and add a - * dereference that is not there. - */ - if (dim == 0) - first_dim_empty = true; - else - vd->ptr_level++; - } else { - int next_dim = read_const_expr(); - - if (dim == 0) { - vd->array_size = next_dim; - } else { - if (dim == 1) - vd->array_dim2 = next_dim; - if (vd->array_size > 0) - vd->array_size *= next_dim; - else - vd->array_size = next_dim; - } - } - lex_expect(T_close_square); - dims++; - } - if (first_dim_empty && dims == 1) - vd->ptr_level++; - vd->is_func = false; - } -} - -/* starting next_token, need to check the type */ -void read_full_var_decl(var_t *vd, bool anon, bool is_param) -{ - char type_name[MAX_ID_LEN]; - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - } - lex_ident(T_identifier, type_name); - type_t *type = find_type(type_name, find_type_flag); - - if (!type) { - printf("Could not find type %s%s\n", - find_type_flag == 2 ? "struct/union " : "", type_name); - fflush(stdout); /* see fatal() */ - abort(); - } - - vd->type = type; - - read_inner_var_decl(vd, anon, is_param); -} - -/* starting next_token, need to check the type */ -void read_partial_var_decl(var_t *vd, var_t *template) -{ - UNUSED(template); - read_inner_var_decl(vd, false, false); -} - -void read_parameter_list_decl(func_t *func, bool anon) -{ - int vn = 0; - lex_expect(T_open_bracket); - - char token[MAX_ID_LEN]; - if (lex_peek(T_identifier, token) && !strncmp(token, "void", 4)) { - lex_next(); - if (lex_accept(T_close_bracket)) - return; - func->param_defs[vn].type = TY_void; - read_inner_var_decl(&func->param_defs[vn], anon, true); - if (!func->param_defs[vn].ptr_level && !func->param_defs[vn].is_func && - !func->param_defs[vn].array_size) - error_at("'void' must be the only parameter and unnamed", - cur_token_loc()); - vn++; - lex_accept(T_comma); - } - - while (lex_peek(T_identifier, NULL) || lex_peek(T_const, NULL)) { - /* Check for const qualifier */ - bool is_const = false; - if (lex_accept(T_const)) - is_const = true; - - if (vn >= MAX_PARAMS) - error_at("Too many parameters", cur_token_loc()); - read_full_var_decl(&func->param_defs[vn], anon, true); - func->param_defs[vn].is_const_qualified = is_const; - vn++; - lex_accept(T_comma); - } - func->num_params = vn; - - /* Up to 'MAX_PARAMS' parameters are accepted for the variadic function. */ - if (lex_accept(T_elipsis)) - func->va_args = 1; - - lex_expect(T_close_bracket); -} - -void read_literal_param(block_t *parent, basic_block_t *bb) -{ - char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN], - combined[MAX_LINE_LEN]; - int combined_len = 0; - - /* Read first string literal */ - lex_ident(T_string, literal); - unescape_string(literal, combined, MAX_LINE_LEN); - combined_len = strlen(combined); - - /* Check for adjacent string literals and concatenate them */ - while (lex_peek(T_string, NULL)) { - lex_ident(T_string, literal); - unescape_string(literal, unescaped, MAX_LINE_LEN - combined_len); - int unescaped_len = strlen(unescaped); - if (combined_len + unescaped_len >= MAX_LINE_LEN - 1) - error_at("Concatenated string literal too long", cur_token_loc()); - - strcpy(combined + combined_len, unescaped); - combined_len += unescaped_len; - } - - const int index = write_symbol(combined); - - var_t *vd = require_typed_ptr_var(parent, TY_char, true); - vd->var_name = gen_name(); - vd->init_val = index; - opstack_push(vd); - /* String literals are now in .rodata section */ - add_insn(parent, bb, OP_load_rodata_address, vd, NULL, NULL, 0, NULL); -} - -void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) -{ - char token[MAX_TOKEN_LEN]; - int value = 0; - int i = 0; - char c; - - lex_ident_n(T_numeric, token, MAX_TOKEN_LEN); - - if (token[0] == '-') { - is_neg = !is_neg; - i++; - } - if (token[0] == '0') { - if ((token[1] | 32) == 'x') { /* hexdecimal */ - i = 2; - do { - c = token[i++]; - if (isdigit(c)) - c -= '0'; - else { - c |= 32; /* convert to lower case */ - if (c >= 'a' && c <= 'f') - c = (c - 'a') + 10; - else - error_at("Invalid numeric constant", cur_token_loc()); - } - - value = (value * 16) + c; - } while (isxdigit(token[i])); - } else if ((token[1] | 32) == 'b') { /* binary */ - i = 2; - do { - c = token[i++]; - if (c != '0' && c != '1') - error_at("Invalid binary constant", cur_token_loc()); - c -= '0'; - value = (value * 2) + c; - } while (token[i] == '0' || token[i] == '1'); - } else { /* octal */ - do { - c = token[i++]; - if (c > '7') - error_at("Invalid numeric constant", cur_token_loc()); - c -= '0'; - value = (value * 8) + c; - } while (isdigit(token[i])); - } - } else { - do { - c = token[i++] - '0'; - value = (value * 10) + c; - } while (isdigit(token[i])); - } - - if (is_neg) - value = -value; - - var_t *vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = value; - opstack_push(vd); - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); -} - -void read_char_param(block_t *parent, basic_block_t *bb) -{ - char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; - - lex_ident(T_char, literal); - unescape_string(literal, unescaped, MAX_TOKEN_LEN); - - var_t *vd = require_typed_var(parent, TY_char); - vd->var_name = gen_name(); - vd->init_val = unescaped[0]; - opstack_push(vd); - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); -} - -void read_logical(opcode_t op, block_t *parent, basic_block_t **bb); -void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) -{ - int param_num = 0; - var_t *params[MAX_PARAMS], *param; - - lex_expect(T_open_bracket); - while (!lex_accept(T_close_bracket)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - - param = opstack_pop(); - - /* Writing past 'params' corrupts this frame, and the damage only - * surfaces later as a wrong argument value. The check has to come - * before the conversions below: those index func->param_defs[], a - * MAX_PARAMS-element array embedded in func_t, so an over-long argument - * list reads past it and dereferences a garbage type pointer -- the - * compiler crashed instead of reporting the limit. - */ - if (param_num >= MAX_PARAMS) - error_at("Too many arguments in function call", cur_token_loc()); - - if (func && param_num < func->num_params) { - var_t *target = &func->param_defs[param_num]; - if (!target->ptr_level && !target->array_size) - param = - scalarize_array_literal(parent, bb, param, target->type); - } - - /* Handle parameter type conversion for direct calls. Indirect calls - * currently don't provide function instance. - */ - if (func && param_num >= func->num_params && func->va_args) { - /* Default promotions apply to scalar varargs, but pointer-like - * values (including array literals) must flow through unchanged so - * "%p" and friends see an address rather than a scalarized value. - */ - if (!is_pointer_like_value(param)) - param = promote(parent, bb, param, TY_int, 0); - } else if (func && param_num < func->num_params) { - /* Only a declared parameter has a type to convert towards. Beyond - * num_params the param_defs[] entry was never filled in, so its - * type is NULL and resize_var() dereferenced it: passing more - * arguments than a non-variadic function declares crashed the - * compiler instead of compiling or diagnosing the call. - */ - param = resize_var(parent, bb, param, &func->param_defs[param_num]); - } - - params[param_num++] = param; - lex_accept(T_comma); - } - - for (int i = 0; i < param_num; i++) { - /* The operand should keep alive before calling function. Pass the - * number of remained parameters to allocator to extend their liveness. - */ - add_insn(parent, *bb, OP_push, NULL, params[i], NULL, param_num - i, - NULL); - } -} - -void read_func_call(func_t *func, block_t *parent, basic_block_t **bb) -{ - /* direct function call */ - read_func_parameters(func, parent, bb); - - add_insn(parent, *bb, OP_call, NULL, NULL, NULL, 0, - func->return_def.var_name); -} - -void read_indirect_call(block_t *parent, basic_block_t **bb) -{ - /* Note: Indirect calls use generic parameter handling */ - read_func_parameters(NULL, parent, bb); - - add_insn(parent, *bb, OP_indirect, NULL, opstack_pop(), NULL, 0, NULL); -} - -void read_lvalue(lvalue_t *lvalue, - var_t *var, - block_t *parent, - basic_block_t **bb, - bool eval, - opcode_t op, - bool allow_ptr_arith); - -/* Maintain a stack of expression values and operators, depending on next - * operators' priority. Either apply it or operator on stack first. +/* The next free field slot of @type. + * + * Struct and union bodies fill these in from two places -- one field per + * declaration, and one more per comma in a multiple declarator -- so the bound + * belongs here rather than being repeated, and missed, at each of them. */ -void handle_address_of_operator(block_t *parent, basic_block_t **bb) -{ - char token[MAX_VAR_LEN]; - lvalue_t lvalue; - var_t *vd, *rs1; - - if (!lex_peek(T_identifier, token)) - error_at("Expected an identifier", next_token_loc()); - var_t *var = find_var(token, parent); - read_lvalue(&lvalue, var, parent, bb, false, OP_generic, true); - - if (!lvalue.is_reference) { - rs1 = opstack_pop(); - vd = require_ref_var(parent, lvalue.type, lvalue.ptr_level); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); - } -} - -void handle_single_dereference(block_t *parent, basic_block_t **bb) +var_t *type_add_field(type_t *type, int *idx) { - var_t *vd, *rs1; - int sz; - - if (lex_peek(T_open_bracket, NULL)) { - /* Handle general expression dereference: *(expr) */ - lex_expect(T_open_bracket); - read_expr(parent, bb); - lex_expect(T_close_bracket); - - rs1 = opstack_pop(); + int i = *idx; - /* For pointer dereference, we need to determine the target type and - * size. Since we do not have full type tracking in expressions, use - * defaults - */ - type_t *deref_type = rs1->type ? rs1->type : TY_int; - int deref_ptr = rs1->ptr_level > 0 ? rs1->ptr_level - 1 : 0; - - /* require_deref_var() takes the *source* pointer level and returns a - * variable one level shallower. Passing the already-decremented value - * dropped two levels per dereference, so "**(q + 0)" on an int** ended - * up reading an int-sized word where a pointer was stored. The sizes - * coincide on the 32-bit targets, which is why it only surfaces here. - */ - vd = require_deref_var(parent, deref_type, rs1->ptr_level); - if (deref_ptr > 0) - sz = PTR_SIZE; - else - sz = deref_type->size; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } else { - /* Handle simple identifier dereference: *var */ - char token[MAX_VAR_LEN]; - lvalue_t lvalue; - - if (!lex_peek(T_identifier, token)) - error_at("Expected an identifier", next_token_loc()); - var_t *var = find_var(token, parent); - read_lvalue(&lvalue, var, parent, bb, true, OP_generic, false); - - rs1 = opstack_pop(); - vd = require_deref_var(parent, var->type, var->ptr_level); - if (lvalue.ptr_level > 1) - sz = PTR_SIZE; - else { - /* For typedef pointers, get the size of the pointed-to type */ - if (lvalue.type && lvalue.type->ptr_level > 0) { - /* This is a typedef pointer */ - switch (lvalue.type->base_type) { - case TYPE_char: - sz = TY_char->size; - break; - case TYPE_short: - sz = TY_short->size; - break; - case TYPE_int: - sz = TY_int->size; - break; - case TYPE_void: - sz = 1; - break; - default: - sz = lvalue.type->size; - break; - } - } else { - sz = lvalue.type->size; - } - } - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } + if (i >= MAX_FIELDS) + error_at("Too many fields in struct or union", cur_token_loc()); + type_ensure_fields(type); + *idx = i + 1; + return &type->fields[i]; } -/* Scan ahead for an assignment operator at the top level of the statement that - * starts at the current token, stopping at its terminating semicolon. - * - * A statement beginning with '*' is either a store through a pointer or a plain - * expression, and the two need opposite treatment of the leading asterisk. - * Deciding by looking at tokens keeps the choice free of side effects: by the - * time an expression has been parsed, its instructions have already been - * emitted and there is no way back. +/* C99 record members start at their natural ABI alignment and a record's extent + * is rounded up to its strongest member. The supported targets use the scalar + * sizes below as natural alignments (up to eight bytes); pointer aliases retain + * pointer alignment even though their base typedef may carry record field + * metadata. */ -bool stmt_starts_assignment(void) +int alignment_type(type_t *type) { - int depth = 0; - - for (token_t *t = cur_token->next; t; t = t->next) { - switch (t->kind) { - case T_open_bracket: - case T_open_square: - depth++; - break; - case T_close_bracket: - case T_close_square: - depth--; - break; - case T_semicolon: - case T_open_curly: - case T_close_curly: - case T_eof: - return false; - case T_assign: - case T_pluseq: - case T_minuseq: - case T_asteriskeq: - case T_divideeq: - case T_modeq: - case T_lshifteq: - case T_rshifteq: - case T_andeq: - case T_oreq: - case T_xoreq: - if (depth == 0) - return true; - break; - default: - break; - } - } - return false; + if (!type) + return 1; + if (type->ptr_level) + return PTR_SIZE; + if ((type->base_type == TYPE_struct || type->base_type == TYPE_union || + (type->base_type == TYPE_typedef && type->num_fields)) && + type->alignment) + return type->alignment; + if (type->size >= 8) + return 8; + if (type->size >= 4) + return 4; + if (type->size >= 2) + return 2; + return 1; } -void handle_multiple_dereference(block_t *parent, basic_block_t **bb) +int alignment_var(var_t *var) { - var_t *vd, *rs1; - int sz; - - /* Handle consecutive asterisks for multiple dereference: **pp, ***ppp, and - * the parenthesized ***(expr) form. - */ - int deref_count = 1; /* We already consumed one asterisk */ - while (lex_accept(T_asterisk)) - deref_count++; - - /* Check if we have a parenthesized expression or simple identifier */ - if (lex_peek(T_open_bracket, NULL)) { - /* Handle ***(expr) case */ - lex_expect(T_open_bracket); - read_expr(parent, bb); - lex_expect(T_close_bracket); - - /* Apply dereferences one by one */ - for (int i = 0; i < deref_count; i++) { - rs1 = opstack_pop(); - /* For expression dereference, use default type info */ - type_t *deref_type = rs1->type ? rs1->type : TY_int; - int deref_ptr = rs1->ptr_level > 0 ? rs1->ptr_level - 1 : 0; - - vd = require_deref_var(parent, deref_type, rs1->ptr_level); - if (deref_ptr > 0) - sz = PTR_SIZE; - else - sz = deref_type->size; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } - } else { - /* Handle **pp, ***ppp case with simple identifier */ - char token[MAX_VAR_LEN]; - lvalue_t lvalue; - - if (!lex_peek(T_identifier, token)) - error_at("Expected an identifier", next_token_loc()); - var_t *var = find_var(token, parent); - read_lvalue(&lvalue, var, parent, bb, true, OP_generic, false); - - /* Apply dereferences one by one */ - for (int i = 0; i < deref_count; i++) { - rs1 = opstack_pop(); - vd = require_deref_var( - parent, var->type, - lvalue.ptr_level > i ? lvalue.ptr_level - i - 1 : 0); - if (lvalue.ptr_level > i + 1) - sz = PTR_SIZE; - else { - /* For typedef pointers, get the size of the pointed-to type */ - if (lvalue.type && lvalue.type->ptr_level > 0 && - i == deref_count - 1) { - /* This is a typedef pointer on the final dereference */ - switch (lvalue.type->base_type) { - case TYPE_char: - sz = TY_char->size; - break; - case TYPE_short: - sz = TY_short->size; - break; - case TYPE_int: - sz = TY_int->size; - break; - case TYPE_void: - sz = 1; - break; - default: - sz = lvalue.type->size; - break; - } - } else { - sz = lvalue.type->size; - } - } - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } - } + if (var->ptr_level || var->is_func || var->type->ptr_level) + return PTR_SIZE; + return alignment_type(var->type); } -void handle_sizeof_operator(block_t *parent, basic_block_t **bb) +int layout_struct_field(int size, var_t *field, int *record_alignment) { - char token[MAX_ID_LEN]; - int ptr_cnt = 0; - token_t *sizeof_tk = cur_token; - type_t *type = NULL; - var_t *vd; - - lex_expect(T_open_bracket); - - /* Check if this is sizeof(type) or sizeof(expression) */ - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) - find_type_flag = 2; - - if (lex_peek(T_identifier, token)) { - /* Try to parse as a type first */ - type = find_type(token, find_type_flag); - if (type) { - /* sizeof(type) */ - lex_expect(T_identifier); - while (lex_accept(T_asterisk)) - ptr_cnt++; - } - } - - if (!type) { - /* sizeof(expression) - parse the expression and get its type */ - read_expr(parent, bb); - read_ternary_operation(parent, bb); - var_t *expr_var = opstack_pop(); - type = expr_var->type; - ptr_cnt = expr_var->ptr_level; - } + int alignment = alignment_var(field); - if (!type) - error_at("Unable to determine type in sizeof", &sizeof_tk->location); - - vd = require_var(parent); - vd->init_val = ptr_cnt ? PTR_SIZE : type->size; - vd->var_name = gen_name(); - opstack_push(vd); - lex_expect(T_close_bracket); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + if (alignment > *record_alignment) + *record_alignment = alignment; + field->offset = ALIGN_UP(size, alignment); + return field->offset + size_var(field); } -void read_expr_operand(block_t *parent, basic_block_t **bb) -{ - var_t *vd, *rs1; - bool is_neg = false; - - if (lex_accept(T_minus)) { - is_neg = true; - if (!lex_peek(T_numeric, NULL) && !lex_peek(T_identifier, NULL) && - !lex_peek(T_open_bracket, NULL)) { - error_at("Unexpected token after unary minus", next_token_loc()); - } - } +/* Stateful packing cursor for the C99 bit-field layout path. Ordinary fields + * flush the cursor before their existing aligned placement; bit-fields will + * share one unit until its remaining bits are exhausted. + */ +typedef struct { + int unit_offset; + int unit_size; + int used_bits; +} bitfield_layout_t; + +/* Switch labels are collected only while enforcing strict C99 constraints. A + * linked list avoids imposing an arbitrary case-label count on a valid + * translation unit merely to check duplicate converted values. + */ +typedef struct switch_case_value { + unsigned int value; + unsigned int value_hi; + struct switch_case_value *next; +} switch_case_value_t; + +int read_const_expr(block_t *scope); +int read_global_address_offset(block_t *scope, + block_t *parent, + basic_block_t *bb); - if (lex_peek(T_string, NULL)) - read_literal_param(parent, *bb); - else if (lex_peek(T_char, NULL)) - read_char_param(parent, *bb); - - else if (lex_peek(T_numeric, NULL)) - read_numeric_param(parent, *bb, is_neg); - else if (lex_accept(T_log_not)) { - read_expr_operand(parent, bb); - - rs1 = opstack_pop(); - - /* Constant folding for logical NOT */ - if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->is_const = true; - vd->init_val = !rs1->init_val; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - } else { - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_log_not, vd, rs1, NULL, 0, NULL); - } - } else if (lex_accept(T_bit_not)) { - read_expr_operand(parent, bb); - - rs1 = opstack_pop(); - - /* Constant folding for bitwise NOT */ - if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->is_const = true; - vd->init_val = ~rs1->init_val; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - } else { - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_bit_not, vd, rs1, NULL, 0, NULL); - } - } else if (lex_accept(T_ampersand)) { - handle_address_of_operator(parent, bb); - } else if (lex_accept(T_asterisk)) { - /* dereference */ - if (lex_peek(T_asterisk, NULL)) { - handle_multiple_dereference(parent, bb); - } else { - handle_single_dereference(parent, bb); - } - } else if (lex_accept(T_open_bracket)) { - /* Check if this is a cast, compound literal, or parenthesized - * expression - */ - char lookahead_token[MAX_ID_LEN]; - bool is_compound_literal = false; - bool is_cast = false; - type_t *cast_or_literal_type = NULL; - int cast_ptr_level = 0; - - /* Look ahead to see if we have a typename followed by ) */ - if (lex_peek(T_identifier, lookahead_token)) { - /* Check if it's a basic type or typedef */ - type_t *type = find_type(lookahead_token, true); - - if (type) { - /* Save current position to backtrack if needed */ - token_t *saved_token = cur_token; - - /* Try to parse as typename */ - lex_expect(T_identifier); - - /* Check for pointer types: int*, char*, etc. */ - int ptr_level = 0; - while (lex_accept(T_asterisk)) { - ptr_level++; - } - - /* Check for array brackets: [size] or [] */ - bool is_array = false; - if (lex_accept(T_open_square)) { - is_array = true; - - /* Skip the array size: it is discarded, and a numeric - * literal can be longer than any small buffer. - */ - if (lex_peek(T_numeric, NULL)) - lex_expect(T_numeric); - lex_expect(T_close_square); - } - - /* Check what follows the closing ) */ - if (lex_accept(T_close_bracket)) { - if (lex_peek(T_open_curly, NULL)) { - /* (type){...} - compound literal */ - is_compound_literal = true; - cast_or_literal_type = type; - cast_ptr_level = ptr_level; - - /* Store is_array flag in cast_ptr_level if it's an - * array - */ - if (is_array) { - /* Special marker for array compound literal */ - cast_ptr_level = -1; - } - } else { - /* (type)expr - cast expression */ - is_cast = true; - cast_or_literal_type = type; - cast_ptr_level = ptr_level; - } - } else { - /* Not a cast or compound literal - backtrack */ - cur_token = saved_token; - } - } - } - - if (is_cast) { - /* Process cast: (type)expr Parse the expression to be cast */ - read_expr_operand(parent, bb); - - /* Get the expression result */ - var_t *expr_var = opstack_pop(); - - /* Create variable for cast result */ - var_t *cast_var = require_typed_ptr_var( - parent, cast_or_literal_type, cast_ptr_level); - cast_var->var_name = gen_name(); - - /* Generate cast IR. A cast down to a narrower type has to discard - * the high bits: OP_cast is only a move, so "(char) 300" kept the - * whole 300 and compared unequal to 44, even though assigning the - * same value to a char produced 44. get_size() decides that the - * same way the rest of the parser does, including for pointers, - * arrays and typedefs. - */ - opcode_t cast_op = OP_cast; - if (get_size(cast_var) < get_size(expr_var)) - cast_op = OP_trunc; - add_insn(parent, *bb, cast_op, cast_var, expr_var, NULL, - get_size(cast_var), NULL); - - /* Push the cast result */ - opstack_push(cast_var); - - } else if (is_compound_literal) { - /* Process compound literal */ - lex_expect(T_open_curly); - - /* Create variable for compound literal result */ - var_t *compound_var = - require_typed_var(parent, cast_or_literal_type); - compound_var->var_name = gen_name(); - compound_var->is_compound_literal = true; - - /* Check if this is an array compound literal (int[]){...} */ - bool is_array_literal = (cast_ptr_level == -1); - if (is_array_literal) - cast_ptr_level = 0; /* Reset for normal processing */ - bool consumed_close_brace = false; - /* Check if this is a pointer compound literal */ - if (is_array_literal) { - compound_var->array_size = 0; - add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, - NULL); - parse_array_compound_literal(compound_var, parent, bb); - - if (compound_var->array_size == 0) { - compound_var->init_val = 0; - add_insn(parent, *bb, OP_load_constant, compound_var, NULL, - NULL, 0, NULL); - } - opstack_push(compound_var); - consumed_close_brace = true; - } else if (cast_ptr_level > 0) { - /* Pointer compound literal: (int*){&x} */ - compound_var->ptr_level = cast_ptr_level; - - /* Parse the pointer value (should be an address) */ - if (!lex_peek(T_close_curly, NULL)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - const var_t *ptr_val = opstack_pop(); - - /* For pointer compound literals, store the address */ - compound_var->init_val = ptr_val->init_val; - - /* Consume additional values if present (for pointer arrays) - */ - while (lex_accept(T_comma)) { - if (lex_peek(T_close_curly, NULL)) - break; - read_expr(parent, bb); - read_ternary_operation(parent, bb); - opstack_pop(); - } - } else { - /* Empty pointer compound literal: (int*){} */ - compound_var->init_val = 0; /* NULL pointer */ - } - - /* Generate code for pointer compound literal */ - opstack_push(compound_var); - add_insn(parent, *bb, OP_load_constant, compound_var, NULL, - NULL, 0, NULL); - } else if (cast_or_literal_type->base_type == TYPE_struct || - cast_or_literal_type->base_type == TYPE_typedef) { - /* Struct compound literal support (including typedef structs) - * For typedef structs, the actual struct info is in the type - */ - - /* Initialize struct compound literal */ - compound_var->init_val = 0; - compound_var->ptr_level = 0; - - /* Parse first field value */ - if (!lex_peek(T_close_curly, NULL)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - const var_t *first_field = opstack_pop(); - compound_var->init_val = first_field->init_val; - - /* Consume additional fields if present */ - while (lex_accept(T_comma)) { - if (lex_peek(T_close_curly, NULL)) { - break; - } - read_expr(parent, bb); - read_ternary_operation(parent, bb); - opstack_pop(); /* Consume additional field values */ - } - } - - /* Generate code for struct compound literal */ - opstack_push(compound_var); - add_insn(parent, *bb, OP_load_constant, compound_var, NULL, - NULL, 0, NULL); - } else if (cast_or_literal_type->base_type == TYPE_int || - cast_or_literal_type->base_type == TYPE_short || - cast_or_literal_type->base_type == TYPE_char) { - /* Handle empty compound literals */ - if (lex_peek(T_close_curly, NULL)) { - /* Empty compound literal: (int){} */ - compound_var->init_val = 0; - compound_var->array_size = 0; - opstack_push(compound_var); - add_insn(parent, *bb, OP_load_constant, compound_var, NULL, - NULL, 0, NULL); - } else if (lex_peek(T_numeric, NULL) || - lex_peek(T_identifier, NULL) || - lex_peek(T_char, NULL)) { - /* Parse first element */ - read_expr(parent, bb); - read_ternary_operation(parent, bb); - - /* Check if there are more elements (comma-separated) */ - if (lex_peek(T_comma, NULL)) { - /* Array compound literal: (int[]){1, 2, 3} */ - var_t *first_element = opstack_pop(); - - /* Store elements temporarily */ - var_t *elements[256]; - elements[0] = first_element; - int element_count = 1; - - /* Parse remaining elements */ - while (lex_accept(T_comma)) { - if (lex_peek(T_close_curly, NULL)) - break; /* Trailing comma */ - - read_expr(parent, bb); - read_ternary_operation(parent, bb); - if (element_count < 256) { - elements[element_count] = opstack_pop(); - } else { - opstack_pop(); /* Discard if too many */ - } - element_count++; - } - - /* Set array metadata */ - compound_var->array_size = element_count; - compound_var->init_val = first_element->init_val; - - /* Allocate space for the array on stack */ - add_insn(parent, *bb, OP_allocat, compound_var, NULL, - NULL, 0, NULL); - - /* Initialize each element */ - for (int i = 0; i < element_count && i < 256; i++) { - if (!elements[i]) - continue; - - /* Store element at offset i * sizeof(element) */ - var_t *elem_offset = require_var(parent); - elem_offset->init_val = - i * cast_or_literal_type->size; - elem_offset->var_name = gen_name(); - add_insn(parent, *bb, OP_load_constant, elem_offset, - NULL, NULL, 0, NULL); - - /* Calculate address of element */ - var_t *elem_addr = require_var(parent); - elem_addr->ptr_level = 1; - elem_addr->var_name = gen_name(); - add_insn(parent, *bb, OP_add, elem_addr, - compound_var, elem_offset, 0, NULL); - - /* Store the element value */ - add_insn(parent, *bb, OP_write, NULL, elem_addr, - elements[i], cast_or_literal_type->size, - NULL); - } - - /* Store first element value for array-to-scalar */ - compound_var->init_val = first_element->init_val; - - /* Create result that provides first element access. - * This enables array compound literals in scalar - * contexts: int x = (int[]){1,2,3}; // x gets 1 int y = - * 5 + (int[]){10}; // adds 5 + 10 - */ - var_t *result_var = require_var(parent); - result_var->var_name = gen_name(); - result_var->type = compound_var->type; - result_var->ptr_level = 0; - result_var->array_size = 0; - - /* Read first element from the array */ - add_insn(parent, *bb, OP_read, result_var, compound_var, - NULL, compound_var->type->size, NULL); - opstack_push(result_var); - } else { - /* Single value: (int){42} - scalar compound literal */ - compound_var = opstack_pop(); - opstack_push(compound_var); - } - } - } - - if (!consumed_close_brace) - lex_expect(T_close_curly); - } else { - /* Regular parenthesized expression */ - read_expr(parent, bb); - read_ternary_operation(parent, bb); - lex_expect(T_close_bracket); - } - } else if (lex_accept(T_sizeof)) { - handle_sizeof_operator(parent, bb); - } else { - /* function call, constant or variable - read token and determine */ - opcode_t prefix_op = OP_generic; - char token[MAX_ID_LEN]; - - if (lex_accept(T_increment)) - prefix_op = OP_add; - else if (lex_accept(T_decrement)) - prefix_op = OP_sub; - - lex_peek(T_identifier, token); - - /* is a constant or variable? */ - const constant_t *con = find_constant(token); - var_t *var = find_var(token, parent); - func_t *func = find_func(token); - - if (con) { - vd = require_var(parent); - vd->init_val = con->value; - vd->var_name = gen_name(); - opstack_push(vd); - lex_expect(T_identifier); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - } else if (var) { - /* evalue lvalue expression */ - lvalue_t lvalue; - read_lvalue(&lvalue, var, parent, bb, true, prefix_op, true); - - /* is it an indirect call with function pointer? */ - if (lex_peek(T_open_bracket, NULL)) { - read_indirect_call(parent, bb); - - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_func_ret, vd, NULL, NULL, 0, NULL); - } - } else if (func) { - lex_expect(T_identifier); - - if (lex_peek(T_open_bracket, NULL)) { - read_func_call(func, parent, bb); - - vd = require_typed_ptr_var(parent, func->return_def.type, - func->return_def.ptr_level); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_func_ret, vd, NULL, NULL, 0, NULL); - } else { - /* indirective function pointer assignment */ - vd = require_func_symbol_var(parent); - vd->is_func = true; - vd->var_name = intern_string(token); - opstack_push(vd); - } - } else if (lex_accept(T_open_curly)) { - parse_array_literal_expr(parent, bb); - } else { - /* unknown expression */ - error_at("Unrecognized expression token", next_token_loc()); - } - - if (is_neg) { - rs1 = opstack_pop(); - - /* Constant folding for negation */ - if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->is_const = true; - vd->init_val = -rs1->init_val; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - } else { - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_negate, vd, rs1, NULL, 0, NULL); - } - } - } +/* Enumerator values must be representable as int. */ +bool checking_enum_constant = false; +bool typed_global_literal_appears_before_initializer_end(token_t *token); +void read_literal_param(block_t *parent, basic_block_t *bb); +var_t *bitfield_constant(block_t *parent, basic_block_t **bb, unsigned value); +unsigned bitfield_mask(const var_t *field); +void write_bitfield_value(block_t *parent, + basic_block_t **bb, + var_t *address, + var_t *value, + const var_t *field); + +bool is_bitfield(const var_t *field) +{ + return field && field->is_bitfield; } -void finalize_logical(opcode_t op, - block_t *parent, - basic_block_t **bb, - basic_block_t *shared_bb); +bool is_bool_type(const type_t *type) +{ + return type && type->is_bool; +} -bool is_logical(opcode_t op) +/* A _Bool object, reached directly or through a typedef. A typedef of a pointer + * to _Bool or of a _Bool array keeps is_bool but is not itself boolean. + */ +bool is_bool_scalar(const type_t *type, int ptr_level) { - return op == OP_log_and || op == OP_log_or; + return is_bool_type(type) && !ptr_level && !type->ptr_level && + !type->array_size; } -/* Consume a compound-assignment operator ("+=", "-=", ...) and report the - * arithmetic it applies. - * - * Returns false and consumes nothing when the next token is not one, so it can - * sit in an else-if chain beside the other statement forms. +/* Empty initializer lists are a useful permissive-mode extension, but C99's + * initializer-list grammar requires at least one initializer. Call this + * immediately after consuming an opening initializer brace. */ -bool accept_compound_assign_op(opcode_t *op) +void reject_empty_initializer_in_strict_c99(void) { - if (lex_accept(T_pluseq)) - op[0] = OP_add; - else if (lex_accept(T_minuseq)) - op[0] = OP_sub; - else if (lex_accept(T_asteriskeq)) - op[0] = OP_mul; - else if (lex_accept(T_divideeq)) - op[0] = OP_div; - else if (lex_accept(T_modeq)) - op[0] = OP_mod; - else if (lex_accept(T_lshifteq)) - op[0] = OP_lshift; - else if (lex_accept(T_rshifteq)) - op[0] = OP_rshift; - else if (lex_accept(T_xoreq)) - op[0] = OP_bit_xor; - else if (lex_accept(T_oreq)) - op[0] = OP_bit_or; - else if (lex_accept(T_andeq)) - op[0] = OP_bit_and; - else - return false; - return true; + if (strict_c99 && lex_peek(T_close_curly, NULL)) + error_at("empty initializer list is not permitted in C99", + cur_token_loc()); } -int get_pointer_element_size(var_t *ptr_var) +/* shecc documents conventional allocation units for C99's required bit-field + * types: four-byte int/unsigned int and one-byte _Bool, packed + * least-significant bit first. + */ +void read_bitfield_width(var_t *field, block_t *scope) { - if (!ptr_var || !ptr_var->type) - return PTR_SIZE; /* Default to pointer size */ + int width; - /* Direct pointer with type info. - * - * Only a single level of indirection points at the base type. For deeper - * pointers (int **, char ***, ...) the element is itself a pointer, so the - * step is PTR_SIZE. Returning the base type size there makes "q + 1" - * advance by 4 instead of 8 on LP64 and drops a level of type information - * from the result. - */ - if (ptr_var->ptr_level && ptr_var->type) { - if (ptr_var->ptr_level > 1) - return PTR_SIZE; - return ptr_var->type->size; - } + if (!lex_accept(T_colon)) + return; + if (field->ptr_level || field->is_func || field->array_size || + field->has_unsized_array || + !(is_bool_type(field->type) || + (field->type->base_type == TYPE_int && field->type->size == 4))) + error_at("Bit-field must have type _Bool, int, or unsigned int", + cur_token_loc()); - /* Typedef pointer or array-derived pointer */ - switch (ptr_var->type->base_type) { - case TYPE_char: - return TY_char->size; - case TYPE_short: - return TY_short->size; - case TYPE_int: - return TY_int->size; - case TYPE_void: - return 1; - default: - break; - } + width = read_const_expr(scope); + int storage_size = is_bool_type(field->type) ? 1 : 4; + int max_width = is_bool_type(field->type) ? 1 : storage_size * 8; + if (width < 0 || width > max_width) + error_at("Bit-field width exceeds its storage unit", cur_token_loc()); + if (!width && field->var_name[0]) + error_at("Zero-width bit-field must be unnamed", cur_token_loc()); - return ptr_var->type->size ? ptr_var->type->size : PTR_SIZE; + field->is_bitfield = true; + field->bit_width = width; + field->bit_offset = 0; + field->bit_storage_size = storage_size; } -/* Helper function to handle pointer arithmetic (add/sub with scaling) */ -void handle_pointer_arithmetic(block_t *parent, - basic_block_t **bb, - opcode_t op, - var_t *rs1, - var_t *rs2) +int flush_bitfield_layout(int size, bitfield_layout_t *bits) { - var_t *ptr_var = NULL; - var_t *int_var = NULL; - int element_size = 0; - - /* Pointer arithmetic: differences (char*, int*, struct*, etc.), - * addition/increment with scaling, and array indexing. - */ - - /* Check if both operands are pointers (pointer difference) */ - if (op == OP_sub) { - /* If both are variables (not temporaries), look them up */ - var_t *orig_rs1 = rs1, *orig_rs2 = rs2; - - /* If they have names, they might be variable references - look them up - */ - if (rs1->var_name[0] && !rs1->init_val) { - var_t *found = find_var(rs1->var_name, parent); - if (found) - orig_rs1 = found; - } - if (rs2->var_name[0] && !rs2->init_val) { - var_t *found = find_var(rs2->var_name, parent); - if (found) - orig_rs2 = found; - } - - /* Check if both have ptr_level or typedef pointer type */ - bool rs1_is_ptr = is_pointer_like_value(orig_rs1); - bool rs2_is_ptr = is_pointer_like_value(orig_rs2); - - /* If variable lookup failed, check the passed variables directly */ - if (!rs1_is_ptr) - rs1_is_ptr = is_pointer_like_value(rs1); - if (!rs2_is_ptr) - rs2_is_ptr = is_pointer_like_value(rs2); - - if (rs1_is_ptr && rs2_is_ptr) { - /* Both are pointers - this is pointer difference Determine element - * size - */ - element_size = PTR_SIZE; /* Default */ - - /* Get element size from the first pointer */ - if (orig_rs1->type) { - /* Check if this is a typedef pointer or regular pointer */ - if (orig_rs1->type->ptr_level > 0) { - /* Typedef pointer - element size from base type */ - switch (orig_rs1->type->base_type) { - case TYPE_char: - element_size = 1; - break; - case TYPE_short: - element_size = 2; - break; - case TYPE_int: - element_size = 4; - break; - default: - /* For struct/union typedef pointers, use the actual - * type size - */ - if (orig_rs1->type->size > 0) - element_size = orig_rs1->type->size; - break; - } - } else if (orig_rs1->ptr_level > 0) { - /* Regular pointer (e.g., int *p) - type gives the base type - */ - switch (orig_rs1->type->base_type) { - case TYPE_char: - element_size = 1; - break; - case TYPE_short: - element_size = 2; - break; - case TYPE_int: - element_size = 4; - break; - case TYPE_void: - element_size = 1; /* void* arithmetic uses byte size */ - break; - default: - /* For struct pointers, use the struct size */ - element_size = orig_rs1->type->size; - break; - } - } - } - - /* Perform subtraction first */ - var_t *diff = require_var(parent); - diff->var_name = gen_name(); - add_insn(parent, *bb, OP_sub, diff, rs1, rs2, 0, NULL); - - /* Then divide by element size if needed */ - if (element_size > 1) { - var_t *size_const = require_var(parent); - size_const->var_name = gen_name(); - size_const->init_val = element_size; - add_insn(parent, *bb, OP_load_constant, size_const, NULL, NULL, - 0, NULL); - - var_t *result = require_var(parent); - result->var_name = gen_name(); - add_insn(parent, *bb, OP_div, result, diff, size_const, 0, - NULL); - opstack_push(result); - } else { - opstack_push(diff); - } - return; - } - } - /* Determine which operand is the pointer for regular pointer arithmetic */ - if (is_pointer_like_value(rs1)) { - ptr_var = rs1; - int_var = rs2; - element_size = get_pointer_element_size(rs1); - } else if (is_pointer_like_value(rs2)) { - /* Only for addition (p + n == n + p) */ - if (op == OP_add) { - ptr_var = rs2; - int_var = rs1; - element_size = get_pointer_element_size(rs2); - /* Swap operands so pointer is rs1 */ - rs1 = ptr_var; - rs2 = int_var; - } + if (bits->used_bits) { + int end = bits->unit_offset + bits->unit_size; + if (end > size) + size = end; } + bits->unit_offset = 0; + bits->unit_size = 0; + bits->used_bits = 0; + return size; +} - /* If we need to scale the integer operand */ - if (ptr_var && element_size > 1) { - /* Create multiplication by element size */ - var_t *size_const = require_var(parent); - size_const->var_name = gen_name(); - size_const->init_val = element_size; - add_insn(parent, *bb, OP_load_constant, size_const, NULL, NULL, 0, - NULL); - - var_t *scaled = require_var(parent); - scaled->var_name = gen_name(); - add_insn(parent, *bb, OP_mul, scaled, int_var, size_const, 0, NULL); - - /* Use scaled value as rs2 */ - rs2 = scaled; - } +int layout_bitfield_field(int size, + var_t *field, + int *record_alignment, + bitfield_layout_t *bits) +{ + int unit_bits = field->bit_storage_size * 8; + int alignment = field->bit_storage_size; - /* Perform the operation */ - var_t *vd = require_var(parent); - /* Preserve pointer type metadata on results of pointer arithmetic */ - if (ptr_var) { - vd->type = ptr_var->type; - vd->ptr_level = ptr_var->ptr_level; + if (alignment > *record_alignment) + *record_alignment = alignment; + if (!field->bit_width) { + size = flush_bitfield_layout(size, bits); + return ALIGN_UP(size, alignment); + } + if (!bits->used_bits || bits->unit_size != field->bit_storage_size || + bits->used_bits + field->bit_width > unit_bits) { + size = flush_bitfield_layout(size, bits); + bits->unit_offset = ALIGN_UP(size, alignment); + bits->unit_size = field->bit_storage_size; } - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); + field->offset = bits->unit_offset; + field->bit_offset = bits->used_bits; + bits->used_bits += field->bit_width; + return size; } -/* Helper function to check if pointer arithmetic is needed */ -bool is_pointer_operation(opcode_t op, var_t *rs1, var_t *rs2) +/* An incomplete array has a distinct meaning in a record: C99 permits it only + * as the final member of a struct, where it contributes no bytes to the + * record's fixed layout. + */ +void mark_flexible_array_member(var_t *field, bool is_union) { - if (op != OP_add && op != OP_sub) - return false; - - return is_pointer_like_value(rs1) || is_pointer_like_value(rs2); + if (!field->has_unsized_array) + return; + if (is_union) + error_at("Flexible array member is not permitted in a union", + cur_token_loc()); + field->is_flexible_array_member = true; } -void read_expr_body(block_t *parent, basic_block_t **bb) +bool is_array_declarator(const var_t *var) { - var_t *vd, *rs1, *rs2; - opcode_t oper_stack[MAX_OPERATOR_STACK_SIZE]; - int oper_stack_idx = 0; + return var->array_size > 0 || var->is_flexible_array_member; +} - /* These variables used for parsing logical-and/or operation. - * - * For the logical-and operation, the false condition code path for testing - * each operand uses the same code snippet (basic block). - * - * Likewise, when testing each operand for the logical-or operation, all of - * them share a unified code path for the true condition. - */ - bool has_prev_log_op = false; - opcode_t prev_log_op = 0, pprev_log_op = 0; - basic_block_t *log_and_shared_bb = bb_create(parent), - *log_or_shared_bb = bb_create(parent); +bool type_has_flexible_array_member(const type_t *type) +{ + return type && (type->has_flexible_array_member || + (type->base_struct && + type->base_struct->has_flexible_array_member)); +} - read_expr_operand(parent, bb); +bool is_flexible_array_member_container(const var_t *field) +{ + return !field->ptr_level && type_has_flexible_array_member(field->type); +} - opcode_t op = get_operator(); - if (op == OP_generic || op == OP_ternary) - return; - if (is_logical(op)) { - bb_connect(*bb, op == OP_log_and ? log_and_shared_bb : log_or_shared_bb, - op == OP_log_and ? ELSE : THEN); - read_logical(op, parent, bb); - has_prev_log_op = true; - prev_log_op = op; - } else { - if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) - fatal("Expression too complex: operator stack exhausted"); - oper_stack[oper_stack_idx++] = op; - } - read_expr_operand(parent, bb); - op = get_operator(); - - while (op != OP_generic && op != OP_ternary) { - if (oper_stack_idx > 0) { - int same = 0; - do { - opcode_t top_op = oper_stack[oper_stack_idx - 1]; - if (get_operator_prio(top_op) >= get_operator_prio(op)) { - rs2 = opstack_pop(); - rs1 = opstack_pop(); - - /* Handle pointer arithmetic for addition and subtraction */ - if (is_pointer_operation(top_op, rs1, rs2)) { - /* handle_pointer_arithmetic handles both pointer - * differences and regular pointer arithmetic internally - */ - handle_pointer_arithmetic(parent, bb, top_op, rs1, rs2); - oper_stack_idx--; - continue; - } - - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, top_op, vd, rs1, rs2, 0, NULL); - - oper_stack_idx--; - } else - same = 1; - } while (oper_stack_idx > 0 && same == 0); - } - if (is_logical(op)) { - if (prev_log_op == 0 || prev_log_op == op) { - bb_connect( - *bb, - op == OP_log_and ? log_and_shared_bb : log_or_shared_bb, - op == OP_log_and ? ELSE : THEN); - read_logical(op, parent, bb); - prev_log_op = op; - has_prev_log_op = true; - } else if (prev_log_op == OP_log_and) { - /* For example: a && b || c - * previous opcode: prev_log_op == OP_log_and current opcode: op - * == OP_log_or current operand: b - * - * Finalize the logical-and operation and test the operand for - * the following logical-or operation. - */ - finalize_logical(prev_log_op, parent, bb, log_and_shared_bb); - log_and_shared_bb = bb_create(parent); - bb_connect(*bb, log_or_shared_bb, THEN); - read_logical(op, parent, bb); - - /* Here are two cases to illustrate the following assignments - * after finalizing the logical-and operation and testing the - * operand for the following logical-or operation. - * - * 1. a && b || c - * pprev opcode: pprev_log_op == 0 (no opcode) - * previous opcode: prev_log_op == OP_log_and - * current opcode: op == OP_log_or - * current operand: b - * - * The current opcode should become the previous opcode, - * and the pprev opcode remains 0. - * - * 2. a || b && c || d - * pprev opcode: pprev_log_op == OP_log_or - * previous opcode: prev_log_op == OP_log_and - * current opcode: op == OP_log_or - * current operand: b - * - * The previous opcode should inherit the pprev opcode, which - * is equivalent to inheriting the current opcode because both - * of pprev opcode and current opcode are logical-or operator. - * - * Thus, pprev opcode is considered used and is cleared to 0. - * - * Eventually, the current opcode becomes the previous opcode - * and pprev opcode is set to 0. - */ - prev_log_op = op; - pprev_log_op = 0; - } else { - /* For example: a || b && c - * previous opcode: prev_log_op == OP_log_or current opcode: op - * == OP_log_and current operand: b - * - * Using the logical-and operation to test the current operand - * instead of using the logical-or operation. - * - * Then, the previous opcode becomes pprev opcode and the - * current opcode becomes the previous opcode. - */ - bb_connect(*bb, log_and_shared_bb, ELSE); - read_logical(op, parent, bb); - pprev_log_op = prev_log_op; - prev_log_op = op; - } - } else { - while (has_prev_log_op && - (get_operator_prio(op) < get_operator_prio(prev_log_op))) { - /* When encountering an operator with lower priority, conclude - * the current logical-and/or and create a new basic block for - * next logical-and/or operator. - */ - finalize_logical(prev_log_op, parent, bb, - prev_log_op == OP_log_and ? log_and_shared_bb - : log_or_shared_bb); - if (prev_log_op == OP_log_and) - log_and_shared_bb = bb_create(parent); - else - log_or_shared_bb = bb_create(parent); - - /* After finalizing the previous logical-and/or operation, the - * prev_log_op should inherit pprev_log_op and continue to check - * whether to finalize a logical-and/or operation. - */ - prev_log_op = pprev_log_op; - has_prev_log_op = prev_log_op != 0; - pprev_log_op = 0; - } - } - read_expr_operand(parent, bb); - if (!is_logical(op)) { - if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) - fatal("Expression too complex: operator stack exhausted"); - oper_stack[oper_stack_idx++] = op; - } - op = get_operator(); - } +void reject_flexible_array_member_container(const var_t *field) +{ + if (is_flexible_array_member_container(field)) + error_at( + "A struct with a flexible array member cannot be embedded by value", + cur_token_loc()); +} - while (oper_stack_idx > 0) { - opcode_t top_op = oper_stack[--oper_stack_idx]; - rs2 = opstack_pop(); - rs1 = opstack_pop(); - - bool rs1_is_placeholder = is_array_literal_placeholder(rs1); - bool rs2_is_placeholder = is_array_literal_placeholder(rs2); - bool rs1_is_ptr_like = is_pointer_like_value(rs1); - bool rs2_is_ptr_like = is_pointer_like_value(rs2); - bool pointer_context = (rs1_is_ptr_like && !rs1_is_placeholder) || - (rs2_is_ptr_like && !rs2_is_placeholder); - - /* Pointer arithmetic handling */ - if (pointer_context && is_pointer_operation(top_op, rs1, rs2)) { - handle_pointer_arithmetic(parent, bb, top_op, rs1, rs2); - continue; /* skip normal processing */ - } - - if (rs1_is_placeholder && rs2_is_placeholder) { - rs1 = scalarize_array_literal(parent, bb, rs1, NULL); - rs2 = scalarize_array_literal(parent, bb, rs2, NULL); - } else { - if (rs1_is_placeholder && !rs2_is_ptr_like) - rs1 = scalarize_array_literal( - parent, bb, rs1, rs2 && rs2->type ? rs2->type : NULL); - - if (rs2_is_placeholder && !rs1_is_ptr_like) - rs2 = scalarize_array_literal( - parent, bb, rs2, rs1 && rs1->type ? rs1->type : NULL); - } - /* Constant folding for binary operations */ - if (rs1 && rs2 && rs1->init_val && !rs1->ptr_level && !rs1->is_global && - rs2->init_val && !rs2->ptr_level && !rs2->is_global) { - /* Both operands are compile-time constants */ - int result = 0; - bool folded = true; - - switch (top_op) { - case OP_add: - result = rs1->init_val + rs2->init_val; - break; - case OP_sub: - result = rs1->init_val - rs2->init_val; - break; - case OP_mul: - result = rs1->init_val * rs2->init_val; - break; - case OP_div: - if (rs2->init_val != 0) - result = rs1->init_val / rs2->init_val; - else - folded = false; /* Division by zero */ - break; - case OP_mod: - if (rs2->init_val != 0) - result = rs1->init_val % rs2->init_val; - else - folded = false; /* Modulo by zero */ - break; - case OP_bit_and: - result = rs1->init_val & rs2->init_val; - break; - case OP_bit_or: - result = rs1->init_val | rs2->init_val; - break; - case OP_bit_xor: - result = rs1->init_val ^ rs2->init_val; - break; - case OP_lshift: - result = rs1->init_val << rs2->init_val; - break; - case OP_rshift: - result = rs1->init_val >> rs2->init_val; - break; - case OP_eq: - result = rs1->init_val == rs2->init_val; - break; - case OP_neq: - result = rs1->init_val != rs2->init_val; - break; - case OP_lt: - result = rs1->init_val < rs2->init_val; - break; - case OP_leq: - result = rs1->init_val <= rs2->init_val; - break; - case OP_gt: - result = rs1->init_val > rs2->init_val; - break; - case OP_geq: - result = rs1->init_val >= rs2->init_val; - break; - default: - folded = false; - break; - } - - if (folded) { - /* Create constant result */ - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = result; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - } else { - /* Normal operation - folding failed or not supported */ - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, top_op, vd, rs1, rs2, 0, NULL); - } - } else { - /* Normal operation */ - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, top_op, vd, rs1, rs2, 0, NULL); - } - } - while (has_prev_log_op) { - finalize_logical( - prev_log_op, parent, bb, - prev_log_op == OP_log_and ? log_and_shared_bb : log_or_shared_bb); - - prev_log_op = pprev_log_op; - has_prev_log_op = prev_log_op != 0; - pprev_log_op = 0; - } +void opstack_push(var_t *var) +{ + if (operand_stack_idx >= MAX_OPERAND_STACK_SIZE) + fatal("Expression too complex: operand stack exhausted"); + operand_stack[operand_stack_idx++] = var; } -/* Nesting counter for read_expr(). The expression grammar descends recursively - * through read_expr_operand(), so input nested deeply enough would run out of - * machine stack before any diagnostic could be printed. +/* The break/continue targets form a stack indexed by loop and switch nesting. + * Pushing through these keeps the depth check in one place instead of at each + * of the sites that open a new nesting level. */ -int expr_depth = 0; +void break_bb_push(basic_block_t *bb) +{ + if (break_exit_idx >= MAX_NESTING) + fatal("Too many nested loops or switch statements"); + break_bb[break_exit_idx++] = bb; +} + +void continue_bb_push(basic_block_t *bb) +{ + if (continue_pos_idx >= MAX_NESTING) + fatal("Too many nested loops"); + continue_bb[continue_pos_idx++] = bb; +} -void read_expr(block_t *parent, basic_block_t **bb) +var_t *opstack_pop(void) { - expr_depth++; - if (expr_depth > MAX_EXPR_DEPTH) - error_at("Expression nesting too deep", cur_token_loc()); - read_expr_body(parent, bb); - expr_depth--; + return operand_stack[--operand_stack_idx]; } +bool is_record_type(const type_t *type); -/* Return the address that an expression points to, or evaluate its value. - * x =; - * x[] =; - * x[expr].field =; - * x[expr]->field =; +/* Pop a value whose expression was evaluated only for its side effects: an + * expression statement, the left operand of a comma, or a for clause. + * + * A named object stands on the operand stack for its own value, and nothing + * reads it until something uses that value. Reading a volatile object is a side + * effect in itself, though (C99 5.1.2.3p2, 6.7.3p6), so "status;" still owes + * the object one read. A copy into a temporary is that read. * - * @allow_ptr_arith says whether a following "+ expr" belongs to this lvalue. - * Normally it does, and the addend is scaled by the element size. The - * dereference handlers pass false, because unary '*' binds tighter than '+': in - * "*p + 1" the sum belongs to the enclosing expression, and reading it as - * pointer arithmetic gives p[1] instead of one more than p[0]. It applies to - * this lvalue alone -- an lvalue parsed further in, as a subscript or a call - * argument, gets the normal behaviour from its own call. + * Only an object no instruction has read since the expression named it is owed + * one: "x = status;" leaves status on the stack as the assignment's value after + * the assignment has read it. */ -void read_lvalue(lvalue_t *lvalue, - var_t *var, - block_t *parent, - basic_block_t **bb, - bool eval, - opcode_t prefix_op, - bool allow_ptr_arith) +void discard_operand(block_t *parent, basic_block_t *bb) { - var_t *vd, *rs1, *rs2; - bool is_address_got = false; - bool is_member = false; - - /* Callers pass a find_var() result, which is NULL for a name that was never - * declared. - */ - if (!var) - error_at("Undeclared identifier", next_token_loc()); - - /* already peeked and have the variable */ - lex_expect(T_identifier); - - lvalue->type = var->type; - lvalue->size = get_size(var); - lvalue->ptr_level = var->ptr_level; - lvalue->is_func = var->is_func; - lvalue->is_reference = false; - - opstack_push(var); - - if (lex_peek(T_open_square, NULL) || lex_peek(T_arrow, NULL) || - lex_peek(T_dot, NULL)) - lvalue->is_reference = true; - - while (lex_peek(T_open_square, NULL) || lex_peek(T_arrow, NULL) || - lex_peek(T_dot, NULL)) { - if (lex_accept(T_open_square)) { - /* if subscripted member's is not yet resolved, dereference to - * resolve base address. e.g., dereference of "->" in "data->raw[0]" - * would be performed here. - */ - if (lvalue->is_reference && lvalue->ptr_level && is_member) { - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); - } - - /* var must be either a pointer or an array of some type For typedef - * pointers, check the type's ptr_level - */ - bool is_typedef_pointer = (var->type && var->type->ptr_level > 0); - if (var->ptr_level == 0 && var->array_size == 0 && - !is_typedef_pointer) - error_at("Cannot apply square operator to non-pointer", - cur_token_loc()); - - /* if nested pointer, still pointer Also handle typedef pointers - * which have ptr_level == 0 - */ - if ((var->ptr_level <= 1 || is_typedef_pointer) && - var->array_size == 0) { - /* For typedef pointers, get the size of the base type that the - * pointer points to - */ - if (lvalue->type->ptr_level > 0) { - /* This is a typedef pointer, get base type size */ - switch (lvalue->type->base_type) { - case TYPE_char: - lvalue->size = TY_char->size; - break; - case TYPE_short: - lvalue->size = TY_short->size; - break; - case TYPE_int: - lvalue->size = TY_int->size; - break; - case TYPE_void: - /* void pointers treated as byte pointers */ - lvalue->size = 1; - break; - default: - lvalue->size = lvalue->type->size; - break; - } - } else { - lvalue->size = lvalue->type->size; - } - } - - read_expr(parent, bb); - - /* multiply by element size For 2D arrays, check if this is the - * first or second dimension - */ - int multiplier = lvalue->size; - - /* If this is the first index of a 2D array, multiply by dim2 * - * element_size - */ - if (!is_address_got && var->array_dim2 > 0) - multiplier = var->array_dim2 * lvalue->size; - - if (multiplier != 1) { - vd = require_var(parent); - vd->init_val = multiplier; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_mul, vd, rs1, rs2, 0, NULL); - } - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); - - lex_expect(T_close_square); - is_address_got = true; - is_member = true; - lvalue->is_reference = true; - } else { - char token[MAX_ID_LEN]; - - if (lex_accept(T_arrow)) { - /* resolve where the pointer points at from the calculated - * address in a structure. - */ - if (is_member) { - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, - NULL); - } - } else { - lex_expect(T_dot); - - if (!is_address_got) { - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, - NULL); - - is_address_got = true; - } - } - - lex_ident(T_identifier, token); - - /* change type currently pointed to */ - var = find_member(token, lvalue->type); - if (!var) - error_at("Unknown struct or union member", next_token_loc()); - lvalue->type = var->type; - lvalue->ptr_level = var->ptr_level; - lvalue->is_func = var->is_func; - lvalue->size = get_size(var); - - /* if it is an array, get the address of first element instead of - * its value. - */ - if (var->array_size > 0) - lvalue->is_reference = false; - - /* move pointer to offset of structure */ - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = var->offset; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); - - is_address_got = true; - is_member = true; - } - } + var_t *var = opstack_pop(); + bool unread = var && var == unread_volatile_object; - if (!eval) + unread_volatile_object = NULL; + if (!unread || var->is_func || var->array_size || + (!var->ptr_level && is_record_type(var->type))) return; - /* Only handle pointer arithmetic if we have a pointer/array that hasn't - * been dereferenced. After array indexing like arr[0], we have a value, not - * a pointer. - */ - if (allow_ptr_arith && lex_peek(T_plus, NULL) && - (var->ptr_level || var->array_size) && !lvalue->is_reference) { - while (lex_peek(T_plus, NULL) && (var->ptr_level || var->array_size)) { - lex_expect(T_plus); - if (lvalue->is_reference) { - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, lvalue->size, - NULL); - } - - read_expr_operand(parent, bb); - - /* The element stepped over is the pointee. For a multi-level - * pointer that pointee is itself a pointer, so the stride is - * PTR_SIZE rather than the base type's width. - */ - if (var->ptr_level > 1) - lvalue->size = PTR_SIZE; - else - lvalue->size = lvalue->type->size; - - if (lvalue->size > 1) { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = lvalue->size; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_mul, vd, rs1, rs2, 0, NULL); - } - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - - /* A pointer plus an integer is still a pointer of the same type; - * without this the result looks like a plain int and a later - * dereference reads the base type's width instead of a pointer. - * - * Only genuine pointers are propagated. An array base has ptr_level - * 0, and copying that would label the sum with the element type, - * making get_size() report the element width for what is actually - * an address. - */ - if (var->ptr_level) { - vd->type = lvalue->type; - vd->ptr_level = var->ptr_level; - } - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); - } - } else { - /* Set and read only under 'is_reference'; the initializer says so to a - * compiler that cannot correlate the two tests. - */ - var_t *t = NULL; - - /* If operand is a reference, read the value and push to stack for the - * incoming addition/subtraction. Otherwise, use the top element of - * stack as the one of operands and the destination. - */ - if (lvalue->is_reference) { - rs1 = operand_stack[operand_stack_idx - 1]; - t = require_var(parent); - t->var_name = gen_name(); - opstack_push(t); - add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, NULL); - } - if (prefix_op != OP_generic) { - vd = require_var(parent); - vd->var_name = gen_name(); - - /* For pointer arithmetic, increment by the size of pointed-to type - */ - if (lvalue->ptr_level) - vd->init_val = lvalue->type->size; - else - vd->init_val = 1; - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - - rs2 = opstack_pop(); - if (lvalue->is_reference) - rs1 = opstack_pop(); - else - rs1 = operand_stack[operand_stack_idx - 1]; - vd = require_var(parent); - vd->var_name = gen_name(); - add_insn(parent, *bb, prefix_op, vd, rs1, rs2, 0, NULL); - - if (lvalue->is_reference) { - rs1 = vd; - vd = opstack_pop(); - - /* The column of arguments of the new insn of 'OP_write' is - * different from 'ph1_ir' - */ - add_insn(parent, *bb, OP_write, NULL, vd, rs1, lvalue->size, - NULL); - /* Push the new value onto the operand stack */ - opstack_push(rs1); - } else { - rs1 = vd; - vd = operand_stack[operand_stack_idx - 1]; - add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); - } - } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { - /* This arm appends three entries, so check for room once before - * writing any of them. - */ - if (se_idx + 3 > MAX_SIDE_EFFECT) - error_at("Too many postfix operators in one statement", - next_token_loc()); - - side_effect[se_idx].opcode = OP_load_constant; - vd = require_var(parent); - vd->var_name = gen_name(); - - /* Calculate increment size based on pointer type */ - int increment_size = 1; - if (lvalue->ptr_level && !lvalue->is_reference) { - increment_size = lvalue->type->size; - } else if (!lvalue->is_reference && lvalue->type && - lvalue->type->ptr_level > 0) { - /* This is a typedef pointer */ - switch (lvalue->type->base_type) { - case TYPE_char: - increment_size = TY_char->size; - break; - case TYPE_short: - increment_size = TY_short->size; - break; - case TYPE_int: - increment_size = TY_int->size; - break; - case TYPE_void: - increment_size = 1; - break; - default: - increment_size = lvalue->type->size; - break; - } - } - vd->init_val = increment_size; - - side_effect[se_idx].rd = vd; - side_effect[se_idx].rs1 = NULL; - side_effect[se_idx].rs2 = NULL; - se_idx++; - - /* Consume whichever operator is actually there. Testing only for - * '++' picks the right opcode but leaves a '--' in the stream, so - * postfix decrement parsed only where the leftover token happened - * to be harmless -- "i--;" as a statement worked, "a = i--" did - * not. - */ - opcode_t postfix_op = OP_sub; - if (lex_accept(T_increment)) - postfix_op = OP_add; - else - lex_expect(T_decrement); - side_effect[se_idx].opcode = postfix_op; - side_effect[se_idx].rs2 = vd; - if (lvalue->is_reference) - side_effect[se_idx].rs1 = opstack_pop(); - else - side_effect[se_idx].rs1 = operand_stack[operand_stack_idx - 1]; - vd = require_var(parent); - vd->var_name = gen_name(); - side_effect[se_idx].rd = vd; - se_idx++; - - if (lvalue->is_reference) { - side_effect[se_idx].opcode = OP_write; - side_effect[se_idx].rs2 = vd; - side_effect[se_idx].rs1 = opstack_pop(); - side_effect[se_idx].sz = lvalue->size; - side_effect[se_idx].rd = NULL; - opstack_push(t); - se_idx++; - } else { - side_effect[se_idx].opcode = OP_assign; - side_effect[se_idx].rs1 = vd; - side_effect[se_idx].rd = operand_stack[operand_stack_idx - 1]; - side_effect[se_idx].rs2 = NULL; - se_idx++; - } - } else { - if (lvalue->is_reference) { - /* pop the address and keep the read value */ - t = opstack_pop(); - opstack_pop(); - opstack_push(t); - } - } - } + var_t *copy = require_typed_ptr_var(parent, var->type, var->ptr_level); + copy->var_name = gen_name(); + add_insn(parent, bb, OP_assign, copy, var, NULL, 0, NULL); } -void read_logical(opcode_t op, block_t *parent, basic_block_t **bb) +/* Declarators with global storage are made available to the constant + * initializer parser through operand_stack. Scalar initialization consumes that + * entry itself, while zero and aggregate initialization do not. + */ +void discard_global_declarator_operand(var_t *var) { - var_t *vd; - - if (op != OP_log_and && op != OP_log_or) - error_at("encounter an invalid logical opcode in read_logical()", - cur_token_loc()); + if (operand_stack_idx && operand_stack[operand_stack_idx - 1] == var) + opstack_pop(); +} - /* Test the operand before the logical-and/or operator */ - vd = opstack_pop(); - add_insn(parent, *bb, OP_branch, NULL, vd, NULL, 0, NULL); +void read_expr(block_t *parent, basic_block_t **bb); - /* Create a proper branch label for the operand of the logical-and/or - * operation. - */ - basic_block_t *new_bb = bb_create(parent); - bb_connect(*bb, new_bb, op == OP_log_and ? THEN : ELSE); +/* Write a decoded string literal of @length bytes, which may include an + * embedded null character, followed by its terminating null character. + */ +int write_string_symbol(const char *data, int length) +{ + const int start_len = elf_rodata->size; - bb[0] = new_bb; + elf_write_blk(elf_rodata, data, length); + elf_write_byte(elf_rodata, 0); + return start_len; } -void finalize_logical(opcode_t op, - block_t *parent, - basic_block_t **bb, - basic_block_t *shared_bb) +/* Write the null-terminated string @data to .rodata. */ +int write_symbol(const char *data) { - basic_block_t *then, *then_next, *else_if, *else_bb; - basic_block_t *end = bb_create(parent); - var_t *vd, *log_op_res; - - if (op == OP_log_and) { - /* For example: a && b - * - * If handling the expression, the basic blocks will connect to each - * other as the following illustration: - * - * bb1 bb2 bb3 - * +-----------+ +-----------+ +---------+ - * | teq a, #0 | True | teq b, #0 | True | ldr 1 | - * | bne bb2 | ----> | bne bb3 | ----> | b bb5 | - * | b bb4 | | b bb4 | +---------+ - * +-----------+ +-----------+ | - * | | | - * | False | False | - * | | | - * | +---------+ +--------+ - * -------------> | ldr 0 | ------> | | - * | b bb5 | | | - * +---------+ +--------+ - * bb4 bb5 - * - * In this case, finalize_logical() should add some instructions to bb2 - * ~ bb5 and properly connect them to each other. - * - * Notice that - * - bb1 has been handled by read_logical(). - * - bb2 is equivalent to '*bb'. - * - bb3 needs to be created. - * - bb4 is 'shared_bb'. - * - bb5 needs to be created. - * - * Thus, here uses 'then', 'then_next', 'else_bb' and 'end' to - * respectively point to bb2 ~ bb5. Subsequently, perform the mentioned - * operations for finalizing. - */ - then = *bb; - then_next = bb_create(parent); - else_bb = shared_bb; - bb_connect(then, then_next, THEN); - bb_connect(then, else_bb, ELSE); - bb_connect(then_next, end, NEXT); - } else if (op == OP_log_or) { - /* For example: a || b - * - * Similar to handling logical-and operations, it should add some - * instructions to the basic blocks and connect them to each other for - * logical-or operations as in the figure: - * - * bb1 bb2 bb3 - * +-----------+ +-----------+ +---------+ - * | teq a, #0 | False | teq b, #0 | False | ldr 0 | - * | bne bb4 | ----> | bne bb4 | ----> | b bb5 | - * | b bb2 | | b bb3 | +---------+ - * +-----------+ +-----------+ | - * | | | - * | True | True | - * | | | - * | +---------+ +--------+ - * -------------> | ldr 1 | ------> | | - * | b bb5 | | | - * +---------+ +--------+ - * bb4 bb5 - * - * Similarly, here uses 'else_if', 'else_bb', 'then' and 'end' to - * respectively point to bb2 ~ bb5, and then finishes the finalization. - */ - then = shared_bb; - else_if = *bb; - else_bb = bb_create(parent); - bb_connect(else_if, then, THEN); - bb_connect(else_if, else_bb, ELSE); - bb_connect(then, end, NEXT); - } else - error_at("encounter an invalid logical opcode in finalize_logical()", - cur_token_loc()); - bb_connect(else_bb, end, NEXT); - - /* Create the branch instruction for final logical-and/or operand */ - vd = opstack_pop(); - add_insn(parent, op == OP_log_and ? then : else_if, OP_branch, NULL, vd, - NULL, 0, NULL); - - /* If handling logical-and operation, here creates a true branch for the - * logical-and operation and assigns a true value. - * - * Otherwise, create a false branch and assign a false value for logical-or - * operation. - */ - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = op == OP_log_and; - add_insn(parent, op == OP_log_and ? then_next : else_bb, OP_load_constant, - vd, NULL, NULL, 0, NULL); - - log_op_res = require_var(parent); - log_op_res->var_name = gen_name(); - add_insn(parent, op == OP_log_and ? then_next : else_bb, OP_assign, - log_op_res, vd, NULL, 0, NULL); - - /* After assigning a value, go to the final basic block, this is done by BB - * fallthrough. - */ - - /* Create the shared branch and assign the other value for the other - * condition of a logical-and/or operation. - * - * If handing a logical-and operation, assign a false value. else, assign a - * true value for a logical-or operation. - */ - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = op != OP_log_and; - add_insn(parent, op == OP_log_and ? else_bb : then, OP_load_constant, vd, - NULL, NULL, 0, NULL); - - add_insn(parent, op == OP_log_and ? else_bb : then, OP_assign, log_op_res, - vd, NULL, 0, NULL); - - log_op_res->is_logical_ret = true; - opstack_push(log_op_res); - - bb[0] = end; + return write_string_symbol(data, strlen(data)); } -void read_ternary_operation(block_t *parent, basic_block_t **bb) +int write_wide_symbol(const int *data, int length) { - var_t *vd; - - if (!lex_accept(T_question)) - return; - - /* ternary-operator */ - vd = opstack_pop(); - add_insn(parent, *bb, OP_branch, NULL, vd, NULL, 0, NULL); - - basic_block_t *then_ = bb_create(parent); - basic_block_t *else_ = bb_create(parent); - basic_block_t *end_ternary = bb_create(parent); - bb_connect(then_, end_ternary, NEXT); - bb_connect(else_, end_ternary, NEXT); - - /* true branch */ - read_expr(parent, &then_); - bb_connect(*bb, then_, THEN); - - if (!lex_accept(T_colon)) { - /* ternary operator in standard C needs three operands */ - error_at("Expected ':' in conditional expression", next_token_loc()); - } + int start_len = elf_rodata->size; - var_t *true_val = opstack_pop(); - - /* false branch */ - read_expr(parent, &else_); - bb_connect(*bb, else_, ELSE); - var_t *false_val = opstack_pop(); - bool true_array = is_array_literal_placeholder(true_val); - bool false_array = is_array_literal_placeholder(false_val); - bool true_ptr_like = is_pointer_like_value(true_val); - bool false_ptr_like = is_pointer_like_value(false_val); - - /* The ternary result must look like whichever side is pointer-like. If the - * "true" expression is still a raw array literal but the "false" side is a - * plain scalar, materialize the literal now so both branches produce - * comparable scalar SSA values. - */ - true_val = scalarize_array_literal_if_needed( - parent, &then_, true_val, false_val ? false_val->type : NULL, - true_array && !false_ptr_like); + for (int i = 0; i < length; i++) + elf_write_int(elf_rodata, data[i]); + elf_write_int(elf_rodata, 0); + return start_len; +} - /* Apply the same conversion symmetrically when only the false branch is a - * literal array. This prevents OP_assign from trying to move array storage - * into a scalar destination later in code generation. - */ - false_val = scalarize_array_literal_if_needed( - parent, &else_, false_val, true_val ? true_val->type : NULL, - false_array && !true_ptr_like); - - vd = require_var(parent); - vd->var_name = gen_name(); - add_insn(parent, then_, OP_assign, vd, true_val, NULL, 0, NULL); - add_insn(parent, else_, OP_assign, vd, false_val, NULL, 0, NULL); - - var_t *array_ref = NULL; - if (is_array_literal_placeholder(true_val)) - array_ref = true_val; - else if (is_array_literal_placeholder(false_val)) - array_ref = false_val; - - if (array_ref) { - vd->array_size = array_ref->array_size; - vd->init_val = array_ref->init_val; - vd->type = array_ref->type; - } +/* The value of a wide character constant comes from wide_character_constant(); + * report one whose escape does not fit an execution-wide unit. + */ +int parse_wide_character_constant(const char *literal) +{ + int value; - vd->is_ternary_ret = true; - opstack_push(vd); - bb[0] = end_ternary; + if (!wide_character_constant(literal, &value)) + error_at("Invalid wide character escape sequence", cur_token_loc()); + return value; } -bool read_body_assignment(char *token, - block_t *parent, - opcode_t prefix_op, - basic_block_t **bb) +int read_wstring_units(int *units, int capacity) { - var_t *var = find_local_var(token, parent), *vd, *rs1, *rs2, *t; - if (!var) - var = find_global_var(token); - - if (var) { - int one = 0; - opcode_t op = OP_generic; - lvalue_t lvalue; - int size = 0; - - /* has memory address that we want to set */ - read_lvalue(&lvalue, var, parent, bb, false, OP_generic, true); - size = lvalue.size; - - if (lex_accept(T_increment)) { - op = OP_add; - one = 1; - } else if (lex_accept(T_decrement)) { - op = OP_sub; - one = 1; - } else if (accept_compound_assign_op(&op)) { - /* op now holds the arithmetic the operator applies */ - } else if (lex_peek(T_open_bracket, NULL)) { - /* Dereference lvalue first if lvalue is a member access; otherwise, - * pass the function pointer value on the stack to - * read_indirect_call. - */ - if (lvalue.is_reference) { - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); - } - - read_indirect_call(parent, bb); - return true; - } else if (prefix_op == OP_generic) { - lex_expect(T_assign); - } else { - op = prefix_op; - one = 1; - } - - if (op != OP_generic) { - int increment_size = 1; - - /* if we have a pointer, shift it by element size But not if we are - * operating on a dereferenced value (array indexing) - */ - if (lvalue.ptr_level && !lvalue.is_reference) - increment_size = lvalue.type->size; - /* Also check for typedef pointers which have is_ptr == 0 */ - else if (!lvalue.is_reference && lvalue.type && - lvalue.type->ptr_level > 0) { - /* This is a typedef pointer, get the base type size */ - switch (lvalue.type->base_type) { - case TYPE_char: - increment_size = TY_char->size; - break; - case TYPE_short: - increment_size = TY_short->size; - break; - case TYPE_int: - increment_size = TY_int->size; - break; - case TYPE_void: - /* void pointers treated as byte pointers */ - increment_size = 1; - break; - default: - /* For struct pointers and other types */ - increment_size = lvalue.type->size; - break; - } - } - - /* If operand is a reference, read the value and push to stack for - * the incoming addition/subtraction. Otherwise, use the top element - * of stack as the one of operands and the destination. - */ - if (one == 1) { - if (lvalue.is_reference) { - t = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, - NULL); - } else - t = operand_stack[operand_stack_idx - 1]; - - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = increment_size; - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - - rs2 = vd; - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); - - if (lvalue.is_reference) { - add_insn(parent, *bb, OP_write, NULL, t, vd, size, NULL); - } else { - vd = resize_var(parent, bb, vd, t); - add_insn(parent, *bb, OP_assign, t, vd, NULL, 0, NULL); - } - } else { - if (lvalue.is_reference) { - t = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, - NULL); - } else - t = operand_stack[operand_stack_idx - 1]; - - read_expr(parent, bb); - - var_t *rhs_val = opstack_pop(); - rhs_val = scalarize_array_literal_if_needed( - parent, bb, rhs_val, lvalue.type, - !lvalue.ptr_level && !lvalue.is_reference); - opstack_push(rhs_val); - vd = require_var(parent); - vd->init_val = increment_size; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_mul, vd, rs1, rs2, 0, NULL); - - rs2 = opstack_pop(); - rs1 = opstack_pop(); - vd = require_var(parent); - vd->var_name = gen_name(); - add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); - - if (lvalue.is_reference) { - add_insn(parent, *bb, OP_write, NULL, t, vd, lvalue.size, - NULL); - } else { - vd = resize_var(parent, bb, vd, t); - add_insn(parent, *bb, OP_assign, t, vd, NULL, 0, NULL); - } - } - } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - - if (lvalue.is_func) { - rs2 = opstack_pop(); - rs1 = opstack_pop(); - - /* is_func labels both function symbols and function-pointer - * variables. A variable on the RHS must contribute its stored - * pointer value, rather than its identifier being lowered as a - * function address. - */ - if (rs2->is_func && find_var(rs2->var_name, parent) == rs2) { - t = require_ref_var(parent, rs2->type, rs2->ptr_level); - t->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, t, rs2, NULL, 0, NULL); - - vd = require_var(parent); - vd->var_name = gen_name(); - add_insn(parent, *bb, OP_read, vd, t, NULL, PTR_SIZE, NULL); - rs2 = vd; - } - - /* Acquire destination address of lvalue if lvalue is a local - * variable. - */ - if (!lvalue.is_reference) { - var_t *addr = - require_ref_var(parent, lvalue.type, lvalue.ptr_level); - addr->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, addr, rs1, NULL, 0, - NULL); - rs1 = addr; - } - - add_insn(parent, *bb, OP_write, NULL, rs1, rs2, PTR_SIZE, NULL); - } else if (lvalue.is_reference) { - rs2 = opstack_pop(); - rs1 = opstack_pop(); - add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); - } else { - rs1 = opstack_pop(); - vd = opstack_pop(); - rs1 = resize_var(parent, bb, rs1, vd); - add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); - } - } - return true; - } - return false; + char literal[MAX_STRING_LEN]; + int length = 0; + + do { + int part_length; + + lex_ident(T_wstring, literal); + part_length = + decode_wstring_units(literal, units + length, capacity - length); + if (part_length < 0) + error_at("Invalid wide string escape sequence", cur_token_loc()); + length += part_length; + } while (lex_peek(T_wstring, NULL)); + return length; } -int read_primary_constant(void) +int get_size(var_t *var) { - /* return signed constant */ - int isneg = 0, res; - char buffer[MAX_TOKEN_LEN]; - if (lex_accept(T_minus)) - isneg = 1; - if (lex_accept(T_open_bracket)) { - res = read_primary_constant(); - lex_expect(T_close_bracket); - } else if (lex_peek(T_numeric, buffer)) { - res = parse_numeric_constant(buffer); - lex_expect(T_numeric); - } else if (lex_peek(T_char, buffer)) { - char unescaped[MAX_TOKEN_LEN]; - unescape_string(buffer, unescaped, MAX_TOKEN_LEN); - res = unescaped[0]; - lex_expect(T_char); - } else - error_at("Invalid value after assignment", next_token_loc()); - if (isneg) - return (-1) * res; - return res; + if (var->ptr_level || var->is_func) + return PTR_SIZE; + return var->type->size; } -int eval_expression_imm(opcode_t op, int op1, int op2) +int get_operator_prio(opcode_t op) { - /* return immediate result */ - int tmp = op2; - int res = 0; + /* https://www.cs.uic.edu/~i109/Notes/COperatorPrecedenceTable.pdf */ switch (op) { - case OP_add: - res = op1 + op2; - break; - case OP_sub: - res = op1 - op2; - break; - case OP_mul: - res = op1 * op2; - break; - case OP_div: - if (!op2) - error_at("Division by zero in constant expression", - cur_token_loc()); - - /* INT_MIN / -1 has no representable result; on x86 it raises SIGFPE - * rather than producing one. - */ - if (op1 == INT_MIN && op2 == -1) - error_at("Overflow in constant expression", cur_token_loc()); - res = op1 / op2; - break; - case OP_mod: - if (!op2) - error_at("Modulo by zero in constant expression", cur_token_loc()); - if (op1 == INT_MIN && op2 == -1) - error_at("Overflow in constant expression", cur_token_loc()); - /* Use bitwise AND for modulo optimization when divisor is power of 2 */ - if (tmp == INT_MIN) { - res = op1 % op2; - break; - } - tmp = tmp < 0 ? -tmp : tmp; - tmp &= (tmp - 1); - if (tmp != 0) { - res = op1 % op2; - break; - } - op2 = op2 < 0 ? -op2 : op2; - res = op1 & (op2 - 1); - if (op1 < 0 && res != 0) - res -= op2; - break; - case OP_lshift: - res = op1 << op2; - break; - case OP_rshift: - res = op1 >> op2; - break; - case OP_log_and: - res = op1 && op2; - break; + case OP_ternary: + return 3; case OP_log_or: - res = op1 || op2; - break; + return 4; + case OP_log_and: + return 5; + case OP_bit_or: + return 6; + case OP_bit_xor: + return 7; + case OP_bit_and: + return 8; case OP_eq: - res = op1 == op2; - break; case OP_neq: - res = op1 != op2; - break; + return 9; case OP_lt: - res = op1 < op2; - break; - case OP_gt: - res = op1 > op2; - break; case OP_leq: - res = op1 <= op2; - break; + case OP_gt: case OP_geq: - res = op1 >= op2; - break; - case OP_bit_and: - res = op1 & op2; - break; - case OP_bit_or: - res = op1 | op2; - break; - case OP_bit_xor: - res = op1 ^ op2; - break; + return 10; + case OP_lshift: + case OP_rshift: + return 11; + case OP_add: + case OP_sub: + return 12; + case OP_mul: + case OP_div: + case OP_mod: + return 13; default: - error_at("The requested operation is not supported.", cur_token_loc()); - } - return res; -} - -bool read_global_assignment(char *token); -void eval_ternary_imm(int cond, char *token) -{ - if (cond == 0) { - while (!lex_peek(T_colon, NULL)) { - lex_next(); - } - lex_accept(T_colon); - read_global_assignment(token); - } else { - read_global_assignment(token); - lex_expect(T_colon); - while (!lex_peek(T_semicolon, NULL)) { - lex_next(); - } - } -} - -bool read_global_assignment(char *token) -{ - var_t *vd, *rs1, *var; - block_t *parent = GLOBAL_BLOCK; - basic_block_t *bb = GLOBAL_FUNC->bbs; - - /* global initialization must be constant */ - var = find_global_var(token); - if (var) { - if (lex_peek(T_string, NULL)) { - /* String literal global initialization: String literals are now - * stored in .rodata section. TODO: Implement compile-time address - * resolution for global pointer initialization with rodata - * addresses (e.g., char *p = "str";) - */ - read_literal_param(parent, bb); - rs1 = opstack_pop(); - vd = var; - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); - return true; - } - - opcode_t op_stack[MAX_OPERATOR_STACK_SIZE]; - opcode_t op, next_op; - int val_stack[MAX_OPERATOR_STACK_SIZE]; - int op_stack_index = 0, val_stack_index = 0; - int operand1, operand2; - operand1 = read_primary_constant(); - op = get_operator(); - /* only one value after assignment */ - if (op == OP_generic) { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = operand1; - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - - rs1 = vd; - vd = opstack_pop(); - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); - return true; - } - if (op == OP_ternary) { - lex_expect(T_question); - eval_ternary_imm(operand1, token); - return true; - } - operand2 = read_primary_constant(); - next_op = get_operator(); - if (next_op == OP_generic) { - /* only two operands, apply and return */ - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = eval_expression_imm(op, operand1, operand2); - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - - rs1 = vd; - vd = opstack_pop(); - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); - return true; - } - - /* using stack if operands more than two */ - op_stack[op_stack_index++] = op; - op = next_op; - val_stack[val_stack_index++] = operand1; - val_stack[val_stack_index++] = operand2; - - while (op != OP_generic && op != OP_ternary) { - if (op_stack_index > 0) { - /* we have a continuation, use stack */ - int same_op = 0; - do { - opcode_t stack_op = op_stack[op_stack_index - 1]; - if (get_operator_prio(stack_op) >= get_operator_prio(op)) { - operand1 = val_stack[val_stack_index - 2]; - operand2 = val_stack[val_stack_index - 1]; - val_stack_index -= 2; - - /* apply stack operator and push result back */ - val_stack[val_stack_index++] = - eval_expression_imm(stack_op, operand1, operand2); - - /* pop op stack */ - op_stack_index--; - } else { - same_op = 1; - } - /* continue util next operation is higher prio */ - } while (op_stack_index > 0 && same_op == 0); - } - /* push next operand on stack */ - if (val_stack_index >= MAX_OPERATOR_STACK_SIZE || - op_stack_index >= MAX_OPERATOR_STACK_SIZE) - fatal("Constant expression too complex"); - val_stack[val_stack_index++] = read_primary_constant(); - /* push operator on stack */ - op_stack[op_stack_index++] = op; - op = get_operator(); - } - /* unwind stack and apply operations */ - while (op_stack_index > 0) { - opcode_t stack_op = op_stack[op_stack_index - 1]; - - /* pop stack and apply operators */ - operand1 = val_stack[val_stack_index - 2]; - operand2 = val_stack[val_stack_index - 1]; - val_stack_index -= 2; - - /* apply stack operator and push value back on stack */ - val_stack[val_stack_index++] = - eval_expression_imm(stack_op, operand1, operand2); - - if (op_stack_index == 1) { - if (op == OP_ternary) { - lex_expect(T_question); - eval_ternary_imm(val_stack[0], token); - } else { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = val_stack[0]; - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - - rs1 = vd; - vd = opstack_pop(); - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); - } - return true; - } - - /* pop op stack */ - op_stack_index--; - } - if (op == OP_ternary) { - lex_expect(T_question); - eval_ternary_imm(val_stack[0], token); - } else { - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = val_stack[0]; - add_insn(parent, GLOBAL_FUNC->bbs, OP_load_constant, vd, NULL, NULL, - 0, NULL); - - rs1 = vd; - vd = opstack_pop(); - add_insn(parent, GLOBAL_FUNC->bbs, OP_assign, vd, rs1, NULL, 0, - NULL); - } - return true; + return 0; } - return false; } -void perform_side_effect(block_t *parent, basic_block_t *bb) +opcode_t get_operator(void) { - for (int i = 0; i < se_idx; i++) { - insn_t *insn = &side_effect[i]; - add_insn(parent, bb, insn->opcode, insn->rd, insn->rs1, insn->rs2, - insn->sz, insn->str); - } - se_idx = 0; + opcode_t op = OP_generic; + if (lex_accept(T_plus)) + op = OP_add; + else if (lex_accept(T_minus)) + op = OP_sub; + else if (lex_accept(T_asterisk)) + op = OP_mul; + else if (lex_accept(T_divide)) + op = OP_div; + else if (lex_accept(T_mod)) + op = OP_mod; + else if (lex_accept(T_lshift)) + op = OP_lshift; + else if (lex_accept(T_rshift)) + op = OP_rshift; + else if (lex_accept(T_log_and)) + op = OP_log_and; + else if (lex_accept(T_log_or)) + op = OP_log_or; + else if (lex_accept(T_eq)) + op = OP_eq; + else if (lex_accept(T_noteq)) + op = OP_neq; + else if (lex_accept(T_lt)) + op = OP_lt; + else if (lex_accept(T_le)) + op = OP_leq; + else if (lex_accept(T_gt)) + op = OP_gt; + else if (lex_accept(T_ge)) + op = OP_geq; + else if (lex_accept(T_ampersand)) + op = OP_bit_and; + else if (lex_accept(T_bit_or)) + op = OP_bit_or; + else if (lex_accept(T_bit_xor)) + op = OP_bit_xor; + else if (lex_peek(T_question, NULL)) + op = OP_ternary; + return op; } -basic_block_t *read_code_block(func_t *func, - block_t *parent, - basic_block_t *bb); - -/* A switch, its cases, and the block they break out of. */ -basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) +var_t *promote_unchecked(block_t *block, + basic_block_t **bb, + var_t *var, + type_t *target_type, + int target_ptr) { - char token[MAX_ID_LEN]; - var_t *vd; - var_t *rs1; - var_t *rs2; - - bool is_default = false; - - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; - - lex_expect(T_open_bracket); - read_expr(parent, &bb); - lex_expect(T_close_bracket); - - /* create exit jump for breaks */ - basic_block_t *switch_end = bb_create(parent); - break_bb_push(switch_end); - basic_block_t *true_body_ = bb_create(parent); - - lex_expect(T_open_curly); - while (lex_peek(T_default, NULL) || lex_peek(T_case, NULL)) { - if (lex_accept(T_default)) - is_default = true; - else { - int case_val; - - lex_accept(T_case); - char literal[MAX_TOKEN_LEN]; - - if (lex_peek_n(T_numeric, literal, MAX_TOKEN_LEN)) { - case_val = parse_numeric_constant(literal); - lex_expect(T_numeric); - } else if (lex_peek_n(T_char, literal, MAX_TOKEN_LEN)) { - char unescaped[MAX_TOKEN_LEN]; - if (unescape_string(literal, unescaped, MAX_TOKEN_LEN) < 0) - error_at("Invalid escape sequence", next_token_loc()); - case_val = unescaped[0]; - lex_expect(T_char); - } else if (lex_peek(T_identifier, token)) { - const constant_t *cd = find_constant(token); - if (!cd) - error_at("Unknown constant in case label", cur_token_loc()); - case_val = cd->value; - lex_expect(T_identifier); - } else { - fatal("Not a valid case value"); - } - - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = case_val; - opstack_push(vd); - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - - vd = require_var(parent); - vd->var_name = gen_name(); - rs1 = opstack_pop(); - rs2 = operand_stack[operand_stack_idx - 1]; - add_insn(parent, bb, OP_eq, vd, rs1, rs2, 0, NULL); - - add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); - } - lex_expect(T_colon); - - if (is_default) - /* there's no condition if it is a default label */ - bb_connect(bb, true_body_, NEXT); - else - bb_connect(bb, true_body_, THEN); - - int control = 0; - - while (!lex_peek(T_case, NULL) && !lex_peek(T_close_curly, NULL) && - !lex_peek(T_default, NULL)) { - true_body_ = read_body_statement(parent, true_body_); - control = 1; - } - - if (control && true_body_) { - /* Create a new body block for next case, and connect the last body - * block which lacks 'break' to it to make that one ignore the - * upcoming cases. - */ - n = bb_create(parent); - bb_connect(true_body_, n, NEXT); - true_body_ = n; - } - - if (!lex_peek(T_close_curly, NULL)) { - if (is_default) - error_at("Label default should be the last one", - next_token_loc()); - - /* create a new conditional block for next case */ - n = bb_create(parent); - bb_connect(bb, n, ELSE); - bb = n; - - /* create a new body block for next case if the last body block - * exits 'switch'. - */ - if (!true_body_) - true_body_ = bb_create(parent); - } else if (!is_default) { - /* handle missing default label */ - bb_connect(bb, switch_end, ELSE); - } - } - - /* remove the expression in switch() */ - opstack_pop(); - lex_expect(T_close_curly); - - if (true_body_) - /* if the last label has no explicit break, connect it to the end */ - bb_connect(true_body_, switch_end, NEXT); - - break_exit_idx--; - - int dangling = 1; - for (int i = 0; i < switch_end->prev_idx; i++) - if (switch_end->prev[i].bb) - dangling = 0; - - if (dangling) - return NULL; + var_t *rd = require_typed_ptr_var(block, target_type, target_ptr); + rd->var_name = gen_name(); - return switch_end; + /* Encode both source and target sizes in src1: Lower 16 bits: target size + * Upper 16 bits: source size This allows codegen to distinguish between + * different promotion types without changing IR semantics. + */ + int encoded_size = ((var->type->size) << 16); + if (target_ptr) + encoded_size |= PTR_SIZE; + else + encoded_size |= target_type->size; + add_insn(block, *bb, OP_sign_ext, rd, var, NULL, encoded_size, NULL); + return rd; } -/* A for loop: setup, condition, body and increment. */ -basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) +var_t *promote(block_t *block, + basic_block_t **bb, + var_t *var, + type_t *target_type, + int target_ptr) { - char token[MAX_ID_LEN]; - type_t *type; - var_t *vd; - var_t *rs1; - var_t *var; - opcode_t prefix_op = OP_generic; - - lex_expect(T_open_bracket); - - /* synthesize for loop block */ - block_t *blk = add_block(parent, parent->func); - - /* setup - execute once */ - basic_block_t *setup = bb_create(blk); - bb_connect(bb, setup, NEXT); - - if (!lex_accept(T_semicolon)) { - if (!lex_peek(T_identifier, token)) - error_at("Unexpected token when parsing for loop", - next_token_loc()); - - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - } - type = find_type(token, find_type_flag); - if (type) { - var = require_typed_var(blk, type); - read_full_var_decl(var, false, false); - add_insn(blk, setup, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(setup, var); - if (lex_accept(T_assign)) { - read_expr(blk, &setup); - read_ternary_operation(blk, &setup); - - rs1 = resize_var(parent, &bb, opstack_pop(), var); - add_insn(blk, setup, OP_assign, var, rs1, NULL, 0, NULL); - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(blk, setup); - - /* multiple (partial) declarations */ - nv = require_typed_var(blk, type); - read_partial_var_decl(nv, var); /* partial */ - add_insn(blk, setup, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(setup, nv); - if (lex_accept(T_assign)) { - read_expr(blk, &setup); - - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); - } - } - } else { - read_body_assignment(token, blk, OP_generic, &setup); - } - - lex_expect(T_semicolon); - } - - basic_block_t *cond_ = bb_create(blk); - basic_block_t *for_end = bb_create(parent); - basic_block_t *cond_start = cond_; - break_bb_push(for_end); - bb_connect(setup, cond_, NEXT); - - /* condition - check before the loop */ - if (!lex_accept(T_semicolon)) { - read_expr(blk, &cond_); - lex_expect(T_semicolon); - } else { - /* always true */ - vd = require_var(blk); - vd->init_val = 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(blk, cond_, OP_load_constant, vd, NULL, NULL, 0, NULL); - } - bb_connect(cond_, for_end, ELSE); - - vd = opstack_pop(); - add_insn(blk, cond_, OP_branch, NULL, vd, NULL, 0, NULL); - - basic_block_t *inc_ = bb_create(blk); - continue_bb_push(inc_); - - /* increment after each loop */ - if (!lex_accept(T_close_bracket)) { - if (lex_accept(T_increment)) - prefix_op = OP_add; - else if (lex_accept(T_decrement)) - prefix_op = OP_sub; - lex_peek(T_identifier, token); - read_body_assignment(token, blk, prefix_op, &inc_); - lex_expect(T_close_bracket); - } - - /* loop body */ - basic_block_t *body_ = bb_create(blk); - bb_connect(cond_, body_, THEN); - body_ = read_body_statement(blk, body_); - - /* Normal fallthrough from the loop body goes through the increment block. A - * continue statement may already have connected another predecessor to - * inc_. - */ - if (body_) - bb_connect(body_, inc_, NEXT); + /* Effectively checking whether var has size of int */ + if (var->type->size == target_type->size || var->ptr_level || + var->array_size) + return var; - /* An empty increment block still needs its back-edge when it is reachable - * through normal fallthrough or continue. - * - * Do not connect a completely unreachable increment block, such as: - * - * for (;;) { - * break; - * } - */ - bool has_pred = false; - for (int i = 0; i < inc_->prev_idx; i++) { - if (inc_->prev[i].bb) { - has_pred = true; - break; - } + if (var->type->size > TY_int->size && !var->ptr_level) { + printf("Warning: Suspicious type promotion %s\n", var->type->type_name); + return var; } - if (has_pred) - bb_connect(inc_, cond_start, NEXT); - /* jump to increment */ - continue_pos_idx--; - break_exit_idx--; - return for_end; + return promote_unchecked(block, bb, var, target_type, target_ptr); } -/* A do-while loop, whose condition is tested after the body. */ -basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) +var_t *truncate_unchecked(block_t *block, + basic_block_t **bb, + var_t *var, + type_t *target_type, + int target_ptr) { - var_t *vd; - - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; - - basic_block_t *cond_ = bb_create(parent); - basic_block_t *do_while_end = bb_create(parent); - - continue_bb_push(cond_); - break_bb_push(do_while_end); - - basic_block_t *do_body = read_body_statement(parent, bb); - if (do_body) - bb_connect(do_body, cond_, NEXT); - - lex_expect(T_while); - lex_expect(T_open_bracket); - read_expr(parent, &cond_); - lex_expect(T_close_bracket); - - vd = opstack_pop(); - add_insn(parent, cond_, OP_branch, NULL, vd, NULL, 0, NULL); - - lex_expect(T_semicolon); - - for (int i = 0; i < cond_->prev_idx; i++) { - if (cond_->prev[i].bb) { - bb_connect(cond_, bb, THEN); - bb_connect(cond_, do_while_end, ELSE); - break; - } - /* if breaking out of loop, skip condition block */ - } - - continue_pos_idx--; - break_exit_idx--; - return do_while_end; + var_t *rd = require_typed_ptr_var(block, target_type, target_ptr); + rd->var_name = gen_name(); + add_insn(block, *bb, OP_trunc, rd, var, NULL, + target_ptr ? PTR_SIZE : target_type->size, NULL); + return rd; } -/* A local struct or union declaration. */ -basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) +var_t *normalize_bool(block_t *block, basic_block_t **bb, var_t *var) { - char token[MAX_ID_LEN]; - type_t *type; - var_t *rs1; - var_t *var; - bool is_const = false; + var_t *zero; + var_t *rd; - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - } - lex_ident(T_identifier, token); - type = find_type(token, find_type_flag); - if (type) { - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - read_partial_var_decl(var, NULL); - add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(bb, var); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { - parse_array_init(var, parent, &bb, 1); /* Always emit code */ - } else if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_typedef)) { - /* C90-compliant struct compound literal support */ - type_t *struct_type = var->type; - - /* Handle typedef by getting actual struct type */ - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - lex_expect(T_open_curly); - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Parse field value expression */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *val = opstack_pop(); - - /* Initialize field if within bounds */ - if (field_idx < struct_type->num_fields) { - var_t *field = &struct_type->fields[field_idx]; - - /* Create target variable for field */ - var_t *field_val = - resize_to(parent, &bb, val, field->type, - field->ptr_level); - - /* Compute field address: &struct + field_offset */ - var_t *struct_addr = require_var(parent); - struct_addr->var_name = gen_name(); - add_insn(parent, bb, OP_address_of, struct_addr, - var, NULL, 0, NULL); - - var_t *field_addr = struct_addr; - if (field->offset > 0) { - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, bb, OP_load_constant, offset, - NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, bb, OP_add, addr, struct_addr, - offset, 0, NULL); - field_addr = addr; - } - - /* Write field value */ - int field_size = size_var(field); - add_insn(parent, bb, OP_write, NULL, field_addr, - field_val, field_size, NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - lex_expect(T_close_curly); - } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - - var_t *rhs = opstack_pop(); - rhs = scalarize_array_literal_if_needed( - parent, &bb, rhs, var->type, - !var->ptr_level && var->array_size == 0); - - rs1 = resize_var(parent, &bb, rhs, var); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); - } - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(parent, bb); - - /* multiple (partial) declarations */ - nv = require_typed_var(parent, type); - read_inner_var_decl(nv, false, false); - add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { - parse_array_init(nv, parent, &bb, true); - } else if (lex_peek(T_open_curly, NULL) && - (nv->type->base_type == TYPE_struct || - nv->type->base_type == TYPE_typedef)) { - /* C90-compliant struct compound literal support */ - type_t *struct_type = nv->type; - - /* Handle typedef by getting actual struct type */ - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - lex_expect(T_open_curly); - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Parse field value expression */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *val = opstack_pop(); - - /* Initialize field if within bounds */ - if (field_idx < struct_type->num_fields) { - var_t *field = &struct_type->fields[field_idx]; - - /* Create target variable for field */ - var_t *field_val = - resize_to(parent, &bb, val, field->type, - field->ptr_level); - - /* Compute field address: &struct + field_offset - */ - var_t *struct_addr = require_var(parent); - struct_addr->var_name = gen_name(); - add_insn(parent, bb, OP_address_of, struct_addr, - nv, NULL, 0, NULL); - - var_t *field_addr = struct_addr; - if (field->offset > 0) { - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, bb, OP_load_constant, - offset, NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, bb, OP_add, addr, - struct_addr, offset, 0, NULL); - field_addr = addr; - } - - /* Write field value */ - int field_size = size_var(field); - add_insn(parent, bb, OP_write, NULL, field_addr, - field_val, field_size, NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - lex_expect(T_close_curly); - } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *rhs = opstack_pop(); - rhs = scalarize_array_literal_if_needed( - parent, &bb, rhs, nv->type, - !nv->ptr_level && nv->array_size == 0); - - rs1 = resize_var(parent, &bb, rhs, nv); - add_insn(parent, bb, OP_assign, nv, rs1, NULL, 0, NULL); - } - } - } - lex_expect(T_semicolon); - return bb; - } - error_at("Unknown struct/union type", next_token_loc()); + if (is_bool_scalar(var->type, var->ptr_level)) + return var; + if (var->is_const && !var->ptr_level) { + rd = require_typed_var(block, TY_bool); + rd->var_name = gen_name(); + rd->init_val = var->init_val || var->init_val_hi; + rd->is_const = true; + add_insn(block, *bb, OP_load_constant, rd, NULL, NULL, 0, NULL); + return rd; + } + + zero = require_typed_var(block, TY_int); + zero->var_name = gen_name(); + zero->init_val = 0; + zero->is_const = true; + add_insn(block, *bb, OP_load_constant, zero, NULL, NULL, 0, NULL); + + rd = require_typed_var(block, TY_bool); + rd->var_name = gen_name(); + add_insn(block, *bb, OP_neq, rd, var, zero, 0, NULL); + return rd; } -/* Everything a statement can still be: a declaration, an assignment, a call, or - * an expression evaluated for its effect. +/* The value an OP_write of a scalar object of @type should store. A store + * narrows to the object width by itself, which is the conversion C wants for + * every integer type except _Bool: 0x100 has to become 1, not its low byte 0. + * Paths that write a plain assignment through an address share this rather than + * each resizing their value. */ -basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) +var_t *convert_stored_value(block_t *block, + basic_block_t **bb, + var_t *value, + type_t *type, + int ptr_level) { - char token[MAX_ID_LEN]; - func_t *func; - type_t *type; - var_t *rs1; - var_t *var; - opcode_t prefix_op = OP_generic; - bool is_const = false; - - if (lex_accept(T_const)) { - is_const = true; - /* After const, we expect a type */ - if (!lex_peek(T_identifier, token)) - error_at("Expected type after const", next_token_loc()); - } - - /* statement with prefix */ - if (!is_const && lex_accept(T_increment)) - prefix_op = OP_add; - else if (!is_const && lex_accept(T_decrement)) - prefix_op = OP_sub; - /* must be an identifier or asterisk (for pointer dereference) */ - bool has_asterisk = lex_peek(T_asterisk, NULL); - if (!is_const && !lex_peek(T_identifier, token) && !has_asterisk) - error_at("Unexpected token", next_token_loc()); - - /* is it a variable declaration? Special handling when statement starts with - * asterisk - */ - if (has_asterisk) { - /* For "*identifier", check if identifier is a type. If not, it's a - * dereference, not a declaration. - */ - token_t *saved_token = cur_token; - - /* Skip the asterisk to peek at the identifier */ - lex_accept(T_asterisk); - char next_ident[MAX_TOKEN_LEN]; - bool could_be_type = false; - - if (lex_peek(T_identifier, next_ident)) { - /* Check if it's a type name */ - type = find_type(next_ident, 0); - if (type) - could_be_type = true; - } - - /* Restore position */ - cur_token = saved_token; - - /* If it's not a type, skip the declaration block */ - if (!could_be_type) - type = NULL; - } else { - /* Normal type checking without asterisk */ - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) - find_type_flag = 2; - type = find_type(token, find_type_flag); - } - - if (type) { - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - read_full_var_decl(var, false, false); - add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(bb, var); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { - /* Emit code for locals in functions */ - parse_array_init(var, parent, &bb, 1); - } else if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_typedef)) { - /* C90-compliant struct compound literal support */ - type_t *struct_type = var->type; - - /* Handle typedef by getting actual struct type */ - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - lex_expect(T_open_curly); - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Parse field value expression */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *val = opstack_pop(); - - /* Initialize field if within bounds */ - if (field_idx < struct_type->num_fields) { - var_t *field = &struct_type->fields[field_idx]; - - /* Create target variable for field */ - var_t *field_val = - resize_to(parent, &bb, val, field->type, - field->ptr_level); - - /* Compute field address: &struct + field_offset */ - var_t *struct_addr = require_var(parent); - struct_addr->var_name = gen_name(); - add_insn(parent, bb, OP_address_of, struct_addr, - var, NULL, 0, NULL); - - var_t *field_addr = struct_addr; - if (field->offset > 0) { - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, bb, OP_load_constant, offset, - NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, bb, OP_add, addr, struct_addr, - offset, 0, NULL); - field_addr = addr; - } - - /* Write field value */ - int field_size = size_var(field); - add_insn(parent, bb, OP_write, NULL, field_addr, - field_val, field_size, NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - lex_expect(T_close_curly); - } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - - var_t *expr_result = opstack_pop(); - - /* Handle array compound literal to scalar assignment */ - if (expr_result && expr_result->array_size > 0 && - !var->ptr_level && var->array_size == 0 && var->type && - (var->type->base_type == TYPE_int || - var->type->base_type == TYPE_short) && - expr_result->var_name[0] == '.') { - /* Extract first element from compound literal array */ - var_t *first_elem = require_var(parent); - first_elem->type = var->type; - first_elem->var_name = gen_name(); - - /* Read first element from array at offset 0 expr_result is - * the array itself, so we can read directly from it - */ - add_insn(parent, bb, OP_read, first_elem, expr_result, NULL, - var->type->size, NULL); - expr_result = first_elem; - } - - rs1 = resize_var(parent, &bb, expr_result, var); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); - } - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(parent, bb); - - /* multiple (partial) declarations */ - nv = require_typed_var(parent, type); - read_partial_var_decl(nv, var); /* partial */ - add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { - /* Emit code for locals */ - parse_array_init(nv, parent, &bb, 1); - } else if (lex_peek(T_open_curly, NULL) && - (nv->type->base_type == TYPE_struct || - nv->type->base_type == TYPE_typedef)) { - /* C90-compliant struct compound literal support */ - type_t *struct_type = nv->type; - - /* Handle typedef by getting actual struct type */ - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - lex_expect(T_open_curly); - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Parse field value expression */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *val = opstack_pop(); - - /* Initialize field if within bounds */ - if (field_idx < struct_type->num_fields) { - var_t *field = &struct_type->fields[field_idx]; - - /* Create target variable for field */ - var_t *field_val = - resize_to(parent, &bb, val, field->type, - field->ptr_level); - - /* Compute field address: &struct + field_offset - */ - var_t *struct_addr = require_var(parent); - struct_addr->var_name = gen_name(); - add_insn(parent, bb, OP_address_of, struct_addr, - nv, NULL, 0, NULL); - - var_t *field_addr = struct_addr; - if (field->offset > 0) { - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, bb, OP_load_constant, - offset, NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, bb, OP_add, addr, - struct_addr, offset, 0, NULL); - field_addr = addr; - } - - /* Write field value */ - int field_size = size_var(field); - add_insn(parent, bb, OP_write, NULL, field_addr, - field_val, field_size, NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - lex_expect(T_close_curly); - } else { - read_expr(parent, &bb); - - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(parent, bb, OP_assign, nv, rs1, NULL, 0, NULL); - } - } - } - lex_expect(T_semicolon); - return bb; - } - - /* is a function call? Skip function call check when has_asterisk is true */ - if (!has_asterisk && !find_local_var(token, parent)) { - func = find_func(token); - if (func) { - lex_expect(T_identifier); - read_func_call(func, parent, &bb); - perform_side_effect(parent, bb); - lex_expect(T_semicolon); - return bb; - } - } - - /* handle pointer dereference expressions like *ptr = value */ - if (lex_peek(T_asterisk, NULL)) { - if (stmt_starts_assignment()) { - /* Consume exactly one asterisk and evaluate what follows as an - * ordinary expression. That expression is the address to store to: - * for "*p" it is p, for "**pp" it is the value of *pp, and for "*(p - * + 1)" it is p + 1. Letting read_expr() consume the leading - * asterisk too would dereference once more than the assignment asks - * for, and the store then went to whatever the pointee happened to - * hold. - */ - lex_expect(T_asterisk); - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *addr = opstack_pop(); - - /* The width of the store is the pointee's, not the address's. */ - int store_sz = get_pointer_element_size(addr); - - opcode_t compound_op = OP_generic; - if (!lex_accept(T_assign) && - !accept_compound_assign_op(&compound_op)) - error_at("Expected assignment after pointer dereference", - next_token_loc()); - - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *rvalue = opstack_pop(); - - if (compound_op != OP_generic) { - /* "*p op= v" reads the pointee, combines, and writes back. */ - var_t *cur = require_var(parent); - cur->var_name = gen_name(); - add_insn(parent, bb, OP_read, cur, addr, NULL, store_sz, NULL); - - var_t *combined = require_var(parent); - combined->var_name = gen_name(); - add_insn(parent, bb, compound_op, combined, cur, rvalue, 0, - NULL); - rvalue = combined; - } - - add_insn(parent, bb, OP_write, NULL, addr, rvalue, store_sz, NULL); - } else { - /* Not a store: an ordinary expression statement. */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - perform_side_effect(parent, bb); - } - lex_expect(T_semicolon); - return bb; - } - - /* is an assignment? */ - if (read_body_assignment(token, parent, prefix_op, &bb)) { - perform_side_effect(parent, bb); - lex_expect(T_semicolon); - return bb; - } - - if (lex_peek(T_identifier, token)) { - lex_accept(T_identifier); - token_t *id_tk = cur_token; - if (lex_accept(T_colon)) { - const label_t *l = find_label(token); - if (l) - error_at("label redefinition", &id_tk->location); - - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - add_label(token, n); - add_insn(parent, n, OP_label, NULL, NULL, NULL, 0, token); - return n; - } - } - - error_at("Unrecognized statement token", next_token_loc()); - return NULL; + if (!is_bool_scalar(type, ptr_level)) + return value; + return normalize_bool(block, bb, value); } -basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) +var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) { - if (!bb) - printf("Warning: unreachable code detected\n"); - - /* statement can be: - * function call, variable declaration, assignment operation, - * keyword, block + /* A function designator and a function pointer are addresses too, whatever + * the return type their signature spells: a bool (*)(int) argument must not + * be converted to _Bool. */ + bool is_from_ptr = from->ptr_level || from->array_size || from->is_func || + from->func_signature, + is_to_ptr = to->ptr_level || to->array_size || to->is_func || + to->func_signature || + (to->type && to->type->ptr_level > 0); - if (lex_peek(T_open_curly, NULL)) - return read_code_block(parent->func, parent, bb); + if (is_from_ptr && is_to_ptr) + return from; - if (lex_accept(T_return)) { - return handle_return_statement(parent, bb); - } + if (!is_to_ptr && is_bool_scalar(to->type, 0)) + return normalize_bool(block, bb, from); - if (lex_accept(T_if)) { - return handle_if_statement(parent, bb); - } + int from_size = get_size(from), to_size = get_size(to); - if (lex_accept(T_while)) { - return handle_while_statement(parent, bb); + if (from_size > to_size) { + /* Truncation */ + return truncate_unchecked(block, bb, from, to->type, to->ptr_level); } - if (lex_accept(T_switch)) - return handle_switch_statement(parent, bb); - - if (lex_accept(T_break)) { - if (!break_exit_idx) - error_at("'break' outside of a loop or switch", cur_token_loc()); - bb_connect(bb, break_bb[break_exit_idx - 1], NEXT); - lex_expect(T_semicolon); - return NULL; - } + if (from_size < to_size) { + /* Widening into a pointer needs no conversion instruction. Values + * already occupy a full register and integer loads sign-extend, so the + * pointer's bits are the value's bits. Emitting the conversion here + * also placed it ahead of the instructions computing its own operand, + * which produced a garbage pointer. + * + * On the 32-bit targets PTR_SIZE equals an int, so this case cannot + * arise there and behaviour is unchanged. + */ + if (is_to_ptr) + return from; - if (lex_accept(T_continue)) { - if (!continue_pos_idx) - error_at("'continue' outside of a loop", cur_token_loc()); - bb_connect(bb, continue_bb[continue_pos_idx - 1], NEXT); - lex_expect(T_semicolon); - return NULL; + /* Sign extend */ + return promote_unchecked(block, bb, from, to->type, to->ptr_level); } - if (lex_accept(T_for)) - return handle_for_statement(parent, bb); - - if (lex_accept(T_do)) - return handle_do_statement(parent, bb); - - if (lex_accept(T_goto)) - return handle_goto_statement(parent, bb); - - /* empty statement */ - if (lex_accept(T_semicolon)) - return bb; - - /* struct/union variable declaration */ - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) - return handle_record_statement(parent, bb); + /* A same-rank unsigned assignment still has a required representation + * conversion on a wider register machine. Keeping an arithmetic result in a + * 64-bit register can leave carry bits above an unsigned int's object + * width; a later equality comparison would then see 2^32 + 1 instead of the + * stored value 1. + * + * A change of signedness needs the conversion on every target, because the + * result is also the value of an assignment expression. Returning the + * source unchanged kept its type: `(long long)(i = u)` zero-extended a + * negative int, and `(long long)(u = -5)` sign-extended an unsigned one. On + * a 32-bit target the same-width truncation is a plain move. + */ + if (!is_from_ptr && !is_to_ptr && to->type && + ((to->type->is_unsigned && to_size < PTR_SIZE) || + (from->type && to_size <= TY_int->size && + to->type->is_unsigned != from->type->is_unsigned))) + return truncate_unchecked(block, bb, from, to->type, to->ptr_level); - /* Handle const qualifier for local variable declarations */ - return handle_declaration(parent, bb); + return from; } -/* Nesting counter for read_code_block(), which recurses through - * read_body_statement() for every nested block. +/* Convert @val to @type at @ptr_level levels of indirection. + * + * resize_var() takes its target as a var_t, and every caller builds that the + * same way: a zeroed local carrying only those two fields. Building it here + * keeps the zeroing in one place -- shecc miscompiles "var_t t = {0};", so it + * has to be a memset, and nine copies of that were nine chances to leave one + * out. */ -int block_depth = 0; - -basic_block_t *read_code_block(func_t *func, block_t *parent, basic_block_t *bb) +var_t *resize_to(block_t *block, + basic_block_t **bb, + var_t *val, + type_t *type, + int ptr_level) { - block_t *blk = add_block(parent, func); - bb->scope = blk; - - block_depth++; - if (block_depth > MAX_BLOCK_DEPTH) - error_at("Block nesting too deep", cur_token_loc()); - - lex_expect(T_open_curly); - - while (!lex_accept(T_close_curly)) { - bb = read_body_statement(blk, bb); - perform_side_effect(blk, bb); - } + var_t target; - block_depth--; - return bb; + memset(&target, 0, sizeof(var_t)); + target.type = type; + target.ptr_level = ptr_level; + return resize_var(block, bb, val, &target); } -void var_add_killed_bb(var_t *var, basic_block_t *bb); - -void read_func_body(func_t *func) +/* C99 6.5.16.1 permits adding qualifiers at the referenced object, but the + * familiar int ** -> const int ** conversion is unsafe: a caller could store a + * pointer-to-const through the converted value and later write through the + * original int *. The compiler retains base qualification and pointer depth, + * which is enough to reject that class without modeling every level yet. + */ +bool incompatible_const_pointer_conversion(const var_t *from, const var_t *to) { - block_t *blk = add_block(NULL, func); - func->bbs = bb_create(blk); - func->exit = bb_create(blk); - - for (int i = 0; i < func->num_params; i++) { - /* arguments */ - add_symbol(func->bbs, &func->param_defs[i]); - func->param_defs[i].base = &func->param_defs[i]; - var_add_killed_bb(&func->param_defs[i], func->bbs); - } - basic_block_t *body = read_code_block(func, NULL, func->bbs); - if (body) - bb_connect(body, func->exit, NEXT); - - for (int i = 0; i < backpatch_bb_idx; i++) { - basic_block_t *bb = backpatch_bb[i]; - insn_t *g = bb->insn_list.tail; - label_t *label = find_label(g->str); - if (!label) - error_at("goto label undefined", cur_token_loc()); - - label->used = true; - bb_connect(bb, label->bb, NEXT); - } + unsigned int from_mask, to_mask; + int from_depth, to_depth; - for (int i = 0; i < label_idx; i++) { - const label_t *label = &labels[i]; - if (label->used) - continue; + if (!from || !to) + return false; - printf("Warning: unused label %s\n", label->label_name); - } + from_depth = from->ptr_level + from->type->ptr_level; + to_depth = to->ptr_level + to->type->ptr_level; + if (!from_depth || !to_depth) + return false; - backpatch_bb_idx = 0; - label_idx = 0; -} + if (from->is_const_qualified && !to->is_const_qualified) + return true; -void print_ptr_level(int level) -{ - while (level > 0) { - printf("*"); - level--; - } + /* A top-level pointer qualifier belongs to the source or destination + * object, not to the pointed-to type used by a value conversion. Keep every + * inner level: `int * const *` must accept `&p` when p is an `int * const`, + * and converting it back to `int **` must be rejected. + */ + from_mask = from->type->pointer_const_mask | + (from->pointer_const_mask << from->type->ptr_level); + to_mask = to->type->pointer_const_mask | + (to->pointer_const_mask << to->type->ptr_level); + if (from_depth <= 32) + from_mask &= ~(1U << (from_depth - 1)); + if (to_depth <= 32) + to_mask &= ~(1U << (to_depth - 1)); + if (from_mask & ~to_mask) + return true; + + return from_depth > 1 && to_depth > 1 && + from->is_const_qualified != to->is_const_qualified; } -void print_func_decl(func_t *func, const char *prefix, bool newline) +/* C still accepts assigning a string literal to char *, but treating the + * literal as read-only catches the common accidental-write case. Keep this + * diagnostic behind an explicit option: shecc's bundled, self-hosted sources + * contain older char * interfaces for string data. + */ +void diagnose_const_pointer_conversion(const var_t *from, const var_t *to) { - if (prefix) - printf("%s", prefix); - - if (func->return_def.is_const_qualified) - printf("const "); - printf("%s ", func->return_def.type->type_name); - print_ptr_level(func->return_def.ptr_level - - func->return_def.type->ptr_level); - printf("%s(", func->return_def.var_name); - - for (int i = 0; i < func->num_params; i++) { - const var_t *var = &func->param_defs[i]; - - if (var->is_const_qualified) - printf("const "); - printf("%s ", var->type->type_name); - - print_ptr_level(var->ptr_level - var->type->ptr_level); - - printf("%s", var->var_name); + if (!incompatible_const_pointer_conversion(from, to)) + return; - if (i != func->num_params - 1) - printf(", "); + if (from->is_string_literal) { + if (warn_string_literals) + printf("Warning: string literal is read-only\n"); + return; } - if (func->va_args) - printf(", ..."); - printf(")"); - - if (newline) - printf("\n"); + error_at("discarding const qualifier", cur_token_loc()); } -/* Emit the optional initializer of a global declarator. Arrays and pointers - * written with a brace list go through the array initializer; everything else - * is a scalar constant. +/* C99 6.5.16.1p1 lets an integer become a pointer without a cast only when it + * is a null pointer constant. @to is the pointer object, parameter or return + * type receiving @from; with @array_is_pointer an array declarator there is the + * pointer that a parameter declared as an array is adjusted to. */ -void read_global_init(var_t *var, block_t *block) +void diagnose_integer_to_pointer_conversion(var_t *from, + const var_t *to, + bool array_is_pointer) { - if (!lex_accept(T_assign)) + if (!from || !to || !from->type || !to->type) + return; + if (!effective_pointer_depth(to) && + !(array_is_pointer && (to->array_size || to->has_unsized_array))) + return; + if (!array_is_pointer && (to->array_size || to->has_unsized_array)) return; - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) - parse_array_init(var, block, &GLOBAL_FUNC->bbs, true); - else - read_global_assignment(var->var_name); + /* A string literal is already a pointer here. A character read out of one, + * `*"ab"`, keeps is_string_literal only so that taking its address again + * points into the literal; the character itself is an integer. + */ + if (effective_pointer_depth(from) || from->array_size || + from->has_unsized_array || from->is_func || from->func_signature || + from->pointee_func_signature || from->type == TY_void || + is_record_type(from->type) || is_null_pointer_constant(from)) + return; + error_at("integer converted to pointer without a cast", cur_token_loc()); } -/* Read one declarator after the first in a global declaration. Each shares the - * declaration's base type: "int a = 1, b, c = 3;". +/* The function type @var points to, when @var is a function pointer object or + * value or a function designator; NULL otherwise. Callback arrays and slots, + * pointers to function pointers, are not function pointers here. */ -void read_global_declarator(block_t *block, type_t *decl_type, bool is_const) +static const func_t *pointed_function_type(const var_t *var) { - var_t *nv = require_typed_var(block, decl_type); - nv->is_global = true; - nv->is_const_qualified = is_const; - read_inner_var_decl(nv, false, false); - add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, nv, NULL, NULL, 0, NULL); - read_global_init(nv, block); + if (!var || !var->type || var->pointee_func_signature || var->array_size || + var->has_unsized_array) + return NULL; + if (var->func_signature) + return var->func_signature; + if (var->type->func_signature && !var->type->is_direct_function_type && + !var->ptr_level) + return var->type->func_signature; + if (var->is_func && var->var_name) + return find_func(var->var_name); + return NULL; } -void read_global_decl(block_t *block, bool is_const) +/* C99 6.5.16.1p1 converts between function pointers only when the functions + * have compatible types. A function without a prototype is compatible with a + * prototype that has no ellipsis and no parameter the default argument + * promotions change (6.7.5.3p15). + */ +static bool compatible_function_conversion(const func_t *from, const func_t *to) { - var_t *var = require_var(block); - var->is_global = true; - var->is_const_qualified = is_const; - - /* new function, or variables under parent */ - read_full_var_decl(var, false, false); - - if (lex_peek(T_open_bracket, NULL)) { - /* function */ - func_t *func = find_func(var->var_name); - func_t func_tmp; - bool check_decl = false; - - if (func) { - memcpy(&func_tmp, func, sizeof(func_t)); - check_decl = true; - } else - func = add_func(var->var_name, false); - - memcpy(&func->return_def, var, sizeof(var_t)); - var_reset_subscripts(&func->return_def); - block->locals.size--; - read_parameter_list_decl(func, 0); - - if (check_decl) { - /* Validate whether the previous declaration and the current one - * differ. - */ - if ((func->return_def.type != func_tmp.return_def.type) || - (func->return_def.ptr_level != func_tmp.return_def.ptr_level) || - (func->return_def.is_const_qualified != - func_tmp.return_def.is_const_qualified)) { - printf("Error: conflicting types for the function %s.\n", - func->return_def.var_name); - print_func_decl(&func_tmp, "before: ", true); - print_func_decl(func, "after: ", true); - fflush(stdout); /* see fatal() */ - abort(); - } - - if (func->num_params != func_tmp.num_params) { - printf( - "Error: conflicting number of arguments for the function " - "%s.\n", - func->return_def.var_name); - print_func_decl(&func_tmp, "before: ", true); - print_func_decl(func, "after: ", true); - fflush(stdout); /* see fatal() */ - abort(); - } - - for (int i = 0; i < func->num_params; i++) { - const var_t *func_var = &func->param_defs[i]; - const var_t *func_tmp_var = &func_tmp.param_defs[i]; - if ((func_var->type != func_tmp_var->type) || - (func_var->ptr_level != func_tmp_var->ptr_level) || - (func_var->is_const_qualified != - func_tmp_var->is_const_qualified)) { - printf("Error: conflicting types for the function %s.\n", - func->return_def.var_name); - print_func_decl(&func_tmp, "before: ", true); - print_func_decl(func, "after: ", true); - fflush(stdout); /* see fatal() */ - abort(); - } - } - - if (func->va_args != func_tmp.va_args) { - printf("Error: conflicting types for the function %s.\n", - func->return_def.var_name); - print_func_decl(&func_tmp, "before: ", true); - print_func_decl(func, "after: ", true); - fflush(stdout); /* see fatal() */ - abort(); - } - } - - if (lex_peek(T_open_curly, NULL)) { - read_func_body(func); - return; - } - if (lex_accept(T_semicolon)) /* forward definition */ - return; - error_at("Syntax error in global declaration", next_token_loc()); - } else - add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); - - /* is a variable */ - if (lex_peek(T_assign, NULL)) { - read_global_init(var, block); - } else if (lex_peek(T_semicolon, NULL)) { - opstack_pop(); - } else if (!lex_peek(T_comma, NULL)) { - error_at("Syntax error in global declaration", next_token_loc()); - } - - /* Continuation: "int a = 1, b, c = 3;". Every declarator after the first - * shares this declaration's base type and is handled exactly like the - * first, mirroring what the struct-tagged global path already does. - */ - while (lex_accept(T_comma)) - read_global_declarator(block, var->type, is_const); + const func_t *prototyped = from->has_prototype ? from : to; - lex_expect(T_semicolon); - return; + if (!compatible_function_signature(from, to)) + return false; + if (from->has_prototype == to->has_prototype) + return true; + if (prototyped->va_args) + return false; + for (int i = 0; i < prototyped->num_params; i++) + if (parameter_changes_under_default_promotion( + &prototyped->param_defs[i])) + return false; + return true; } -void consume_global_compound_literal(void) +/* Diagnose a conversion of @from to the function pointer @to, or of a function + * pointer @from to the object pointer @to, in an initializer, assignment, + * argument or return. Only a null pointer constant converts to a function + * pointer from anything but a compatible function pointer or designator. + */ +void diagnose_function_pointer_conversion(var_t *from, const var_t *to) { - lex_expect(T_open_curly); - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Just consume constant values for now */ - if (lex_peek(T_numeric, NULL)) { - lex_accept(T_numeric); - } else if (lex_peek(T_minus, NULL)) { - lex_accept(T_minus); - lex_accept(T_numeric); - } else if (lex_peek(T_string, NULL)) { - lex_accept(T_string); - } else if (lex_peek(T_char, NULL)) { - lex_accept(T_char); - } else { - error_at( - "Global struct initialization requires constant values", - next_token_loc()); - } - - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } + const func_t *from_function = pointed_function_type(from); + const func_t *to_function = pointed_function_type(to); + + if (!from || !to || !from->type || !to->type || + from->pointee_func_signature || to->pointee_func_signature) + return; + if (to_function && from_function) { + if (!compatible_function_conversion(from_function, to_function)) + error_at("incompatible function pointer types", cur_token_loc()); + return; + } + if (to_function) { + if (is_null_pointer_constant(from) || from->is_void_null_pointer || + from->array_size || from->has_unsized_array) + return; + if (effective_pointer_depth(from)) + error_at("incompatible function pointer types", cur_token_loc()); + if (!is_record_type(from->type) && from->type != TY_void) + error_at("integer converted to pointer without a cast", + cur_token_loc()); + return; } - lex_expect(T_close_curly); + if (from_function && effective_pointer_depth(to) && !to->array_size && + !to->has_unsized_array) + error_at("incompatible function pointer types", cur_token_loc()); } -void initialize_struct_field(var_t *nv, var_t *v, int offset) +/* Plain, signed, and unsigned char are distinct C types despite sharing a byte + * representation. Preserve that identity for implicit pointer conversions + * without changing the legacy non-character pointer extensions. + */ +bool incompatible_character_pointer_conversion(const var_t *from, + const var_t *to) { - nv->type = v->type; - nv->var_name = ""; - nv->ptr_level = 0; - nv->is_func = false; - nv->is_global = false; - nv->is_const_qualified = false; - nv->array_size = 0; - nv->offset = offset; - nv->init_val = 0; - nv->base = NULL; - nv->subscript = 0; - var_reset_subscripts(nv); - nv->is_compound_literal = false; + type_t *from_pointee, *to_pointee; + + if (!from || !to || !(from->ptr_level || from->type->ptr_level) || + !(to->ptr_level || to->type->ptr_level)) + return false; + + from_pointee = pointee_type_from_pointer_typedef(from->type); + to_pointee = pointee_type_from_pointer_typedef(to->type); + return from_pointee->base_type == TYPE_char && + to_pointee->base_type == TYPE_char && + !compatible_decl_type(from_pointee, to_pointee); } -void read_global_statement(void) +/* Whether @var is a `void *` with no function type behind it. */ +static bool is_plain_void_pointer(const var_t *var) { - char token[MAX_ID_LEN]; - block_t *block = GLOBAL_BLOCK; /* global block */ - bool is_const = false; - - /* Handle const qualifier */ - if (lex_accept(T_const)) - is_const = true; - - if (lex_accept(T_struct)) { - int i = 0, size = 0; - - lex_ident(T_identifier, token); - token_t *id_tk = cur_token; - - /* variable declaration using existing struct tag? */ - if (!lex_peek(T_open_curly, NULL)) { - type_t *decl_type = find_type(token, 2); - if (!decl_type) - error_at("Unknown struct type", &id_tk->location); - - /* one or more declarators */ - var_t *var = require_typed_var(block, decl_type); - var->is_global = true; /* Global struct variable */ - var->is_const_qualified = is_const; - read_partial_var_decl(var, NULL); - add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, - NULL); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { - parse_array_init(var, block, &GLOBAL_FUNC->bbs, true); - } else if (lex_peek(T_open_curly, NULL) && - var->array_size == 0 && var->ptr_level == 0 && - (decl_type->base_type == TYPE_struct || - decl_type->base_type == TYPE_typedef)) { - /* Global struct compound literal support Currently we just - * consume the syntax - actual initialization would require - * runtime code which globals don't support - */ - consume_global_compound_literal(); - } else { - read_global_assignment(var->var_name); - } - } - while (lex_accept(T_comma)) { - var_t *nv = require_typed_var(block, decl_type); - read_inner_var_decl(nv, false, false); - add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, nv, NULL, NULL, 0, - NULL); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { - parse_array_init(nv, block, &GLOBAL_FUNC->bbs, true); - } else if (lex_peek(T_open_curly, NULL) && - nv->array_size == 0 && nv->ptr_level == 0 && - (decl_type->base_type == TYPE_struct || - decl_type->base_type == TYPE_typedef)) { - /* Global struct compound literal support for - * continuation Currently we just consume the syntax - */ - consume_global_compound_literal(); - } else { - read_global_assignment(nv->var_name); - } - } - } - lex_expect(T_semicolon); - return; - } - - /* struct definition has forward declaration? */ - type_t *type = find_type(token, 2); - if (!type) - type = add_type(); - - set_type_name(type, token); - type->base_type = TYPE_struct; - - lex_expect(T_open_curly); - do { - var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); - v->offset = size; - size += size_var(v); - - /* Handle multiple variable declarations with same base type */ - while (lex_accept(T_comma)) { - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, true); - nv->offset = size; - size += size_var(nv); - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - - type->size = size; - type->num_fields = i; - lex_expect(T_semicolon); - } else if (lex_accept(T_union)) { - int i = 0, max_size = 0; - - lex_ident(T_identifier, token); - - /* has forward declaration? */ - type_t *type = find_type(token, 2); - if (!type) - type = add_type(); - - set_type_name(type, token); - type->base_type = TYPE_union; - - lex_expect(T_open_curly); - do { - var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); - v->offset = 0; /* All union fields start at offset 0 */ - int field_size = size_var(v); - if (field_size > max_size) - max_size = field_size; - - /* Handle multiple variable declarations with same base type */ - while (lex_accept(T_comma)) { - var_t *nv = type_add_field(type, &i); - /* All union fields start at offset 0 */ - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, true); - field_size = size_var(nv); - if (field_size > max_size) - max_size = field_size; - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - - type->size = max_size; - type->num_fields = i; - lex_expect(T_semicolon); - } else if (lex_accept(T_typedef)) { - if (lex_accept(T_enum)) { - int val = 0; - type_t *type = add_type(); - - type->base_type = TYPE_int; - type->size = 4; - lex_expect(T_open_curly); - do { - lex_ident(T_identifier, token); - if (lex_accept(T_assign)) { - char value[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, value, MAX_TOKEN_LEN); - val = parse_numeric_constant(value); - } - add_constant(token, val++); - } while (lex_accept(T_comma)); - lex_expect(T_close_curly); - lex_ident(T_identifier, token); - set_type_name(type, token); - lex_expect(T_semicolon); - } else if (lex_accept(T_struct)) { - int i = 0, size = 0; - bool has_struct_def = false; - type_t *tag = NULL, *type = add_type(); - - /* is struct definition? */ - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - - /* is existent? */ - tag = find_type(token, 2); - if (!tag) { - tag = add_type(); - tag->base_type = TYPE_struct; - set_type_name(tag, token); - } - } - - /* typedef with struct definition */ - if (lex_accept(T_open_curly)) { - has_struct_def = true; - do { - var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); - v->offset = size; - size += size_var(v); - - /* Handle multiple variable declarations with same base type - */ - while (lex_accept(T_comma)) { - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, true); - nv->offset = size; - size += size_var(nv); - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - } - - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->size = size; - type->num_fields = i; - type->base_type = TYPE_typedef; - - if (tag && has_struct_def == 1) { - strcpy(token, tag->type_name); - memcpy(tag, type, sizeof(type_t)); - tag->base_type = TYPE_struct; - set_type_name(tag, token); - } else { - /* If it is a forward declaration, build a connection between - * structure tag and alias. In 'find_type', it will retrieve - * infomation from base structure for alias. - */ - type->base_struct = tag; - } - - lex_expect(T_semicolon); - } else if (lex_accept(T_union)) { - int i = 0, max_size = 0; - bool has_union_def = false; - type_t *tag = NULL, *type = add_type(); - - /* is union definition? */ - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - - /* is existent? */ - tag = find_type(token, 2); - if (!tag) { - tag = add_type(); - tag->base_type = TYPE_union; - set_type_name(tag, token); - } - } - - /* typedef with union definition */ - if (lex_accept(T_open_curly)) { - has_union_def = true; - do { - var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); - v->offset = 0; /* All union fields start at offset 0 */ - int field_size = size_var(v); - if (field_size > max_size) - max_size = field_size; - - /* Handle multiple variable declarations with same base type - */ - while (lex_accept(T_comma)) { - var_t *nv = type_add_field(type, &i); - /* All union fields start at offset 0 */ - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, true); - field_size = size_var(nv); - if (field_size > max_size) - max_size = field_size; - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - } - - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->size = max_size; - type->num_fields = i; - type->base_type = TYPE_typedef; - - if (tag && has_union_def == 1) { - strcpy(token, tag->type_name); - memcpy(tag, type, sizeof(type_t)); - tag->base_type = TYPE_union; - set_type_name(tag, token); - } else { - /* If it is a forward declaration, build a connection between - * union tag and alias. In 'find_type', it will retrieve - * information from base union for alias. - */ - type->base_struct = tag; - } - - lex_expect(T_semicolon); - } else { - char base_type[MAX_ID_LEN]; - const type_t *base; - type_t *type = add_type(); - lex_ident(T_identifier, base_type); - base = find_type(base_type, true); - if (!base) - error_at("Unable to find base type", cur_token_loc()); - type->base_type = base->base_type; - type->size = base->size; - type->num_fields = 0; - type->ptr_level = 0; - - /* Handle pointer types in typedef: typedef char *string; */ - while (lex_accept(T_asterisk)) { - type->ptr_level++; - type->size = PTR_SIZE; - } - - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - lex_expect(T_semicolon); - } - } else if (lex_peek(T_identifier, NULL)) { - read_global_decl(block, is_const); - } else - error_at("Syntax error in global statement", next_token_loc()); + return var->type && var->type == TY_void && var->ptr_level == 1 && + !var->func_signature && !var->pointee_func_signature && + !var->is_func && !var->array_size; } -void parse_internal(void) +bool incompatible_pointee_callback_conversion(const var_t *from, + const var_t *to) { - /* set starting point of global stack manually */ - GLOBAL_FUNC = add_func("", true); + func_t *from_signature; + func_t *to_signature; + + if (!from || !to) + return false; + if (is_null_pointer_constant((var_t *) from) || + (!from->pointee_func_signature && !from->func_signature && + !(from->ptr_level || from->type->ptr_level) && !from->is_func && + !from->init_val && !from->init_val_hi)) + return false; - /* The first global slot retains the synthetic global-frame pointer. It must - * occupy a full target pointer, not the historic 32-bit word. + /* A callback slot is an object pointer, which converts to and from void * + * at any depth. */ - GLOBAL_FUNC->stack_size = PTR_SIZE; - GLOBAL_FUNC->bbs = arena_calloc(BB_ARENA, 1, sizeof(basic_block_t)); - GLOBAL_FUNC->bbs->belong_to = GLOBAL_FUNC; /* Prevent nullptr deref in RA */ - GLOBAL_FUNC->bbs->elf_offset = -1; /* not yet emitted */ - - /* built-in types */ - TY_void = add_named_type("void"); - TY_void->base_type = TYPE_void; - TY_void->size = 0; - - TY_char = add_named_type("char"); - TY_char->base_type = TYPE_char; - TY_char->size = 1; - - TY_int = add_named_type("int"); - TY_int->base_type = TYPE_int; - TY_int->size = 4; - - TY_short = add_named_type("short"); - TY_short->base_type = TYPE_short; - TY_short->size = 2; - - /* builtin type _Bool was introduced in C99 specification, it is more - * well-known as macro type bool, which is defined in (in - * shecc, it is defined in 'lib/c.c'). + if ((from->pointee_func_signature && is_plain_void_pointer(to)) || + (to->pointee_func_signature && is_plain_void_pointer(from))) + return false; + + /* A pointer to a row of callbacks, `fn_t (*rows_t)[2]`, points to the whole + * row, which `&row` for such an array is too: no slot is involved. */ - TY_bool = add_named_type("_Bool"); - TY_bool->base_type = TYPE_char; - TY_bool->size = 1; + if (to->type && to->type->pointee_array_size && + to->type->pointee_array_element_type && + to->type->pointee_array_element_type->func_signature) + return false; + from_signature = from->pointee_func_signature; + to_signature = to->pointee_func_signature; - GLOBAL_BLOCK = add_block(NULL, NULL); /* global block */ - elf_add_symbol("", 0); /* undef symbol */ + /* A pointer to a const callback, `&f` for a const one or a `cfn_t *`, may + * not become a pointer to a modifiable one. + */ + if ((from->callback_is_const || points_to_const_callback(from)) && + !to->callback_is_const && !points_to_const_callback(to) && + (to->pointee_func_signature || + (to->ptr_level == 1 && to->type && to->type->func_signature && + !to->type->is_direct_function_type && !to->type->ptr_level))) + return true; - if (dynlink) { - /* In dynamic mode, __syscall won't be implemented. - * - * Simply declare a 'syscall' function as follows if the program needs - * to use 'syscall': - * - * int syscall(int number, ...); - * - * shecc will treat it as an external function, and the compiled program - * will eventually use the implementation provided by the external C - * library. - * - * If shecc supports the 'long' data type in the future, it would be - * better to declare syscall using its original prototype: - * - * long syscall(long number, ...); - */ - } else { - /* Linux syscall */ - func_t *func = add_func("__syscall", true); - func->return_def.type = TY_int; - func->num_params = 0; - func->va_args = 1; - func->bbs = NULL; - /* Otherwise, allocate a basic block to implement in static mode. */ - func->bbs = arena_calloc(BB_ARENA, 1, sizeof(basic_block_t)); - func->bbs->elf_offset = -1; /* not yet emitted */ - } + /* A slot may add, but not discard, qualifiers of the callback it reaches. + * Deeper slots must agree exactly, as C99 6.5.16.1 requires below the top. + */ + if (from_signature && to_signature && + ((from->callback_is_const && !to->callback_is_const) || + (from->callback_is_volatile && !to->callback_is_volatile) || + (callback_slot_depth(to) > 1 && + (from->callback_is_const != to->callback_is_const || + from->callback_is_volatile != to->callback_is_volatile)))) + return true; - /* Add a global object to the .data section. - * - * This object saves the global stack pointer, so it is written back as a - * pointer and must reserve a full one: on an LP64 target the historic - * 32-bit word left four bytes belonging to the next global. + /* Slots at different depths point to different types. An element of a + * callback slot array keeps its depth on the array descriptor instead. */ - elf_write_ptr(elf_data, 0); + if (from_signature && to_signature && + !(from->type && from->type->array_element_pointee_func_signature) && + !(to->type && to->type->array_element_pointee_func_signature) && + callback_slot_depth(from) != callback_slot_depth(to)) + return true; - /* lexer initialization */ - do { - read_global_statement(); - } while (!lex_accept(T_eof)); + /* An array parameter of callbacks is adjusted to a callback slot. The + * parser keeps its element callback signature in `func_signature` while + * retaining the written array bound, rather than in the ordinary slot field + * used by a spelled `(**slot)` declarator. + */ + if (!to_signature && to->func_signature && + (to->array_size || to->has_unsized_array)) + to_signature = to->func_signature; + + /* An array of callbacks, as its own operand, decays to a slot pointer. */ + if (!from_signature && from->is_func && from->func_signature && + (from->array_size || from->has_unsized_array)) + from_signature = from->func_signature; + + /* A value cast to a callback typedef, `(callback_t) f`, carries its own + * signature: it is the callback, not a pointer to a callback slot. + */ + if (!from_signature && !from->func_signature && + effective_pointer_depth(from) > 0 && from->type && + from->type->func_signature && !from->type->is_direct_function_type) + from_signature = from->type->func_signature; + if (!to_signature && effective_pointer_depth(to) > 0 && to->type && + to->type->func_signature && !to->type->is_direct_function_type) + to_signature = to->type->func_signature; + if (!(from_signature || to_signature)) + return false; + return !from_signature || !to_signature || + !compatible_function_signature(from_signature, to_signature); } -void parse(token_t *tk) +/* Diagnose an initializer whose value @from converts to @to between + * incompatible callback slot types. + */ +void diagnose_callback_slot_initializer(const var_t *from, const var_t *to) { - token_t head; - head.kind = T_start; - head.next = tk; - cur_token = &head; - - parse_internal(); + if (incompatible_pointee_callback_conversion(from, to)) + error_at("incompatible callback slot types in initializer", + cur_token_loc()); } + +/* The rest of the parser, in the order it was written. Each file depends on + * what the ones before it define, so the order below is load-bearing and must + * not be sorted. + */ +/* clang-format off */ +#include "parser-init.c" +#include "parser-decl.c" +#include "parser-call.c" +#include "parser-sizeof.c" +#include "parser-expr.c" +#include "parser-const.c" +#include "parser-stmt.c" +#include "parser-global.c" +/* clang-format on */ diff --git a/src/peephole.c b/src/peephole.c index 06fb273d..27b53b6a 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -40,6 +40,120 @@ bool is_fusible_insn(const ph2_ir_t *ph2_ir) } } +/* Opcodes whose "dest" names a register they write. Only opcodes that certainly + * do are listed, so a live register is never mistaken for dead. + */ +bool op_writes_dest(opcode_t op) +{ + switch (op) { + case OP_cmov: + case OP_load: + case OP_load_constant: + case OP_global_load: + case OP_assign: + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + case OP_lshift: + case OP_rshift: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + case OP_bit_not: + case OP_negate: + case OP_log_not: + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + case OP_read: + case OP_address_of: + case OP_global_address_of: + case OP_trunc: + case OP_sign_ext: + return true; + default: + return false; + } +} + +/* Opcodes whose src0 holds something other than a register number. Anything not + * listed is assumed to read src0, which only costs a missed rewrite. + */ +bool op_src0_is_reg(opcode_t op) +{ + switch (op) { + case OP_load: + case OP_load_constant: + case OP_global_load: + case OP_address_of: + case OP_global_address_of: + case OP_load_data_address: + case OP_load_rodata_address: + case OP_define: + case OP_label: + case OP_jump: + return false; + default: + return true; + } +} + +/* Opcodes whose src1 is a register rather than a width, slot or immediate. */ +bool op_src1_is_reg(opcode_t op) +{ + switch (op) { + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + case OP_lshift: + case OP_rshift: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + case OP_write: + case OP_cmov: + return true; + default: + return false; + } +} + +/* Whether src2 names a register. Only a select does: it is the value kept when + * the condition does not hold, and a scan that missed it would take that value + * for dead and drop whatever computed it. + */ +bool op_src2_is_reg(opcode_t op) +{ + return op == OP_cmov; +} + +/* Whether @ir reads @reg as an operand. A 32-bit target names the high half of + * a wide operand in src0_hi or src1_hi, which are -1 when unused. + */ +bool ir_reads_reg(ph2_ir_t *ir, int reg) +{ + if (reg >= 0 && (ir->src0_hi == reg || ir->src1_hi == reg)) + return true; + if (op_src2_is_reg(ir->op) && ir->src2 == reg) + return true; + if (op_src0_is_reg(ir->op) && ir->src0 == reg) + return true; + return op_src1_is_reg(ir->op) && ir->src1 == reg; +} + /* Main peephole optimization function that applies pattern matching and * transformation rules to consecutive IR instructions. * Returns true if any optimization was applied, false otherwise. Drop the @@ -57,6 +171,149 @@ void ph2_ir_drop_after(basic_block_t *bb, ph2_ir_t *ir, ph2_ir_t *last) bb->ph2_ir_list.tail = ir; } +/* How many argument registers the call @ir reads. A call names none of them. A + * prototyped, non-variadic callee reads only the words its own parameters + * occupy, the aggregate-return address included; any other call, or a call + * through a pointer, may read all of them. + */ +int call_arg_regs(ph2_ir_t *ir) +{ + func_t *callee = ir->op == OP_call ? find_func(ir->func_name) : NULL; + + if (!callee || !callee->has_prototype || callee->va_args) + return MAX_ARGS_IN_REG; + + int words = callee->returns_aggregate ? 1 : 0; + for (int i = 0; i < callee->num_params && words < MAX_ARGS_IN_REG; i++) + words = abi_arg_next(words, &callee->param_defs[i], false); + return words < MAX_ARGS_IN_REG ? words : MAX_ARGS_IN_REG; +} + +/* True when @reg still holds a live value past the end of @bb. + * + * reg_alloc() hands its register file to a successor that this block is the + * only way into (bb_export_regs()), so a register can outlive the block that + * filled it. Scans that stop at the block boundary conclude there according to + * this: with nothing carried, every register dies at the end of a block and an + * unread value is dead. + */ +bool reg_live_out_of_bb(basic_block_t *bb, int reg) +{ + basic_block_t *succs[3]; + + if (!bb || reg < 0 || reg >= REG_CNT) + return false; + + /* A register the allocator pinned holds its variable on every path, so it + * is live out of every block regardless of what the successors record. + */ + if (bb->belong_to && ((bb->belong_to->pinned_regs >> reg) & 1)) + return true; + + succs[0] = bb->next; + succs[1] = bb->then_; + succs[2] = bb->else_; + + for (int i = 0; i < 3; i++) { + if (succs[i] && succs[i]->entry_regs && succs[i]->entry_regs[reg]) + return true; + } + return false; +} + +/* Whether @reg may still be read after @ir, before anything writes it again. + * + * The scan covers the rest of @bb, with a call reading its argument registers. + * Past the end of the block a register keeps its value only when it is pinned, + * or when reg_alloc() handed it to a successor through bb_export_regs(); every + * other successor loads what it reads. + */ +bool reg_read_after(basic_block_t *bb, ph2_ir_t *ir, int reg) +{ + if (reg < 0) + return false; + if (reg >= REG_CNT) + return true; + + for (ph2_ir_t *p = ir->next; p; p = p->next) { + if (p->op == OP_call || p->op == OP_indirect) { + if (reg < call_arg_regs(p)) + return true; + continue; + } + if (ir_reads_reg(p, reg)) + return true; + if (op_writes_dest(p->op) && (p->dest == reg || p->dest_hi == reg)) + return false; + } + + return reg_live_out_of_bb(bb, reg); +} + +/* Whether folding @ir into @last loses a value that is still wanted. The folded + * instruction writes only what @last writes, so a register @ir wrote and @last + * does not keeps whatever it held before, and nothing may read it afterwards. + * peephole() hands every window with a register pair to pair_insn_fusion(), so + * both are single registers here. + */ +bool fold_loses_dest(basic_block_t *bb, ph2_ir_t *ir, ph2_ir_t *last) +{ + return ir->dest != last->dest && reg_read_after(bb, last, ir->dest); +} + +/* Whether {li t, K; op rd, a, b}, with t one of the operands, may become a + * single instruction on rd. Every such rewrite leaves t without the constant, + * which is dropped, moved to rd or replaced by another one. So t has to be dead + * after the operation, and it cannot be both operands, since the rewrite still + * reads the other one. + */ +bool const_fold_ok(basic_block_t *bb, ph2_ir_t *li, ph2_ir_t *op) +{ + return op->src0 != op->src1 && !fold_loses_dest(bb, li, op); +} + +/* Whether @ir names a 32-bit target's register pair. */ +bool ph2_ir_has_pair(const ph2_ir_t *ir) +{ + return ir && (ir->dest_hi >= 0 || ir->src0_hi >= 0 || ir->src1_hi >= 0); +} + +/* The rewrites in this file match registers by their low halves and build their + * results without high halves, so none of them is sound on a register pair. + * Only the plain move fusion is kept, when the move copies the whole pair and + * its destination is disjoint from every register the operation reads: the + * backends emit a pair operation expecting the allocator's guarantee that its + * result does not overlap an operand, and a low result written first would + * otherwise clobber a high operand still to be read. + */ +bool pair_insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) +{ + ph2_ir_t *next = ph2_ir->next; + + if (!next || next->op != OP_assign || !is_fusible_insn(ph2_ir)) + return false; + if (ph2_ir->dest_hi < 0 || ph2_ir->dest != next->src0 || + ph2_ir->dest_hi != next->src0_hi || next->dest_hi < 0) + return false; + if (ir_reads_reg(ph2_ir, next->dest) || ir_reads_reg(ph2_ir, next->dest_hi)) + return false; + + /* The fused operation no longer writes its own pair, so that pair must be + * dead after the move. Common subexpression elimination leaves exactly the + * copy that is not: "(a & b) ^ (b & a)" became "x = a & b; y = x; x ^ y", + * and fusing the move left the XOR reading a stale x. + */ + if ((ph2_ir->dest != next->dest && + reg_read_after(bb, next, ph2_ir->dest)) || + (ph2_ir->dest_hi != next->dest_hi && + reg_read_after(bb, next, ph2_ir->dest_hi))) + return false; + ph2_ir->dest = next->dest; + ph2_ir->dest_hi = next->dest_hi; + ph2_ir_drop_after(bb, ph2_ir, next); + return true; +} + bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) { ph2_ir_t *next = ph2_ir->next; @@ -68,9 +325,14 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) * that removes temporary register usage. */ if (next->op == OP_assign) { - if (is_fusible_insn(ph2_ir) && ph2_ir->dest == next->src0) { + if (is_fusible_insn(ph2_ir) && ph2_ir->dest == next->src0 && + !fold_loses_dest(bb, ph2_ir, next)) { /* Pattern: {ALU rn, rs1, rs2; mv rd, rn} → {ALU rd, rs1, rs2} * Example: {add t1, a, b; mv result, t1} → {add result, a, b} + * + * Only when nothing reads rn afterwards. A value with two names is + * copied and then read through the first one as well, and the fused + * instruction no longer writes it. */ ph2_ir->dest = next->dest; ph2_ir_drop_after(bb, ph2_ir, next); @@ -79,9 +341,11 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) } /* Arithmetic identity with zero constant */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0) { + if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && + ph2_ir->src1 == 0) { if (next->op == OP_add && - (ph2_ir->dest == next->src0 || ph2_ir->dest == next->src1)) { + (ph2_ir->dest == next->src0 || ph2_ir->dest == next->src1) && + const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 0; add x, 0} → {mov x} (additive identity: x+0 = x) * Handles both operand positions due to addition commutativity * Example: {li t1, 0; add result, var, t1} → {mov result, var} @@ -96,7 +360,7 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) return true; } - if (next->op == OP_sub) { + if (next->op == OP_sub && const_fold_ok(bb, ph2_ir, next)) { if (ph2_ir->dest == next->src1) { /* Pattern: {li 0; sub x, 0} → {mov x} (x - 0 = x) * Example: {li t1, 0; sub result, var, t1} → {mov result, var} @@ -121,7 +385,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) } if (next->op == OP_mul && - (ph2_ir->dest == next->src0 || ph2_ir->dest == next->src1)) { + (ph2_ir->dest == next->src0 || ph2_ir->dest == next->src1) && + const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 0; mul x, 0} → {li 0} (absorbing element: x * 0 = 0) * Example: {li t1, 0; mul result, var, t1} → {li result, 0} * Eliminates multiplication entirely @@ -135,9 +400,11 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) } /* Multiplicative identity with one constant */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 1) { + if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 1 && + ph2_ir->src1 == 0) { if (next->op == OP_mul && - (ph2_ir->dest == next->src0 || ph2_ir->dest == next->src1)) { + (ph2_ir->dest == next->src0 || ph2_ir->dest == next->src1) && + const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 1; mul x, 1} → {mov x} (multiplicative identity: x * * 1 = x) Example: {li t1, 1; mul result, var, t1} → {mov result, * var} Handles both operand positions due to multiplication @@ -151,9 +418,13 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) } } - /* Bitwise identity operations */ + /* Bitwise identity operations. src1 is the constant's high word, which an + * eight-byte operation includes: 0xffffffffULL is no identity for it. + */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == -1 && - next->op == OP_bit_and && ph2_ir->dest == next->src1) { + ph2_ir->src1 == (next->size_bytes > 4 ? -1 : 0) && + next->op == OP_bit_and && ph2_ir->dest == next->src1 && + const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li -1; and x, -1} → {mov x} (x & 0xFFFFFFFF = x) Example: * {li t1, -1; and result, var, t1} → {mov result, var} Eliminates * bitwise AND with all-ones mask @@ -166,8 +437,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) } if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - (next->op == OP_lshift || next->op == OP_rshift) && - ph2_ir->dest == next->src1) { + ph2_ir->src1 == 0 && (next->op == OP_lshift || next->op == OP_rshift) && + ph2_ir->dest == next->src1 && const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 0; shl/shr x, 0} → {mov x} (x << 0 = x >> 0 = x) * Example: {li t1, 0; shl result, var, t1} → {mov result, var} * Eliminates no-op shift operations @@ -180,7 +451,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) } if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - next->op == OP_bit_or && ph2_ir->dest == next->src1) { + ph2_ir->src1 == 0 && next->op == OP_bit_or && + ph2_ir->dest == next->src1 && const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 0; or x, 0} → {mov x} (x | 0 = x) Example: {li t1, 0; or * result, var, t1} → {mov result, var} Eliminates bitwise OR with zero * (identity element) @@ -196,9 +468,9 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) * significantly faster than multiplication */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 > 0 && - next->op == OP_mul && ph2_ir->dest == next->src1) { + ph2_ir->src1 == 0 && next->op == OP_mul && ph2_ir->dest == next->src1) { int shift_amount = exact_log2(ph2_ir->src0); - if (shift_amount >= 0) { + if (shift_amount >= 0 && const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 2^n; mul x, 2^n} → {li n; shl x, n} Example: {li t1, * 4; mul result, var, t1} → * {li t1, 2; shl result, var, t1} @@ -211,7 +483,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) /* XOR identity operation */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - next->op == OP_bit_xor && ph2_ir->dest == next->src1) { + ph2_ir->src1 == 0 && next->op == OP_bit_xor && + ph2_ir->dest == next->src1 && const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 0; xor x, 0} → {mov x} (x ^ 0 = x) Example: {li t1, 0; * xor result, var, t1} → {mov result, var} Completes bitwise identity * optimization coverage @@ -227,7 +500,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) * case where constant 1 is in src0 position of multiplication */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 1 && - next->op == OP_mul && ph2_ir->dest == next->src0) { + ph2_ir->src1 == 0 && next->op == OP_mul && ph2_ir->dest == next->src0 && + const_fold_ok(bb, ph2_ir, next)) { /* Pattern: {li 1; mul 1, x} → {mov x} (1 * x = x) Example: {li t1, 1; * mul result, t1, var} → {mov result, var} Covers multiplication * commutativity edge case @@ -242,8 +516,25 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) return false; } -/* Redundant move elimination Eliminates unnecessary move operations that are - * overwritten or redundant +/* Whether @ir does nothing but set its destination register: a move, a slot + * load or a constant load. + */ +bool is_plain_register_def(const ph2_ir_t *ir) +{ + return ir->op == OP_assign || ir->op == OP_load || + ir->op == OP_global_load || ir->op == OP_load_constant; +} + +/* Redundant move elimination: a move, load or constant load whose register is + * immediately overwritten by another one is dead. {mov rd, rs1; mov rd, rs2}, + * {load rd, ofs; mov rd, rs}, {li rd, imm; load rd, ofs} and the other + * combinations all reduce to their second instruction. + * + * The survivor is the second instruction in full. Carrying over only its opcode + * and sources kept the first one's width and signedness, and x86-64 narrows a + * move by those: a move of a stack address that inherited the flags of an + * overwritten unsigned int constant kept only its low 32 bits. A constant's + * high word in src1 was likewise dropped. */ bool redundant_move_elim(basic_block_t *bb, ph2_ir_t *ph2_ir) { @@ -251,99 +542,18 @@ bool redundant_move_elim(basic_block_t *bb, ph2_ir_t *ph2_ir) if (!next) return false; - /* Pattern 1: Consecutive assignments to same destination {mov rd, rs1; mov - * rd, rs2} → {mov rd, rs2} The first move is completely overwritten by the - * second - */ - if (ph2_ir->op == OP_assign && next->op == OP_assign && - ph2_ir->dest == next->dest) { - /* Replace first move with second, skip second */ - ph2_ir->src0 = next->src0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 2: Redundant load immediately overwritten {load rd, offset; mov - * rd, rs} → {mov rd, rs} Loading a value that's immediately replaced is - * wasteful - */ - if ((ph2_ir->op == OP_load || ph2_ir->op == OP_global_load) && - next->op == OP_assign && ph2_ir->dest == next->dest) { - /* Replace load with move */ - ph2_ir->op = OP_assign; - ph2_ir->src0 = next->src0; - ph2_ir->src1 = 0; /* Clear unused field */ - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 3: Load constant immediately overwritten {li rd, imm; mov rd, rs} - * → {mov rd, rs} Loading a constant that's immediately replaced - */ - if (ph2_ir->op == OP_load_constant && next->op == OP_assign && - ph2_ir->dest == next->dest) { - /* Replace constant load with move */ - ph2_ir->op = OP_assign; - ph2_ir->src0 = next->src0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 4: Consecutive loads to same register {load rd, offset1; load rd, - * offset2} → {load rd, offset2} First load is pointless if immediately - * overwritten - */ - if ((ph2_ir->op == OP_load || ph2_ir->op == OP_global_load) && - (next->op == OP_load || next->op == OP_global_load) && - ph2_ir->dest == next->dest) { - /* Keep only the second load */ - ph2_ir->op = next->op; - ph2_ir->src0 = next->src0; - ph2_ir->src1 = next->src1; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 5: Consecutive constant loads (already handled in main loop but - * included here for completeness) {li rd, imm1; li rd, imm2} → {li rd, - * imm2} - */ - if (ph2_ir->op == OP_load_constant && next->op == OP_load_constant && - ph2_ir->dest == next->dest) { - /* Keep only the second constant */ - ph2_ir->src0 = next->src0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 6: Move followed by load {mov rd, rs; load rd, offset} → {load - * rd, offset} The move is pointless if immediately overwritten by load - */ - if (ph2_ir->op == OP_assign && - (next->op == OP_load || next->op == OP_global_load) && - ph2_ir->dest == next->dest) { - /* Replace move+load with just the load */ - ph2_ir->op = next->op; - ph2_ir->src0 = next->src0; - ph2_ir->src1 = next->src1; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 7: Move followed by constant load {mov rd, rs; li rd, imm} → {li - * rd, imm} The move is pointless if immediately overwritten by constant + /* A volatile load is an access the program performs, not only a value it + * computes, so it stays even when its register is overwritten unread. */ - if (ph2_ir->op == OP_assign && next->op == OP_load_constant && - ph2_ir->dest == next->dest) { - /* Replace move+li with just the li */ - ph2_ir->op = OP_load_constant; - ph2_ir->src0 = next->src0; - ph2_ir->src1 = 0; /* Clear unused field */ - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } + if (!is_plain_register_def(ph2_ir) || !is_plain_register_def(next) || + ph2_ir->is_volatile || ph2_ir->dest != next->dest || + (ph2_ir->dest_hi >= 0 && ph2_ir->dest_hi != next->dest_hi) || + ir_reads_reg(next, ph2_ir->dest) || ir_reads_reg(next, ph2_ir->dest_hi)) + return false; - return false; + memcpy(ph2_ir, next, sizeof(ph2_ir_t)); + ph2_ir_drop_after(bb, ph2_ir, ph2_ir); + return true; } /* Load/store elimination for consecutive memory operations. Removes redundant @@ -372,6 +582,7 @@ bool eliminate_load_store_pairs(basic_block_t *bb, ph2_ir_t *ph2_ir) * first put there, so dropping the first loses them. */ if (ph2_ir->src1 == next->src1 && ph2_ir->src1 >= 0 && + !ph2_ir->is_volatile && !next->is_volatile && ph2_ir->size_bytes == next->size_bytes && ph2_ir->is_pointer == next->is_pointer && ph2_ir->ofs_based_on_stack_top == next->ofs_based_on_stack_top) { @@ -383,6 +594,12 @@ bool eliminate_load_store_pairs(basic_block_t *bb, ph2_ir_t *ph2_ir) } } + /* None of the rewrites below may turn a volatile load into a copy: the + * second access is as much a side effect as the first. + */ + if (next->is_volatile) + return false; + /* Pattern 2: Redundant consecutive loads from same local location {load * rd1, [addr]; load rd2, [addr]} → {load rd1, [addr]; mov rd2, rd1} Second * load can reuse the first load's result Only apply if addresses are simple @@ -487,7 +704,7 @@ bool eliminate_load_store_pairs(basic_block_t *bb, ph2_ir_t *ph2_ir) * * Returns true if optimization was applied */ -bool strength_reduction(ph2_ir_t *ph2_ir) +bool strength_reduction(basic_block_t *bb, ph2_ir_t *ph2_ir) { if (!ph2_ir || !ph2_ir->next) return false; @@ -498,10 +715,22 @@ bool strength_reduction(ph2_ir_t *ph2_ir) if (ph2_ir->op != OP_load_constant) return false; + /* Each rewrite below loads a different constant into the register, which + * const_fold_ok() allows only once nothing else wants the original. + */ + if (next->op != OP_div && next->op != OP_mod && next->op != OP_mul) + return false; + if (next->src0 != ph2_ir->dest && next->src1 != ph2_ir->dest) + return false; + int value = ph2_ir->src0; - /* Check if value is a power of 2 */ - if (value <= 0 || (value & (value - 1)) != 0) + /* Check if value is a power of 2. src1 is the high word of an eight-byte + * constant, and 0x100000004ULL is no power of two. + */ + if (ph2_ir->src1 || value <= 0 || (value & (value - 1)) != 0) + return false; + if (!const_fold_ok(bb, ph2_ir, next)) return false; /* Calculate shift amount for power of 2 */ @@ -515,7 +744,8 @@ bool strength_reduction(ph2_ir_t *ph2_ir) /* Pattern 1: Division by power of 2 → right shift x / 2^n = x >> n (for * unsigned) */ - if (next->op == OP_div && next->src1 == ph2_ir->dest) { + if (next->op == OP_div && next->src0_is_unsigned && + next->src1 == ph2_ir->dest) { /* Convert division to right shift */ ph2_ir->src0 = shift; /* Load shift amount instead */ next->op = OP_rshift; @@ -523,7 +753,8 @@ bool strength_reduction(ph2_ir_t *ph2_ir) } /* Pattern 2: Modulo by power of 2 → bitwise AND x % 2^n = x & (2^n - 1) */ - if (next->op == OP_mod && next->src1 == ph2_ir->dest) { + if (next->op == OP_mod && next->src0_is_unsigned && + next->src1 == ph2_ir->dest) { /* Convert modulo to bitwise AND */ ph2_ir->src0 = value - 1; /* Load mask (2^n - 1) */ next->op = OP_bit_and; @@ -564,9 +795,11 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) ph2_ir_t *next = ph2_ir->next; - /* Pattern 1: Double complement → identity ~(~x) = x */ + /* Pattern 1: Double complement → identity ~(~x) = x. The first complement + * no longer writes its register, so nothing else may read it. + */ if (ph2_ir->op == OP_bit_not && next->op == OP_bit_not && - next->src0 == ph2_ir->dest) { + next->src0 == ph2_ir->dest && !fold_loses_dest(bb, ph2_ir, next)) { /* Replace with simple assignment */ ph2_ir->op = OP_assign; ph2_ir->dest = next->dest; @@ -574,71 +807,20 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) return true; } - /* Pattern 2: AND with all-ones mask → identity x & 0xFFFFFFFF = x (for - * 32-bit) - */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == -1 && - next->op == OP_bit_and && next->src1 == ph2_ir->dest) { - /* Replace AND with assignment */ - next->op = OP_assign; - next->src1 = 0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 3: OR with zero → identity x | 0 = x */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - next->op == OP_bit_or && next->src1 == ph2_ir->dest) { - /* Replace OR with assignment */ - next->op = OP_assign; - next->src1 = 0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 4: XOR with zero → identity x ^ 0 = x */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - next->op == OP_bit_xor && next->src1 == ph2_ir->dest) { - /* Replace XOR with assignment */ - next->op = OP_assign; - next->src1 = 0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 5: AND with zero → zero x & 0 = 0 */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - next->op == OP_bit_and && - (next->src0 == ph2_ir->dest || next->src1 == ph2_ir->dest)) { - /* Replace with constant load of 0 */ - next->op = OP_load_constant; - next->src0 = 0; - next->src1 = 0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 6: OR with all-ones → all-ones x | 0xFFFFFFFF = 0xFFFFFFFF */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == -1 && - next->op == OP_bit_or && - (next->src0 == ph2_ir->dest || next->src1 == ph2_ir->dest)) { - /* Replace with constant load of -1 */ - next->op = OP_load_constant; - next->src0 = -1; - next->src1 = 0; - ph2_ir_drop_after(bb, ph2_ir, next); - return true; - } - - /* Pattern 7: Shift by zero → identity - * x << 0 = x, x >> 0 = x + /* Pattern 2: AND with zero → zero x & 0 = 0, and OR with all-ones → + * all-ones x | 0xFFFFFFFF = 0xFFFFFFFF. The constant load is retargeted to + * the result and the operation goes. An eight-byte OR needs the high word, + * src1, all ones as well. The identities x & -1, x | 0, x ^ 0 and x << 0 + * belong to insn_fusion(), which tries them first. */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - (next->op == OP_lshift || next->op == OP_rshift) && - next->src1 == ph2_ir->dest) { - /* Replace shift with assignment */ - next->op = OP_assign; - next->src1 = 0; + if (ph2_ir->op == OP_load_constant && + ((ph2_ir->src0 == 0 && ph2_ir->src1 == 0 && next->op == OP_bit_and) || + (ph2_ir->src0 == -1 && + ph2_ir->src1 == (next->size_bytes > 4 ? -1 : 0) && + next->op == OP_bit_or)) && + (next->src0 == ph2_ir->dest || next->src1 == ph2_ir->dest) && + const_fold_ok(bb, ph2_ir, next)) { + ph2_ir->dest = next->dest; ph2_ir_drop_after(bb, ph2_ir, next); return true; } @@ -662,13 +844,15 @@ bool triple_pattern_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * store val2, addr} The middle load is pointless if not used elsewhere */ if (ph2_ir->op == OP_store && second->op == OP_load && - third->op == OP_store && + !second->is_volatile && third->op == OP_store && ph2_ir->src1 == second->src0 && /* same address */ ph2_ir->dest == second->src1 && /* same offset */ second->src0 == third->src1 && /* same address */ second->src1 == third->dest) { /* same offset */ - /* Check if the loaded value is used by the third store */ - if (third->src0 != second->dest) { + /* Only when nothing reads the loaded value, the third store included: + * without the load the register keeps what it held before. + */ + if (!reg_read_after(bb, second, second->dest)) { /* The load result is not used, can eliminate it */ ph2_ir->next = third; return true; @@ -679,7 +863,8 @@ bool triple_pattern_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * addr; store v3, addr} Only the last store matters */ if (ph2_ir->op == OP_store && second->op == OP_store && - third->op == OP_store && ph2_ir->src1 == second->src1 && + third->op == OP_store && !ph2_ir->is_volatile && !second->is_volatile && + !third->is_volatile && ph2_ir->src1 == second->src1 && ph2_ir->dest == second->dest && second->src1 == third->src1 && second->dest == third->dest) { /* All three stores go to the same location Only the last one matters, @@ -752,7 +937,8 @@ void peephole(void) * handles most cases, but register allocation might create new * self-assignments */ - if (next->op == OP_assign && next->dest == next->src0) { + if (next->op == OP_assign && next->dest == next->src0 && + next->dest_hi == next->src0_hi) { ph2_ir_drop_after(bb, ir, next); continue; } @@ -769,6 +955,13 @@ void peephole(void) ((func->pinned_regs >> ir->dest) & 1)) continue; + if (PTR_SIZE < 8 && + (ph2_ir_has_pair(ir) || ph2_ir_has_pair(next) || + ph2_ir_has_pair(next->next))) { + pair_insn_fusion(bb, ir); + continue; + } + /* Try triple pattern optimization first (3-instruction * sequences) */ @@ -780,7 +973,7 @@ void peephole(void) continue; /* Apply strength reduction for power-of-2 operations */ - if (strength_reduction(ir)) + if (strength_reduction(bb, ir)) continue; /* Apply bitwise operation optimizations */ diff --git a/src/preprocessor.c b/src/preprocessor.c index 422a9c7e..27bfe5f6 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -81,6 +81,7 @@ typedef struct macro { int param_num; token_t *param_names[MAX_PARAMS]; token_t *replacement; + bool is_function_like; bool is_variadic; token_t *variadic_tk; bool is_disabled; @@ -95,6 +96,575 @@ bool is_macro_defined(char *name) return macro && !macro->is_disabled; } +/* The freestanding runtime supplies these declarations internally, so their + * standard headers intentionally remain optional when no implementation search + * path is configured. All other angle headers must resolve through -I. + */ +bool is_builtin_system_header(const char *name) +{ + return !strcmp(name, "assert.h") || !strcmp(name, "ctype.h") || + !strcmp(name, "errno.h") || !strcmp(name, "limits.h") || + !strcmp(name, "stdarg.h") || !strcmp(name, "stdbool.h") || + !strcmp(name, "stddef.h") || !strcmp(name, "stdint.h") || + !strcmp(name, "signal.h") || !strcmp(name, "wchar.h") || + !strcmp(name, "stdio.h") || !strcmp(name, "stdlib.h") || + !strcmp(name, "string.h") || !strcmp(name, "sys/stat.h"); +} + +void define_builtin_object_macro(const char *name, + token_kind_t kind, + const char *replacement) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + + macro->name = intern_string((char *) name); + macro->replacement = + new_token(kind, &synth_built_in_loc, strlen(replacement)); + macro->replacement->literal = intern_string((char *) replacement); + hashmap_put(MACROS, macro->name, macro); +} + +/* Integer minimum macros are preprocessing token sequences rather than one + * negative literal token: this preserves C's ordinary unary-minus spelling for + * INT_MIN and LLONG_MIN after macro expansion. + */ +void define_builtin_negative_macro(const char *name, const char *magnitude) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + token_t *minus = new_token(T_minus, &synth_built_in_loc, 1); + token_t *numeric = + new_token(T_numeric, &synth_built_in_loc, strlen(magnitude)); + + macro->name = intern_string((char *) name); + minus->literal = "-"; + numeric->literal = intern_string((char *) magnitude); + minus->next = numeric; + macro->replacement = minus; + hashmap_put(MACROS, macro->name, macro); +} + +token_t *append_builtin_macro_token(token_t **replacement, + token_t **tail, + token_kind_t kind, + const char *literal); + +/* C99's integer construction macros append a target representation suffix to + * their single integer-token argument. The preprocessor's ordinary ## path + * rescans the joined spelling, so the replacement remains a numeric token. + */ +void define_builtin_integer_construction_macro(const char *name, + const char *suffix) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + token_t *tail = NULL; + + macro->name = intern_string((char *) name); + macro->is_function_like = true; + macro->param_num = 1; + macro->param_names[0] = new_token(T_identifier, &synth_built_in_loc, 5); + macro->param_names[0]->literal = "value"; + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "value"); + if (suffix[0]) { + append_builtin_macro_token(¯o->replacement, &tail, T_hashhash, + "##"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + suffix); + } + hashmap_put(MACROS, macro->name, macro); +} + +token_t *append_builtin_macro_token(token_t **replacement, + token_t **tail, + token_kind_t kind, + const char *literal) +{ + token_t *token = new_token(kind, &synth_built_in_loc, strlen(literal)); + + token->literal = intern_string((char *) literal); + if (*tail) { + token_t *previous = *tail; + + previous->next = token; + } else + *replacement = token; + *tail = token; + return token; +} + +/* Spell signed minima as subtraction from a representable maximum. This keeps + * the macro's expression type equal to the represented C type. + */ +void define_builtin_minimum_macro(const char *name, + const char *maximum, + const char *one) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + token_t *tail = NULL; + + macro->name = intern_string((char *) name); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_minus, "-"); + append_builtin_macro_token(¯o->replacement, &tail, T_numeric, maximum); + append_builtin_macro_token(¯o->replacement, &tail, T_minus, "-"); + append_builtin_macro_token(¯o->replacement, &tail, T_numeric, one); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + hashmap_put(MACROS, macro->name, macro); +} + +/* offsetof is specified as a function-like macro. Route its type/member + * operands to the parser so record layout, rather than a null-pointer member + * expression, determines the result. + */ +void install_stddef_offsetof_macro(void) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + token_t *tail = NULL; + + macro->name = intern_string("offsetof"); + macro->is_function_like = true; + macro->param_num = 2; + macro->param_names[0] = new_token(T_identifier, &synth_built_in_loc, 4); + macro->param_names[0]->literal = "type"; + macro->param_names[1] = new_token(T_identifier, &synth_built_in_loc, 6); + macro->param_names[1]->literal = "member"; + + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__builtin_offsetof"); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "type"); + append_builtin_macro_token(¯o->replacement, &tail, T_comma, ","); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "member"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + hashmap_put(MACROS, macro->name, macro); +} + +/* shecc currently has signed plain char, 16-bit short, 32-bit int/long, and + * 64-bit long long on every target. Keep tied to those actual + * language types rather than to the host compiler's ABI. + */ +void install_limits_header(void) +{ + define_builtin_object_macro("CHAR_BIT", T_numeric, "8"); + define_builtin_object_macro("SCHAR_MAX", T_numeric, "127"); + define_builtin_object_macro("UCHAR_MAX", T_numeric, "255U"); + define_builtin_object_macro("CHAR_MAX", T_numeric, "127"); + define_builtin_object_macro("SHRT_MAX", T_numeric, "32767"); + define_builtin_object_macro("USHRT_MAX", T_numeric, "65535U"); + define_builtin_object_macro("INT_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("UINT_MAX", T_numeric, "4294967295U"); + define_builtin_object_macro("LONG_MAX", T_numeric, "2147483647L"); + define_builtin_object_macro("ULONG_MAX", T_numeric, "4294967295UL"); + define_builtin_object_macro("LLONG_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("ULLONG_MAX", T_numeric, + "18446744073709551615ULL"); + define_builtin_object_macro("MB_LEN_MAX", T_numeric, "1"); + + define_builtin_negative_macro("SCHAR_MIN", "128"); + define_builtin_negative_macro("CHAR_MIN", "128"); + define_builtin_negative_macro("SHRT_MIN", "32768"); + define_builtin_minimum_macro("INT_MIN", "2147483647", "1"); + define_builtin_minimum_macro("LONG_MIN", "2147483647L", "1L"); + define_builtin_minimum_macro("LLONG_MIN", "9223372036854775807LL", "1LL"); +} + +/* A C null pointer constant may be the integer constant expression 0. */ +void install_stddef_header(void) +{ + define_builtin_object_macro("NULL", T_numeric, "0"); + install_stddef_offsetof_macro(); +} + +/* 's typedefs are parser-provided target types; install the target + * macro surface alongside them when the header is included. + */ +void install_stdint_header(void) +{ + define_builtin_negative_macro("INT8_MIN", "128"); + define_builtin_object_macro("INT8_MAX", T_numeric, "127"); + define_builtin_object_macro("UINT8_MAX", T_numeric, "255U"); + define_builtin_negative_macro("INT16_MIN", "32768"); + define_builtin_object_macro("INT16_MAX", T_numeric, "32767"); + define_builtin_object_macro("UINT16_MAX", T_numeric, "65535U"); + define_builtin_minimum_macro("INT32_MIN", "2147483647", "1"); + define_builtin_object_macro("INT32_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("UINT32_MAX", T_numeric, "4294967295U"); + define_builtin_minimum_macro("INT64_MIN", "9223372036854775807LL", "1LL"); + define_builtin_object_macro("INT64_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("UINT64_MAX", T_numeric, + "18446744073709551615ULL"); + define_builtin_minimum_macro("INTMAX_MIN", "9223372036854775807LL", "1LL"); + define_builtin_object_macro("INTMAX_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("UINTMAX_MAX", T_numeric, + "18446744073709551615ULL"); + define_builtin_minimum_macro("SIG_ATOMIC_MIN", "2147483647", "1"); + define_builtin_object_macro("SIG_ATOMIC_MAX", T_numeric, "2147483647"); + define_builtin_minimum_macro("WCHAR_MIN", "2147483647", "1"); + define_builtin_object_macro("WCHAR_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("WINT_MIN", T_numeric, "0U"); + define_builtin_object_macro("WINT_MAX", T_numeric, "4294967295U"); + define_builtin_negative_macro("INT_LEAST8_MIN", "128"); + define_builtin_object_macro("INT_LEAST8_MAX", T_numeric, "127"); + define_builtin_object_macro("UINT_LEAST8_MAX", T_numeric, "255U"); + define_builtin_negative_macro("INT_LEAST16_MIN", "32768"); + define_builtin_object_macro("INT_LEAST16_MAX", T_numeric, "32767"); + define_builtin_object_macro("UINT_LEAST16_MAX", T_numeric, "65535U"); + define_builtin_minimum_macro("INT_LEAST32_MIN", "2147483647", "1"); + define_builtin_object_macro("INT_LEAST32_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("UINT_LEAST32_MAX", T_numeric, "4294967295U"); + define_builtin_minimum_macro("INT_LEAST64_MIN", "9223372036854775807LL", + "1LL"); + define_builtin_object_macro("INT_LEAST64_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("UINT_LEAST64_MAX", T_numeric, + "18446744073709551615ULL"); + define_builtin_minimum_macro("INT_FAST8_MIN", "2147483647", "1"); + define_builtin_object_macro("INT_FAST8_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("UINT_FAST8_MAX", T_numeric, "4294967295U"); + define_builtin_minimum_macro("INT_FAST16_MIN", "2147483647", "1"); + define_builtin_object_macro("INT_FAST16_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("UINT_FAST16_MAX", T_numeric, "4294967295U"); + define_builtin_minimum_macro("INT_FAST32_MIN", "2147483647", "1"); + define_builtin_object_macro("INT_FAST32_MAX", T_numeric, "2147483647"); + define_builtin_object_macro("UINT_FAST32_MAX", T_numeric, "4294967295U"); + define_builtin_minimum_macro("INT_FAST64_MIN", "9223372036854775807LL", + "1LL"); + define_builtin_object_macro("INT_FAST64_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("UINT_FAST64_MAX", T_numeric, + "18446744073709551615ULL"); + define_builtin_integer_construction_macro("INT8_C", ""); + define_builtin_integer_construction_macro("UINT8_C", ""); + define_builtin_integer_construction_macro("INT16_C", ""); + define_builtin_integer_construction_macro("UINT16_C", ""); + define_builtin_integer_construction_macro("INT32_C", ""); + define_builtin_integer_construction_macro("UINT32_C", "U"); + define_builtin_integer_construction_macro("INT64_C", "LL"); + define_builtin_integer_construction_macro("UINT64_C", "ULL"); + define_builtin_integer_construction_macro("INTMAX_C", "LL"); + define_builtin_integer_construction_macro("UINTMAX_C", "ULL"); + if (PTR_SIZE == 8) { + define_builtin_minimum_macro("INTPTR_MIN", "9223372036854775807LL", + "1LL"); + define_builtin_object_macro("INTPTR_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("UINTPTR_MAX", T_numeric, + "18446744073709551615ULL"); + define_builtin_minimum_macro("PTRDIFF_MIN", "9223372036854775807LL", + "1LL"); + define_builtin_object_macro("PTRDIFF_MAX", T_numeric, + "9223372036854775807LL"); + define_builtin_object_macro("SIZE_MAX", T_numeric, + "18446744073709551615ULL"); + } else { + define_builtin_minimum_macro("INTPTR_MIN", "2147483647L", "1L"); + define_builtin_object_macro("INTPTR_MAX", T_numeric, "2147483647L"); + define_builtin_object_macro("UINTPTR_MAX", T_numeric, "4294967295UL"); + define_builtin_minimum_macro("PTRDIFF_MIN", "2147483647L", "1L"); + define_builtin_object_macro("PTRDIFF_MAX", T_numeric, "2147483647L"); + define_builtin_object_macro("SIZE_MAX", T_numeric, "4294967295UL"); + } +} + +/* stdbool.h is entirely macro-defined in C99. Supplying it here makes the + * freestanding compiler usable with --no-libc too, rather than relying on the + * private definitions prepended from lib/c.h. + */ +void install_stdbool_header(void) +{ + define_builtin_object_macro("bool", T_identifier, "_Bool"); + define_builtin_object_macro("true", T_numeric, "1"); + define_builtin_object_macro("false", T_numeric, "0"); + define_builtin_object_macro("__bool_true_false_are_defined", T_numeric, + "1"); +} + +/* iso646.h is likewise a pure C99 macro header. Keep the replacement token + * kinds explicit so the operators retain their ordinary parser precedence. + */ +void install_iso646_header(void) +{ + define_builtin_object_macro("and", T_log_and, "&&"); + define_builtin_object_macro("and_eq", T_andeq, "&="); + define_builtin_object_macro("bitand", T_ampersand, "&"); + define_builtin_object_macro("bitor", T_bit_or, "|"); + define_builtin_object_macro("compl", T_bit_not, "~"); + define_builtin_object_macro("not", T_log_not, "!"); + define_builtin_object_macro("not_eq", T_noteq, "!="); + define_builtin_object_macro("or", T_log_or, "||"); + define_builtin_object_macro("or_eq", T_oreq, "|="); + define_builtin_object_macro("xor", T_bit_xor, "^"); + define_builtin_object_macro("xor_eq", T_xoreq, "^="); +} + +/* assert.h is a function-like macro header. Reinstall it on each inclusion so + * an NDEBUG definition made before that inclusion controls its replacement. + */ +void install_assert_header(void) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + token_t *tail = NULL; + + macro->name = intern_string("assert"); + macro->is_function_like = true; + macro->param_num = 1; + macro->param_names[0] = new_token(T_identifier, &synth_built_in_loc, 4); + macro->param_names[0]->literal = "expr"; + + if (!is_macro_defined("NDEBUG")) { + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, + "("); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, + "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "expr"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_question, "?"); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, + "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "void"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_numeric, "0"); + append_builtin_macro_token(¯o->replacement, &tail, T_colon, ":"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__assert_fail"); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, + "("); + append_builtin_macro_token(¯o->replacement, &tail, T_hash, "#"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "expr"); + append_builtin_macro_token(¯o->replacement, &tail, T_comma, ","); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__FILE__"); + append_builtin_macro_token(¯o->replacement, &tail, T_comma, ","); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__LINE__"); + append_builtin_macro_token(¯o->replacement, &tail, T_comma, ","); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__func__"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + } else { + /* C99 requires the disabled form to remain a void expression while not + * evaluating its operand. + */ + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, + "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "void"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_numeric, "0"); + } + hashmap_put(MACROS, macro->name, macro); +} + +macro_t *new_stdarg_macro(const char *name, + const char *first_param, + const char *second_param) +{ + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + + macro->name = intern_string((char *) name); + macro->is_function_like = true; + macro->param_num = second_param ? 2 : 1; + macro->param_names[0] = + new_token(T_identifier, &synth_built_in_loc, strlen(first_param)); + macro->param_names[0]->literal = intern_string((char *) first_param); + if (second_param) { + macro->param_names[1] = + new_token(T_identifier, &synth_built_in_loc, strlen(second_param)); + macro->param_names[1]->literal = intern_string((char *) second_param); + } + return macro; +} + +/* The current ABI spills every variadic argument into a pointer-sized slot. + * These macros deliberately cover integer and pointer arguments only; floating + * argument promotion waits for the compiler's explicit FP ABI support. + */ +void install_stdarg_header(void) +{ + macro_t *macro; + token_t *tail; + + define_builtin_object_macro("__VA_SLOT_WORDS", T_numeric, + PTR_SIZE == 8 ? "2" : "1"); + define_builtin_object_macro("__VA_SLOT_BYTES", T_numeric, + PTR_SIZE == 8 ? "8" : "4"); + + macro = new_stdarg_macro("va_start", "ap", "last"); + tail = NULL; + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, "ap"); + append_builtin_macro_token(¯o->replacement, &tail, T_assign, "="); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "va_list"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_ampersand, "&"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "last"); + append_builtin_macro_token(¯o->replacement, &tail, T_plus, "+"); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_sizeof, "sizeof"); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "last"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_plus, "+"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__VA_SLOT_BYTES"); + append_builtin_macro_token(¯o->replacement, &tail, T_minus, "-"); + append_builtin_macro_token(¯o->replacement, &tail, T_numeric, "1"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_divide, "/"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__VA_SLOT_BYTES"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_asterisk, "*"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__VA_SLOT_WORDS"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + hashmap_put(MACROS, macro->name, macro); + + macro = new_stdarg_macro("va_arg", "ap", "type"); + tail = NULL; + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "__builtin_va_arg"); + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_ampersand, "&"); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, "ap"); + append_builtin_macro_token(¯o->replacement, &tail, T_comma, ","); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "type"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + hashmap_put(MACROS, macro->name, macro); + + macro = new_stdarg_macro("va_copy", "dest", "src"); + tail = NULL; + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "dest"); + append_builtin_macro_token(¯o->replacement, &tail, T_assign, "="); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, "src"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + hashmap_put(MACROS, macro->name, macro); + + macro = new_stdarg_macro("va_end", "ap", NULL); + tail = NULL; + append_builtin_macro_token(¯o->replacement, &tail, T_open_bracket, "("); + append_builtin_macro_token(¯o->replacement, &tail, T_identifier, + "void"); + append_builtin_macro_token(¯o->replacement, &tail, T_close_bracket, + ")"); + append_builtin_macro_token(¯o->replacement, &tail, T_numeric, "0"); + hashmap_put(MACROS, macro->name, macro); +} + +/* An angle header is lexed as ordinary preprocessing tokens. Recover its + * spelling from the immutable source buffer so dotted and slash-separated names + * do not need special punctuation reconstruction here. That buffer holds the + * physical bytes, so read it through translation phases 1 and 2: a trigraph + * stands for its character and a backslash-newline joins the name. + */ +bool resolve_angle_include(token_t *open, + token_t *close, + char *resolved, + int resolved_size) +{ + strbuf_t *source = get_file_buf(open->location.physical_filename); + int pos = open->location.pos + open->location.len; + int end = close->location.pos; + int len = 0; + char name[MAX_LINE_LEN]; + + while ((pos = skip_splices(source, pos)) < end) { + if (len >= MAX_LINE_LEN - 1) + error_at("Invalid #include <...> header name", &open->location); + name[len++] = source_char_at(source, pos); + pos += source_char_width(source, pos); + } + if (len <= 0) + error_at("Invalid #include <...> header name", &open->location); + name[len] = '\0'; + for (int i = 0; i < len; i++) + if (name[i] == ' ' || name[i] == '\t') + error_at("Whitespace is not permitted in #include <...>", + &open->location); + + for (int i = 0; i < include_dirs_idx; i++) { + FILE *file; + + /* A truncated path would name some other file, or none, and let a + * built-in header silently stand in for the one in this directory. + */ + if (snprintf(resolved, resolved_size, "%s/%s", include_dirs[i], name) >= + resolved_size) + error_at("#include path is too long", &open->location); + file = fopen(resolved, "rb"); + if (file) { + fclose(file); + return true; + } + } + if (!strcmp(name, "stdbool.h")) { + install_stdbool_header(); + return false; + } + if (!strcmp(name, "iso646.h")) { + install_iso646_header(); + return false; + } + if (!strcmp(name, "limits.h")) { + install_limits_header(); + return false; + } + if (!strcmp(name, "stddef.h")) { + install_stddef_header(); + return false; + } + if (!strcmp(name, "stdint.h")) { + install_stdint_header(); + return false; + } + if (!strcmp(name, "assert.h")) { + install_assert_header(); + return false; + } + if (!strcmp(name, "stdarg.h")) { + install_stdarg_header(); + return false; + } + if (is_builtin_system_header(name)) + return false; + error_at("Angle header not found in -I search paths", &open->location); + return false; +} + /* file_macro_handler is responsible for expanding built-in macro "__FILE__" * inplace with a string token with file's relative path's name literally */ @@ -122,6 +692,52 @@ token_t *line_macro_handler(token_t *tk) return new_tk; } +/* C99 6.10.8 requires these strings to describe the translation time. The + * generated configuration supplies that time once for all bootstrap stages; + * consulting a clock while compiling would make stage 1 and stage 2 differ. + * + * A handler receives the outermost invocation, which is the name of whatever + * macro led here rather than __DATE__ or __TIME__ itself, so each gets its own + * handler instead of one that looks at the name. + */ +token_t *translation_timestamp_token(token_t *tk, char *literal) +{ + token_t *new_tk = copy_token(tk); + + new_tk->kind = T_string; + new_tk->literal = literal; + memcpy(&new_tk->location, &tk->location, sizeof(source_location_t)); + return new_tk; +} + +token_t *date_macro_handler(token_t *tk) +{ + return translation_timestamp_token(tk, SHECC_TRANSLATION_DATE); +} + +token_t *time_macro_handler(token_t *tk) +{ + return translation_timestamp_token(tk, SHECC_TRANSLATION_TIME); +} + +/* Remap the remaining physical tokens from one source stream for #line. + * Included files are preprocessed recursively from separate token streams, so + * they retain their own logical locations. + */ +void pp_apply_line_directive(token_t *first, + char *original_filename, + char *logical_filename, + int line_delta) +{ + for (token_t *it = first; it && it->kind != T_eof; it = it->next) { + if (strcmp(it->location.physical_filename, original_filename)) + continue; + it->location.line += line_delta; + if (logical_filename) + it->location.filename = logical_filename; + } +} + /* hide_set_t is used to track which macros have been expanded in the previous * expanding context, if so, it'll get added into hide set of context to prevent * endless recursion macro expansion. @@ -194,6 +810,12 @@ cond_incl_t *push_cond(cond_incl_t *ci, token_t *tk, bool included) */ typedef struct preprocess_ctx { hide_set_t *hide_set; + + /* The hide set of the invocation whose arguments macro_args holds. An + * argument is replaced before it is substituted (C99 6.10.3.1), outside the + * macro it is an argument to, so "A(A(1))" expands both. + */ + hide_set_t *arg_hide_set; hashmap_t *macro_args; token_t *expanded_from; token_t *end_of_token; /* end of token stream of current context */ @@ -203,6 +825,74 @@ typedef struct preprocess_ctx { token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx); char *token_to_string(token_t *tk, char *dest); +/* Macro expansion for #line must be confined to its operands: sending the live + * stream through pp_preprocess_internal() could consume following source + * directives. Copy through (but not including) the directive newline and append + * a private EOF sentinel for a normal standalone rescan. + */ +token_t *pp_expand_line_operands(token_t *directive) +{ + token_t head; + token_t *tail = &head; + token_t *raw = directive->next; + preprocess_ctx_t ctx; + + head.next = NULL; + while (raw && raw->kind != T_newline && raw->kind != T_eof) { + tail->next = copy_token(raw); + tail = tail->next; + raw = raw->next; + } + if (!raw || raw->kind != T_newline) + error_at("Unterminated #line directive", &directive->location); + + tail->next = new_token(T_eof, &raw->location, 0); + ctx.expanded_from = directive; + ctx.hide_set = NULL; + ctx.arg_hide_set = NULL; + ctx.macro_args = NULL; + ctx.trim_eof = false; + return pp_preprocess_internal(head.next, &ctx); +} + +void pp_read_line_operands(token_t *directive, + int *requested_line, + char **logical_filename) +{ + token_t head; + token_t *cursor; + token_t *expanded = pp_expand_line_operands(directive); + + head.next = expanded; + cursor = &head; + if (!pp_lex_peek_token(cursor, T_numeric, true)) + error_at("#line requires a decimal line number", &directive->location); + cursor = pp_lex_next_token(cursor, true); + + /* C99 6.10.4 takes a digit sequence, which is decimal even with a leading + * zero and admits neither a prefix nor a suffix, naming a line from 1 to + * 2147483647. + */ + *requested_line = 0; + for (int i = 0; cursor->literal[i]; i++) { + int digit = cursor->literal[i] - '0'; + + if (digit < 0 || digit > 9) + error_at("#line requires a decimal line number", &cursor->location); + if (*requested_line > (2147483647 - digit) / 10) + error_at("#line number is out of range", &cursor->location); + *requested_line = *requested_line * 10 + digit; + } + if (*requested_line <= 0) + error_at("#line number must be positive", &cursor->location); + if (pp_lex_peek_token(cursor, T_string, true)) { + cursor = pp_lex_next_token(cursor, true); + *logical_filename = intern_string(cursor->literal); + } + if (!pp_lex_peek_token(cursor, T_eof, true)) + error_at("Unexpected token in #line directive", &cursor->location); +} + int pp_get_operator_prio(opcode_t op) { /* https://www.cs.uic.edu/~i109/Notes/COperatorPrecedenceTable.pdf */ @@ -227,6 +917,9 @@ int pp_get_operator_prio(opcode_t op) case OP_gt: case OP_geq: return 10; + case OP_lshift: + case OP_rshift: + return 11; case OP_add: case OP_sub: return 12; @@ -252,7 +945,11 @@ int pp_get_unary_operator_prio(opcode_t op) } } -token_t *pp_get_operator(token_t *tk, opcode_t *op) +/* Look ahead at the next operator. Infix parsing needs to inspect an operator's + * precedence before it consumes it, while unary parsing consumes its operator + * immediately. + */ +token_t *pp_get_operator(token_t *tk, opcode_t *op, bool consume) { tk = pp_lex_skip_space(tk); @@ -267,6 +964,12 @@ token_t *pp_get_operator(token_t *tk, opcode_t *op) case T_minus: op[0] = OP_sub; break; + case T_bit_not: + op[0] = OP_bit_not; + break; + case T_log_not: + op[0] = OP_log_not; + break; case T_asterisk: op[0] = OP_mul; break; @@ -323,43 +1026,536 @@ token_t *pp_get_operator(token_t *tk, opcode_t *op) op[0] = OP_generic; return tk; } - tk = pp_lex_next_token(tk, true); - return tk; + return consume ? pp_lex_next_token(tk, true) : tk; +} + +/* C99 evaluates all integer constants in #if as intmax_t or uintmax_t. Keep the + * bit pattern separate from its signedness so unsigned comparisons and shifts + * do not accidentally use the host compiler's signed int semantics. + */ +typedef struct pp_integer { + unsigned int lo; + unsigned int hi; + unsigned int is_unsigned; + int enum_width; +} pp_integer_t; + +/* The parser reuses this exact two-word evaluator for typed enum constant + * expressions. Outside that narrowly scoped use, #if's standard rule still + * treats unknown identifiers as zero. + */ +block_t *pp_integer_constant_scope = NULL; + +void pp_enum_normalize(pp_integer_t *val) +{ + if (val->enum_width != 32) + return; + val->hi = val->is_unsigned || !(val->lo & 0x80000000U) ? 0 : ~0U; +} + +void pp_enum_convert_width(pp_integer_t *val, int width, int is_unsigned) +{ + if (width == 64 && val->enum_width == 32 && !val->is_unsigned && + (val->lo & 0x80000000U)) + val->hi = ~0U; + if (width == 32) + val->hi = 0; + val->enum_width = width; + val->is_unsigned = is_unsigned; + pp_enum_normalize(val); +} + +void pp_enum_usual_arithmetic(pp_integer_t *lhs, pp_integer_t *rhs) +{ + int width = + lhs->enum_width > rhs->enum_width ? lhs->enum_width : rhs->enum_width; + int is_unsigned; + + if (width == 64) + is_unsigned = (lhs->enum_width == 64 && lhs->is_unsigned) || + (rhs->enum_width == 64 && rhs->is_unsigned); + else + is_unsigned = lhs->is_unsigned || rhs->is_unsigned; + pp_enum_convert_width(lhs, width, is_unsigned); + pp_enum_convert_width(rhs, width, is_unsigned); +} + +void pp_set_boolean(pp_integer_t *val, int truth) +{ + val->lo = truth != 0; + val->hi = 0; + val->is_unsigned = false; + val->enum_width = 32; +} + +int pp_is_true(const pp_integer_t *val) +{ + return val->lo || val->hi; +} + +int PP_USES_UNSIGNED(const pp_integer_t *lhs, const pp_integer_t *rhs) +{ + return lhs->is_unsigned || rhs->is_unsigned; +} + +int pp_is_negative(const pp_integer_t *val) +{ + return !val->is_unsigned && (val->hi & 0x80000000U); +} + +int pp_compare_unsigned(const pp_integer_t *lhs, const pp_integer_t *rhs) +{ + if (lhs->hi != rhs->hi) + return lhs->hi > rhs->hi ? 1 : -1; + if (lhs->lo != rhs->lo) + return lhs->lo > rhs->lo ? 1 : -1; + return 0; } -token_t *pp_read_constant_expr_operand(token_t *tk, int *val) +void pp_negate(pp_integer_t *val) { + val->lo = ~val->lo; + val->hi = ~val->hi; + val->lo++; + if (val->lo == 0) + val->hi++; +} + +void pp_add(pp_integer_t *lhs, const pp_integer_t *rhs) +{ + unsigned int old_lo = lhs->lo; + + lhs->lo += rhs->lo; + lhs->hi += rhs->hi + (lhs->lo < old_lo); +} + +void pp_subtract(pp_integer_t *lhs, const pp_integer_t *rhs) +{ + unsigned int old_lo = lhs->lo; + + lhs->lo -= rhs->lo; + lhs->hi -= rhs->hi + (old_lo < rhs->lo); +} + +void pp_shift_left_one(pp_integer_t *val) +{ + val->hi = (val->hi << 1) | (val->lo >> 31); + val->lo <<= 1; +} + +void pp_multiply_small(pp_integer_t *val, unsigned int amount) +{ + unsigned int original_lo = val->lo, original_hi = val->hi; + + val->lo = val->hi = 0; + for (unsigned int i = 0; i < amount; i++) { + unsigned int old_lo = val->lo; + + val->lo += original_lo; + val->hi += original_hi + (val->lo < old_lo); + } +} + +int pp_literal_will_overflow(const pp_integer_t *value, int base, int digit) +{ + unsigned int limit_lo, limit_hi; + int remainder; + + if (base == 2) { + limit_lo = 0xffffffffU; + limit_hi = 0x7fffffffU; + remainder = 1; + } else if (base == 8) { + limit_lo = 0xffffffffU; + limit_hi = 0x1fffffffU; + remainder = 7; + } else if (base == 10) { + limit_lo = 0x99999999U; + limit_hi = 0x19999999U; + remainder = 5; + } else { + limit_lo = 0xffffffffU; + limit_hi = 0x0fffffffU; + remainder = 15; + } + return value->hi > limit_hi || + (value->hi == limit_hi && value->lo > limit_lo) || + (value->hi == limit_hi && value->lo == limit_lo && + digit > remainder); +} + +int pp_numeric_suffix_is_valid(const char *suffix) +{ + int pos = 0; + + if ((suffix[pos] | 32) == 'u') + pos++; + if ((suffix[pos] | 32) == 'l') { + pos++; + if ((suffix[pos] | 32) == 'l') + pos++; + } + if ((suffix[pos] | 32) == 'u') + pos++; + return suffix[pos] == '\0'; +} + +void pp_multiply(pp_integer_t *lhs, const pp_integer_t *rhs) +{ + pp_integer_t multiplicand, multiplier, product = {0}; + + multiplicand.lo = lhs->lo; + multiplicand.hi = lhs->hi; + multiplicand.is_unsigned = lhs->is_unsigned; + multiplier.lo = rhs->lo; + multiplier.hi = rhs->hi; + multiplier.is_unsigned = rhs->is_unsigned; + + for (int i = 0; i < 64; i++) { + if (multiplier.lo & 1) + pp_add(&product, &multiplicand); + multiplier.lo = (multiplier.lo >> 1) | (multiplier.hi << 31); + multiplier.hi >>= 1; + pp_shift_left_one(&multiplicand); + } + product.is_unsigned = PP_USES_UNSIGNED(lhs, rhs); + product.enum_width = lhs->enum_width; + lhs->lo = product.lo; + lhs->hi = product.hi; + lhs->is_unsigned = product.is_unsigned; + lhs->enum_width = product.enum_width; +} + +void pp_shift_right_one(pp_integer_t *val, int arithmetic) +{ + unsigned int sign = arithmetic && (val->hi & 0x80000000U) ? 0x80000000U : 0; + + val->lo = (val->lo >> 1) | (val->hi << 31); + val->hi = (val->hi >> 1) | sign; +} + +void pp_divmod_unsigned(const pp_integer_t *numerator, + const pp_integer_t *denominator, + pp_integer_t *quotient, + pp_integer_t *remainder) +{ + quotient->lo = quotient->hi = remainder->lo = remainder->hi = 0; + quotient->is_unsigned = remainder->is_unsigned = false; + for (int i = 63; i >= 0; i--) { + unsigned int bit = i >= 32 ? (numerator->hi >> (i - 32)) & 1 + : (numerator->lo >> i) & 1; + + pp_shift_left_one(remainder); + remainder->lo |= bit; + pp_shift_left_one(quotient); + if (pp_compare_unsigned(remainder, denominator) >= 0) { + pp_subtract(remainder, denominator); + quotient->lo |= 1; + } + } +} + +void pp_divmod(pp_integer_t *lhs, const pp_integer_t *rhs, int remainder) +{ + int use_unsigned = PP_USES_UNSIGNED(lhs, rhs); + int enum_width = lhs->enum_width; + int lhs_negative = !use_unsigned && pp_is_negative(lhs); + int rhs_negative = !use_unsigned && pp_is_negative(rhs); + pp_integer_t numerator, denominator, quotient, modulo; + + numerator.lo = lhs->lo; + numerator.hi = lhs->hi; + numerator.is_unsigned = lhs->is_unsigned; + denominator.lo = rhs->lo; + denominator.hi = rhs->hi; + denominator.is_unsigned = rhs->is_unsigned; + + if (lhs_negative) + pp_negate(&numerator); + if (rhs_negative) + pp_negate(&denominator); + pp_divmod_unsigned(&numerator, &denominator, "ient, &modulo); + if (!remainder && lhs_negative != rhs_negative) + pp_negate("ient); + if (remainder && lhs_negative) + pp_negate(&modulo); + if (remainder) { + lhs->lo = modulo.lo; + lhs->hi = modulo.hi; + } else { + lhs->lo = quotient.lo; + lhs->hi = quotient.hi; + } + lhs->is_unsigned = use_unsigned; + lhs->enum_width = enum_width; +} + +void pp_parse_integer_literal(token_t *tk, pp_integer_t *val) +{ + const char *literal = tk->literal; + int i = 0, base = 10; + int is_decimal; + pp_integer_t parsed = {0}; + + if (literal[0] == '0') { + if ((literal[1] | 32) == 'x') { + base = 16; + i = 2; + } else if ((literal[1] | 32) == 'b') { + base = 2; + i = 2; + } else if (literal[1] && (literal[1] | 32) != 'u' && + (literal[1] | 32) != 'l') { + base = 8; + i = 1; + } + } + is_decimal = base == 10; + while (literal[i] && (literal[i] | 32) != 'u' && (literal[i] | 32) != 'l') { + int digit; + char c = literal[i++]; + + if (isdigit(c)) + digit = c - '0'; + else { + c |= 32; + digit = c - 'a' + 10; + } + if (digit < 0 || digit >= base) + error_at("Invalid digit in integer constant", &tk->location); + if (pp_literal_will_overflow(&parsed, base, digit)) + error_at("Integer constant exceeds uintmax_t", &tk->location); + pp_multiply_small(&parsed, base); + pp_integer_t addend = {digit, 0, false, 0}; + + pp_add(&parsed, &addend); + } + val->is_unsigned = false; + const char *suffix = literal + i; + while (literal[i]) { + if (literal[i] == 'u' || literal[i] == 'U') { + val->is_unsigned = true; + } else if (literal[i] != 'l' && literal[i] != 'L') + error_at("Invalid integer constant suffix", &tk->location); + i++; + } + if (!pp_numeric_suffix_is_valid(suffix)) + error_at("Invalid integer constant suffix", &tk->location); + if (!val->is_unsigned && (parsed.hi & 0x80000000U)) { + if (is_decimal) + error_at("Integer constant exceeds intmax_t", &tk->location); + val->is_unsigned = true; + } + val->lo = parsed.lo; + val->hi = parsed.hi; + if (pp_integer_constant_scope) { + int long_count = 0; + + for (int j = 0; suffix[j]; j++) + if ((suffix[j] | 32) == 'l') + long_count++; + if (long_count >= 2) { + val->enum_width = 64; + } else if (val->is_unsigned) { + val->enum_width = parsed.hi ? 64 : 32; + } else if (!is_decimal && !parsed.hi && parsed.lo > 0x7fffffffU) { + /* In this ILP32 model, nondecimal constants select unsigned int or + * unsigned long before they reach signed long long. + */ + val->is_unsigned = true; + val->enum_width = 32; + } else { + val->enum_width = parsed.hi || parsed.lo > 0x7fffffffU ? 64 : 32; + } + pp_enum_normalize(val); + } +} + +token_t *pp_read_constant_infix_expr(int precedence, + token_t *tk, + pp_integer_t *val, + bool evaluate); + +/* Expand one function-like macro invocation in a #if token stream. The normal + * preprocessor already owns argument substitution, rescanning, and hide-set + * handling; give it only this balanced invocation so it cannot consume the rest + * of the directive. + */ +token_t *pp_expand_function_macro_in_constant_expr(token_t *before) +{ + token_t head; + token_t *tail = &head; + token_t *first = before->next; + token_t *end = first; + int bracket_depth = 0; + preprocess_ctx_t ctx; + + head.next = NULL; + while (end) { + if (end->kind == T_open_bracket) + bracket_depth++; + else if (end->kind == T_close_bracket && --bracket_depth == 0) + break; + end = end->next; + } + if (!end) + error_at("Unterminated function-like macro invocation", + &first->location); + + for (token_t *cur = first;; cur = cur->next) { + tail->next = copy_token(cur); + tail = tail->next; + if (cur == end) + break; + } + tail->next = NULL; + + ctx.expanded_from = first; + ctx.hide_set = NULL; + ctx.arg_hide_set = NULL; + ctx.macro_args = NULL; + ctx.trim_eof = true; + token_t *expanded = pp_preprocess_internal(head.next, &ctx); + token_t *after = end->next; + + before->next = expanded; + if (expanded) + ctx.end_of_token->next = after; + else + before->next = after; + return before; +} + +token_t *pp_read_constant_expr_operand(token_t *tk, + pp_integer_t *val, + bool evaluate) +{ + opcode_t unary; + + tk = pp_get_operator(tk, &unary, false); + if (pp_get_unary_operator_prio(unary)) { + tk = pp_lex_next_token(tk, true); + tk = pp_read_constant_expr_operand(tk, val, evaluate); + if (evaluate) { + if (unary == OP_sub) + pp_negate(val); + else if (unary == OP_bit_not) + val->lo = ~val->lo, val->hi = ~val->hi; + else if (unary == OP_log_not) + pp_set_boolean(val, !pp_is_true(val)); + } + return tk; + } + + if (pp_lex_peek_token(tk, T_floating, true)) + error_at("Floating constant is not permitted in #if expression", + &tk->location); + + /* Parser-side enum evaluation shares this token walker, but unlike #if it + * admits C's sizeof integer constant expressions. Reuse the parser's + * unevaluated sizeof reader and leave both cursors at the consumed operand. + */ + if (pp_integer_constant_scope && pp_lex_peek_token(tk, T_sizeof, true)) { + cur_token = tk; + lex_expect(T_sizeof); + val->lo = read_sizeof_constant(pp_integer_constant_scope); + val->hi = 0; + val->is_unsigned = false; + val->enum_width = 32; + return cur_token; + } + if (pp_lex_peek_token(tk, T_numeric, true)) { tk = pp_lex_next_token(tk, true); - val[0] = parse_numeric_constant(tk->literal); + pp_parse_integer_literal(tk, val); + return tk; + } + + if (pp_lex_peek_token(tk, T_char, true) || + pp_lex_peek_token(tk, T_wchar, true)) { + char unescaped[MAX_TOKEN_LEN]; + + tk = pp_lex_next_token(tk, true); + if (unescape_string(tk->literal, unescaped, MAX_TOKEN_LEN) < 0) + error_at("Invalid escape sequence", &tk->location); + int character; + + if (tk->kind != T_wchar) + character = parse_character_constant(tk->literal); + else if (!wide_character_constant(tk->literal, &character)) + error_at("Invalid wide character escape sequence", &tk->location); + + /* A character constant has type int, so a typed enumerator or case + * label gives it the int width a numeric operand gets. + */ + val->lo = character; + val->hi = character < 0 ? ~0U : 0; + val->is_unsigned = false; + val->enum_width = 32; return tk; } if (pp_lex_peek_token(tk, T_open_bracket, true)) { tk = pp_lex_next_token(tk, true); - tk = pp_read_constant_expr_operand(tk, val); + tk = pp_read_constant_infix_expr(0, tk, val, evaluate); tk = pp_lex_expect_token(tk, T_close_bracket, true); return tk; } if (pp_lex_peek_token(tk, T_identifier, true)) { + token_t *before_identifier = tk; + tk = pp_lex_next_token(tk, true); - if (!strcmp("defined", tk->literal)) { - tk = pp_lex_expect_token(tk, T_open_bracket, true); + /* offsetof is an integer constant as well. The parser's reader owns its + * type and member syntax, so hand it the operand as for sizeof. + */ + if (pp_integer_constant_scope && + !strcmp(tk->literal, "__builtin_offsetof")) { + cur_token = before_identifier; + val->lo = read_const_expr_operand(pp_integer_constant_scope); + val->hi = 0; + val->is_unsigned = false; + val->enum_width = 32; + return cur_token; + } + if (pp_integer_constant_scope) { + constant_t *constant = + find_scoped_constant(tk->literal, pp_integer_constant_scope); + + if (!constant) + error_at("Identifier is not an integer constant", + &tk->location); + val->lo = constant->value; + val->hi = constant->value < 0 ? ~0U : 0; + val->is_unsigned = false; + val->enum_width = 32; + } else if (!strcmp("defined", tk->literal)) { + bool parenthesized = pp_lex_peek_token(tk, T_open_bracket, true); + + if (parenthesized) + tk = pp_lex_next_token(tk, true); tk = pp_lex_expect_token(tk, T_identifier, true); - val[0] = is_macro_defined(tk->literal); - tk = pp_lex_expect_token(tk, T_close_bracket, true); + pp_set_boolean(val, is_macro_defined(tk->literal)); + if (parenthesized) + tk = pp_lex_expect_token(tk, T_close_bracket, true); } else { - /* Any identifier will fallback and evaluate as 0 */ - macro_t *macro = hashmap_get(MACROS, tk->literal); + /* Any identifier in #if falls back to zero. */ + macro_t *macro = MACROS ? hashmap_get(MACROS, tk->literal) : NULL; /* Disallow function-like macro to be expanded */ - if (macro && !(macro->param_num > 0 || macro->is_variadic)) { + if (macro && macro->is_function_like) { + if (pp_lex_peek_token(tk, T_open_bracket, true)) { + tk = pp_expand_function_macro_in_constant_expr( + before_identifier); + return pp_read_constant_expr_operand(tk, val, evaluate); + } + } else if (macro) { token_t *expanded_tk, *tmp; preprocess_ctx_t ctx; ctx.expanded_from = tk; ctx.hide_set = NULL; + ctx.arg_hide_set = NULL; ctx.macro_args = NULL; ctx.trim_eof = false; expanded_tk = pp_preprocess_internal(macro->replacement, &ctx); @@ -368,10 +1564,10 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val) tk->next = expanded_tk; ctx.end_of_token->next = tmp; } - return pp_read_constant_expr_operand(tk, val); + return pp_read_constant_expr_operand(tk, val, evaluate); } - val[0] = 0; + pp_set_boolean(val, false); } return tk; @@ -385,166 +1581,274 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val) return tk; } -token_t *pp_read_constant_infix_expr(int precedence, token_t *tk, int *val) +token_t *pp_read_constant_infix_expr(int precedence, + token_t *tk, + pp_integer_t *val, + bool evaluate) { - int lhs, rhs; + pp_integer_t lhs = {0}, rhs = {0}; + int comparison; /* Evaluate unary expression first */ opcode_t op; - tk = pp_get_operator(tk, &op); + tk = pp_get_operator(tk, &op, true); int current_precedence = pp_get_unary_operator_prio(op); if (current_precedence != 0 && current_precedence >= precedence) { - tk = pp_read_constant_infix_expr(current_precedence, tk, &lhs); + tk = + pp_read_constant_infix_expr(current_precedence, tk, &lhs, evaluate); - switch (op) { - case OP_add: - break; - case OP_sub: - lhs = -lhs; - break; - case OP_bit_not: - lhs = ~lhs; - break; - case OP_log_not: - lhs = !lhs; - break; - default: { - source_location_t *loc = - tk->next ? &tk->next->location : &tk->location; + if (evaluate) { + switch (op) { + case OP_add: + break; + case OP_sub: + pp_negate(&lhs); + break; + case OP_bit_not: + lhs.lo = ~lhs.lo; + lhs.hi = ~lhs.hi; + break; + case OP_log_not: + pp_set_boolean(&lhs, !pp_is_true(&lhs)); + break; + default: { + source_location_t *loc = + tk->next ? &tk->next->location : &tk->location; - error_at("Unexpected unary token while evaluating constant", loc); - } + error_at("Unexpected unary token while evaluating constant", + loc); + } + } } } else { - tk = pp_read_constant_expr_operand(tk, &lhs); + tk = pp_read_constant_expr_operand(tk, &lhs, evaluate); } while (true) { - tk = pp_get_operator(tk, &op); + tk = pp_get_operator(tk, &op, false); current_precedence = pp_get_operator_prio(op); if (current_precedence == 0 || current_precedence <= precedence) break; - tk = pp_read_constant_infix_expr(current_precedence, tk, &rhs); + tk = pp_lex_next_token(tk, true); + + if (op == OP_ternary) { + pp_integer_t if_true = {0}, if_false = {0}; + bool condition = pp_is_true(&lhs); + + tk = pp_read_constant_infix_expr(0, tk, &if_true, + evaluate && condition); + tk = pp_lex_expect_token(tk, T_colon, true); + /* Conditional expressions are right-associative. */ + tk = pp_read_constant_infix_expr(current_precedence - 1, tk, + &if_false, evaluate && !condition); + if (pp_integer_constant_scope) + pp_enum_usual_arithmetic(&if_true, &if_false); + bool result_is_unsigned = PP_USES_UNSIGNED(&if_true, &if_false); + if (evaluate) { + pp_integer_t *selected = condition ? &if_true : &if_false; + + lhs.lo = selected->lo; + lhs.hi = selected->hi; + lhs.enum_width = selected->enum_width; + } else + lhs.lo = lhs.hi = 0; + lhs.is_unsigned = result_is_unsigned; + continue; + } + + bool rhs_evaluate = evaluate; + if (op == OP_log_and && !pp_is_true(&lhs)) + rhs_evaluate = false; + if (op == OP_log_or && pp_is_true(&lhs)) + rhs_evaluate = false; + tk = pp_read_constant_infix_expr(current_precedence, tk, &rhs, + rhs_evaluate); + + if (pp_integer_constant_scope && op != OP_lshift && op != OP_rshift) + pp_enum_usual_arithmetic(&lhs, &rhs); switch (op) { case OP_add: - lhs += rhs; - break; case OP_sub: - lhs -= rhs; - break; case OP_mul: - lhs *= rhs; - break; case OP_div: - lhs /= rhs; - break; + case OP_mod: case OP_bit_and: - lhs &= rhs; - break; case OP_bit_or: - lhs |= rhs; - break; case OP_bit_xor: - lhs ^= rhs; - break; - case OP_lshift: - lhs <<= rhs; - break; - case OP_rshift: - lhs >>= rhs; - break; - case OP_gt: - lhs = lhs > rhs; - break; - case OP_geq: - lhs = lhs >= rhs; - break; - case OP_lt: - lhs = lhs < rhs; - break; - case OP_leq: - lhs = lhs <= rhs; - break; - case OP_eq: - lhs = lhs == rhs; - break; - case OP_neq: - lhs = lhs != rhs; - break; - case OP_log_and: - lhs = lhs && rhs; - break; - case OP_log_or: - lhs = lhs || rhs; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; default: - error_at("Unexpected infix token while evaluating constant", - &tk->location); + break; + } + + if (evaluate) { + switch (op) { + case OP_add: + pp_add(&lhs, &rhs); + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); + break; + case OP_sub: + pp_subtract(&lhs, &rhs); + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); + break; + case OP_mul: + pp_multiply(&lhs, &rhs); + break; + case OP_div: + if (!pp_is_true(&rhs)) + error_at("Division by zero in #if expression", + &tk->location); + pp_divmod(&lhs, &rhs, false); + break; + case OP_mod: + if (!pp_is_true(&rhs)) + error_at("Modulo by zero in #if expression", &tk->location); + pp_divmod(&lhs, &rhs, true); + break; + case OP_bit_and: + lhs.lo &= rhs.lo; + lhs.hi &= rhs.hi; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); + break; + case OP_bit_or: + lhs.lo |= rhs.lo; + lhs.hi |= rhs.hi; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); + break; + case OP_bit_xor: + lhs.lo ^= rhs.lo; + lhs.hi ^= rhs.hi; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); + break; + case OP_lshift: + if (rhs.hi || + rhs.lo >= (unsigned int) (pp_integer_constant_scope + ? lhs.enum_width + : 64)) + error_at("Shift count out of range in #if expression", + &tk->location); + for (unsigned int i = 0; i < rhs.lo; i++) { + if (pp_integer_constant_scope && !lhs.is_unsigned && + (lhs.enum_width == 32 ? lhs.lo & 0x40000000U + : lhs.hi & 0x40000000U)) + error_at("Enumerator value exceeds int range", + &tk->location); + pp_shift_left_one(&lhs); + } + break; + case OP_rshift: + if (rhs.hi || + rhs.lo >= (unsigned int) (pp_integer_constant_scope + ? lhs.enum_width + : 64)) + error_at("Shift count out of range in #if expression", + &tk->location); + for (unsigned int i = 0; i < rhs.lo; i++) + pp_shift_right_one(&lhs, !lhs.is_unsigned); + break; + case OP_gt: + if (!lhs.is_unsigned && !rhs.is_unsigned && + (lhs.hi >> 31) != (rhs.hi >> 31)) + pp_set_boolean(&lhs, !(lhs.hi >> 31)); + else { + comparison = pp_compare_unsigned(&lhs, &rhs); + pp_set_boolean(&lhs, comparison > 0); + } + break; + case OP_geq: + if (!lhs.is_unsigned && !rhs.is_unsigned && + (lhs.hi >> 31) != (rhs.hi >> 31)) + pp_set_boolean(&lhs, !(lhs.hi >> 31)); + else { + comparison = pp_compare_unsigned(&lhs, &rhs); + pp_set_boolean(&lhs, comparison >= 0); + } + break; + case OP_lt: + if (!lhs.is_unsigned && !rhs.is_unsigned && + (lhs.hi >> 31) != (rhs.hi >> 31)) + pp_set_boolean(&lhs, lhs.hi >> 31); + else { + comparison = pp_compare_unsigned(&lhs, &rhs); + pp_set_boolean(&lhs, comparison < 0); + } + break; + case OP_leq: + if (!lhs.is_unsigned && !rhs.is_unsigned && + (lhs.hi >> 31) != (rhs.hi >> 31)) + pp_set_boolean(&lhs, lhs.hi >> 31); + else { + comparison = pp_compare_unsigned(&lhs, &rhs); + pp_set_boolean(&lhs, comparison <= 0); + } + break; + case OP_eq: + pp_set_boolean(&lhs, lhs.lo == rhs.lo && lhs.hi == rhs.hi); + break; + case OP_neq: + pp_set_boolean(&lhs, lhs.lo != rhs.lo || lhs.hi != rhs.hi); + break; + case OP_log_and: + pp_set_boolean(&lhs, pp_is_true(&lhs) && pp_is_true(&rhs)); + break; + case OP_log_or: + pp_set_boolean(&lhs, pp_is_true(&lhs) || pp_is_true(&rhs)); + break; + default: + error_at("Unexpected infix token while evaluating constant", + &tk->location); + } + if (pp_integer_constant_scope) + pp_enum_normalize(&lhs); } - tk = pp_get_operator(tk, &op); } - val[0] = lhs; + if (!evaluate) + lhs.lo = lhs.hi = 0; + val->lo = lhs.lo; + val->hi = lhs.hi; + val->is_unsigned = lhs.is_unsigned; + val->enum_width = lhs.enum_width; return tk; } -token_t *pp_read_constant_expr(token_t *tk, int *val) +token_t *pp_read_constant_expr(token_t *tk, pp_integer_t *val) { - tk = pp_read_constant_infix_expr(0, tk, val); + tk = pp_read_constant_infix_expr(0, tk, val, true); /* advance to fully consume constant expression */ tk = pp_lex_next_token(tk, true); return tk; } -token_t *pp_skip_inner_cond_incl(token_t *tk) +/* Skip a group whose condition failed, returning the #elif, #else or #endif + * that ends it. A conditional nested inside the group is skipped whole: its own + * #elif and #else belong to it, and only its #endif brings the count back. + */ +token_t *pp_skip_cond_incl(token_t *tk) { - token_kind_t kind; + int depth = 0; - while (tk->kind != T_eof) { - kind = tk->kind; + for (; tk->kind != T_eof; tk = tk->next) { + token_kind_t kind = tk->kind; if (kind == T_cppd_if || kind == T_cppd_ifdef || kind == T_cppd_ifndef) { - if (!tk->next || !tk->next->next) - error_at("Unexpected error when skipping conditional inclusion", - &tk->location); - - tk = pp_skip_inner_cond_incl(tk->next->next); + depth++; continue; } if (kind == T_cppd_endif) { - if (!tk->next || !tk->next->next) - error_at("Unexpected error when skipping conditional inclusion", - &tk->location); - return tk->next->next; - } - - tk = tk->next; - } - return tk; -} - -token_t *pp_skip_cond_incl(token_t *tk) -{ - token_kind_t kind; - - while (tk->kind != T_eof) { - kind = tk->kind; - - if (kind == T_cppd_if || kind == T_cppd_ifdef || - kind == T_cppd_ifndef) { - tk = pp_skip_inner_cond_incl(tk); + if (!depth) + break; + depth--; continue; } - if (kind == T_cppd_elif || kind == T_cppd_else || kind == T_cppd_endif) + if ((kind == T_cppd_elif || kind == T_cppd_else) && !depth) break; - - tk = tk->next; } return tk; } @@ -597,6 +1901,72 @@ token_t *pp_stringify(token_t *arg, source_location_t *loc) return out; } +/* C99's #error directive displays the rest of its directive line as a + * diagnostic message. These are raw preprocessing tokens, not a macro + * replacement list, so spell them directly and retain their source order. + */ +__noreturn void pp_error_directive(token_t *directive) +{ + char message[MAX_LINE_LEN], scratch[MAX_TOKEN_LEN]; + int len = 0; + bool needs_space = false; + source_location_t *loc = &directive->location; + token_t *tk = directive; + + while (tk->next && tk->next->kind != T_newline && tk->next->kind != T_eof) { + tk = pp_lex_next_token(tk, false); + if (pp_is_layout(tk)) { + needs_space = len > 0; + continue; + } + + char *spelling = token_to_string(tk, scratch); + if (!spelling) + continue; + if (needs_space && len < MAX_LINE_LEN - 1) + message[len++] = ' '; + needs_space = true; + loc = &tk->location; + for (int i = 0; spelling[i] && len < MAX_LINE_LEN - 1; i++) + message[len++] = spelling[i]; + } + + if (!len) + strcpy(message, "#error"); + else + message[len] = '\0'; + error_at(message, loc); +} + +/* C99's _Pragma operator is processed after macro replacement. The compiler has + * no standard pragma semantics, so its destringized directive is ignored just + * like an unknown #pragma, except for "once", which #pragma honours as well; + * consume the operator syntax so no tokens reach the parser. @owner is the + * token in the source file, which for an operator that came out of a macro is + * the invocation rather than the macro's definition. + */ +token_t *pp_pragma_operator(token_t *tk, token_t *owner) +{ + tk = pp_lex_expect_token(tk, T_open_bracket, true); + tk = pp_lex_expect_token(tk, T_string, true); + + /* Destringizing only removes a backslash before '"' or '\\', neither of + * which can spell "once", so the literal as stored is compared directly. + */ + const char *p = tk->literal; + int len; + + while (*p == ' ' || *p == '\t') + p++; + len = strlen(p); + while (len > 0 && (p[len - 1] == ' ' || p[len - 1] == '\t')) + len--; + if (len == 4 && !strncmp(p, "once", 4)) + hashmap_put(PRAGMA_ONCE, owner->location.physical_filename, NULL); + + return pp_lex_expect_token(tk, T_close_bracket, true); +} + /* Join two tokens into one, as '##' requires. * * Pasting is textual, so the result has to be scanned again: "a" and "1" give @@ -630,6 +2000,8 @@ token_t *pp_paste_tokens(token_t *lhs, token_t *rhs, source_location_t *loc) source_location_t scan_loc; memcpy(&scan_loc, loc, sizeof(source_location_t)); + /* A pasted '#' is never a directive, whatever column it came from. */ + lex_at_line_start = false; token_t *pasted = lex_token(buf, &scan_loc); bool whole = buf->size == len; strbuf_free(buf); @@ -692,6 +2064,36 @@ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) if (!lhs_present || !operand) error_at("'##' needs a token on each side", &tk->location); + /* GNU comma elision: in ", ## __VA_ARGS__" nothing is pasted. The + * variadic argument follows the comma as written, and when it has + * no tokens the comma is deleted instead. + */ + if (args && tail->kind == T_comma && + operand->kind == T_identifier && + !strcmp(operand->literal, "__VA_ARGS__") && + hashmap_contains(args, operand->literal)) { + token_t *comma_prev = tail_prev; + bool any = false; + + for (token_t *t = hashmap_get(args, operand->literal); t; + t = t->next) { + if (pp_is_layout(t)) + continue; + tail_prev = tail; + tail->next = copy_token(t); + tail = tail->next; + any = true; + } + if (!any) { + tail = comma_prev; + tail->next = NULL; + lhs_present = tail != &head; + } + lhs_empty = false; + tk = operand; + continue; + } + /* The right operand joins as written; a parameter contributes its * argument rather than its expansion. */ @@ -809,6 +2211,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) expansion_ctx.expanded_from = ctx->expanded_from ? ctx->expanded_from : tk; expansion_ctx.macro_args = ctx->macro_args; + expansion_ctx.arg_hide_set = ctx->arg_hide_set; expansion_ctx.trim_eof = true; token_t *macro_arg_replacement = NULL; @@ -835,7 +2238,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (macro_arg_replacement) { /* Recursively expand the argument to handle nested macros */ - expansion_ctx.hide_set = ctx->hide_set; + expansion_ctx.hide_set = ctx->arg_hide_set; expansion_ctx.macro_args = NULL; /* Don't take account of macro arguments, this might run into infinite loop @@ -851,6 +2254,12 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) continue; } + if (!strcmp(tk->literal, "_Pragma")) { + tk = pp_pragma_operator(tk, expansion_ctx.expanded_from); + tk = pp_lex_next_token(tk, false); + continue; + } + /* Prevent infinite recursion by checking hide set */ if (hide_set_contains(ctx->hide_set, tk->literal)) break; @@ -873,7 +2282,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } /* Check if this is a function-like macro invocation */ - if (pp_lex_peek_token(tk, T_open_bracket, true)) { + if (macro->is_function_like && + pp_lex_peek_token(tk, T_open_bracket, true)) { token_t arg_head; token_t *arg_cur = &arg_head; int arg_idx = 0; @@ -890,6 +2300,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) */ expansion_ctx.hide_set = hide_set_union(ctx->hide_set, new_hide_set(tk->literal)); + expansion_ctx.arg_hide_set = ctx->hide_set; /* Create parameter mapping table for this macro invocation */ expansion_ctx.macro_args = hashmap_create(8); @@ -919,7 +2330,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (arg_tk) { preprocess_ctx_t arg_expansion_ctx; arg_expansion_ctx.expanded_from = tk->next; - arg_expansion_ctx.hide_set = expansion_ctx.hide_set; + arg_expansion_ctx.hide_set = ctx->arg_hide_set; + arg_expansion_ctx.arg_hide_set = NULL; arg_expansion_ctx.macro_args = NULL; arg_tk = pp_preprocess_internal(arg_tk, &arg_expansion_ctx); @@ -952,9 +2364,20 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } token_t *param_tk; + bool empty_zero_arg_call = bracket_depth < 0 && + arg_idx == 0 && !arg_head.next && + macro->param_num == 0; - /* Bind argument to corresponding parameter */ - if (arg_idx < macro->param_num) { + /* M() has no arguments when M declares none. A macro with + * one parameter, in contrast, receives one empty argument + * and must retain that distinction. + */ + if (empty_zero_arg_call) { + if (macro->is_variadic) + hashmap_put(expansion_ctx.macro_args, + macro->variadic_tk->literal, NULL); + /* Bind argument to corresponding parameter */ + } else if (arg_idx < macro->param_num) { param_tk = macro->param_names[arg_idx++]; hashmap_put(expansion_ctx.macro_args, param_tk->literal, arg_head.next); @@ -1014,6 +2437,17 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) error_at("Too few arguments supplied to macro invocation", ¯o_tk->location); + /* F(1) for F(a, ...) supplies nothing past the named + * parameters. C99 wants at least one more argument, but gcc + * accepts the call, and __VA_ARGS__ has to be bound to the + * empty list for it rather than be left an ordinary name. + */ + if (macro->is_variadic && + !hashmap_contains(expansion_ctx.macro_args, + macro->variadic_tk->literal)) + hashmap_put(expansion_ctx.macro_args, + macro->variadic_tk->literal, NULL); + /* Expand macro body with collected arguments Replace parameter * references with supplied argument tokens */ @@ -1056,8 +2490,12 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) case T_cppd_include: { char inclusion_path[MAX_LINE_LEN]; token_stream_t *file_tks = NULL; + token_t *include_tk = tk; + bool angle_header = false; + bool angle_form = false; preprocess_ctx_t inclusion_ctx; inclusion_ctx.hide_set = ctx->hide_set; + inclusion_ctx.arg_hide_set = NULL; inclusion_ctx.expanded_from = NULL; inclusion_ctx.macro_args = NULL; inclusion_ctx.trim_eof = true; @@ -1066,9 +2504,83 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) tk = pp_lex_next_token(tk, true); strcpy(inclusion_path, tk->literal); - /* normalize path */ + /* A header name may be supplied by an object-like macro. The + * replacement is rescanned here rather than treating the macro + * name as an angle include, and aliases may name another alias + * before finally producing the required string token. + */ + } else if (pp_lex_peek_token(tk, T_identifier, true)) { + macro_t *aliases[MAX_TOKEN_LEN]; + int alias_count = 0; + + tk = pp_lex_next_token(tk, true); + macro = hashmap_get(MACROS, tk->literal); + while (macro && !macro->is_disabled && + !macro->is_function_like && macro->replacement && + !macro->replacement->next && + macro->replacement->kind == T_identifier) { + for (int i = 0; i < alias_count; i++) + if (aliases[i] == macro) + error_at("cyclic macro expansion in #include", + &tk->location); + if (alias_count == MAX_TOKEN_LEN) + error_at("#include macro alias chain is too deep", + &tk->location); + aliases[alias_count++] = macro; + macro = hashmap_get(MACROS, macro->replacement->literal); + } + if (!macro || macro->is_disabled || macro->is_function_like || + !macro->replacement) + error_at("#include macro must expand to a header name", + &tk->location); + if (macro->replacement->kind == T_string && + !macro->replacement->next) { + strcpy(inclusion_path, macro->replacement->literal); + } else if (macro->replacement->kind == T_lt) { + token_t *open = macro->replacement; + token_t *close = open; + + angle_form = true; + while (close && close->kind != T_gt) + close = close->next; + if (!close || close->next) + error_at("#include macro must expand to a header name", + &tk->location); + inclusion_path[0] = '\0'; + angle_header = resolve_angle_include( + open, close, inclusion_path, sizeof(inclusion_path)); + } else { + error_at("#include macro must expand to a header name", + &tk->location); + } + } else { + tk = pp_lex_expect_token(tk, T_lt, true); + token_t *open = tk; + angle_form = true; + inclusion_path[0] = '\0'; + while (!pp_lex_peek_token(tk, T_gt, false)) { + if (pp_lex_peek_token(tk, T_newline, false) || + pp_lex_peek_token(tk, T_eof, false)) + error_at("Unterminated #include <...>", &tk->location); + tk = pp_lex_next_token(tk, false); + } + + token_t *close = pp_lex_next_token(tk, false); + angle_header = resolve_angle_include( + open, close, inclusion_path, sizeof(inclusion_path)); + tk = close; + } + + if (angle_form && !angle_header) { + tk = pp_lex_expect_token(tk, T_newline, true); + tk = pp_lex_next_token(tk, false); + continue; + } + + if (!angle_header) { + /* Quoted headers are relative to the physical source path. */ char path[MAX_LINE_LEN]; - const char *file = tk->location.filename; + const char *file = include_tk->location.physical_filename; int c = strlen(file) - 1; while (c > 0 && file[c] != '/') @@ -1077,7 +2589,6 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (c) { if (c >= MAX_LINE_LEN - 1) c = MAX_LINE_LEN - 2; - memcpy(path, file, c); path[c] = '\0'; } else { @@ -1089,37 +2600,22 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) snprintf(path + c, MAX_LINE_LEN - c, "/%s", inclusion_path); strncpy(inclusion_path, path, MAX_LINE_LEN - 1); inclusion_path[MAX_LINE_LEN - 1] = '\0'; - } else { - tk = pp_lex_expect_token(tk, T_lt, true); - - /* The path is ignored (see the FIXME below), so just consume - * it. Stopping at a newline too keeps an unterminated "#include - * ", &tk->location); - tk = pp_lex_next_token(tk, false); - } - - tk = pp_lex_next_token(tk, false); - - /* FIXME: We ignore #include <...> at this moment, since all - * libc functions are included done by inlining. - */ - tk = pp_lex_expect_token(tk, T_newline, true); - tk = pp_lex_next_token(tk, false); - continue; } tk = pp_lex_expect_token(tk, T_newline, true); tk = pp_lex_next_token(tk, false); - if (hashmap_contains(PRAGMA_ONCE, inclusion_path)) + file_tks = gen_file_token_stream(intern_string(inclusion_path)); + + /* gen_file_token_stream() canonicalizes lexical components such as + * "./" and "../". PRAGMA_ONCE is keyed by that same physical + * filename, so test the canonical spelling rather than the raw + * include directive. + */ + if (hashmap_contains(PRAGMA_ONCE, + file_tks->head->location.physical_filename)) continue; - file_tks = gen_file_token_stream(intern_string(inclusion_path)); token_t *included = pp_preprocess_internal(file_tks->head, &inclusion_ctx); if (included) { @@ -1130,6 +2626,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } case T_cppd_define: { token_t *r_head = NULL, *r_tail = NULL, *r_cur; + token_t *r_last = NULL; /* last token that is not white space */ tk = pp_lex_expect_token(tk, T_identifier, true); macro = hashmap_get(MACROS, tk->literal); @@ -1142,8 +2639,17 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) macro->is_disabled = false; } + /* A redefinition replaces the complete macro signature, not only + * its replacement list. + */ + macro->is_function_like = false; + macro->is_variadic = false; + macro->param_num = 0; + macro->variadic_tk = NULL; + if (pp_lex_peek_token(tk, T_open_bracket, false)) { /* function-like macro */ + macro->is_function_like = true; tk = pp_lex_next_token(tk, false); while (pp_lex_peek_token(tk, T_identifier, true)) { tk = pp_lex_next_token(tk, true); @@ -1176,8 +2682,6 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) &tk->location); else tk = pp_lex_expect_token(tk, T_newline, false); - - tk = pp_lex_next_token(tk, false); continue; } @@ -1192,8 +2696,19 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) r_tail->next = r_cur; r_tail = r_cur; } + if (r_cur->kind != T_whitespace && r_cur->kind != T_tab) + r_last = r_cur; } + /* White space after the replacement list is not part of it (C99 + * 6.10.3p7), and a list that kept it would no longer be the single + * string that #include H looks for. + */ + if (r_last) + r_last->next = NULL; + else + r_head = NULL; + tk = pp_lex_expect_token(tk, T_newline, false); tk = pp_lex_next_token(tk, false); macro->replacement = r_head; @@ -1213,11 +2728,11 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } case T_cppd_if: { token_t *cond_tk = tk; - int defined; - tk = pp_read_constant_expr(tk, &defined); - ci = push_cond(ci, cond_tk, defined); + pp_integer_t condition; + tk = pp_read_constant_expr(tk, &condition); + ci = push_cond(ci, cond_tk, pp_is_true(&condition)); - if (!defined) + if (!pp_is_true(&condition)) tk = pp_skip_cond_incl(tk); continue; } @@ -1248,11 +2763,11 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) case T_cppd_elif: { if (!ci || ci->ctx == CK_else_then) error_at("Stray #elif", &tk->location); - int included; + pp_integer_t included; ci->ctx = CK_elif_then; tk = pp_read_constant_expr(tk, &included); - if (!ci->included && included) + if (!ci->included && pp_is_true(&included)) ci->included = true; else tk = pp_skip_cond_incl(tk); @@ -1280,7 +2795,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) tk = pp_lex_next_token(tk, true); if (!strcmp("once", tk->literal)) - hashmap_put(PRAGMA_ONCE, tk->location.filename, NULL); + hashmap_put(PRAGMA_ONCE, tk->location.physical_filename, + NULL); } while (!pp_lex_peek_token(tk, T_newline, true)) @@ -1289,19 +2805,29 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) tk = pp_lex_expect_token(tk, T_newline, true); continue; } - case T_cppd_error: { - if (pp_lex_peek_token(tk, T_string, true)) { - tk = pp_lex_next_token(tk, true); + case T_cppd_line: { + char *original_filename = tk->location.physical_filename; + char *logical_filename = NULL; + int requested_line; - error_at(tk->literal, &tk->location); - } else { - error_at( - "Internal error, #error does not support non-string error " - "message", - &tk->location); - } - break; + pp_read_line_operands(tk, &requested_line, &logical_filename); + while (!pp_lex_peek_token(tk, T_newline, false)) + tk = pp_lex_next_token(tk, false); + if (!pp_lex_peek_token(tk, T_newline, true)) + error_at("Unexpected token in #line directive", &tk->location); + tk = pp_lex_expect_token(tk, T_newline, true); + tk = pp_lex_next_token(tk, false); + if (tk->kind != T_eof) + pp_apply_line_directive(tk, original_filename, logical_filename, + requested_line - tk->location.line); + continue; } + case T_cppd_error: { + pp_error_directive(tk); + } + case T_cppd_unknown: + error_at("Unsupported directive", &tk->location); + break; case T_backslash: { /* This branch is designed to be failed since backslash should be * consumed by #define, and upon later expansion, it should not be @@ -1337,6 +2863,49 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) return head.next; } +/* Report whether a string literal payload ends inside a numeric escape that a + * following digit would extend: a hexadecimal escape, whose digit run has no + * limit, or an octal escape of fewer than three digits. + * + * Returns 16 or 8 for those, and 0 when the payload ends in anything else. + */ +int pp_string_open_escape_base(const char *text) +{ + for (int i = 0; text[i]; i++) { + if (text[i] != '\\') + continue; + i++; + if (text[i] == 'x') { + while (isxdigit((unsigned char) text[i + 1])) + i++; + if (!text[i + 1]) + return 16; + } else if (text[i] >= '0' && text[i] <= '7') { + int digits = 1; + + while (digits < 3 && text[i + 1] >= '0' && text[i + 1] <= '7') { + i++; + digits++; + } + if (!text[i + 1] && digits < 3) + return 8; + } else if (!text[i]) { + break; + } + } + return 0; +} + +/* Diagnose a finished narrow string literal whose hexadecimal escape does not + * fit a byte. Called only for a T_string token, once no further literal can + * join it. + */ +void pp_check_narrow_string(token_t *tk) +{ + if (hex_escape_exceeds_byte(tk->literal)) + error_at("Hexadecimal escape sequence out of range", &tk->location); +} + /* Drop the whitespace, tab and newline tokens from a fully preprocessed stream, * on the way into the parser. * @@ -1358,9 +2927,57 @@ token_t *pp_strip_layout(token_t *tk) if (tk->kind == T_whitespace || tk->kind == T_newline || tk->kind == T_tab) continue; + + /* C99 translation phase 6 concatenates adjacent string literal tokens + * after macro expansion. Do it at the parser boundary: whitespace is + * already irrelevant there, and this covers both source-adjacent and + * macro-produced strings without changing -E's token spelling. When + * either literal is wide, C99 6.4.5 makes the joined literal wide. + */ + if (cur != &head && (cur->kind == T_string || cur->kind == T_wstring) && + (tk->kind == T_string || tk->kind == T_wstring)) { + char combined[MAX_STRING_LEN]; + int left_len = strlen(cur->literal); + int right_len = strlen(tk->literal); + int base = pp_string_open_escape_base(cur->literal); + char first = tk->literal[0]; + int respell = 0; + + /* Each literal's escapes end with that literal, but the payloads + * are joined as spelled and decoded later. When the left payload + * ends in an escape the right one's first digit would extend, spell + * that digit as a complete three-digit octal escape, which nothing + * can extend and which decodes to the same character. + */ + if ((base == 16 && isxdigit((unsigned char) first)) || + (base == 8 && first >= '0' && first <= '7')) + respell = 3; + if (left_len + right_len + respell >= MAX_STRING_LEN) + error_at("Concatenated string literal too long", &tk->location); + const char *rest = tk->literal; + + memcpy(combined, cur->literal, left_len); + if (respell) { + combined[left_len++] = '\\'; + combined[left_len++] = '0' + ((first >> 6) & 7); + combined[left_len++] = '0' + ((first >> 3) & 7); + combined[left_len++] = '0' + (first & 7); + rest++; + } + strcpy(combined + left_len, rest); + cur->literal = intern_string(combined); + cur->location.len += tk->location.len; + if (tk->kind == T_wstring) + cur->kind = T_wstring; + continue; + } + if (cur != &head && cur->kind == T_string) + pp_check_narrow_string(cur); cur->next = tk; cur = tk; } + if (cur != &head && cur->kind == T_string) + pp_check_narrow_string(cur); cur->next = NULL; return head.next; } @@ -1369,6 +2986,7 @@ token_t *preprocess(token_t *tk) { preprocess_ctx_t ctx; ctx.hide_set = NULL; + ctx.arg_hide_set = NULL; ctx.expanded_from = NULL; ctx.macro_args = NULL; ctx.trim_eof = false; @@ -1383,25 +3001,57 @@ token_t *preprocess(token_t *tk) synth_built_in_loc.line = 1; synth_built_in_loc.filename = ""; - macro_t *macro = calloc(1, sizeof(macro_t)); + macro_t *macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = "__FILE__"; macro->handler = file_macro_handler; hashmap_put(MACROS, "__FILE__", macro); - macro = calloc(1, sizeof(macro_t)); + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = "__LINE__"; macro->handler = line_macro_handler; hashmap_put(MACROS, "__LINE__", macro); + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__DATE__"; + macro->handler = date_macro_handler; + hashmap_put(MACROS, "__DATE__", macro); + + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__TIME__"; + macro->handler = time_macro_handler; + hashmap_put(MACROS, "__TIME__", macro); + + /* C99-required implementation macros. shecc supplies its own small runtime + * rather than a complete hosted library, so advertise freestanding mode + * while retaining the C99 language-version identifier. + */ + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__STDC__"; + macro->replacement = new_token(T_numeric, &synth_built_in_loc, 1); + macro->replacement->literal = "1"; + hashmap_put(MACROS, "__STDC__", macro); + + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__STDC_VERSION__"; + macro->replacement = new_token(T_numeric, &synth_built_in_loc, 7); + macro->replacement->literal = "199901L"; + hashmap_put(MACROS, "__STDC_VERSION__", macro); + + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__STDC_HOSTED__"; + macro->replacement = new_token(T_numeric, &synth_built_in_loc, 1); + macro->replacement->literal = "0"; + hashmap_put(MACROS, "__STDC_HOSTED__", macro); + /* architecture defines */ - macro = calloc(1, sizeof(macro_t)); + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = ARCH_PREDEFINED; macro->replacement = new_token(T_numeric, &synth_built_in_loc, 1); macro->replacement->literal = "1"; hashmap_put(MACROS, ARCH_PREDEFINED, macro); /* shecc run-time defines */ - macro = calloc(1, sizeof(macro_t)); + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = "__SHECC__"; macro->replacement = new_token(T_numeric, &synth_built_in_loc, 1); macro->replacement->literal = "1"; @@ -1412,7 +3062,7 @@ token_t *preprocess(token_t *tk) * all -- are unavailable and libc resolves through the PLT instead. */ if (dynlink) { - macro = calloc(1, sizeof(macro_t)); + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = "__SHECC_DYNLINK__"; macro->replacement = new_token(T_numeric, &synth_built_in_loc, 1); macro->replacement->literal = "1"; @@ -1437,15 +3087,22 @@ char *token_to_string(token_t *tk, char *dest) &tk->location); return NULL; case T_numeric: + case T_floating: return tk->literal; case T_identifier: return tk->literal; case T_string: snprintf(dest, MAX_TOKEN_LEN, "\"%s\"", tk->literal); return dest; + case T_wstring: + snprintf(dest, MAX_TOKEN_LEN, "L\"%s\"", tk->literal); + return dest; case T_char: snprintf(dest, MAX_TOKEN_LEN, "'%s'", tk->literal); return dest; + case T_wchar: + snprintf(dest, MAX_TOKEN_LEN, "L'%s'", tk->literal); + return dest; case T_comma: return ","; case T_open_bracket: @@ -1574,6 +3231,34 @@ char *token_to_string(token_t *tk, char *dest) return "goto"; case T_const: return "const"; + case T_volatile: + return "volatile"; + case T_static: + return "static"; + case T_extern: + return "extern"; + case T_register: + return "register"; + case T_auto: + return "auto"; + case T_restrict: + return "restrict"; + case T_inline: + return "inline"; + case T_signed: + return "signed"; + case T_unsigned: + return "unsigned"; + case T_long: + return "long"; + case T_float: + return "float"; + case T_double: + return "double"; + case T_complex: + return "_Complex"; + case T_imaginary: + return "_Imaginary"; case T_newline: return "\n"; case T_backslash: @@ -1605,6 +3290,8 @@ char *token_to_string(token_t *tk, char *dest) case T_cppd_ifdef: case T_cppd_ifndef: case T_cppd_pragma: + case T_cppd_line: + case T_cppd_unknown: error_at( "Internal error, preprocessor directives should be ommited " "after preprocessing", diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 3e38c15c..6960043e 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -44,12 +44,31 @@ bool is_address_like(var_t *v) * widths and int narrowing by it, so it keeps counting pointer-like operands * alone rather than acquiring arrays and changing a settled target. */ +bool is_unsigned_scalar(const var_t *var) +{ + return var && !var->ptr_level && !var->is_func && var->type && + var->type->is_unsigned; +} + void set_ptr_flags(ph2_ir_t *ir, insn_t *insn) { ir->src0_is_pointer = is_address_like(insn->rs1); ir->src1_is_pointer = is_address_like(insn->rs2); ir->is_pointer = is_pointer_like(insn->rd) || is_pointer_like(insn->rs1) || is_pointer_like(insn->rs2); + ir->is_unsigned = is_unsigned_scalar(insn->rd) || + is_unsigned_scalar(insn->rs1) || + is_unsigned_scalar(insn->rs2); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + ir->src1_is_unsigned = is_unsigned_scalar(insn->rs2); + + /* Addresses are ordered as unsigned values (C99 6.5.8): one above the + * signed range still compares greater than one below it. + */ + if ((insn->opcode == OP_lt || insn->opcode == OP_leq || + insn->opcode == OP_gt || insn->opcode == OP_geq) && + (ir->src0_is_pointer || ir->src1_is_pointer)) + ir->src0_is_unsigned = true; } /* Width of the value a local's frame slot actually holds. @@ -74,11 +93,22 @@ int var_slot_size(var_t *v) * carries the type of the expression that made it, which for pointer * arithmetic is still int, and a four-byte slot truncates the address. */ - if (!v->address_taken) - return PTR_SIZE; if (!v->type) return PTR_SIZE; + /* A direct wide scalar needs both words even when its address never + * escapes: spilling an SSA temporary is still a memory round trip. + */ + if (!is_record_type(v->type) && v->type->size == 8) + return 8; + + /* A global is memory another function reads, and its data slot is only as + * wide as its type: "unsigned int a, b;" places b four bytes after a on + * LP64, so a full-width store to a overwrites b. + */ + if (!v->address_taken && !v->is_global) + return PTR_SIZE; + /* Classify by the stored width rather than by type identity: an enum is a * distinct type_t that still stores as a 4-byte int, and a store through an * enum pointer writes only those 4 bytes. Reading the slot any wider then @@ -91,12 +121,36 @@ int var_slot_size(var_t *v) return PTR_SIZE; } +/* High halves stay out of REGS[]: its users assume a register entry names one + * ordinary scalar owner. This parallel reservation table prevents a pair's high + * machine register from being handed to a different value. + */ +var_t *pair_high_owner[REG_CNT]; + void vreg_map_to_phys(var_t *var, int phys_reg) { if (var) var->phys_reg = phys_reg; } +/* A wide scalar on a 32-bit target owns two independent machine registers. + * Keeping the high mapping on the SSA value, rather than duplicating @var in + * REGS[], lets the existing register file continue to mean one complete + * single-word owner per entry while paired allocation is introduced. + */ +void vreg_map_pair_to_phys(var_t *var, int low_reg, int high_reg) +{ + if (!var) + return; + if (high_reg < 0 || high_reg >= REG_CNT) + fatal("Invalid high register for wide value"); + if (pair_high_owner[high_reg] && pair_high_owner[high_reg] != var) + fatal("Wide value high register is already reserved"); + var->phys_reg = low_reg; + var->phys_reg_hi = high_reg; + pair_high_owner[high_reg] = var; +} + int vreg_get_phys(var_t *var) { if (var) @@ -104,10 +158,71 @@ int vreg_get_phys(var_t *var) return -1; } -void vreg_clear_phys(var_t *var) +int vreg_get_phys_hi(var_t *var) { if (var) + return var->phys_reg_hi; + return -1; +} + +/* ABI argument locations are measured in machine words, not source parameters. + * Keeping that calculation in one place matters once a direct 64-bit scalar + * occupies two words on a 32-bit target: its first word must be even-aligned, + * and every following argument starts after both words. + */ +int abi_arg_words(const var_t *var) +{ + return var_needs_register_pair(var) ? 2 : 1; +} + +/* Where an argument starts, given the next free ABI word. + * + * AAPCS32 starts every long long at an even word, in registers and on the stack + * alike. The RV32 calling convention does so only for a variadic argument and + * on the stack, whose slots are eight-byte aligned; a named one takes the next + * two words, and when only a7 is left its low word goes there and its high word + * to the first stack slot. + */ +int abi_arg_start(int cursor, const var_t *var, bool variadic) +{ + if (abi_arg_words(var) == 2 && + (ELF_MACHINE != 0xf3 || variadic || cursor >= MAX_ARGS_IN_REG)) + cursor = ALIGN_UP(cursor, 2); + return cursor; +} + +int abi_arg_next(int cursor, const var_t *var, bool variadic) +{ + return abi_arg_start(cursor, var, variadic) + abi_arg_words(var); +} + +/* Whether an argument starting at @word is split between the last argument + * register and the stack, which only a named RV32 long long can be. + */ +bool abi_arg_is_split(int word, const var_t *var) +{ + return word < MAX_ARGS_IN_REG && + word + abi_arg_words(var) > MAX_ARGS_IN_REG; +} + +int abi_param_start(const func_t *func, int param_idx) +{ + int cursor = func->returns_aggregate ? 1 : 0; + + for (int i = 0; i < param_idx; i++) + cursor = abi_arg_next(cursor, &func->param_defs[i], false); + return abi_arg_start(cursor, &func->param_defs[param_idx], false); +} + +void vreg_clear_phys(var_t *var) +{ + if (var) { + if (var->phys_reg_hi >= 0 && var->phys_reg_hi < REG_CNT && + pair_high_owner[var->phys_reg_hi] == var) + pair_high_owner[var->phys_reg_hi] = NULL; var->phys_reg = -1; + var->phys_reg_hi = -1; + } } /* Aligns size to nearest multiple of 4, this meets ARMv7's alignment @@ -209,6 +324,9 @@ ph2_ir_t *bb_add_ph2_ir(basic_block_t *bb, opcode_t op) */ n->src2 = 0; n->dest = 0; + n->src0_hi = -1; + n->src1_hi = -1; + n->dest_hi = -1; n->func_name = NULL; n->next_bb = NULL; n->then_bb = NULL; @@ -216,8 +334,12 @@ ph2_ir_t *bb_add_ph2_ir(basic_block_t *bb, opcode_t op) n->ofs_based_on_stack_top = false; n->size_bytes = PTR_SIZE; /* default to the full slot; see add_ph2_ir */ n->is_pointer = false; + n->is_unsigned = false; n->src0_is_pointer = false; n->src1_is_pointer = false; + n->src0_is_unsigned = false; + n->src1_is_unsigned = false; + n->is_volatile = false; if (!bb->ph2_ir_list.head) bb->ph2_ir_list.head = n; @@ -299,12 +421,22 @@ var_t *pinned_base[REG_CNT]; */ int reg_locked; -/* Whether @reg holds an operand of the instruction being lowered. */ +/* ABI argument registers already staged for the call being lowered. A staged + * register can lose its REGS[] owner while the call still needs its value: a + * variable passed twice as a pair, as in "f(g, g, ...)", is moved to its second + * argument pair, which released the first, and the next argument was loaded + * straight over it. + */ +int abi_args_staged; + +/* Whether @reg holds an operand of the instruction being lowered, or an + * argument of the call being staged. + */ bool reg_is_locked(int reg) { if (reg < 0 || reg >= REG_CNT) return false; - return (reg_locked >> reg) & 1; + return ((reg_locked | abi_args_staged) >> reg) & 1; } /* The register @var's base is pinned to, or -1. */ @@ -393,7 +525,8 @@ void slot_var_track(var_t *var) */ bool slot_is_private(const var_t *var) { - if (var->address_taken || var->array_size || var->has_backing_storage) + if (var->address_taken || var->array_size || var->has_backing_storage || + var_is_volatile_object(var)) return false; if (var->is_global || var->ofs_based_on_stack_top) return false; @@ -538,10 +671,18 @@ bool ph2_writes_reg(const ph2_ir_t *ir, int reg) case OP_indirect: return true; /* the call clobbers the caller-saved registers */ default: - return ir->dest == reg; + /* A pair result writes its high register too. */ + return ir->dest == reg || (reg >= 0 && ir->dest_hi == reg); } } +/* Whether @ir clobbers either register of the value stored from @store. */ +bool ph2_writes_stored_value(const ph2_ir_t *ir, const ph2_ir_t *store) +{ + return ph2_writes_reg(ir, store->src0) || + (store->src0_hi >= 0 && ph2_writes_reg(ir, store->src0_hi)); +} + /* Collapse a value that goes out to a stack slot and comes straight back. * * Unwinding a phi writes the value to the phi's own slot, and the next @@ -601,7 +742,7 @@ void collapse_slot_roundtrip(func_t *func) ok = true; break; } - if (ph2_writes_reg(ir, store->src0)) + if (ph2_writes_stored_value(ir, store)) break; prev = ir; } @@ -626,21 +767,26 @@ void collapse_slot_roundtrip(func_t *func) break; } } - if (store && ph2_writes_reg(ir, store->src0)) + if (store && ph2_writes_stored_value(ir, store)) break; prev = ir; } } - if (!ok) + + /* A pair is moved as a pair: forwarding only the low register turned + * the load into a move that left the high register unwritten. + */ + if (!ok || (store->src0_hi >= 0) != (load->dest_hi >= 0)) continue; load->op = OP_assign; load->src0 = store->src0; + load->src0_hi = store->src0_hi; /* Removing the store first would leave prev_load stale when the store * is the load's predecessor, so drop the later one first. */ - if (load->dest == load->src0) + if (load->dest == load->src0 && load->dest_hi == load->src0_hi) ph2_list_remove(home, prev_load, load); ph2_list_remove(home, prev_store, store); } @@ -682,11 +828,20 @@ void dead_store_elim(func_t *func) */ void alloc_var_slot(func_t *func, var_t *var) { + int slot_size = var_slot_size(var); + if (var->address_taken) func->stack_size = ALIGN_UP(func->stack_size, 16); + + /* A future 32-bit wide scalar occupies a low/high word pair. It must not + * start at an arbitrary four-byte offset: both AAPCS32 and the RV32 ABI + * require the pair's stack home to be eight-byte aligned. + */ + if (slot_size > PTR_SIZE) + func->stack_size = ALIGN_UP(func->stack_size, slot_size); var->offset = func->stack_size; var->space_is_allocated = true; - func->stack_size += PTR_SIZE; + func->stack_size += slot_size; slot_var_track(var); } @@ -698,10 +853,13 @@ void store_var(basic_block_t *bb, var_t *var, int idx) ph2_ir_t *ir = var->is_global ? bb_add_ph2_ir(bb, OP_global_store) : bb_add_ph2_ir(bb, OP_store); ir->src0 = idx; + ir->src0_hi = vreg_get_phys_hi(var); ir->src1 = var->offset; ir->ofs_based_on_stack_top = var->ofs_based_on_stack_top; ir->is_pointer = is_pointer_like(var); + ir->is_unsigned = is_unsigned_scalar(var); ir->size_bytes = var_slot_size(var); + ir->is_volatile = var_is_volatile_object(var); REGS[idx].polluted = 0; } @@ -722,7 +880,8 @@ void spill_var(basic_block_t *bb, var_t *var, int idx) */ bool reg_is_free(int i) { - return !REGS[i].var && !pinned_base[i]; + return !REGS[i].var && !pinned_base[i] && !pair_high_owner[i] && + !((abi_args_staged >> i) & 1); } /* Return the index of register for given variable. Otherwise, return -1. */ @@ -742,6 +901,8 @@ bool var_is_pinnable(var_t *var) { if (!var || var->is_const || !var->base) return false; + if (var_is_volatile_object(var)) + return false; /* slot_is_private() rules out everything reachable other than by name: * globals, address-taken variables, arrays and backing storage. @@ -764,6 +925,7 @@ void pin_registers(func_t *func) { int limit = REG_CNT / 2; bool calls = false; + bool pairs = false; for (int i = 0; i < REG_CNT; i++) pinned_base[i] = NULL; @@ -776,12 +938,23 @@ void pin_registers(func_t *func) if (insn->opcode == OP_call || insn->opcode == OP_indirect || insn->opcode == OP_push) calls = true; + if (!pairs && (var_needs_register_pair(insn->rd) || + var_needs_register_pair(insn->rs1) || + var_needs_register_pair(insn->rs2))) + pairs = true; /* Taking an address makes the frame reachable by other means. */ if (insn->opcode == OP_address_of) return; } } + /* A binary operation on register pairs holds two operand pairs and a + * destination pair at once. Pinning half the file left a 32-bit target with + * too few registers to lower one, so keep six of them unpinned. + */ + if (pairs && limit > REG_CNT - 6) + limit = REG_CNT - 6; + /* Across a call only the registers the callee preserves will still hold * their value. Those sit at the top of the file, which is the end the loop * below hands out from, so capping the count is all that is needed. @@ -827,7 +1000,9 @@ void pin_registers(func_t *func) for (int q = 0; q < func->num_params; q++) { if (var->base != &func->param_defs[q]) continue; - if (q < MAX_ARGS_IN_REG) + int word = abi_param_start(func, q); + if (word + abi_arg_words(&func->param_defs[q]) <= + MAX_ARGS_IN_REG) in_reg = true; else on_stack = true; @@ -916,10 +1091,16 @@ void pin_registers(func_t *func) * them; the variable would then read a parameter's value instead. */ int reg = REG_CNT - 1 - taken; - int args_in_reg = func->num_params < MAX_ARGS_IN_REG ? func->num_params - : MAX_ARGS_IN_REG; + int arg_words_in_reg = func->returns_aggregate ? 1 : 0; + for (int i = 0; i < func->num_params; i++) { + int word = abi_param_start(func, i); - if (reg < args_in_reg) + if (word + abi_arg_words(&func->param_defs[i]) > MAX_ARGS_IN_REG) + break; + arg_words_in_reg = abi_arg_next(word, &func->param_defs[i], false); + } + + if (reg < arg_words_in_reg) return; pinned_base[reg] = best; @@ -927,6 +1108,11 @@ void pin_registers(func_t *func) } } +/* Defined beside OP_push handling below; allocation must not reclaim ABI + * argument registers once argument staging has begun. + */ +extern bool is_pushing_args; + void load_var(basic_block_t *bb, var_t *var, int idx) { ph2_ir_t *ir; @@ -935,6 +1121,7 @@ void load_var(basic_block_t *bb, var_t *var, int idx) if (var->is_const) { ir = bb_add_ph2_ir(bb, OP_load_constant); ir->src0 = var->init_val; + ir->src1 = var->init_val_hi; } else if (var->is_global && var->array_size) { /* A global array's address is fixed for the life of the program, and * the initialiser recorded where its storage sits. Computing it beats @@ -949,16 +1136,85 @@ void load_var(basic_block_t *bb, var_t *var, int idx) : bb_add_ph2_ir(bb, OP_load); ir->src0 = var->offset; ir->ofs_based_on_stack_top = var->ofs_based_on_stack_top; + ir->is_volatile = var_is_volatile_object(var); } ir->dest = idx; + ir->dest_hi = vreg_get_phys_hi(var); ir->is_pointer = is_pointer_like(var); - ir->size_bytes = var_slot_size(var); + ir->is_unsigned = is_unsigned_scalar(var); + + /* Incoming stack arguments use ABI-sized slots but carry a scalar in the + * low declared-width bytes. Reloading all eight bytes can retain a caller's + * sign extension; use the declaration width to preserve unsigned argument + * semantics. + */ + ir->size_bytes = var->ofs_based_on_stack_top && !var->ptr_level && + !var->is_func && var->type + ? var->type->size + : var_slot_size(var); REGS[idx].var = var; REGS[idx].polluted = 0; vreg_map_to_phys(var, idx); } +/* The two lowest free registers, in *@low and *@high. */ +bool find_free_pair(int *low, int *high) +{ + *low = -1; + *high = -1; + for (int r = 0; r < REG_CNT; r++) { + if (!reg_is_free(r)) + continue; + if (*low < 0) { + *low = r; + } else { + *high = r; + return true; + } + } + return false; +} + +/* Two free registers for a register pair, in *@low and *@high, spilling to make + * room when the file is full. The ordinary chooser frees one register, which + * cannot help a pair, so spill whole pairs through their slots first, which + * keeps both words, and then one-word values, whether live or dead. Neither + * @operand_0 nor @operand_1 is touched, nor a pinned register, and a one-word + * spill also leaves reserved high halves and locked inputs alone. Once OP_push + * has installed ABI arguments, those registers are live until the call, so + * nothing is spilled then. + */ +bool claim_free_pair(basic_block_t *bb, + int operand_0, + int operand_1, + int *low, + int *high) +{ + if (find_free_pair(low, high) || is_pushing_args) + return *high >= 0; + + for (int scalars = 0; scalars < 2; scalars++) { + for (int r = 0; r < REG_CNT; r++) { + var_t *owner = REGS[r].var; + + if (!owner || r == operand_0 || r == operand_1 || pinned_base[r]) + continue; + if (scalars) { + if (var_needs_register_pair(owner) || pair_high_owner[r] || + reg_is_locked(r)) + continue; + } else if (!var_needs_register_pair(owner)) { + continue; + } + spill_var(bb, owner, r); + if (find_free_pair(low, high)) + return true; + } + } + return false; +} + int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) { /* A pinned variable is already where it always is -- unless this version of @@ -974,18 +1230,62 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) return pinned; } - /* Check VReg mapping first for O(1) lookup */ + /* Check VReg mapping first for O(1) lookup. A register holding a volatile + * variable only as its storage last held it is left to the reload below. + */ int phys_reg = vreg_get_phys(var); - if (phys_reg >= 0 && phys_reg < REG_CNT && REGS[phys_reg].var == var) - return phys_reg; + if (phys_reg >= 0 && phys_reg < REG_CNT && REGS[phys_reg].var == var) { + if (!var_is_volatile_object(var) || REGS[phys_reg].polluted) + return phys_reg; + + /* Reload a volatile variable into the register, or the pair, that + * already names it. Loading it into others left two registers naming + * the variable, and store_addressed_def(), which finds the first, took + * the stale one for the one a later assignment wrote and skipped the + * store. + */ + if (phys_reg != operand_0) { + load_var(bb, var, phys_reg); + return phys_reg; + } + } + + if (var_needs_register_pair(var)) { + int low, high; + + if (!claim_free_pair(bb, operand_0, -1, &low, &high)) + fatal("Wide operand needs two free registers"); + vreg_map_pair_to_phys(var, low, high); + load_var(bb, var, low); + return low; + } - /* Force reload for address-taken variables (may be modified via pointer) */ + /* Force reload for address-taken variables (may be modified via pointer). + * + * Not when the register holds a value the slot never received, though. No + * pointer can have reached the slot since: taking the address stores the + * register first, and a write through a pointer or a call spills or drops + * every register. A parameter still in the register it arrived in is that + * case, and its slot may not exist yet, so reloading it read whatever sat + * at offset zero of the frame instead. + * + * A volatile global is reloaded for the same reason and one more: every + * read of it is an access the program has to perform (C99 6.7.3p6), not + * just a way to learn its value. A volatile local is address-taken already. + */ int i = find_in_regs(var); - if (i > -1 && !var->address_taken) { + if (i > -1 && (!(var->address_taken || var_is_volatile_object(var)) || + REGS[i].polluted)) { vreg_map_to_phys(var, i); return i; } + /* As above, when the register lost the mapping but still names it. */ + if (i > -1 && i != operand_0 && var_is_volatile_object(var)) { + load_var(bb, var, i); + return i; + } + for (i = 0; i < REG_CNT; i++) { if (reg_is_free(i)) { load_var(bb, var, i); @@ -1010,9 +1310,9 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) } } - if (REGS[spilled].var) - vreg_clear_phys(REGS[spilled].var); - + /* spill_var() forgets the mapping once it has stored the value. Clearing it + * first dropped a pair's high register, so only the low word was stored. + */ spill_var(bb, REGS[spilled].var, spilled); load_var(bb, var, spilled); vreg_map_to_phys(var, spilled); @@ -1054,7 +1354,10 @@ bool var_read_later_in_bb(basic_block_t *bb, insn_t *from, const var_t *var) void clobber_caller_saved(void) { for (int i = 0; i < REG_CNT; i++) { - REGS[i].var = pinned_base[i] ? REGS[i].var : NULL; + if (!pinned_base[i] && REGS[i].var) { + vreg_clear_phys(REGS[i].var); + REGS[i].var = NULL; + } if (!REGS[i].var) REGS[i].polluted = 0; } @@ -1125,6 +1428,25 @@ int prepare_dest(basic_block_t *bb, int operand_0, int operand_1) { + if (var_needs_register_pair(var)) { + int mapped_low = vreg_get_phys(var); + int mapped_high = vreg_get_phys_hi(var); + if (mapped_low >= 0 && mapped_low < REG_CNT && mapped_high >= 0 && + mapped_high < REG_CNT && REGS[mapped_low].var == var && + pair_high_owner[mapped_high] == var) { + REGS[mapped_low].polluted = 1; + return mapped_low; + } + int low, high; + + if (!claim_free_pair(bb, operand_0, operand_1, &low, &high)) + fatal("Wide destination needs two free registers"); + REGS[low].var = var; + REGS[low].polluted = 1; + vreg_map_pair_to_phys(var, low, high); + return low; + } + int pinned = pinned_reg_of(var); if (pinned >= 0) { REGS[pinned].var = var; @@ -1191,9 +1513,9 @@ int prepare_dest(basic_block_t *bb, if (spilled < 0) return -1; - if (REGS[spilled].var) - vreg_clear_phys(REGS[spilled].var); - + /* As in prepare_operand(), spill before forgetting the mapping, or a pair's + * high word is not stored. + */ spill_var(bb, REGS[spilled].var, spilled); REGS[spilled].var = var; REGS[spilled].polluted = 1; @@ -1204,13 +1526,16 @@ int prepare_dest(basic_block_t *bb, void spill_alive(basic_block_t *bb, const insn_t *insn) { - /* Spill all locals on pointer writes (conservative aliasing handling) */ + /* Spill every variable on pointer writes (conservative aliasing handling). + * A global is no exception: the pointer can hold its address, and a copy + * left in a register is what the next read of it would use. + */ if (insn && insn->opcode == OP_write) { for (int i = 0; i < REG_CNT; i++) { /* A pinned variable has no address, so no write through a pointer * can reach it. */ - if (REGS[i].var && !REGS[i].var->is_global && !pinned_base[i]) + if (REGS[i].var && !pinned_base[i]) spill_var(bb, REGS[i].var, i); } return; @@ -1370,22 +1695,176 @@ void extend_liveness(basic_block_t *bb, var->consumed = insn->idx + offset; } +/* Materialize a wide argument exactly where the ABI consumes it. Moving a pair + * after arbitrary allocation is not generally safe: the source and destination + * pairs can overlap in a cycle. A spill/reload costs a little more in that + * uncommon path, but gives both 32-bit backends an unambiguous pair in the + * required ABI registers. + */ +void prepare_pair_argument(basic_block_t *bb, var_t *var, int low) +{ + int high = low + 1; + int old_low = vreg_get_phys(var); + int old_high = vreg_get_phys_hi(var); + + if (old_low == low && old_high == high && REGS[low].var == var && + pair_high_owner[high] == var) + return; + + if (old_low >= 0 && old_low < REG_CNT && REGS[old_low].var == var) + spill_var(bb, var, old_low); + else + vreg_clear_phys(var); + + if (REGS[low].var) + spill_var(bb, REGS[low].var, low); + if (pair_high_owner[high]) { + var_t *owner = pair_high_owner[high]; + int owner_low = vreg_get_phys(owner); + + if (owner_low >= 0 && owner_low < REG_CNT && + REGS[owner_low].var == owner) + spill_var(bb, owner, owner_low); + else + vreg_clear_phys(owner); + } + + vreg_map_pair_to_phys(var, low, high); + load_var(bb, var, low); +} + +/* Reserve the outgoing stack-argument area @func's calls need. + * + * add_func() reserves one pointer-sized word for each source parameter that + * cannot go in a register, which is enough only while every argument takes one + * ABI word. A long long takes two on a 32-bit target, and may be preceded by a + * padding word, so a call can reach well past that reservation: RISC-V, which + * passes MAX_PARAMS arguments in registers, reserves nothing at all. The words + * abi_lower_call_args() stores above the reservation then overwrote the first + * locals of the caller's frame. Nothing is placed in the frame before register + * allocation, so growing the reservation here moves every slot above it. + */ +void reserve_outgoing_args(func_t *func) +{ + for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { + int cursor = 0; + + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { + if (insn->opcode != OP_push) { + cursor = 0; + continue; + } + /* Aligning every argument bounds both placement rules. */ + cursor = abi_arg_next(cursor, insn->rs1, true); + if ((cursor - MAX_ARGS_IN_REG) * PTR_SIZE > func->stack_size) + func->stack_size = (cursor - MAX_ARGS_IN_REG) * PTR_SIZE; + } + } +} + +/* The first ABI word of each argument of the call being staged, in push order, + * and the next one OP_push takes. abi_lower_call_args() fills them in once per + * call, since only it sees the callee and so which arguments are variadic. + */ +int call_arg_starts[MAX_PARAMS + 1]; +int call_arg_next; + +/* Load only the low word of the register pair @var into @reg. */ +void load_low_word(basic_block_t *bb, var_t *var, int reg) +{ + int held = vreg_get_phys(var); + ph2_ir_t *ir; + + if (held >= 0 && held < REG_CNT && REGS[held].var == var) { + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = held; + } else if (var->is_const) { + ir = bb_add_ph2_ir(bb, OP_load_constant); + ir->src0 = var->init_val; + } else { + ir = bb_add_ph2_ir(bb, var->is_global ? OP_global_load : OP_load); + ir->src0 = var->offset; + ir->ofs_based_on_stack_top = var->ofs_based_on_stack_top; + ir->size_bytes = 4; + ir->is_volatile = var_is_volatile_object(var); + } + ir->dest = reg; +} + /* Return whether extra arguments are pushed onto stack. */ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) { + /* An aggregate-return call has one ABI-only destination in addition to the + * MAX_PARAMS source arguments. + */ + insn_t *pushes[MAX_PARAMS + 1]; + int *starts = call_arg_starts; int num_of_args = 0; - int stack_args = 0; + int cursor = 0; + insn_t *call = insn; + func_t *callee = NULL; + int first_param = 0; + + while (call && call->opcode == OP_push) + call = call->next; + if (call && call->opcode == OP_call) + callee = find_func(call->str); + else if (call && call->opcode == OP_indirect) + callee = get_func_signature(call->rs1); + if (callee && callee->returns_aggregate) + first_param = 1; + + call_arg_next = 0; while (insn && insn->opcode == OP_push) { - num_of_args += 1; + bool variadic; + + if (num_of_args >= MAX_PARAMS + 1) + fatal("Too many call arguments"); + variadic = callee && callee->va_args && + num_of_args - first_param >= callee->num_params; + starts[num_of_args] = abi_arg_start(cursor, insn->rs1, variadic); + pushes[num_of_args++] = insn; + cursor = abi_arg_next(cursor, insn->rs1, variadic); insn = insn->next; } - if (num_of_args <= MAX_ARGS_IN_REG) + if (cursor <= MAX_ARGS_IN_REG) return false; - insn = insn->prev; - stack_args = num_of_args - MAX_ARGS_IN_REG; - while (stack_args) { + for (int i = num_of_args - 1; i >= 0; i--) { + insn = pushes[i]; + + /* A split long long leaves its high word in the first stack slot. Its + * low word is placed with the register arguments. + */ + if (abi_arg_is_split(starts[i], insn->rs1)) { + int scratch = MAX_ARGS_IN_REG - 2; + + prepare_pair_argument(bb, insn->rs1, scratch); + ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = scratch + 1; + ir->src1 = 0; + ir->size_bytes = 4; + spill_var(bb, insn->rs1, scratch); + continue; + } + + if (starts[i] < MAX_ARGS_IN_REG) + continue; + + if (abi_arg_words(insn->rs1) == 2) { + int scratch = MAX_ARGS_IN_REG - 2; + + prepare_pair_argument(bb, insn->rs1, scratch); + ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = scratch; + ir->src0_hi = scratch + 1; + ir->src1 = (starts[i] - MAX_ARGS_IN_REG) * PTR_SIZE; + ir->size_bytes = 8; + spill_var(bb, insn->rs1, scratch); + continue; + } + /* A pinned variable has no slot to load from: its value only ever lives * in its register, so reading the frame here handed the callee whatever * the slot happened to hold. @@ -1401,10 +1880,7 @@ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) } ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_store); ir->src0 = MAX_ARGS_IN_REG - 1; - /* One pointer-sized slot per stack-passed argument. */ - ir->src1 = (stack_args - 1) * PTR_SIZE; - stack_args -= 1; - insn = insn->prev; + ir->src1 = (starts[i] - MAX_ARGS_IN_REG) * PTR_SIZE; } REGS[MAX_ARGS_IN_REG - 1].var = NULL; return true; @@ -2031,6 +2507,107 @@ void coalesce_phi_slots(func_t *func) } } +/* Fill a lowered binary operation from its SSA instruction. Function bodies and + * global setup share it, so a wide initializer such as the element of "long + * long a[] = {1LL << 40}" names the high registers of its operands. + */ +void fill_binary_ph2_ir(ph2_ir_t *ir, + insn_t *insn, + int src0, + int src1, + int dest) +{ + ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(insn->rs1); + ir->src1 = src1; + ir->src1_hi = vreg_get_phys_hi(insn->rs2); + ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + + /* Record whether the result is an address, and which operand it came from. + * On LP64 an int-typed result has to wrap at 32 bits, while a pointer must + * keep all 64, and pointer arithmetic has to widen the int index beside the + * address. + */ + set_ptr_flags(ir, insn); + ir->size_bytes = insn->rd->ptr_level ? PTR_SIZE : insn->rd->type->size; + + /* SSA temporaries normally retain their result type, but width is a + * property of the operation as well: a wide operand must not be narrowed + * merely because an intermediate lost its annotation. This includes + * comparisons: their result is int, while CMP must inspect the common + * operand width rather than stale high halves of 32-bit register values. + */ + int left_size = insn->rs1->ptr_level ? PTR_SIZE : insn->rs1->type->size; + int right_size = insn->rs2->ptr_level ? PTR_SIZE : insn->rs2->type->size; + if (left_size > ir->size_bytes) + ir->size_bytes = left_size; + + /* A shift is the exception: its type is that of the promoted left operand + * alone, so "(x ^ y) >> (n & 31)" with a long long n is an int shift, and a + * 64-bit shift of that int register brought zeros in as its sign. + */ + if (right_size > ir->size_bytes && insn->opcode != OP_lshift && + insn->opcode != OP_rshift) + ir->size_bytes = right_size; +} + +/* The unary counterpart of fill_binary_ph2_ir(). */ +void fill_unary_ph2_ir(ph2_ir_t *ir, insn_t *insn, int src0, int dest) +{ + ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(insn->rs1); + ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + + /* As for OP_branch: the width of the test follows the operand, not the + * result. + */ + ir->src0_is_pointer = is_address_like(insn->rs1); + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + ir->size_bytes = insn->rd->ptr_level ? PTR_SIZE : insn->rd->type->size; + + /* !x yields an int, but it tests all of a long long operand. */ + if (insn->opcode == OP_log_not && !insn->rs1->ptr_level && + insn->rs1->type->size > ir->size_bytes) + ir->size_bytes = insn->rs1->type->size; +} + +/* Narrow a conversion result in @dest again by its own type. + * + * A byte or short sits in its register extended by its own signedness, which a + * later widening or comparison relies on. OP_cast is a move and OP_sign_ext + * extends by the source alone, so a conversion that changes signedness broke + * that: a signed char -17 became unsigned short 0xffffffef, and cast to + * unsigned char it kept every high bit. A signed destination only needs this + * when it is no wider than its unsigned source; any other value already fits. + * The 64-bit x86 backend redoes the extension itself, the others do not. + */ +void narrow_conversion_result(basic_block_t *bb, insn_t *insn, int dest) +{ + var_t *rd = insn->rd, *rs = insn->rs1; + + if (!rd || !rs || rd->ptr_level || rd->is_func || !rd->type || !rs->type || + is_record_type(rd->type) || is_record_type(rs->type)) + return; + + int size = rd->type->size; + bool rd_unsigned = is_unsigned_scalar(rd); + + if ((size != 1 && size != 2) || rd_unsigned == is_unsigned_scalar(rs)) + return; + if (!rd_unsigned && (rs->ptr_level || rs->type->size < size)) + return; + + ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_trunc); + ir->src0 = dest; + ir->src1 = size; + ir->dest = dest; + ir->is_unsigned = rd_unsigned; + ir->size_bytes = size; +} + /* Place one global initializer, which has no basic block of its own. */ void reg_alloc_global(insn_t *global_insn) { @@ -2056,7 +2633,7 @@ void reg_alloc_global(insn_t *global_insn) /* Stash base offset for this array variable */ global_insn->rd->init_val = src0; - if (global_insn->rd->ptr_level) + if (global_insn->rd->ptr_level || global_insn->rd->is_func) GLOBAL_FUNC->stack_size += align_size(PTR_SIZE * global_insn->rd->array_size); else { @@ -2075,7 +2652,7 @@ void reg_alloc_global(insn_t *global_insn) } else { global_insn->rd->offset = GLOBAL_FUNC->stack_size; global_insn->rd->space_is_allocated = true; - if (global_insn->rd->ptr_level) + if (global_insn->rd->ptr_level || global_insn->rd->is_func) GLOBAL_FUNC->stack_size += PTR_SIZE; else if (global_insn->rd->type != TY_int && global_insn->rd->type != TY_short && @@ -2094,25 +2671,94 @@ void reg_alloc_global(insn_t *global_insn) dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); ir->src0 = global_insn->rd->init_val; + ir->src1 = global_insn->rd->init_val_hi; ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(global_insn->rd); + ir->is_unsigned = is_unsigned_scalar(global_insn->rd); + ir->size_bytes = + global_insn->rd->ptr_level ? PTR_SIZE : global_insn->rd->type->size; break; case OP_assign: - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + if (global_insn->rs1 && global_insn->rs1->is_global_address) { + src0 = + prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rs1, -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); + ir->src0 = global_insn->rs1->offset; + ir->dest = src0; + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + } else + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_assign); + + /* An initializer such as "unsigned long long g = c ? 7U : 1U" has an + * int-sized value; a move would leave the high register unwritten, so + * extend it by its own signedness instead. + */ + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, + var_needs_register_pair(global_insn->rd) && + !var_needs_register_pair(global_insn->rs1) + ? OP_cast + : OP_assign); ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(global_insn->rs1); + ir->src0_is_unsigned = is_unsigned_scalar(global_insn->rs1); + ir->src0_is_pointer = is_address_like(global_insn->rs1); ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(global_insn->rd); spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); /* release the unused constant number in register manually */ REGS[src0].polluted = 0; vreg_clear_phys(REGS[src0].var); REGS[src0].var = NULL; break; + case OP_address_of: + case OP_global_address_of: + /* A global function-pointer initializer first forms the address of its + * global storage slot. Its address is GP-relative, unlike an address + * formed in a function body. + */ + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); + + /* Global arrays have a pointer slot followed by their backing region; + * an address constant for the array denotes that backing region. + */ + ir->src0 = global_insn->rs1->array_size ? global_insn->rs1->init_val + : global_insn->rs1->offset; + ir->dest = dest; + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + if (global_insn->rd->is_global_address) { + global_insn->rd->offset = ir->src0; + global_insn->rd->space_is_allocated = true; + + /* This temporary carries a compile-time GP-relative offset, not a + * stack-resident value. Leaving it mapped in REGS[] lets the next + * global setup instruction spill its address to `sp + offset`; on a + * global array that offset is the backing-region address, so the + * spill overwrites an initialized element. The folded + * OP_add/OP_assign paths rematerialize this address from offset, so + * it has no runtime register lifetime. + */ + REGS[dest].polluted = 0; + vreg_clear_phys(global_insn->rd); + REGS[dest].var = NULL; + } + break; case OP_add: { /* Special-case address computation for globals: if rs1 is a global base * and rs2 is a constant, propagate absolute offset to rd so OP_write * can fold into OP_global_store. */ + if (global_insn->rs1 && global_insn->rs1->is_global_address && + global_insn->rs2) { + global_insn->rd->offset = + global_insn->rs1->offset + global_insn->rs2->init_val; + global_insn->rd->is_global_address = true; + global_insn->rd->space_is_allocated = true; + break; + } if (global_insn->rs1 && global_insn->rs1->is_global && global_insn->rs2) { int base_off = global_insn->rs1->offset; @@ -2125,25 +2771,109 @@ void reg_alloc_global(insn_t *global_insn) global_insn->rd->is_global = true; break; } - /* Fallback: generate an add */ + /* Fall through to the ordinary scalar binary lowering below. */ + goto lower_global_binary; + } + case OP_negate: + case OP_bit_not: + case OP_log_not: { + /* Unary wide global constant expressions use the normal phase-2 + * instruction too. Keeping these separate from binary lowering avoids + * preparing a nonexistent right operand. + */ + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + fill_unary_ph2_ir(ir, global_insn, src0, dest); + break; + } + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + case OP_lshift: + case OP_rshift: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + lower_global_binary: { + /* Global scalar initializers may have been parsed as a binary constant + * expression. Use the same phase-2 operation as a function body so wide + * division, comparison, and bitwise expressions do not stop at global + * setup merely because they are not pointer-address arithmetic. + */ int src1; src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, src1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_add); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = dest; - set_ptr_flags(ir, global_insn); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + fill_binary_ph2_ir(ir, global_insn, src0, src1, dest); break; } case OP_write: { + if (global_insn->rs2 && global_insn->rs2->is_func) { + /* A direct aggregate field address is itself a global object, not a + * pointer value stored in that object. Loading it here turns an + * all-zero callback slot into the destination address. + */ + if (global_insn->rs1 && global_insn->rs1->is_global) { + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rs1, + -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); + ir->src0 = global_insn->rs1->array_size + ? global_insn->rs1->init_val + : global_insn->rs1->offset; + ir->dest = dest; + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + src0 = dest; + } else { + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + } + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_address_of_func); + ir->src0 = src0; + ir->func_name = intern_string(global_insn->rs2->var_name); + + /* The GP-relative address exists only to receive this one + * relocation. In particular, an address temporary for a later array + * element must not be spilled: its slot allocation can be the very + * element being initialized, which would overwrite the function + * address with the address itself. + */ + REGS[src0].polluted = 0; + vreg_clear_phys(REGS[src0].var); + REGS[src0].var = NULL; + if (dynlink) { + func_t *target_fn = find_func(ir->func_name); + if (target_fn) + target_fn->is_used = true; + } + break; + } /* Fold (addr, val) where addr carries GP-relative offset */ if (global_insn->rs1 && (global_insn->rs1->is_global)) { - int vreg = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, -1); + int vreg; + + if (global_insn->rs2 && global_insn->rs2->is_global_address) { + vreg = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rs2, + -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); + ir->src0 = global_insn->rs2->offset; + ir->dest = vreg; + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + } else + vreg = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, -1); ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_store); ir->src0 = vreg; + ir->src0_hi = vreg_get_phys_hi(global_insn->rs2); /* For array variables used as base, store to the backing region's * base offset (cached in init_val). @@ -2152,6 +2882,26 @@ void reg_alloc_global(insn_t *global_insn) if (global_insn->rs1->array_size > 0) base_off = global_insn->rs1->init_val; ir->src1 = base_off; + + /* OP_global_store selects its instruction width from this field. + * Leaving it at zero falls through to an eight-byte store, which + * makes a global int field initializer overwrite its successor. + */ + ir->size_bytes = global_insn->sz; + ir->is_pointer = global_insn->rs2->ptr_level > 0; + ir->is_unsigned = is_unsigned_scalar(global_insn->rs2); + + /* A materialized global address is consumed by this one store. Its + * descriptor offset is GP-relative data, not a spill slot; + * retaining the vreg mapping lets the next initializer spill the + * address at sp + offset and overwrite the source element (for + * example, a pointer row initialized from &values[0][1]). + */ + if (global_insn->rs2 && global_insn->rs2->is_global_address) { + REGS[vreg].polluted = 0; + vreg_clear_phys(global_insn->rs2); + REGS[vreg].var = NULL; + } break; } /* Fallback generic write */ @@ -2161,7 +2911,9 @@ void reg_alloc_global(insn_t *global_insn) ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_write); ir->src0 = src0; ir->src1 = src1; + ir->src1_hi = vreg_get_phys_hi(global_insn->rs2); ir->dest = global_insn->sz; + set_ptr_flags(ir, global_insn); break; } case OP_trunc: @@ -2177,6 +2929,17 @@ void reg_alloc_global(insn_t *global_insn) ir->src0 = src0; ir->src1 = global_insn->sz; ir->dest = dest; + + /* Widening "long long g[] = {1}" extends into a register pair, and the + * backends select that form and the extension's sign from these fields + * exactly as they do inside a function. + */ + ir->src0_hi = vreg_get_phys_hi(global_insn->rs1); + ir->dest_hi = vreg_get_phys_hi(global_insn->rd); + ir->is_unsigned = is_unsigned_scalar(global_insn->rd); + ir->src0_is_unsigned = is_unsigned_scalar(global_insn->rs1); + if (global_insn->opcode != OP_trunc) + narrow_conversion_result(GLOBAL_FUNC->bbs, global_insn, dest); break; default: printf("Unsupported global operation: %d\n", global_insn->opcode); @@ -2185,14 +2948,44 @@ void reg_alloc_global(insn_t *global_insn) } } +/* Write the value @insn just gave an address-taken scalar to its slot. + * + * A pointer to the variable reads the slot, never the register, and nothing the + * allocator tracks says when that happens: the variable need not be named again + * for a read through the pointer, or a callee handed it, to want this value. A + * global is stored straight after its assignment for the same reason. Records + * and arrays are left alone, since their name holds an address and what a + * pointer reaches is the storage behind it. + */ +void store_addressed_def(basic_block_t *bb, const insn_t *insn) +{ + var_t *var = insn->rd; + + if (!var || !var->address_taken || var->is_global) + return; + if (insn->opcode == OP_allocat) + return; + if (var->array_size || var->has_backing_storage) + return; + if (!var->ptr_level && (!var->type || is_record_type(var->type))) + return; + + int reg = find_in_regs(var); + if (reg < 0 || !REGS[reg].polluted) + return; + store_var(bb, var, reg); +} + /* Assign registers across one basic block, and emit the phase-2 IR that carries * the assignment. */ void reg_alloc_bb(func_t *func, basic_block_t *bb) { bool handle_abi = false, args_on_stack = false; + bool riscv_indirect_target_staged = false; is_pushing_args = false; + abi_args_staged = 0; int args = 0; bb->visited++; @@ -2259,6 +3052,11 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) src0 = prepare_operand(bb, insn->rs1, -1); ir = bb_add_ph2_ir(bb, OP_store); ir->src0 = src0; + + /* The phi's slot is eight bytes for a pair; a store of the low + * register alone left the high word of the previous iteration. + */ + ir->src0_hi = vreg_get_phys_hi(insn->rs1); ir->src1 = insn->rd->offset; ir->ofs_based_on_stack_top = insn->rd->ofs_based_on_stack_top; ir->is_pointer = is_pointer_like(insn->rd); @@ -2276,7 +3074,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) func->stack_size += PTR_SIZE; src0 = func->stack_size; - if (insn->rd->ptr_level) + if (insn->rd->ptr_level || insn->rd->is_func) sz = PTR_SIZE; else { sz = insn->rd->type->size; @@ -2301,6 +3099,21 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) /* For arrays, store the base address just like global arrays do */ if (insn->rd->array_size) spill_var(bb, insn->rd, dest); + else if (insn->rd->is_func || + (!insn->rd->ptr_level && !insn->rd->type->ptr_level && + is_record_type(insn->rd->type) && + !insn->rd->has_backing_storage)) { + /* OP_allocat's result is the backing address for a callback + * object, not the pointer value stored in that object. Keeping + * it mapped as the object lets a later conservative spill + * overwrite an initialized callback with its own address. A + * record is reached through &record, which names the slot + * itself, so the same spill would overwrite its first member. + */ + REGS[dest].var = NULL; + REGS[dest].polluted = 0; + vreg_clear_phys(insn->rd); + } break; case OP_load_constant: case OP_load_data_address: @@ -2308,13 +3121,20 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) dest = prepare_dest(bb, insn, insn->rd, -1, -1); ir = bb_add_ph2_ir(bb, insn->opcode); ir->src0 = insn->rd->init_val; + ir->src1 = insn->rd->init_val_hi; ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->size_bytes = + insn->rd->ptr_level ? PTR_SIZE : insn->rd->type->size; /* store global variable immediately after assignment */ if (insn->rd->is_global) { ir = bb_add_ph2_ir(bb, OP_global_store); ir->src0 = dest; + ir->src0_hi = vreg_get_phys_hi(insn->rd); ir->src1 = insn->rd->offset; + ir->is_volatile = var_is_volatile_object(insn->rd); REGS[dest].polluted = 0; } @@ -2325,6 +3145,59 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) insn->rs1->address_taken = true; insn->rs1->is_const = false; + /* Source-level record parameters arrive as hidden pointers to the + * caller's by-value copy. Their declaration must stay a record for + * parsing and type checks, but taking its address produces the ABI + * pointer rather than allocating a scalar spill slot. + */ + if (insn->rs1->is_aggregate_param) { + int param_idx = -1; + for (int i = 0; i < func->num_params; i++) + if (insn->rs1->base == &func->param_defs[i] || + insn->rs1 == &func->param_defs[i]) { + param_idx = i; + break; + } + if (param_idx < 0) + fatal("Aggregate parameter is not owned by its function"); + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + + /* A variadic callee reserves the final named aggregate's + * complete ABI footprint in its contiguous argument-save area. + * `va_start` obtains that slot's address, then advances by the + * same rounded extent to the first unnamed argument. + */ + if (func->va_args && param_idx + 1 == func->num_params && + insn->rs1->space_is_allocated) { + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = insn->rs1->offset; + ir->dest = dest; + ir->ofs_based_on_stack_top = + insn->rs1->ofs_based_on_stack_top; + } else if (insn->rs1->space_is_allocated) { + ir = bb_add_ph2_ir(bb, OP_load); + ir->src0 = insn->rs1->offset; + ir->dest = dest; + ir->ofs_based_on_stack_top = + insn->rs1->ofs_based_on_stack_top; + } else if (abi_param_start(func, param_idx) < MAX_ARGS_IN_REG) { + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = abi_param_start(func, param_idx); + ir->dest = dest; + } else { + ir = bb_add_ph2_ir(bb, OP_load); + ir->src0 = + (abi_param_start(func, param_idx) - MAX_ARGS_IN_REG) * + PTR_SIZE; + ir->dest = dest; + ir->ofs_based_on_stack_top = true; + } + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + break; + } + /* OP_allocat puts a local aggregate's spill slot before its backing * storage. &aggregate must name the backing storage, not the spill * slot. @@ -2368,11 +3241,17 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) if (REGS[i].var == insn->rs1 && !pinned_base[i]) { ir = bb_add_ph2_ir(bb, OP_store); ir->src0 = i; + + /* A pair spills both words: the slot is about to be + * read through the address, not from this register. + */ + ir->src0_hi = vreg_get_phys_hi(insn->rs1); ir->src1 = insn->rs1->offset; ir->ofs_based_on_stack_top = insn->rs1->ofs_based_on_stack_top; /* Clear stale register tracking */ REGS[i].var = NULL; + vreg_clear_phys(insn->rs1); } } @@ -2446,17 +3325,39 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->src1 = taken; ir->src2 = other; ir->dest = dest; - ir->size_bytes = var_slot_size(insn->rd); + + /* As for OP_branch, the recorded width is the condition's: a select + * tests that value, and the arms are moved whole. An int condition + * is decided by its low word alone, whatever the width of the + * result. + */ + ir->size_bytes = + insn->rs2->ptr_level ? PTR_SIZE : insn->rs2->type->size; + ir->src0_is_pointer = is_address_like(insn->rs2); ir->is_pointer = is_pointer_like(insn->rd); break; } case OP_assign: - if (insn->rd->consumed == -1) + /* A copy nothing reads is dropped, unless it copies from or to a + * volatile object. Copying from one is the read a discarded + * expression such as "status;" asks for, and a write to one is a + * side effect however little the program reads it back. + */ + if (insn->rd->consumed == -1 && + !var_is_volatile_object(insn->rs1) && + !var_is_volatile_object(insn->rd)) break; track_var_use(insn->rs1, insn->idx); src0 = find_in_regs(insn->rs1); + /* A volatile object whose register matches its storage is read + * again, as prepare_operand() does. + */ + if (src0 > -1 && var_is_volatile_object(insn->rs1) && + !REGS[src0].polluted) + src0 = -1; + /* If operand is loaded from stack, clear the original slot after * moving. */ @@ -2469,13 +3370,24 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) dest = prepare_dest(bb, insn, insn->rd, src0, -1); ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(insn->rs1); ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + ir->src0_is_pointer = is_address_like(insn->rs1); + ir->size_bytes = + insn->rd->ptr_level ? PTR_SIZE : insn->rd->type->size; /* store global variable immediately after assignment */ if (insn->rd->is_global) { ir = bb_add_ph2_ir(bb, OP_global_store); ir->src0 = dest; + ir->src0_hi = vreg_get_phys_hi(insn->rd); ir->src1 = insn->rd->offset; + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->size_bytes = var_slot_size(insn->rd); + ir->is_volatile = var_is_volatile_object(insn->rd); REGS[dest].polluted = 0; } @@ -2484,6 +3396,11 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) REGS[src0].var = NULL; } + /* An assignment creates storage-backed state. It must not retain a + * literal's compile-time cache for later reads. + */ + insn->rd->is_const = false; + break; case OP_read: src0 = prepare_operand(bb, insn->rs1, -1); @@ -2492,6 +3409,9 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->src0 = src0; ir->src1 = insn->sz; ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + ir->is_volatile = insn_reads_volatile(insn); + set_ptr_flags(ir, insn); break; case OP_write: if (insn->rs2->is_func) { @@ -2515,12 +3435,19 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_write); ir->src0 = src0; ir->src1 = src1; + ir->src1_hi = vreg_get_phys_hi(insn->rs2); ir->dest = insn->sz; + set_ptr_flags(ir, insn); } break; case OP_branch: src0 = prepare_operand(bb, insn->rs1, -1); + /* Read the high register now: spill_live_out_keep() below forgets + * the mapping of a condition that does not outlive the block. + */ + src1 = vreg_get_phys_hi(insn->rs1); + /* REGS[src0].var had been set to NULL, but the actual content is * still holded in the register. * @@ -2533,11 +3460,19 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_branch); ir->src0 = src0; + ir->src0_hi = src1; /* An LP64 backend tests an address over its full width and an int * over its low word only. */ ir->src0_is_pointer = is_address_like(insn->rs1); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + + /* A long long is tested whole too, which AArch64 selects by the + * width recorded here. + */ + ir->size_bytes = + insn->rs1->ptr_level ? PTR_SIZE : insn->rs1->type->size; ir->then_bb = bb->then_; ir->else_bb = bb->else_; break; @@ -2545,6 +3480,24 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) extend_liveness(bb, insn, insn->rs1, insn->sz); if (!is_pushing_args) { + /* RV32 has four ABI argument registers. A call with two aligned + * 64-bit arguments fills all four, leaving no virtual register + * in which OP_indirect may materialize its target. Stage it + * before assigning that file. The RV32 backend keeps this value + * in s2, an otherwise-unused callee-saved register. + */ + if (ELF_MACHINE == 0xf3) { + insn_t *call = insn; + + while (call && call->opcode == OP_push) + call = call->next; + if (call && call->opcode == OP_indirect) { + src0 = prepare_operand(bb, call->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_load_func); + ir->src0 = src0; + riscv_indirect_target_staged = true; + } + } spill_alive(bb, insn); is_pushing_args = true; } @@ -2553,15 +3506,38 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) handle_abi = true; } - if (args_on_stack && args >= MAX_ARGS_IN_REG) + args = call_arg_starts[call_arg_next++]; + if (args_on_stack && args >= MAX_ARGS_IN_REG) { + args += abi_arg_words(insn->rs1); break; + } + + if (abi_arg_words(insn->rs1) == 2) { + if (abi_arg_is_split(args, insn->rs1)) { + /* Nothing is placed after it in a register, so the last one + * takes the low word without being claimed. + */ + load_low_word(bb, insn->rs1, args); + abi_args_staged |= 1 << args; + args += 2; + break; + } + prepare_pair_argument(bb, insn->rs1, args); + abi_args_staged |= 3 << args; + args += 2; + break; + } src0 = prepare_operand(bb, insn->rs1, -1); ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = src0; - ir->dest = args++; + ir->dest = args; + ir->is_unsigned = is_unsigned_scalar(insn->rs1); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); REGS[ir->dest].var = insn->rs1; REGS[ir->dest].polluted = 0; + abi_args_staged |= 1 << args; + args++; break; case OP_call: callee_func = find_func(insn->str); @@ -2576,6 +3552,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->func_name = insn->str; is_pushing_args = false; + abi_args_staged = 0; args = 0; handle_abi = false; @@ -2586,15 +3563,20 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) if (!args) spill_alive(bb, insn); - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_load_func); - ir->src0 = src0; + if (!riscv_indirect_target_staged) { + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_load_func); + ir->src0 = src0; + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + } bb_add_ph2_ir(bb, OP_indirect); is_pushing_args = false; + abi_args_staged = 0; args = 0; handle_abi = false; + riscv_indirect_target_staged = false; clobber_caller_saved(); break; @@ -2602,7 +3584,13 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) dest = prepare_dest(bb, insn, insn->rd, -1, -1); ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = 0; + if (var_needs_register_pair(insn->rd)) + ir->src0_hi = 1; ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->size_bytes = + insn->rd->ptr_level ? PTR_SIZE : insn->rd->type->size; break; case OP_return: if (insn->rs1) @@ -2612,6 +3600,11 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_return); ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(insn->rs1); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + if (insn->rs1) + ir->size_bytes = + insn->rs1->ptr_level ? PTR_SIZE : insn->rs1->type->size; break; case OP_add: case OP_sub: @@ -2635,16 +3628,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) src1 = prepare_operand(bb, insn->rs2, src0); dest = prepare_dest(bb, insn, insn->rd, src0, src1); ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = dest; - - /* Record whether the result is an address, and which operand it - * came from. On LP64 an int-typed result has to wrap at 32 bits, - * while a pointer must keep all 64, and pointer arithmetic has to - * widen the int index beside the address. - */ - set_ptr_flags(ir, insn); + fill_binary_ph2_ir(ir, insn, src0, src1, dest); break; case OP_negate: case OP_bit_not: @@ -2652,13 +3636,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) src0 = prepare_operand(bb, insn->rs1, -1); dest = prepare_dest(bb, insn, insn->rd, src0, -1); ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = src0; - ir->dest = dest; - - /* As for OP_branch: the width of the test follows the operand, not - * the result. - */ - ir->src0_is_pointer = is_address_like(insn->rs1); + fill_unary_ph2_ir(ir, insn, src0, dest); break; case OP_trunc: case OP_sign_ext: @@ -2668,13 +3646,23 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, insn->opcode); ir->src1 = insn->sz; ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(insn->rs1); ir->dest = dest; + ir->dest_hi = vreg_get_phys_hi(insn->rd); + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + ir->size_bytes = + insn->rd->ptr_level ? PTR_SIZE : insn->rd->type->size; + if (insn->opcode != OP_trunc) + narrow_conversion_result(bb, insn, dest); break; default: printf("Unknown opcode\n"); fflush(stdout); /* see fatal() */ abort(); } + + store_addressed_def(bb, insn); } if (bb->next) { @@ -2720,23 +3708,39 @@ void reg_alloc(void) continue; func->visited++; + fatal_function_context = func->return_def.var_name; if (!strcmp(func->return_def.var_name, "main")) MAIN_BB = func->bbs; - for (int i = 0; i < REG_CNT; i++) + for (int i = 0; i < REG_CNT; i++) { REGS[i].var = NULL; + pair_high_owner[i] = NULL; + } slot_var_count = 0; + reserve_outgoing_args(func); coalesce_phi_slots(func); pin_registers(func); - /* set arguments available */ - int args_in_reg = func->num_params < MAX_ARGS_IN_REG ? func->num_params - : MAX_ARGS_IN_REG; - for (int i = 0; i < args_in_reg; i++) { - REGS[i].var = var_subscript0(&func->param_defs[i]); - REGS[i].polluted = 1; + /* Set arguments available. The register file is indexed by ABI word, + * whereas param_defs is indexed by source parameter. + */ + int args_in_reg = 0; + if (func->returns_aggregate) { + REGS[0].var = var_subscript0(&func->sret_def); + REGS[0].polluted = 1; + } + for (int i = 0; i < func->num_params; i++) { + int word = abi_param_start(func, i); + + if (word + abi_arg_words(&func->param_defs[i]) > MAX_ARGS_IN_REG) + break; + REGS[word].var = var_subscript0(&func->param_defs[i]); + REGS[word].polluted = 1; + if (abi_arg_words(&func->param_defs[i]) == 2) + vreg_map_pair_to_phys(REGS[word].var, word, word + 1); + args_in_reg++; } /* Move a pinned parameter out of the argument register it arrived in @@ -2747,17 +3751,18 @@ void reg_alloc(void) for (int i = 0; i < args_in_reg; i++) { var_t *param = var_subscript0(&func->param_defs[i]); int home = pinned_reg_of(param); + int word = abi_param_start(func, i); if (home < 0) continue; ph2_ir_t *mv = bb_add_ph2_ir(func->bbs, OP_assign); - mv->src0 = i; + mv->src0 = word; mv->dest = home; REGS[home].var = param; REGS[home].polluted = 0; - REGS[i].var = NULL; - REGS[i].polluted = 0; + REGS[word].var = NULL; + REGS[word].polluted = 0; } /* variadic function implementation */ @@ -2765,8 +3770,27 @@ void reg_alloc(void) /* When encountering a variadic function, allocate space for all * arguments on the local stack to ensure their addresses are * contiguous. + * + * On a 32-bit target a long long argument takes two words, after a + * padding word when it would start at an odd one, so a call can + * pass twice as many words as it has arguments: save that many. + * va_arg rounds its cursor to an even word by rounding the address + * to eight bytes, so the first word must sit at an address that is + * a multiple of eight too. */ - for (int i = 0; i < MAX_PARAMS; i++) { + int va_words = PTR_SIZE < 8 ? 2 * MAX_PARAMS : MAX_PARAMS; + + /* A stack word is fetched through the register after the argument + * registers. RISC-V passes as many words in registers as the file + * holds, so it borrows the last argument register instead, which + * has been saved by then. + */ + int scratch = + MAX_ARGS_IN_REG < REG_CNT ? MAX_ARGS_IN_REG : REG_CNT - 1; + + if (PTR_SIZE < 8) + func->stack_size = ALIGN_UP(func->stack_size, 8); + for (int i = 0; i < va_words; i++) { ph2_ir_t *ir; int src0 = i; @@ -2775,14 +3799,26 @@ void reg_alloc(void) * arguments. */ ir = bb_add_ph2_ir(func->bbs, OP_load); - ir->dest = MAX_ARGS_IN_REG; + ir->dest = scratch; ir->src0 = (i - MAX_ARGS_IN_REG) * PTR_SIZE; ir->ofs_based_on_stack_top = true; - src0 = MAX_ARGS_IN_REG; + src0 = scratch; } - if (i < args_in_reg) { - var_t *param = var_subscript0(&func->param_defs[i]); + /* Slot i saves ABI word i. The parameter homed there is found + * by its ABI position, since a hidden aggregate-return pointer + * takes word 0 and shifts every named parameter up. + */ + int param_idx = -1; + for (int q = 0; q < func->num_params; q++) { + if (abi_param_start(func, q) == i) { + param_idx = q; + break; + } + } + + if (param_idx >= 0) { + var_t *param = var_subscript0(&func->param_defs[param_idx]); param->offset = func->stack_size; param->space_is_allocated = true; } @@ -2791,6 +3827,40 @@ void reg_alloc(void) ir->src0 = src0; ir->src1 = func->stack_size; func->stack_size += PTR_SIZE; + if (param_idx >= 0) { + var_t *param = var_subscript0(&func->param_defs[param_idx]); + + if (param_idx + 1 == func->num_params && + param->is_aggregate_param) { + int footprint = ALIGN_UP(param->type->size, PTR_SIZE); + + /* The first word was just saved above. Reserve the + * remaining words before spilling unnamed arguments. + */ + func->stack_size += footprint - PTR_SIZE; + } + } + } + + /* A parameter that arrived in the borrowed register now lives only + * in its save slot. + */ + if (va_words > MAX_ARGS_IN_REG && scratch < MAX_ARGS_IN_REG) { + var_t *owner = pair_high_owner[scratch]; + + if (owner) { + int low = vreg_get_phys(owner); + + if (low >= 0 && low < REG_CNT && REGS[low].var == owner) { + REGS[low].var = NULL; + REGS[low].polluted = 0; + } + vreg_clear_phys(owner); + } else if (REGS[scratch].var) { + vreg_clear_phys(REGS[scratch].var); + REGS[scratch].var = NULL; + REGS[scratch].polluted = 0; + } } } else { /* If the number of function arguments is fixed, the extra arguments @@ -2826,14 +3896,53 @@ void reg_alloc(void) * cfg_flatten(), the operand's offset will be recalculated by * adding the function's stack size. */ - for (int i = MAX_ARGS_IN_REG; i < func->num_params; i++) { + for (int i = 0; i < func->num_params; i++) { var_t *param = var_subscript0(&func->param_defs[i]); - param->offset = (i - MAX_ARGS_IN_REG) * PTR_SIZE; + int word = abi_param_start(func, i); + + if (abi_arg_is_split(word, param)) { + /* A long long split between a7 and the first stack slot + * gets a slot of its own: store the low word, then fetch + * the high word through a7, which holds nothing else. + */ + ph2_ir_t *ir; + + if (!param->space_is_allocated) + alloc_var_slot(func, param); + ir = bb_add_ph2_ir(func->bbs, OP_store); + ir->src0 = word; + ir->src1 = param->offset; + ir->size_bytes = 4; + ir = bb_add_ph2_ir(func->bbs, OP_load); + ir->dest = word; + ir->src0 = 0; + ir->ofs_based_on_stack_top = true; + ir->size_bytes = 4; + ir = bb_add_ph2_ir(func->bbs, OP_store); + ir->src0 = word; + ir->src1 = param->offset + 4; + ir->size_bytes = 4; + continue; + } + if (word < MAX_ARGS_IN_REG) + continue; + param->offset = (word - MAX_ARGS_IN_REG) * PTR_SIZE; param->space_is_allocated = true; param->ofs_based_on_stack_top = true; } } + /* A volatile parameter lives in its slot, so write it there on entry. + * Its register then matches the slot, and every read reloads it. + */ + for (int i = 0; i < args_in_reg; i++) { + var_t *param = var_subscript0(&func->param_defs[i]); + int word = abi_param_start(func, i); + + if (var_is_volatile_object(param) && REGS[word].var == param) + store_var(func->bbs, param, word); + } + for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { reg_alloc_bb(func, bb); } @@ -2861,6 +3970,15 @@ void reg_alloc(void) } } +/* Name the high register of a 32-bit target's register pair after its low one, + * as "%x2:%x3", when @reg is one. + */ +void dump_pair_high(int reg) +{ + if (reg >= 0) + printf(":%%x%c", reg + 48); +} + void dump_ph2_ir(void) { for (int i = 0; i < ph2_ir_idx; i++) { @@ -2914,16 +4032,24 @@ void dump_ph2_ir(void) printf("\tret %%x%c", rs1); break; case OP_load: - printf("\tload %%x%c, %d(sp)", rd, ph2_ir->src0); + printf("\tload %%x%c", rd); + dump_pair_high(ph2_ir->dest_hi); + printf(", %d(sp)", ph2_ir->src0); break; case OP_store: - printf("\tstore %%x%c, %d(sp)", rs1, ph2_ir->src1); + printf("\tstore %%x%c", rs1); + dump_pair_high(ph2_ir->src0_hi); + printf(", %d(sp)", ph2_ir->src1); break; case OP_global_load: - printf("\tload %%x%c, %d(gp)", rd, ph2_ir->src0); + printf("\tload %%x%c", rd); + dump_pair_high(ph2_ir->dest_hi); + printf(", %d(gp)", ph2_ir->src0); break; case OP_global_store: - printf("\tstore %%x%c, %d(gp)", rs1, ph2_ir->src1); + printf("\tstore %%x%c", rs1); + dump_pair_high(ph2_ir->src0_hi); + printf(", %d(gp)", ph2_ir->src1); break; case OP_read: printf("\t%%x%c = (%%x%c)", rd, rs1); diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index d394d10e..a41a61d4 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -88,6 +88,12 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 8; else elf_offset += 4; + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src1 < -2048 || ph2_ir->src1 > 2047) + elf_offset += 8; + else + elf_offset += 4; + } return; case OP_address_of: case OP_global_address_of: @@ -98,70 +104,140 @@ void update_elf_offset(ph2_ir_t *ph2_ir) return; case OP_assign: elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->dest_hi != ph2_ir->src0_hi) + elf_offset += 4; + /* Exchanging the two registers of a pair takes a third move. */ + if (ph2_ir->dest_hi >= 0 && ph2_ir->dest == ph2_ir->src0_hi && + ph2_ir->dest_hi == ph2_ir->src0) + elf_offset += 4; return; case OP_load: case OP_global_load: if (ph2_ir->src0 < -2048 || ph2_ir->src0 > 2047) - elf_offset += 16; + if (ph2_ir->dest_hi >= 0) + elf_offset += 20; + else + elf_offset += 16; + else if (ph2_ir->dest_hi >= 0) + elf_offset += 8; else elf_offset += 4; return; case OP_store: case OP_global_store: if (ph2_ir->src1 < -2048 || ph2_ir->src1 > 2047) - elf_offset += 16; + if (ph2_ir->src0_hi >= 0) + elf_offset += 20; + else + elf_offset += 16; + else if (ph2_ir->src0_hi >= 0) + elf_offset += 8; else elf_offset += 4; return; case OP_read: + elf_offset += ph2_ir->dest_hi >= 0 ? 8 : 4; + return; case OP_write: - case OP_jump: + elf_offset += ph2_ir->src1_hi >= 0 ? 8 : 4; + return; case OP_call: + /* A call through the PLT may be farther than a JAL reaches. */ + elf_offset += dynlink && !find_func(ph2_ir->func_name)->bbs ? 8 : 4; + return; + case OP_jump: case OP_load_func: case OP_indirect: - case OP_add: - case OP_sub: case OP_lshift: case OP_rshift: + elf_offset += ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 ? 52 : 4; + return; case OP_gt: case OP_lt: + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 28 : 4; + return; + case OP_negate: + elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) + elf_offset += 12; + return; + case OP_add: + case OP_sub: + elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + elf_offset += 12; + return; case OP_bit_and: case OP_bit_or: case OP_bit_xor: - case OP_negate: + elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + elf_offset += 4; + return; case OP_bit_not: elf_offset += 4; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_mul: - if (hard_mul_div) - elf_offset += 4; + if (hard_mul_div) { + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + elf_offset += 24; + else + elf_offset += 4; + } else if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + elf_offset += 100; else elf_offset += 52; return; case OP_div: case OP_mod: - if (hard_mul_div) + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + + /* Four copied input words, a quotient pair, a remainder pair, and a + * fixed 64-round restoring loop. The modulo form copies the final + * remainder out after the loop. Signed operands add magnitude + * conversion and a sign-restoration sequence. + */ + elf_offset += ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned + ? (ph2_ir->op == OP_mod ? 168 : 160) + : 244; + else if (hard_mul_div) elf_offset += 4; else - elf_offset += 108; + elf_offset += 116; return; case OP_load_data_address: case OP_load_rodata_address: - case OP_neq: case OP_geq: case OP_leq: + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 32 : 8; + return; case OP_log_not: - elf_offset += 8; + elf_offset += ph2_ir->src0_hi >= 0 ? 12 : 8; return; case OP_address_of_func: - case OP_eq: elf_offset += 12; return; + case OP_eq: + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 20 : 12; + return; + case OP_neq: + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 16 : 8; + return; case OP_branch: - elf_offset += 20; + elf_offset += ph2_ir->src0_hi >= 0 ? 24 : 20; return; case OP_return: elf_offset += 24; + if (ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_trunc: /* A byte and a short each need a shift pair to keep the sign. */ @@ -173,6 +249,18 @@ void update_elf_offset(ph2_ir_t *ph2_ir) case OP_sign_ext: { /* Decode source size from upper 16 bits */ int source_size = (ph2_ir->src1 >> 16) & 0xFFFF; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi < 0) { + /* Mirror the emitter: a zero-extended byte is one ANDI, a + * sign-extended byte and either halfword take a shift pair, and a + * word is one move; the high word adds one more instruction. The + * byte cases were swapped, so a (long long) cast of an unsigned + * char moved every later branch target by four bytes. + */ + bool zero_ext = ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer; + elf_offset += + source_size == 2 || (source_size == 1 && !zero_ext) ? 12 : 8; + return; + } if (source_size == 2) elf_offset += 8; /* short extension: 2 instructions */ else @@ -180,7 +268,11 @@ void update_elf_offset(ph2_ir_t *ph2_ir) return; } case OP_cast: - elf_offset += 4; + elf_offset += + ph2_ir->dest_hi >= 0 && + (ph2_ir->src0_hi < 0 || ph2_ir->dest_hi != ph2_ir->src0_hi) + ? 8 + : 4; return; default: fatal("Unknown opcode"); @@ -192,13 +284,14 @@ void cfg_flatten(void) func_t *func; if (dynlink) { - /* When using dynamic linking, 20 instructions are generated at the + /* When using dynamic linking, 25 instructions are generated at the * program entry point to perform the following operations: * - prepare arguments and call __libc_start_main() * - preserve a0 ('argc'), a1 ('argv') and sp. - * - allocate a global stack and jump to global init function. + * - allocate and clear a global stack, then jump to global init + * function. */ - elf_offset = 80; + elf_offset = 100; } else { /* Under static linking, "__syscall" must be generated to allow the * program to invoke system calls. @@ -295,6 +388,9 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) int rd = ph2_ir->dest + 10; int rs1 = ph2_ir->src0 + 10; int rs2 = ph2_ir->src1 + 10; + int rd_hi = ph2_ir->dest_hi + 10; + int rs1_hi = ph2_ir->src0_hi + 10; + int rs2_hi = ph2_ir->src1_hi + 10; int ofs; /* Prepare the variables to reuse the same code for the instruction sequence @@ -307,6 +403,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) switch (ph2_ir->op) { case OP_define: + fatal_function_context = ph2_ir->func_name; ofs = ALIGN_UP(ph2_ir->src0 + 4, RV32_ALIGNMENT); emit(__sw(__ra, __sp, -4)); emit(__lui(__t0, rv_hi(ofs))); @@ -320,6 +417,13 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } else emit(__addi(rd, __zero, ph2_ir->src0)); + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src1 < -2048 || ph2_ir->src1 > 2047) { + emit(__lui(rd_hi, rv_hi(ph2_ir->src1))); + emit(__addi(rd_hi, rd_hi, rv_lo(ph2_ir->src1))); + } else + emit(__addi(rd_hi, __zero, ph2_ir->src1)); + } return; case OP_address_of: case OP_global_address_of: @@ -332,41 +436,131 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__addi(rd, interm, ph2_ir->src0)); return; case OP_assign: + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && rd == rs1_hi) { + /* A pair moved one register over, as a slot round trip collapsed + * into a move can leave it: the low destination is the high source, + * so move the high word first, through t0 when the two registers + * trade places. + */ + if (rd_hi == rs1) { + emit(__addi(__t0, rs1, 0)); + emit(__addi(rd, rs1_hi, 0)); + emit(__addi(rd_hi, __t0, 0)); + } else { + emit(__addi(rd_hi, rs1_hi, 0)); + emit(__addi(rd, rs1, 0)); + } + return; + } emit(__addi(rd, rs1, 0)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->dest_hi != ph2_ir->src0_hi) + emit(__addi(rd_hi, rs1_hi, 0)); return; case OP_load: case OP_global_load: interm = ph2_ir->op == OP_load ? __sp : __gp; + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src0 < -2048 || ph2_ir->src0 > 2047) { + emit(__lui(__t0, rv_hi(ph2_ir->src0))); + emit(__addi(__t0, __t0, rv_lo(ph2_ir->src0))); + emit(__add(__t0, interm, __t0)); + emit(__lw(rd, __t0, 0)); + emit(__lw(rd_hi, __t0, 4)); + } else { + emit(__lw(rd, interm, ph2_ir->src0)); + emit(__lw(rd_hi, interm, ph2_ir->src0 + 4)); + } + return; + } if (ph2_ir->src0 < -2048 || ph2_ir->src0 > 2047) { emit(__lui(__t0, rv_hi(ph2_ir->src0))); emit(__addi(__t0, __t0, rv_lo(ph2_ir->src0))); emit(__add(__t0, interm, __t0)); - emit(__lw(rd, __t0, 0)); - } else + if (ph2_ir->size_bytes == 1) + emit(ph2_ir->is_unsigned ? __lbu(rd, __t0, 0) + : __lb(rd, __t0, 0)); + else if (ph2_ir->size_bytes == 2) + emit(ph2_ir->is_unsigned ? __lhu(rd, __t0, 0) + : __lh(rd, __t0, 0)); + else + emit(__lw(rd, __t0, 0)); + } else if (ph2_ir->size_bytes == 1) + emit(ph2_ir->is_unsigned ? __lbu(rd, interm, ph2_ir->src0) + : __lb(rd, interm, ph2_ir->src0)); + else if (ph2_ir->size_bytes == 2) + emit(ph2_ir->is_unsigned ? __lhu(rd, interm, ph2_ir->src0) + : __lh(rd, interm, ph2_ir->src0)); + else emit(__lw(rd, interm, ph2_ir->src0)); return; case OP_store: case OP_global_store: interm = ph2_ir->op == OP_store ? __sp : __gp; + if (ph2_ir->src0_hi >= 0) { + if (ph2_ir->src1 < -2048 || ph2_ir->src1 > 2047) { + emit(__lui(__t0, rv_hi(ph2_ir->src1))); + emit(__addi(__t0, __t0, rv_lo(ph2_ir->src1))); + emit(__add(__t0, interm, __t0)); + emit(__sw(rs1, __t0, 0)); + emit(__sw(rs1_hi, __t0, 4)); + } else { + emit(__sw(rs1, interm, ph2_ir->src1)); + emit(__sw(rs1_hi, interm, ph2_ir->src1 + 4)); + } + return; + } if (ph2_ir->src1 < -2048 || ph2_ir->src1 > 2047) { emit(__lui(__t0, rv_hi(ph2_ir->src1))); emit(__addi(__t0, __t0, rv_lo(ph2_ir->src1))); emit(__add(__t0, interm, __t0)); - emit(__sw(rs1, __t0, 0)); - } else + if (ph2_ir->size_bytes == 1) + emit(__sb(rs1, __t0, 0)); + else if (ph2_ir->size_bytes == 2) + emit(__sh(rs1, __t0, 0)); + else + emit(__sw(rs1, __t0, 0)); + } else if (ph2_ir->size_bytes == 1) + emit(__sb(rs1, interm, ph2_ir->src1)); + else if (ph2_ir->size_bytes == 2) + emit(__sh(rs1, interm, ph2_ir->src1)); + else emit(__sw(rs1, interm, ph2_ir->src1)); return; case OP_read: + if (ph2_ir->dest_hi >= 0) { + /* A pair read through a pointer. When the low destination is the + * address register itself, fetch the high word first so the address + * survives until both words are loaded. + */ + if (ph2_ir->src1 != 8) + fatal("unsupported RISC-V pair load width"); + if (rd == rs1) { + emit(__lw(rd_hi, rs1, 4)); + emit(__lw(rd, rs1, 0)); + } else { + emit(__lw(rd, rs1, 0)); + emit(__lw(rd_hi, rs1, 4)); + } + return; + } if (ph2_ir->src1 == 1) - emit(__lb(rd, rs1, 0)); + emit(ph2_ir->is_unsigned ? __lbu(rd, rs1, 0) : __lb(rd, rs1, 0)); else if (ph2_ir->src1 == 2) - emit(__lh(rd, rs1, 0)); + emit(ph2_ir->is_unsigned ? __lhu(rd, rs1, 0) : __lh(rd, rs1, 0)); else if (ph2_ir->src1 == 4) emit(__lw(rd, rs1, 0)); else - abort(); + fatal("unsupported RISC-V load width"); return; case OP_write: + if (ph2_ir->src1_hi >= 0) { + if (ph2_ir->dest != 8) + fatal("unsupported RISC-V pair store width"); + emit(__sw(rs2, rs1, 0)); + emit(__sw(rs2_hi, rs1, 4)); + return; + } if (ph2_ir->dest == 1) emit(__sb(rs2, rs1, 0)); else if (ph2_ir->dest == 2) @@ -374,12 +568,18 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) else if (ph2_ir->dest == 4) emit(__sw(rs2, rs1, 0)); else - abort(); + fatal("unsupported RISC-V store width"); return; case OP_branch: ofs = elf_code_start + ph2_ir->then_bb->elf_offset; emit(__lui(__t0, rv_hi(ofs))); emit(__addi(__t0, __t0, rv_lo(ofs))); + + /* A pair is true when either word is nonzero. */ + if (ph2_ir->src0_hi >= 0) { + emit(__or(__t1, rs1, rs1_hi)); + rs1 = __t1; + } emit(__beq(rs1, __zero, 8)); emit(__jalr(__zero, __t0, 0)); emit(__jal(__zero, ph2_ir->else_bb->elf_offset - elf_code->size)); @@ -392,8 +592,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) if (func->bbs) ofs = func->bbs->elf_offset - elf_code->size; else if (dynlink) { + /* The PLT follows the code and read-only data, so a large image + * puts it beyond the 1 MiB a JAL reaches. Call it PC-relative + * through AUIPC and JALR, which reach anywhere. + */ ofs = (dynamic_sections.elf_plt_start + func->plt_offset) - (elf_code_start + elf_code->size); + emit(__auipc(__ra, rv_hi(ofs))); + emit(__jalr(__ra, __ra, rv_lo(ofs))); + return; } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); fflush(stdout); /* see fatal() */ @@ -425,16 +632,18 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__sw(__t0, rs1, 0)); return; case OP_load_func: - emit(__addi(__t0, rs1, 0)); + emit(__addi(__s2, rs1, 0)); return; case OP_indirect: - emit(__jalr(__ra, __t0, 0)); + emit(__jalr(__ra, __s2, 0)); return; case OP_return: if (ph2_ir->src0 == -1) emit(__addi(__zero, __zero, 0)); else emit(__addi(__a0, rs1, 0)); + if (ph2_ir->src0_hi >= 0) + emit(__addi(__a1, rs1_hi, 0)); ofs = ALIGN_UP(ph2_ir->src1 + 4, RV32_ALIGNMENT); emit(__lui(__t0, rv_hi(ofs))); emit(__addi(__t0, __t0, rv_lo(ofs))); @@ -444,14 +653,69 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_add: emit(__add(rd, rs1, rs2)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + emit(__sltu(__t0, rd, rs1)); + emit(__add(rd_hi, rs1_hi, rs2_hi)); + emit(__add(rd_hi, rd_hi, __t0)); + } return; case OP_sub: emit(__sub(rd, rs1, rs2)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + emit(__sltu(__t0, rs1, rs2)); + emit(__sub(rd_hi, rs1_hi, rs2_hi)); + emit(__sub(rd_hi, rd_hi, __t0)); + } return; case OP_mul: - if (hard_mul_div) - emit(__mul(rd, rs1, rs2)); - else { + if (hard_mul_div) { + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + emit(__mul(rd, rs1, rs2)); + emit(__mulhu(rd_hi, rs1, rs2)); + emit(__mul(__t0, rs1, rs2_hi)); + emit(__mul(__t1, rs1_hi, rs2)); + emit(__add(rd_hi, rd_hi, __t0)); + emit(__add(rd_hi, rd_hi, __t1)); + } else + emit(__mul(rd, rs1, rs2)); + } else { + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + /* Multiply two 64-bit pairs modulo 2^64 without the M + * extension. Keep the product in the destination pair, so t0-t3 + * can hold the shifting multiplicand and multiplier; the loop + * ends once both multiplier words are zero. + */ + emit(__addi(rd, __zero, 0)); + emit(__addi(rd_hi, __zero, 0)); + emit(__addi(__t0, rs1, 0)); + emit(__addi(__t1, rs1_hi, 0)); + emit(__addi(__t2, rs2, 0)); + emit(__addi(__t3, rs2_hi, 0)); + emit(__andi(__t4, __t2, 1)); + emit(__sub(__t4, __zero, __t4)); + emit(__and(__t5, __t0, __t4)); + emit(__and(__t4, __t1, __t4)); + emit(__add(__t5, rd, __t5)); + emit(__sltu(__t6, __t5, rd)); + emit(__add(rd_hi, rd_hi, __t4)); + emit(__add(rd_hi, rd_hi, __t6)); + emit(__addi(rd, __t5, 0)); + emit(__srli(__t6, __t0, 31)); + emit(__slli(__t0, __t0, 1)); + emit(__slli(__t1, __t1, 1)); + emit(__or(__t1, __t1, __t6)); + emit(__slli(__t6, __t3, 31)); + emit(__srli(__t2, __t2, 1)); + emit(__or(__t2, __t2, __t6)); + emit(__srli(__t3, __t3, 1)); + emit(__or(__t6, __t2, __t3)); + emit(__bne(__t6, __zero, -72)); + return; + } emit(__addi(__t0, __zero, 0)); emit(__addi(__t1, __zero, 0)); emit(__addi(__t3, rs1, 0)); @@ -469,11 +733,118 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_div: case OP_mod: + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) { + bool is_unsigned = + ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned; + + /* 64-bit restoring division. t0:t1 is the remainder, t2:t3 is the + * shifting dividend, t4:t5 is the divisor, and rd:rd_hi accumulates + * the quotient. Two stack words hold the incoming dividend bit and + * the iteration counter while t6 carries the remainder/borrow; this + * avoids borrowing operand registers, because an SSA destination + * may legally coalesce with a dying operand pair. Signed inputs + * reserve two additional words for their sign masks and are + * converted to magnitudes before entering the same loop. + */ + emit(__addi(__sp, __sp, is_unsigned ? -8 : -16)); + emit(__addi(__t0, __zero, 0)); + emit(__addi(__t1, __zero, 0)); + if (is_unsigned) { + emit(__addi(__t2, rs1, 0)); + emit(__addi(__t3, rs1_hi, 0)); + emit(__addi(__t4, rs2, 0)); + emit(__addi(__t5, rs2_hi, 0)); + } else { + /* x ^ sign + -sign is abs(x), including INT64_MIN's + * representable unsigned magnitude. + */ + emit(__srai(__t6, rs1_hi, 31)); + emit(__sw(__t6, __sp, 8)); + emit(__xor(__t2, rs1, __t6)); + emit(__xor(__t3, rs1_hi, __t6)); + emit(__sub(__t6, __zero, __t6)); + emit(__add(__t2, __t2, __t6)); + emit(__sltu(__t6, __t2, __t6)); + emit(__add(__t3, __t3, __t6)); + emit(__srai(__t6, rs2_hi, 31)); + emit(__sw(__t6, __sp, 12)); + emit(__xor(__t4, rs2, __t6)); + emit(__xor(__t5, rs2_hi, __t6)); + emit(__sub(__t6, __zero, __t6)); + emit(__add(__t4, __t4, __t6)); + emit(__sltu(__t6, __t4, __t6)); + emit(__add(__t5, __t5, __t6)); + } + emit(__addi(rd, __zero, 0)); + emit(__addi(rd_hi, __zero, 0)); + emit(__addi(__t6, __zero, 64)); + emit(__sw(__t6, __sp, 4)); + + /* Bring down one dividend bit, shift the quotient, then subtract if + * the two-word remainder is at least the divisor. + */ + emit(__srli(__t6, __t3, 31)); + emit(__sw(__t6, __sp, 0)); + emit(__srli(__t6, __t0, 31)); + emit(__slli(__t0, __t0, 1)); + emit(__slli(__t1, __t1, 1)); + emit(__or(__t1, __t1, __t6)); + emit(__lw(__t6, __sp, 0)); + emit(__or(__t0, __t0, __t6)); + emit(__srli(__t6, __t2, 31)); + emit(__slli(__t2, __t2, 1)); + emit(__slli(__t3, __t3, 1)); + emit(__or(__t3, __t3, __t6)); + emit(__srli(__t6, rd, 31)); + emit(__slli(rd, rd, 1)); + emit(__slli(rd_hi, rd_hi, 1)); + emit(__or(rd_hi, rd_hi, __t6)); + emit(__bltu(__t1, __t5, 32)); + emit(__bltu(__t5, __t1, 8)); + emit(__bltu(__t0, __t4, 24)); + emit(__sltu(__t6, __t0, __t4)); + emit(__sub(__t0, __t0, __t4)); + emit(__sub(__t1, __t1, __t5)); + emit(__sub(__t1, __t1, __t6)); + emit(__addi(rd, rd, 1)); + emit(__lw(__t6, __sp, 4)); + emit(__addi(__t6, __t6, -1)); + emit(__sw(__t6, __sp, 4)); + emit(__bne(__t6, __zero, -108)); + + if (ph2_ir->op == OP_mod) { + emit(__addi(rd, __t0, 0)); + emit(__addi(rd_hi, __t1, 0)); + } + if (!is_unsigned) { + /* A quotient is negative for unlike operand signs; a remainder + * follows the dividend's sign. + */ + emit(__lw(__t6, __sp, 8)); + if (ph2_ir->op == OP_div) { + emit(__lw(__t0, __sp, 12)); + emit(__xor(__t6, __t6, __t0)); + } + emit(__xor(rd, rd, __t6)); + emit(__xor(rd_hi, rd_hi, __t6)); + emit(__sub(__t6, __zero, __t6)); + emit(__add(rd, rd, __t6)); + emit(__sltu(__t6, rd, __t6)); + emit(__add(rd_hi, rd_hi, __t6)); + } + emit(__addi(__sp, __sp, is_unsigned ? 8 : 16)); + return; + } if (hard_mul_div) { if (ph2_ir->op == OP_div) - emit(__div(rd, rs1, rs2)); + emit(ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned + ? __divu(rd, rs1, rs2) + : __div(rd, rs1, rs2)); else - emit(__mod(rd, rs1, rs2)); + emit(ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned + ? __modu(rd, rs1, rs2) + : __mod(rd, rs1, rs2)); return; } interm = __t0; @@ -486,26 +857,48 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) interm = __t2; divisor_mask = __zero; } - /* Obtain absolute values of the dividend and divisor */ + + /* Obtain absolute values of the dividend and divisor. Unsigned values + * already are magnitudes; keep the same instruction count as the signed + * path because the fixed branch displacements below depend on it. + */ emit(__addi(__t2, rs1, 0)); emit(__addi(__t3, rs2, 0)); - emit(__srai(__t0, __t2, 31)); - emit(__add(__t2, __t2, __t0)); - emit(__xor(__t2, __t2, __t0)); - emit(__srai(__t1, __t3, 31)); - emit(__add(__t3, __t3, __t1)); - emit(__xor(__t3, __t3, __t1)); - emit(__xor(__t5, __t0, divisor_mask)); + if (ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) { + emit(__addi(__t0, __zero, 0)); + emit(__addi(__t2, __t2, 0)); + emit(__addi(__t2, __t2, 0)); + emit(__addi(__t1, __zero, 0)); + emit(__addi(__t3, __t3, 0)); + emit(__addi(__t3, __t3, 0)); + emit(__addi(__t5, __zero, 0)); + } else { + emit(__srai(__t0, __t2, 31)); + emit(__add(__t2, __t2, __t0)); + emit(__xor(__t2, __t2, __t0)); + emit(__srai(__t1, __t3, 31)); + emit(__add(__t3, __t3, __t1)); + emit(__xor(__t3, __t3, __t1)); + emit(__xor(__t5, __t0, divisor_mask)); + } /* Unsigned integer division */ emit(__addi(__t0, __zero, 0)); emit(__addi(__t1, __zero, 1)); - emit(__beq(__t3, __zero, 52)); - emit(__beq(__t2, __zero, 48)); - emit(__beq(__t2, __t3, 20)); - emit(__bltu(__t2, __t3, 16)); + emit(__beq(__t3, __zero, 60)); + emit(__beq(__t2, __zero, 56)); + emit(__beq(__t2, __t3, 28)); + emit(__bltu(__t2, __t3, 24)); + + /* Stop scaling before the divisor's high bit would wrap to zero. + * Without this guard a high-bit unsigned dividend can loop forever + * after the next left shift turns both the divisor and quotient bit + * marker into zero. + */ + emit(__slt(__t4, __t3, __zero)); + emit(__bne(__t4, __zero, 16)); emit(__slli(__t3, __t3, 1)); emit(__slli(__t1, __t1, 1)); - emit(__jal(__zero, -16)); + emit(__jal(__zero, -24)); emit(__bltu(__t2, __t3, 12)); emit(__sub(__t2, __t2, __t3)); emit(__add(__t0, __t0, __t1)); @@ -518,56 +911,168 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__sub(rd, __zero, rd)); return; case OP_lshift: + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) { + emit(__addi(__t0, __zero, 32)); + emit(__bltu(rs2, __t0, 20)); + emit(__sub(__t1, rs2, __t0)); + emit(__sll(rd_hi, rs1, __t1)); + emit(__addi(rd, __zero, 0)); + emit(__jal(__zero, 32)); + + /* The bits crossing into the high word are the low word shifted + * right by 32 - n. RV32 takes a shift amount modulo 32, so a shift + * by zero did not shift at all and ORed the whole low word in. + * Shift by 1 and then by 31 - n, which stays in range. + */ + emit(__addi(__t1, __zero, 31)); + emit(__srli(__t2, rs1, 1)); + emit(__sll(rd, rs1, rs2)); + emit(__sll(rd_hi, rs1_hi, rs2)); + emit(__sub(__t1, __t1, rs2)); + emit(__srl(__t2, __t2, __t1)); + emit(__or(rd_hi, rd_hi, __t2)); + return; + } emit(__sll(rd, rs1, rs2)); return; case OP_rshift: - emit(__sra(rd, rs1, rs2)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) { + bool is_unsigned = ph2_ir->src0_is_unsigned; + + emit(__addi(__t0, __zero, 32)); + emit(__bltu(rs2, __t0, 20)); + emit(__sub(__t1, rs2, __t0)); + emit(is_unsigned ? __srl(rd, rs1_hi, __t1) + : __sra(rd, rs1_hi, __t1)); + emit(is_unsigned ? __addi(rd_hi, __zero, 0) + : __srai(rd_hi, rs1_hi, 31)); + emit(__jal(__zero, 32)); + + /* As for a left shift, bring the high word's bits across with two + * in-range shifts, by 1 and by 31 - n. + */ + emit(__addi(__t1, __zero, 31)); + emit(__slli(__t2, rs1_hi, 1)); + + /* The low word takes the high word's bits in, never its own sign: + * the sign lives in the high word. + */ + emit(__srl(rd, rs1, rs2)); + emit(__sub(__t1, __t1, rs2)); + emit(__sll(__t2, __t2, __t1)); + emit(__or(rd, rd, __t2)); + emit(is_unsigned ? __srl(rd_hi, rs1_hi, rs2) + : __sra(rd_hi, rs1_hi, rs2)); + return; + } + emit(ph2_ir->src0_is_unsigned ? __srl(rd, rs1, rs2) + : __sra(rd, rs1, rs2)); return; case OP_eq: + if (ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { + emit(__xor(__t0, rs1, rs2)); + emit(__xor(rd, rs1_hi, rs2_hi)); + emit(__or(rd, rd, __t0)); + emit(__sltu(rd, __zero, rd)); + emit(__xori(rd, rd, 1)); + return; + } emit(__sub(rd, rs1, rs2)); emit(__sltu(rd, __zero, rd)); emit(__xori(rd, rd, 1)); return; case OP_neq: + if (ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { + emit(__xor(__t0, rs1, rs2)); + emit(__xor(rd, rs1_hi, rs2_hi)); + emit(__or(rd, rd, __t0)); + emit(__sltu(rd, __zero, rd)); + return; + } emit(__sub(rd, rs1, rs2)); emit(__sltu(rd, __zero, rd)); return; case OP_gt: - emit(__slt(rd, rs2, rs1)); - return; - case OP_geq: - emit(__slt(rd, rs1, rs2)); - emit(__xori(rd, rd, 1)); - return; case OP_lt: - emit(__slt(rd, rs1, rs2)); - return; + case OP_geq: case OP_leq: - emit(__slt(rd, rs2, rs1)); - emit(__xori(rd, rd, 1)); + if (ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { + bool unsigned_cmp = + ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned; + bool reverse = ph2_ir->op == OP_gt || ph2_ir->op == OP_leq; + bool invert = ph2_ir->op == OP_geq || ph2_ir->op == OP_leq; + + emit(unsigned_cmp ? __sltu(__t0, reverse ? rs2_hi : rs1_hi, + reverse ? rs1_hi : rs2_hi) + : __slt(__t0, reverse ? rs2_hi : rs1_hi, + reverse ? rs1_hi : rs2_hi)); + emit(__xor(__t1, rs1_hi, rs2_hi)); + emit(__sltu(__t1, __zero, __t1)); + emit(__xori(__t1, __t1, 1)); + emit(__sltu(rd, reverse ? rs2 : rs1, reverse ? rs1 : rs2)); + emit(__and(rd, rd, __t1)); + emit(__or(rd, rd, __t0)); + if (invert) + emit(__xori(rd, rd, 1)); + return; + } + if (ph2_ir->op == OP_gt || ph2_ir->op == OP_leq) + emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + ? __sltu(rd, rs2, rs1) + : __slt(rd, rs2, rs1)); + else + emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + ? __sltu(rd, rs1, rs2) + : __slt(rd, rs1, rs2)); + if (ph2_ir->op == OP_geq || ph2_ir->op == OP_leq) + emit(__xori(rd, rd, 1)); return; case OP_negate: emit(__sub(rd, __zero, rs1)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) { + emit(__sltu(__t0, __zero, rs1)); + emit(__sub(rd_hi, __zero, rs1_hi)); + emit(__sub(rd_hi, rd_hi, __t0)); + } return; case OP_bit_not: emit(__xori(rd, rs1, -1)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0) + emit(__xori(rd_hi, rs1_hi, -1)); return; case OP_bit_and: emit(__and(rd, rs1, rs2)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + emit(__and(rd_hi, rs1_hi, rs2_hi)); return; case OP_bit_or: emit(__or(rd, rs1, rs2)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + emit(__or(rd_hi, rs1_hi, rs2_hi)); return; case OP_bit_xor: emit(__xor(rd, rs1, rs2)); + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && + ph2_ir->src1_hi >= 0) + emit(__xor(rd_hi, rs1_hi, rs2_hi)); return; case OP_log_not: + if (ph2_ir->src0_hi >= 0) { + emit(__or(__t0, rs1, rs1_hi)); + rs1 = __t0; + } emit(__sltu(rd, __zero, rs1)); emit(__xori(rd, rd, 1)); return; case OP_trunc: - /* Narrowing keeps the sign: there are no unsigned types. */ - if (ph2_ir->src1 == 1) { + if (ph2_ir->is_unsigned && (ph2_ir->src1 == 1 || ph2_ir->src1 == 2)) { + int shift = ph2_ir->src1 == 1 ? 24 : 16; + + emit(__slli(rd, rs1, shift)); + emit(__srli(rd, rd, shift)); + } else if (ph2_ir->src1 == 1) { emit(__slli(rd, rs1, 24)); emit(__srai(rd, rd, 24)); } else if (ph2_ir->src1 == 2) { @@ -581,14 +1086,37 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; case OP_sign_ext: { - /* Decode size information: Lower 16 bits: target size Upper 16 bits: - * source size - */ - int target_size = ph2_ir->src1 & 0xFFFF; + /* The upper 16 bits of src1 hold the source size. */ int source_size = (ph2_ir->src1 >> 16) & 0xFFFF; - /* Calculate shift amount based on target and source sizes */ - int shift_amount = (target_size - source_size) * 8; + if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi < 0) { + if (source_size == 1) { + if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) + emit(__andi(rd, rs1, 0xFF)); + else { + emit(__slli(rd, rs1, 24)); + emit(__srai(rd, rd, 24)); + } + } else if (source_size == 2) { + emit(__slli(rd, rs1, 16)); + emit(ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer + ? __srli(rd, rd, 16) + : __srai(rd, rd, 16)); + } else + emit(__addi(rd, rs1, 0)); + if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) + emit(__addi(rd_hi, __zero, 0)); + else + emit(__srai(rd_hi, rd, 31)); + return; + } + + /* The shifts act on the whole 32-bit register, so the source's top bit + * has to reach bit 31 whatever the target width: "unsigned short x = c" + * with c a negative signed char shifted by only 8, leaving 0x94 for + * -108. + */ + int shift_amount = (4 - source_size) * 8; if (source_size == 2) { /* Sign extend from short to word (16-bit shift) For 16-bit sign @@ -596,18 +1124,31 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) * for RISC-V immediate field */ emit(__slli(rd, rs1, shift_amount)); - emit(__srai(rd, rd, shift_amount)); + emit(ph2_ir->src0_is_unsigned ? __srli(rd, rd, shift_amount) + : __srai(rd, rd, shift_amount)); } else { /* Fallback for other sizes */ emit(__andi(rd, rs1, 0xFF)); emit(__slli(rd, rd, shift_amount)); - emit(__srai(rd, rd, shift_amount)); + emit(ph2_ir->src0_is_unsigned ? __srli(rd, rd, shift_amount) + : __srai(rd, rd, shift_amount)); } return; } case OP_cast: - /* Generic cast operation - for now, just move the value */ + /* Widen scalars into the paired representation, or preserve both halves + * when casting between two direct wide scalar types. + */ emit(__addi(rd, rs1, 0)); + if (ph2_ir->dest_hi >= 0) { + if (ph2_ir->src0_hi >= 0) { + if (ph2_ir->dest_hi != ph2_ir->src0_hi) + emit(__addi(rd_hi, rs1_hi, 0)); + } else if (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) + emit(__addi(rd_hi, __zero, 0)); + else + emit(__srai(rd_hi, rs1, 31)); + } return; default: fatal("Unknown opcode"); @@ -651,8 +1192,8 @@ void code_generate(void) * __libc_start_main(main_wrapper, argc, argv, NULL, * NULL, NULL, stack_end) */ - emit(__lui(__a0, rv_hi(elf_code_start + 36))); - emit(__addi(__a0, __a0, rv_lo(elf_code_start + 36))); + emit(__lui(__a0, rv_hi(elf_code_start + 40))); + emit(__addi(__a0, __a0, rv_lo(elf_code_start + 40))); emit(__lw(__a1, __sp, 0)); emit(__addi(__a2, __sp, 4)); emit(__addi(__a3, __zero, 0)); @@ -660,10 +1201,13 @@ void code_generate(void) emit(__addi(__a5, __zero, 0)); emit(__addi(__a6, __sp, 0)); - /* Call __libc_start_main() via PLT[1] */ + /* Call __libc_start_main() via PLT[1], PC-relative through AUIPC and + * JALR: the PLT may lie beyond the 1 MiB a JAL reaches. + */ ofs = (dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE) - (elf_code_start + elf_code->size); - emit(__jal(__ra, ofs)); + emit(__auipc(__ra, rv_hi(ofs))); + emit(__jalr(__ra, __ra, rv_lo(ofs))); /* The main wrapper is located here under the dynamic linking mode * @@ -700,6 +1244,20 @@ void code_generate(void) emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sub(__sp, __sp, __t0)); emit(__addi(__gp, __sp, 0)); /* Set up global pointer */ + + /* A static image runs first on untouched stack memory, which reads as zero, + * but a dynamic one follows the loader and glibc's startup over the same + * memory. Clear the global stack so a global with no initializer starts at + * zero there too, storing from the top word down; t0 still holds 'ofs', a + * nonzero multiple of four. No libc call is involved, so this holds with + * --no-libc as well. + */ + if (dynlink) { + emit(__addi(__t0, __t0, -4)); + emit(__add(__t1, __gp, __t0)); + emit(__sw(__zero, __t1, 0)); + emit(__bne(__t0, __zero, -12)); + } emit(__jal(__ra, GLOBAL_FUNC->bbs->elf_offset - elf_code->size)); if (!dynlink) { diff --git a/src/riscv.c b/src/riscv.c index 0baf159f..82cd9d1c 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -31,6 +31,8 @@ typedef enum { rv_lb = 3 /* 0b11 */, rv_lh = 4099 /* 0b11 + (1 << 12) */, rv_lw = 8195 /* 0b11 + (2 << 12) */, + rv_lbu = 16387 /* 0b11 + (4 << 12) */, + rv_lhu = 20483 /* 0b11 + (5 << 12) */, rv_sb = 35 /* 0b0100011 */, rv_sh = 4131 /* 0b0100011 + (1 << 12) */, rv_sw = 8227 /* 0b0100011 + (2 << 12) */, @@ -48,8 +50,11 @@ typedef enum { rv_ebreak = 1048691 /* 0b1110011 + (1 << 20) */, /* m */ rv_mul = 33554483 /* 0b0110011 + (1 << 25) */, + rv_mulhu = 33566771 /* rv_mul + (3 << 12): unsigned high half */, rv_div = 33570867 /* 0b0110011 + (1 << 25) + (4 << 12) */, - rv_mod = 33579059 /* 0b0110011 + (1 << 25) + (6 << 12) */ + rv_divu = 33574963 /* 0b0110011 + (1 << 25) + (5 << 12) */, + rv_mod = 33579059 /* 0b0110011 + (1 << 25) + (6 << 12) */, + rv_modu = 33583155 /* 0b0110011 + (1 << 25) + (7 << 12) */ } rv_op; /* registers */ @@ -191,6 +196,11 @@ int __sub(rv_reg rd, rv_reg rs1, rv_reg rs2) return rv_encode_R(rv_sub, rd, rs1, rs2); } +int __mulhu(rv_reg rd, rv_reg rs1, rv_reg rs2) +{ + return rv_encode_R(rv_mulhu, rd, rs1, rs2); +} + int __xor(rv_reg rd, rv_reg rs1, rv_reg rs2) { return rv_encode_R(rv_xor, rd, rs1, rs2); @@ -271,6 +281,16 @@ int __lh(rv_reg rd, rv_reg rs1, int imm) return rv_encode_I(rv_lh, rd, rs1, imm); } +int __lbu(rv_reg rd, rv_reg rs1, int imm) +{ + return rv_encode_I(rv_lbu, rd, rs1, imm); +} + +int __lhu(rv_reg rd, rv_reg rs1, int imm) +{ + return rv_encode_I(rv_lhu, rd, rs1, imm); +} + int __lw(rv_reg rd, rv_reg rs1, int imm) { return rv_encode_I(rv_lw, rd, rs1, imm); @@ -342,7 +362,17 @@ int __div(rv_reg rd, rv_reg rs1, rv_reg rs2) return rv_encode_R(rv_div, rd, rs1, rs2); } +int __divu(rv_reg rd, rv_reg rs1, rv_reg rs2) +{ + return rv_encode_R(rv_divu, rd, rs1, rs2); +} + int __mod(rv_reg rd, rv_reg rs1, rv_reg rs2) { return rv_encode_R(rv_mod, rd, rs1, rs2); } + +int __modu(rv_reg rd, rv_reg rs1, rv_reg rs2) +{ + return rv_encode_R(rv_modu, rd, rs1, rs2); +} diff --git a/src/ssa.c b/src/ssa.c index eb6c9860..fc32239c 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -22,6 +22,84 @@ /* Dead store elimination window size */ #define OVERWRITE_WINDOW 3 +/* Whether @var lives in memory that other code can reach without naming it: a + * global, or a variable whose address is taken. SSA gives such a variable one + * name, and a write to it is observable however unused it looks. + */ +bool var_in_memory(const var_t *var) +{ + return var->is_global || var->address_taken; +} + +/* Whether @insn accesses a volatile object, which C99 6.7.3p6 counts as a side + * effect whether or not anything uses the value: it names such an object as an + * operand, or reads through an address mark_volatile_addresses() traced to one. + * Taking an object's address does not access it. + */ +bool insn_reads_volatile(const insn_t *insn) +{ + if (insn->opcode == OP_address_of || insn->opcode == OP_allocat) + return false; + if (insn->opcode == OP_read && insn->rs1 && insn->rs1->is_volatile) + return !insn->rd || !insn->rd->is_assignment_reload; + return var_is_volatile_object(insn->rs1) || + var_is_volatile_object(insn->rs2) || + var_is_volatile_object(insn->rs3); +} + +/* Whether @var holds an address into a volatile object: a volatile pointer or + * array, or a temporary already traced to one. + */ +bool var_is_volatile_address(const var_t *var) +{ + if (!var || !var->is_volatile) + return false; + return var->ptr_level || var->array_size || + (var->type && var->type->ptr_level) || var->var_name[0] == '.'; +} + +/* Mark every temporary that holds an address into a volatile object. + * + * A declaration says its object is volatile, but a member or an element of the + * object, or whatever a pointer to volatile designates, is read through an + * address the parser computed into a temporary, and nothing on the temporary + * says what it points into. Follow each address from the object or pointer it + * was derived from, so that the read at the end of the chain is recognizable as + * the side effect it is. Only addresses are followed: an int computed from a + * volatile int is an ordinary value. + */ +void mark_volatile_addresses(func_t *func) +{ + for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { + var_t *rd = insn->rd; + bool from_volatile = false; + + if (!rd || rd->is_volatile || rd->var_name[0] != '.') + continue; + + switch (insn->opcode) { + case OP_address_of: + from_volatile = insn->rs1 && insn->rs1->is_volatile; + break; + case OP_add: + case OP_sub: + from_volatile = var_is_volatile_address(insn->rs1) || + var_is_volatile_address(insn->rs2); + break; + case OP_assign: + case OP_cast: + from_volatile = var_is_volatile_address(insn->rs1); + break; + default: + break; + } + if (from_volatile) + rd->is_volatile = true; + } + } +} + void var_list_ensure_capacity(var_list_t *list, int min_capacity) { if (list->capacity >= min_capacity) @@ -89,7 +167,7 @@ void bb_forward_traversal(bb_traversal_args_t *args) basic_block_t *bb = args->bb; func_t *func = args->func; - bb->visited++; + bb->visited = func->visited; if (args->preorder_cb) args->preorder_cb(func, bb); @@ -125,7 +203,7 @@ void bb_backward_traversal(bb_traversal_args_t *args) basic_block_t *bb = args->bb; func_t *func = args->func; - bb->visited++; + bb->visited = func->visited; if (args->preorder_cb) args->preorder_cb(func, bb); @@ -727,6 +805,9 @@ bool var_check_in_scope(const var_t *var, block_t *block) return true; } + if (func->returns_aggregate && &func->sret_def == var) + return true; + return false; } @@ -800,7 +881,10 @@ void solve_phi_insertion(void) if (df == func->exit) continue; - if (var->is_global) + /* Neither a global nor an address-taken variable is + * renamed, so there is no version for a phi to choose. + */ + if (var_in_memory(var)) continue; if (insert_phi_insn(df, var)) { @@ -850,6 +934,7 @@ var_t *new_const_var(block_t *scope, int val) var->var_name = gen_name(); var->is_const = true; var->init_val = val; + var->init_val_hi = 0; return var; } bool is_dominate(const basic_block_t *pred, basic_block_t *succ); @@ -873,6 +958,22 @@ void rename_stack_push(var_t *base, int sub) r->stack[r->stack_idx++] = sub; } +/* The one name an address-taken variable keeps for its whole life. + * + * A store through a pointer changes such a variable without any instruction + * naming it, and a read through one sees whatever its last assignment by name + * wrote. Separate SSA versions would give each assignment a place of its own, + * so neither kind of access would reach the other. Like a global, it keeps one + * var_t for every definition and use: the entry version for a parameter, under + * which the allocator finds its incoming register, and the declaration itself + * for anything else. + */ +var_t *addressed_name(var_t *v) +{ + var_t *entry = var_subscript0(v->base); + return entry ? entry : v->base; +} + void new_name(block_t *block, var_t **var) { var_t *v = *var; @@ -880,6 +981,10 @@ void new_name(block_t *block, var_t **var) v->base = v; if (v->is_global) return; + if (v->address_taken) { + var[0] = addressed_name(v); + return; + } rename_t *r = var_rename(v->base); int i = r->counter++; @@ -887,6 +992,12 @@ void new_name(block_t *block, var_t **var) var_t *vd = require_var(block); memcpy(vd, *var, sizeof(var_t)); var_reset_subscripts(vd); /* the copy shares nothing with its base */ + /* A fresh SSA definition has no physical-register residence. In particular, + * it must not inherit either half of a future wide pair from the version it + * was copied from. + */ + vd->phys_reg = -1; + vd->phys_reg_hi = -1; vd->base = *var; vd->subscript = i; var_add_subscript(v, vd); @@ -916,13 +1027,17 @@ void rename_var(var_t **var) v->base = v; if (v->is_global) return; + if (v->address_taken) { + var[0] = addressed_name(v); + return; + } var[0] = get_stack_top_subscript_var(*var); } void pop_name(var_t *var) { - if (var->is_global) + if (var_in_memory(var)) return; /* Pop unconditionally, creating the state if the variable has none: the @@ -1006,14 +1121,22 @@ void solve_phi_params(void) if (!func->bbs) continue; - for (int i = 0; i < func->num_params; i++) { + /* The aggregate return destination is an ABI parameter even though it + * is not a source-language parameter. Build its entry SSA version + * before the visible parameters. + */ + for (int i = -1; i < func->num_params; i++) { /* FIXME: Direct argument renaming in SSA construction phase may * interfere with later optimization passes */ var_t *var = require_var(func->bbs->scope); - var_t *base = &func->param_defs[i]; + if (i < 0 && !func->returns_aggregate) + continue; + var_t *base = i < 0 ? &func->sret_def : &func->param_defs[i]; memcpy(var, base, sizeof(var_t)); var_reset_subscripts(var); /* the copy shares nothing with base */ + var->phys_reg = -1; + var->phys_reg_hi = -1; var->base = base; var->subscript = 0; @@ -1224,6 +1347,12 @@ insn_t *new_insn(opcode_t op, var_t *rd, var_t *rs1, var_t *rs2) */ bool insn_is_speculatable(const insn_t *insn) { + /* Running an arm unconditionally would read a volatile object on a path + * that never evaluates it, and that read is a side effect. + */ + if (insn_reads_volatile(insn)) + return false; + switch (insn->opcode) { case OP_add: case OP_sub: @@ -1290,7 +1419,7 @@ basic_block_t *if_arm_chain(basic_block_t *arm, * store is a side effect however plain the arithmetic producing it * looks. */ - if (insn->rd && (insn->rd->is_global || insn->rd->address_taken)) + if (insn->rd && var_in_memory(insn->rd)) return NULL; *count = *count + 1; } @@ -1346,6 +1475,16 @@ bool if_arm_reads_other(basic_block_t **a, return false; } +/* Whether @var is an integer scalar a 32-bit target keeps in a register pair. + * The widening pass below, the allocator and the backends all ask this. + */ +bool var_needs_register_pair(const var_t *var) +{ + return PTR_SIZE < 8 && var && !var->ptr_level && !var->is_func && + !var->array_size && var->type && var->type->size == 8 && + !is_record_type(var->type); +} + /* Flatten "if (c) x = A; else x = B;" into both computations and a select. * * A branch the hardware cannot predict costs far more than the arm it skips: @@ -1385,7 +1524,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) insn_t *e_phi = if_chain_phi(e_chain, e_len); if (!t_phi || !e_phi || t_phi->rd != e_phi->rd) return false; - if (t_phi->rd->is_global || t_phi->rd->address_taken) + if (var_in_memory(t_phi->rd)) return false; /* Both arms handing over the same value makes the select pointless. */ if (t_phi->rs1 == e_phi->rs1) @@ -1588,8 +1727,12 @@ bool thread_const_branch(func_t *func, basic_block_t *join) break; } - basic_block_t *target = - def->rs1->init_val ? join->then_ : join->else_; + /* A wide constant with a zero low word, such as 1ULL << 32, is + * still true. + */ + basic_block_t *target = def->rs1->init_val || def->rs1->init_val_hi + ? join->then_ + : join->else_; bb_remove_insn(pred, def); bb_disconnect(pred, join); @@ -1689,6 +1832,7 @@ var_t *inline_lookup(var_t *var, block_t *scope) copy->ptr_level = var->ptr_level; copy->is_const = var->is_const; copy->init_val = var->init_val; + copy->init_val_hi = var->init_val_hi; inline_from[inline_map_n] = var; inline_to[inline_map_n] = copy; inline_map_n++; @@ -1805,7 +1949,7 @@ bool func_is_inlinable(func_t *func) */ if (!insn_is_speculatable(insn)) return false; - if (insn->rd && (insn->rd->is_global || insn->rd->address_taken)) + if (insn->rd && var_in_memory(insn->rd)) return false; /* Writing a parameter would mean the copy assigns to the caller's @@ -2323,7 +2467,7 @@ bool sr_collect_chain(func_t *func, var_t *var, insn_t **chain, int *len) if (!base || base->def_cnt != 1) return false; - if (base->is_global || base->address_taken) + if (var_in_memory(base)) return false; chain[*len] = def; @@ -2496,7 +2640,7 @@ void sr_sweep_dead_consts(func_t *func) next = insn->next; if (insn->opcode != OP_load_constant || !insn->rd) continue; - if (insn->rd->is_global || insn->rd->address_taken) + if (var_in_memory(insn->rd)) continue; if (var_read_by(func, insn->rd, NULL, 0, true)) continue; @@ -2609,6 +2753,194 @@ void sr_loop(func_t *func, basic_block_t *header, basic_block_t *latch) sr_sweep_dead_consts(func); } +/* Whether @var is an integer scalar narrower than a register pair. */ +bool ssa_is_narrow_scalar(const var_t *var) +{ + return var && !var->ptr_level && !var->is_func && !var->array_size && + var->type && var->type->size < 8 && !var->type->ptr_level && + !is_record_type(var->type); +} + +/* The int type of @var's signedness, which a narrow operand converts to. */ +type_t *int_type_of(const var_t *var) +{ + return var->type->is_unsigned ? TY_uint : TY_int; +} + +/* Insert before @at a copy of the narrow @var widened to @type, sign- or + * zero-extended by @var's own signedness as the usual arithmetic conversions + * require, and return it. + */ +var_t *widen_before(basic_block_t *bb, insn_t *at, var_t *var, type_t *type) +{ + var_t *wide = require_var(bb->scope); + insn_t *ext; + + wide->var_name = gen_name(); + wide->type = type; + ext = new_insn(OP_sign_ext, wide, var, NULL); + ext->sz = (var->type->size << 16) | 8; + bb_insert_after(bb, at->prev, ext); + return wide; +} + +/* Make @insn define a new variable of @type and convert that into its original + * destination straight after, with @conv (an extension or a truncation). + */ +void convert_result_after(basic_block_t *bb, + insn_t *insn, + type_t *type, + opcode_t conv) +{ + var_t *result = insn->rd; + var_t *inner = require_var(bb->scope); + insn_t *copy; + + inner->var_name = gen_name(); + inner->type = type; + insn->rd = inner; + copy = new_insn(conv, result, inner, NULL); + copy->sz = conv == OP_trunc ? result->type->size : (4 << 16) | 8; + bb_insert_after(bb, insn, copy); +} + +/* Give every register-pair operation operands of its own width. + * + * The 32-bit backends lower a wide operation only when all of its values are + * pairs; with a narrow operand they fall back to the one-word form, which keeps + * no high word. The parser does not convert every operand it builds, and an + * LP64 target never noticed, since its registers are already wide: a long long + * compared with an int temporary, or stored from one, lost the high word on Arm + * and RISC-V. Convert those operands here, after the optimizer, so that the + * allocator and the backends see one width per operation. Where the low word of + * a narrow result is exact -- addition, subtraction, multiplication, bitwise + * operations and left shifts -- a narrow destination is left as it is. + */ +void widen_pair_operands(void) +{ + if (PTR_SIZE >= 8) + return; + + for (func_t *func = FUNC_LIST.head; func; func = func->next) { + if (!func->bbs) + continue; + for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { + var_t *rd = insn->rd; + bool wide1 = var_needs_register_pair(insn->rs1); + bool wide2 = var_needs_register_pair(insn->rs2); + + switch (insn->opcode) { + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + if (wide1 && ssa_is_narrow_scalar(insn->rs2)) + insn->rs2 = + widen_before(bb, insn, insn->rs2, insn->rs1->type); + else if (wide2 && ssa_is_narrow_scalar(insn->rs1)) + insn->rs1 = + widen_before(bb, insn, insn->rs1, insn->rs2->type); + break; + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + if (var_needs_register_pair(rd)) { + if (!wide1 && !wide2) { + /* Both operands narrow: the operation is an int one + * whose result is then converted. + */ + if (ssa_is_narrow_scalar(insn->rs1) && + ssa_is_narrow_scalar(insn->rs2)) + convert_result_after( + bb, insn, + insn->rs1->type->is_unsigned || + insn->rs2->type->is_unsigned + ? TY_uint + : TY_int, + OP_sign_ext); + break; + } + if (!wide1 && ssa_is_narrow_scalar(insn->rs1)) + insn->rs1 = + widen_before(bb, insn, insn->rs1, rd->type); + if (!wide2 && ssa_is_narrow_scalar(insn->rs2)) + insn->rs2 = + widen_before(bb, insn, insn->rs2, rd->type); + break; + } + + /* A quotient's low word depends on the high words. */ + if ((insn->opcode == OP_div || insn->opcode == OP_mod) && + ssa_is_narrow_scalar(rd) && (wide1 || wide2)) { + type_t *type = + wide1 ? insn->rs1->type : insn->rs2->type; + + if (!wide1 && ssa_is_narrow_scalar(insn->rs1)) + insn->rs1 = widen_before(bb, insn, insn->rs1, type); + if (!wide2 && ssa_is_narrow_scalar(insn->rs2)) + insn->rs2 = widen_before(bb, insn, insn->rs2, type); + convert_result_after(bb, insn, type, OP_trunc); + } + break; + case OP_lshift: + case OP_rshift: + /* The result has the left operand's type. */ + if (var_needs_register_pair(rd) && + ssa_is_narrow_scalar(insn->rs1)) + convert_result_after(bb, insn, int_type_of(insn->rs1), + OP_sign_ext); + else if (insn->opcode == OP_rshift && wide1 && + ssa_is_narrow_scalar(rd)) + convert_result_after(bb, insn, insn->rs1->type, + OP_trunc); + break; + case OP_negate: + case OP_bit_not: + if (var_needs_register_pair(rd) && + ssa_is_narrow_scalar(insn->rs1)) + convert_result_after(bb, insn, int_type_of(insn->rs1), + OP_sign_ext); + break; + case OP_assign: + case OP_unwound_phi: + if (var_needs_register_pair(rd) && + ssa_is_narrow_scalar(insn->rs1)) + insn->rs1 = widen_before(bb, insn, insn->rs1, rd->type); + break; + case OP_read: + if (var_needs_register_pair(rd) && insn->sz == 4) + convert_result_after(bb, insn, int_type_of(rd), + OP_sign_ext); + break; + case OP_write: + if (insn->sz == 8 && ssa_is_narrow_scalar(insn->rs2)) + insn->rs2 = widen_before(bb, insn, insn->rs2, + insn->rs2->type->is_unsigned + ? TY_ulong_long + : TY_long_long); + break; + case OP_return: + if (var_needs_register_pair(&func->return_def) && + ssa_is_narrow_scalar(insn->rs1)) + insn->rs1 = widen_before(bb, insn, insn->rs1, + func->return_def.type); + break; + default: + break; + } + } + } + } +} + void strength_reduce(void) { for (func_t *func = FUNC_LIST.head; func; func = func->next) { @@ -3198,6 +3530,36 @@ void prune_unused_funcs(void) for (func_t *func = FUNC_LIST.head; func;) { func_t *next = func->next; + /* A reachable call to a function with no body is resolved by the + * dynamic linker, which cannot see a static function and is absent from + * a static link. Either way the input is incomplete, so report it here + * once rather than leave every backend its own abort. + */ + if (func->is_used && !func->bbs && (func->is_static || !dynlink)) { + char message[MAX_LINE_LEN]; + + snprintf(message, MAX_LINE_LEN, "undefined %sfunction '%s'", + func->is_static ? "static " : "", + func->return_def.var_name); + error_at(message, NULL); + } + + /* The dynamic linker path leaves a bodiless function reachable, but one + * returning a record would be called through a function pointer with + * shecc's destination-pointer convention rather than the platform's. + * Direct calls are rejected when parsing ends; a reachable one here has + * had its address taken. + */ + if (func->is_used && !func->bbs && func->returns_aggregate) { + char message[MAX_LINE_LEN]; + + snprintf(message, MAX_LINE_LEN, + "aggregate-return function '%s' has its address taken " + "but is not defined", + func->return_def.var_name); + error_at(message, NULL); + } + func->next = NULL; if (func->bbs && !func->is_used) { func = next; @@ -3281,44 +3643,40 @@ bool cse(insn_t *insn, const basic_block_t *bb) if (base->is_global || idx->is_global) return false; - /* Look for identical add+read patterns */ - for (use_chain_t *user = base->users_head; user; user = user->next) { - insn_t *i = user->insn; - if (i == prev) - continue; - if (i->opcode != OP_add) - continue; - if (!i->next) - continue; - if (i->next->opcode != OP_read) - continue; - if (i->rs1 != base || i->rs2 != idx) + /* Each read of a volatile object is an access of its own. */ + if (insn->rs1->is_volatile) + return false; + + /* A read is a copy of memory, not a function of its operands: the same + * address holds something else once anything has written there. So only + * a repeat further down this block qualifies, and the search stops at + * the first instruction that could have changed memory -- a write + * through a pointer, a call, or an assignment to a variable that lives + * in memory -- since nothing says where that write landed. + * + * The repeat's own addition is left in place. Its result can have other + * users, such as the write "p[i] += 1" performs to the same element, + * and removing it left that write with no address at all. Dead code + * elimination takes it once nothing needs it. + */ + for (insn_t *i = insn->next; i; i = i->next) { + if (i->opcode == OP_write || i->opcode == OP_call || + i->opcode == OP_indirect) + break; + if (i->rd && var_in_memory(i->rd)) + break; + if (i->opcode != OP_add || i->rs1 != base || i->rs2 != idx) continue; - /* Check dominance */ - basic_block_t *i_bb = i->belong_to; - bool check_dom = false; - for (;; i_bb = i_bb->idom) { - if (i_bb == bb) { - check_dom = true; - break; - } - if (i_bb == i_bb->idom) - break; - } - if (!check_dom) + insn_t *read = i->next; + if (!read || read->opcode != OP_read || read->rs1 != i->rd || + read->sz != insn->sz) continue; /* Replace with assignment */ - i->next->opcode = OP_assign; - i->next->rs1 = def; - if (i->prev) { - i->prev->next = i->next; - i->next->prev = i->prev; - } else { - i->belong_to->insn_list.head = i->next; - i->next->prev = NULL; - } + read->opcode = OP_assign; + read->rs1 = def; + read->rs2 = NULL; } return true; } @@ -3334,6 +3692,14 @@ bool cse(insn_t *insn, const basic_block_t *bb) if (insn->rs1->is_global || insn->rs2->is_global) return false; + /* Nor with an address-taken one. It is named by one var_t for every value + * it takes, so matching operands say nothing about matching values: a store + * through a pointer, or by a callee handed the address, can change it + * between the two instructions without any instruction naming it. + */ + if (insn->rs1->address_taken || insn->rs2->address_taken) + return false; + /* Look for identical binary operations */ for (insn_t *other = bb->insn_list.head; other; other = other->next) { if (other == insn) @@ -3380,7 +3746,7 @@ bool mark_const(insn_t *insn) * materialise the initialiser instead of reading the slot, which is how * "int a = 0; f(&a); int b = a;" left b holding zero. */ - if (insn->rd && insn->rd->address_taken) + if (insn->rd && (insn->rd->address_taken || insn->rd->is_volatile)) return false; if (insn->opcode == OP_load_constant) { @@ -3399,9 +3765,11 @@ bool mark_const(insn_t *insn) /* Copying from such a variable is no better: the value read is whatever the * pointer last wrote, not the constant the source was assigned. */ - if (insn->rs1->address_taken) + if (insn->rs1->address_taken || insn->rs1->is_volatile) return false; if (!insn->rs1->is_const) { + if (insn->rs1->init_val_hi) + return false; if (!insn->prev) return false; if (insn->prev->opcode != OP_load_constant) @@ -3413,20 +3781,56 @@ bool mark_const(insn_t *insn) insn->opcode = OP_load_constant; insn->rd->is_const = true; insn->rd->init_val = insn->rs1->init_val; + insn->rd->init_val_hi = insn->rs1->init_val_hi; insn->rs1 = NULL; return true; } +bool ssa_is_unsigned_scalar(const var_t *var) +{ + return var && !var->ptr_level && var->type && var->type->is_unsigned; +} + +/* Whether @insn computes a scalar wider than int, a long long on any target. */ +bool ssa_is_wide_operation(const insn_t *insn) +{ + var_t *ops[2]; + + ops[0] = insn->rd; + ops[1] = insn->rs1; + for (int i = 0; i < 2; i++) { + if (ops[i] && !ops[i]->ptr_level && ops[i]->type && + !ops[i]->type->ptr_level && ops[i]->type->size > TY_int->size) + return true; + } + return false; +} + bool eval_const_arithmetic(insn_t *insn) { if (!insn->rs1) return false; if (!insn->rs1->is_const) return false; + if (insn->rs1->init_val_hi) + return false; if (!insn->rs2) return false; if (!insn->rs2->is_const) return false; + if (insn->rs2->init_val_hi) + return false; + + /* Constant folding predates unsigned arithmetic and evaluates every + * operation as signed int. Leave unsigned and wider-than-int expressions to + * target lowering until this pass gains width-aware evaluation. + */ + if (ssa_is_unsigned_scalar(insn->rs1) || + ssa_is_unsigned_scalar(insn->rs2) || ssa_is_unsigned_scalar(insn->rd) || + (insn->rs1->type && insn->rs1->type->size > TY_int->size) || + (insn->rs2->type && insn->rs2->type->size > TY_int->size) || + (insn->rd && insn->rd->type && insn->rd->type->size > TY_int->size)) + return false; int res; int l = insn->rs1->init_val, r = insn->rs2->init_val; @@ -3451,10 +3855,18 @@ bool eval_const_arithmetic(insn_t *insn) return false; /* avoid modulo by zero */ res = l % r; break; + + /* As in the parser's folder, an out-of-range count keeps the shift for the + * target rather than asking the host for an undefined result. + */ case OP_lshift: - res = l << r; + if (r < 0 || r >= 32) + return false; + res = (int) ((unsigned int) l << r); break; case OP_rshift: + if (r < 0 || r >= 32) + return false; res = l >> r; break; case OP_bit_and: @@ -3508,6 +3920,11 @@ bool eval_const_unary(insn_t *insn) return false; if (!insn->rs1->is_const) return false; + if (insn->rs1->init_val_hi) + return false; + if ((insn->rs1->type && insn->rs1->type->size > TY_int->size) || + (insn->rd && insn->rd->type && insn->rd->type->size > TY_int->size)) + return false; int res; int val = insn->rs1->init_val; @@ -3649,10 +4066,23 @@ int dce_init_mark(insn_t *insn, insn_t *work_list[], int work_list_idx) } break; default: + /* So is an access to a volatile object, whether or not anything uses + * the value it produces. + */ + if (insn_reads_volatile(insn)) { + insn->useful = true; + insn->belong_to->useful = true; + dce_init_push(work_list, work_list_idx, &mark_num, insn); + break; + } + if (!insn->rd) break; - /* if the instruction affects a global value, set "useful" */ - if (insn->rd->is_global && !insn->useful) { + + /* A global, or a variable a pointer can read, is observable whether or + * not a later instruction names it. + */ + if (var_in_memory(insn->rd) && !insn->useful) { insn->useful = true; insn->belong_to->useful = true; dce_init_push(work_list, work_list_idx, &mark_num, insn); @@ -3773,7 +4203,7 @@ void dce_sweep(void) basic_block_t *jump_bb = bb->r_idom; bb_disconnect(bb, bb->then_); bb_disconnect(bb, bb->else_); - while (jump_bb != bb->belong_to->exit) { + while (jump_bb && jump_bb != bb->belong_to->exit) { if (jump_bb->useful) { bb_connect(bb, jump_bb, NEXT); break; @@ -3810,6 +4240,12 @@ void optimize(void) use_chain_build(); + /* Before anything below decides which reads it can drop or share. */ + for (func_t *func = FUNC_LIST.head; func; func = func->next) { + if (func->bbs) + mark_volatile_addresses(func); + } + /* Run constant cast optimization for truncation */ for (func_t *func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ @@ -3830,6 +4266,7 @@ void optimize(void) /* instruction level optimizations */ for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { /* record the instruction assigned value to rd */ + insn_t *prior_assign = insn->rd ? insn->rd->last_assign : NULL; if (insn->rd) insn->rd->last_assign = insn; @@ -3842,7 +4279,12 @@ void optimize(void) /* Eliminate redundant assignments: x = x */ if (insn->opcode == OP_assign && insn->rd && insn->rs1 && insn->rd == insn->rs1) { - /* Convert to no-op that DCE will remove */ + /* Convert to no-op that DCE will remove. A global is not + * renamed, so "g = g" names one variable twice; a later + * read of g must depend on the store before this one, or + * DCE keeps an assignment with no operands. + */ + insn->rd->last_assign = prior_assign; insn->rd = NULL; insn->rs1 = NULL; continue; @@ -3961,9 +4403,22 @@ void optimize(void) } /* Identity and constant optimizations */ - if (insn->rs2 && insn->rs2->is_const && insn->rd) { + if (insn->rs2 && insn->rs2->is_const && + !insn->rs2->init_val_hi && insn->rd) { int val = insn->rs2->init_val; + /* init_val holds only the low word. In an operation wider + * than int, a low word of -1 is all ones only when the + * constant is a signed narrow one that the usual + * conversions sign-extend: 0xffffffffULL has a zero high + * word, so "x & 0xffffffffULL" is not x. + */ + bool all_ones = + val == -1 && !(ssa_is_wide_operation(insn) && + (!insn->rs2->type || + insn->rs2->type->size > TY_int->size || + insn->rs2->type->is_unsigned)); + /* x + 0 = x, x - 0 = x, x | 0 = x, x ^ 0 = x */ if (val == 0) { if (insn->opcode == OP_add || insn->opcode == OP_sub || @@ -4004,7 +4459,7 @@ void optimize(void) } } /* x & -1 = x (all bits set) */ - else if (val == -1) { + else if (all_ones) { if (insn->opcode == OP_bit_and) { insn->opcode = OP_assign; insn->rs2 = NULL; @@ -4014,6 +4469,8 @@ void optimize(void) insn->opcode = OP_load_constant; insn->rd->is_const = true; insn->rd->init_val = -1; + insn->rd->init_val_hi = + ssa_is_wide_operation(insn) ? -1 : 0; insn->rs1 = NULL; insn->rs2 = NULL; } @@ -4028,7 +4485,8 @@ void optimize(void) /* Multi-instruction analysis and optimization Store-to-load * forwarding */ - if (insn->opcode == OP_load && insn->rs1 && insn->rd) { + if (insn->opcode == OP_load && insn->rs1 && insn->rd && + !insn->rs1->is_volatile) { insn_t *search = insn->prev; int search_limit = 10; /* Look back up to 10 instructions */ @@ -4076,7 +4534,8 @@ void optimize(void) } /* Redundant load elimination */ - if (insn->opcode == OP_load && insn->rs1 && insn->rd) { + if (insn->opcode == OP_load && insn->rs1 && insn->rd && + !insn->rs1->is_volatile) { insn_t *search = bb->insn_list.head; while (search && search != insn) { @@ -4125,9 +4584,12 @@ void optimize(void) * var_t, and that var_t is what its defining OP_load_constant * materialises, so rewriting init_val in place changes the * value every other use sees: "int k = 8; return a*k + b*k;" - * returned 2 << 3 + 3 * 3. + * returned 2 << 3 + 3 * 3. A wide constant with a high word, + * such as 0x100000004ULL, is not the power of two its low word + * is. */ - if (insn->rs2 && insn->rs2->is_const && insn->rd) { + if (insn->rs2 && insn->rs2->is_const && + !insn->rs2->init_val_hi && insn->rd) { int val = insn->rs2->init_val; int shift = exact_log2(val); opcode_t reduced = OP_generic; @@ -4140,12 +4602,14 @@ void optimize(void) operand = shift; } /* x / power_of_2 = x >> shift (unsigned) */ - else if (insn->opcode == OP_div) { + else if (insn->opcode == OP_div && + ssa_is_unsigned_scalar(insn->rs1)) { reduced = OP_rshift; operand = shift; } /* x % power_of_2 = x & (power_of_2 - 1) */ - else if (insn->opcode == OP_mod) { + else if (insn->opcode == OP_mod && + ssa_is_unsigned_scalar(insn->rs1)) { reduced = OP_bit_and; operand = val - 1; } @@ -4427,6 +4891,8 @@ void liveness_analysis(void) bb_forward_traversal(args); /* Add function parameters as killed in entry block */ + if (func->returns_aggregate) + bb_add_killed_var(func->bbs, var_subscript0(&func->sret_def)); for (int i = 0; i < func->num_params; i++) bb_add_killed_var(func->bbs, var_subscript0(&func->param_defs[i])); } diff --git a/src/x64-codegen.c b/src/x64-codegen.c index d1560077..adc8461d 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -22,6 +22,28 @@ void wrap_to_int(int rd, bool is_ptr) emit_narrow_move(rd, rd, 4, !is_ptr); } +/* Whether a value of @size_bytes, or a pointer when @is_pointer, occupies the + * whole register. + * + * A narrow value means only its low bytes here. Arithmetic runs in 64-bit + * registers and leaves whatever it likes above bit 31: an unsigned int + * subtraction leaves a borrow there, and ~x on a zero-extended unsigned leaves + * ones. Comparisons already respect that by taking their width from the + * operation, and the tests that ask whether a value is zero have to as well. + */ +bool test_is_wide(int size_bytes, bool is_pointer) +{ + return is_pointer || size_bytes > 4; +} + +/* TEST @reg against itself at the width @wide selects. */ +void emit_test_self(bool wide, int reg) +{ + emit_rex(wide, reg, reg); + emit_byte(0x85); + emit_byte(modrm(MOD_DIRECT, reg_low3(reg), reg_low3(reg))); +} + /* Materialise a condition as 0 or 1 in @rd, given a SETcc opcode byte. * * SETcc lands in R11B and is then zero-extended into rd. R11 is outside @@ -90,12 +112,6 @@ bool folds_off; /* Callee-saved registers the function being emitted preserves. */ int cur_saved_regs; -/* Registers the allocator is holding a variable in for the whole of the - * function being emitted, one bit each. Such a register is live everywhere, so - * nothing that writes it may be dropped as dead. - */ -int cur_pinned_regs; - int x64_incoming_arg_base(int stack_size, int saved) { /* Past the frame, the saved registers, RBP and the return address. */ @@ -139,11 +155,14 @@ int rodata_ref_count = 0; /* Pending function-address relocations, for OP_address_of_func. A function's * address is elf_code_start + its entry block offset, which is only final once - * every function has been emitted. + * every function has been emitted. A function the dynamic linker supplies has + * no entry block; its address is its PLT entry, placed only once the code size + * is final. */ typedef struct funcaddr_ref { int patch_location; basic_block_t *target_bb; + int plt_offset; } funcaddr_ref_t; #define FUNCADDR_REF_MAX 4096 @@ -261,7 +280,7 @@ bool fused_cc_pending; * by the unfused path. */ -int branch_cc_for(opcode_t op) +int branch_cc_for(opcode_t op, bool is_unsigned) { switch (op) { case OP_eq: @@ -269,13 +288,13 @@ int branch_cc_for(opcode_t op) case OP_neq: return 0x85; /* JNE */ case OP_lt: - return 0x8C; /* JL */ + return is_unsigned ? 0x82 : 0x8C; /* JB / JL */ case OP_leq: - return 0x8E; /* JLE */ + return is_unsigned ? 0x86 : 0x8E; /* JBE / JLE */ case OP_gt: - return 0x8F; /* JG */ + return is_unsigned ? 0x87 : 0x8F; /* JA / JG */ case OP_geq: - return 0x8D; /* JGE */ + return is_unsigned ? 0x83 : 0x8D; /* JAE / JGE */ default: return 0; } @@ -547,7 +566,6 @@ int src0_override_at; * named directly as the compare's second operand. -1 when unused. */ int cmp_mem_slot; -int cmp_mem_width; /* A comparison whose second operand is a tracked literal, so it becomes an * immediate and the instruction that materialised it falls away. @@ -555,116 +573,6 @@ int cmp_mem_width; bool cmp_imm_known; int cmp_imm_val; -/* Opcodes whose "dest" names a register they write. Only opcodes that certainly - * do are listed, so a live register is never mistaken for dead. - */ -bool op_writes_dest(opcode_t op) -{ - switch (op) { - case OP_cmov: - case OP_load: - case OP_load_constant: - case OP_global_load: - case OP_assign: - case OP_add: - case OP_sub: - case OP_mul: - case OP_div: - case OP_mod: - case OP_lshift: - case OP_rshift: - case OP_bit_and: - case OP_bit_or: - case OP_bit_xor: - case OP_bit_not: - case OP_negate: - case OP_log_not: - case OP_eq: - case OP_neq: - case OP_lt: - case OP_leq: - case OP_gt: - case OP_geq: - case OP_read: - case OP_address_of: - case OP_global_address_of: - case OP_trunc: - case OP_sign_ext: - return true; - default: - return false; - } -} - -/* Opcodes whose src0 holds something other than a register number. Anything not - * listed is assumed to read src0, which only costs a missed rewrite. - */ -bool op_src0_is_reg(opcode_t op) -{ - switch (op) { - case OP_load: - case OP_load_constant: - case OP_global_load: - case OP_address_of: - case OP_global_address_of: - case OP_load_data_address: - case OP_load_rodata_address: - case OP_define: - case OP_label: - case OP_jump: - return false; - default: - return true; - } -} - -/* Opcodes whose src1 is a register rather than a width, slot or immediate. */ -bool op_src1_is_reg(opcode_t op) -{ - switch (op) { - case OP_add: - case OP_sub: - case OP_mul: - case OP_div: - case OP_mod: - case OP_lshift: - case OP_rshift: - case OP_bit_and: - case OP_bit_or: - case OP_bit_xor: - case OP_eq: - case OP_neq: - case OP_lt: - case OP_leq: - case OP_gt: - case OP_geq: - case OP_write: - case OP_cmov: - return true; - default: - return false; - } -} - -/* Whether src2 names a register. Only a select does: it is the value kept when - * the condition does not hold, and a scan that missed it would take that value - * for dead and drop whatever computed it. - */ -bool op_src2_is_reg(opcode_t op) -{ - return op == OP_cmov; -} - -/* Whether @ir reads @reg as an operand. */ -bool ir_reads_reg(ph2_ir_t *ir, int reg) -{ - if (op_src2_is_reg(ir->op) && ir->src2 == reg) - return true; - if (op_src0_is_reg(ir->op) && ir->src0 == reg) - return true; - return op_src1_is_reg(ir->op) && ir->src1 == reg; -} - /* How far the forward scans below look. They answer "is this register dead from * here?", and a definite answer is only useful near the instruction being * emitted; scanning whole blocks made these quadratic in block size and cost @@ -709,38 +617,6 @@ int func_saved_regs(func_t *func) return top - (X64_FIRST_CALLEE_SAVED - 1); } -/* True when @reg still holds a live value past the end of @bb. - * - * reg_alloc() hands its register file to a successor that this block is the - * only way into (bb_export_regs()), so a register can outlive the block that - * filled it. Both scans below stop at the block boundary, and what they may - * conclude there depends on this: with nothing carried, every register dies at - * the end of a block and an unread value is dead. - */ -bool reg_live_out_of_bb(basic_block_t *bb, int reg) -{ - basic_block_t *succs[3]; - - if (!bb || reg < 0 || reg >= REG_CNT) - return false; - - /* A register the allocator pinned holds its variable on every path, so it - * is live out of every block regardless of what the successors record. - */ - if ((cur_pinned_regs >> reg) & 1) - return true; - - succs[0] = bb->next; - succs[1] = bb->then_; - succs[2] = bb->else_; - - for (int i = 0; i < 3; i++) { - if (succs[i] && succs[i]->entry_regs && succs[i]->entry_regs[reg]) - return true; - } - return false; -} - /* True when @reg is overwritten before any later read in its block, and is not * carried into a successor. */ @@ -871,8 +747,12 @@ bool reg_low32_sufficient(int idx, int reg, int depth) } else if (ir->op == OP_lshift || ir->op == OP_rshift) { /* Also narrows, unless it is the one case that deliberately * does not: a shift feeding a pointer add straight away keeps - * its full width, and then the bits going into it do matter. + * its full width, and then the bits going into it do matter. A + * right shift moves the bits above 31 of the value it shifts + * into the result, and SAR copies bit 63 into it. */ + if (ir->op == OP_rshift && ir->src0 == reg) + return false; if (reg_feeds_address_only(j + 1, ir->dest)) return false; } else if (ir->op == OP_add || ir->op == OP_sub || @@ -1289,7 +1169,8 @@ bool const_load_dead(int idx, int reg, int val) /* The three-operand IMUL carries its multiplier as an immediate. */ if (ir->op == OP_mul) folded_count = true; - if (branch_cc_for(ir->op)) + if (branch_cc_for(ir->op, + ir->src0_is_unsigned || ir->src1_is_unsigned)) folded_count = true; } @@ -1379,25 +1260,31 @@ basic_block_t *bb_sole_code_pred(basic_block_t *bb) } /* CMP rs1 against rs2, or against the slot a folded load named instead. */ -void emit_cmp(int rs1, int rs2) +void emit_cmp(int size_bytes, int rs1, int rs2) { if (cmp_imm_known) { cmp_imm_known = false; cmp_mem_slot = -1; - emit_alu_imm(rs1, 7, cmp_imm_val); /* CMP rs1, imm */ + emit_rex(size_bytes > 4, -1, rs1); + if (cmp_imm_val >= -128 && cmp_imm_val <= 127) { + emit_byte(0x83); /* CMP r32/r64, imm8 */ + emit_byte(modrm(MOD_DIRECT, 7, reg_low3(rs1))); + emit_byte(cmp_imm_val); + } else { + emit_byte(0x81); /* CMP r32/r64, imm32 */ + emit_byte(modrm(MOD_DIRECT, 7, reg_low3(rs1))); + emit_dword(cmp_imm_val); + } return; } if (cmp_mem_slot >= 0) { - if (cmp_mem_width == 8) - emit_rex(1, rs1, -1); - else if (rs1 >= 8) - emit_byte(REX_R); + emit_rex(size_bytes > 4, rs1, -1); emit_byte(0x3B); /* CMP rs1, [rsp + slot] */ emit_rsp_mem(rs1, cmp_mem_slot); cmp_mem_slot = -1; return; } - emit_rex(1, rs2, rs1); + emit_rex(size_bytes > 4, rs2, rs1); emit_byte(0x39); /* CMP rs1, rs2 */ emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), reg_low3(rs1))); } @@ -1761,50 +1648,76 @@ void emit_arith(ph2_ir_t *ph2_ir, emit_byte(0xAF); emit_byte(modrm(MOD_DIRECT, reg_low3(rd), reg_low3(rs2))); } - if (!reg_low32_sufficient(emit_ir_index + 1, ph2_ir->dest, 0)) + if (ph2_ir->size_bytes <= 4 && + !reg_low32_sufficient(emit_ir_index + 1, ph2_ir->dest, 0)) wrap_to_int(rd, ph2_ir->is_pointer); return; } case OP_div: case OP_mod: { - /* Signed division. IDIV forces the dividend into RDX:RAX and returns - * the quotient in RAX and remainder in RDX. Both are allocatable - * registers here (reg_map[6] and reg_map[2]) and the allocator does not - * know they are clobbered, so save and restore them around the - * sequence. R10 and R11 are outside reg_map and can stage values; RDX - * goes on the stack rather than into a third scratch, because every - * register that once served as one has since joined the file and - * staging RDX there destroyed a dividend pinned to it. + /* DIV/IDIV force the dividend into RDX:RAX and return the quotient in + * RAX and remainder in RDX. Both are allocatable registers here + * (reg_map[6] and reg_map[2]) and the allocator does not know they are + * clobbered, so save and restore them around the sequence. R10 and R11 + * are outside reg_map and can stage values; RDX goes on the stack + * rather than into a third scratch, because every register that once + * served as one has since joined the file and staging RDX there + * destroyed a dividend pinned to it. */ - emit_byte(REX_W | REX_B); /* MOV r10, rax (save) */ + bool wide = ph2_ir->size_bytes > 4; + bool is_unsigned = ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned; + + /* A 32-bit unsigned operation must use EDX:EAX, not its 64-bit + * counterpart. For example, C requires -1 / 2U to be 2147483647, + * whereas a 64-bit DIV sees 2^64 - 1. The save of RAX is always 64-bit: + * RAX may hold a live long long beside an int division. + */ + emit_rex(1, 0, 10); /* MOV r10, rax (save) */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, 0, 2)); - emit_push_reg(2); /* PUSH rdx (save) */ - emit_rex(1, rs2, 11); /* MOV r11, rs2 */ + emit_push_reg(2); /* PUSH rdx (save) */ + emit_rex(wide, rs2, 11); /* MOV r11{d}, rs2{d} */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), 3)); - emit_rex(1, rs1, -1); /* MOV rax, rs1 */ + emit_rex(wide, rs1, -1); /* MOV rax{d}, rs1{d} */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, reg_low3(rs1), 0)); - emit_byte(REX_W); /* CQO */ - emit_byte(0x99); - emit_byte(REX_W | REX_B); /* IDIV r11 */ + if (is_unsigned) { + /* Unsigned DIV consumes a zero-extended RDX:RAX dividend. */ + emit_byte(0x31); /* XOR edx, edx */ + emit_byte(modrm(MOD_DIRECT, 2, 2)); + } else { + if (wide) + emit_byte(REX_W); /* CQO */ + emit_byte(0x99); + } + emit_rex(wide, -1, 11); /* DIV/IDIV r11{d} */ emit_byte(0xF7); - emit_byte(modrm(MOD_DIRECT, 7, 3)); + emit_byte(modrm(MOD_DIRECT, is_unsigned ? 6 : 7, 3)); /* Capture the result into R11 before restoring RAX/RDX, so rd may * itself be RAX or RDX. */ - emit_byte(REX_W | REX_B); /* MOV r11, rax | rdx */ + emit_rex(wide, ph2_ir->op == OP_div ? 0 : 2, 11); + /* MOV r11{d}, rax{d} | rdx{d} */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, ph2_ir->op == OP_div ? 0 : 2, 3)); - emit_byte(REX_W | REX_R); /* MOV rax, r10 (restore) */ + emit_rex(1, 10, 0); /* MOV rax, r10 (restore) */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, 2, 0)); - emit_pop_reg(2); /* POP rdx (restore) */ - emit_rex(1, 11, rd); /* MOV rd, r11 */ + emit_pop_reg(2); /* POP rdx (restore) */ + emit_rex(wide, 11, rd); /* MOV rd{d}, r11{d} */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, 3, reg_low3(rd))); + + /* The 32-bit move above zero-extends, but a signed int result must sit + * in its register sign-extended like every other narrow scalar: a later + * widening to long long reads the whole register, and -5000 was seen as + * 4294962296. + */ + if (!wide && !is_unsigned && + !reg_low32_sufficient(emit_ir_index + 1, ph2_ir->dest, 0)) + wrap_to_int(rd, false); return; } default: @@ -1878,7 +1791,7 @@ void emit_bitwise(ph2_ir_t *ph2_ir, /* A count the block already loaded as a literal needs neither CL nor * the save/restore around it: SHL r64, imm8 is one instruction. */ - if (src1_const_known && src1_const >= 0 && src1_const < 32) { + if (src1_const_known && src1_const >= 0 && src1_const < 64) { int want_src = ph2_ir->src0; int dest_ir = ph2_ir->dest; @@ -1915,7 +1828,7 @@ void emit_bitwise(ph2_ir_t *ph2_ir, reg_feeds_address_only(emit_ir_index + 1, ph2_ir->dest); emit_mov_reg(rd, rs1); emit_shift_imm(rd, SHIFT_EXT_SHL, src1_const); - if (!addr_only && + if (ph2_ir->size_bytes <= 4 && !addr_only && !reg_low32_sufficient(emit_ir_index + 1, ph2_ir->dest, 0)) wrap_to_int(rd, ph2_ir->is_pointer); @@ -1963,17 +1876,31 @@ void emit_bitwise(ph2_ir_t *ph2_ir, * << 31" (which strength reduction also produces for "x * 2") stayed * positive and every later comparison took the wrong branch. */ - wrap_to_int(rd, ph2_ir->is_pointer); + if (ph2_ir->size_bytes <= 4) + wrap_to_int(rd, ph2_ir->is_pointer); return; } case OP_rshift: { /* A count the block already loaded as a literal needs neither CL nor * the save/restore around it: SAR r64, imm8 is one instruction. */ - if (src1_const_known && src1_const >= 0 && src1_const < 32) { - emit_mov_reg(rd, rs1); - emit_shift_imm(rd, SHIFT_EXT_SAR, src1_const); - if (!reg_low32_sufficient(emit_ir_index + 1, ph2_ir->dest, 0)) + if (src1_const_known && src1_const >= 0 && src1_const < 64) { + /* A right shift is the one reader of the bits above 31 that a + * narrow value leaves unspecified, so it puts its source in range + * first: zero-extended for an unsigned one, sign-extended for a + * signed one, which SAR then replicates correctly. + */ + if (ph2_ir->src0_is_unsigned) + emit_zero_extend(rd, rs1, ph2_ir->size_bytes); + else if (ph2_ir->size_bytes <= 4 && !ph2_ir->src0_is_pointer) + emit_narrow_move(rd, rs1, 4, true); + else + emit_mov_reg(rd, rs1); + emit_shift_imm( + rd, ph2_ir->src0_is_unsigned ? SHIFT_EXT_SHR : SHIFT_EXT_SAR, + src1_const); + if (ph2_ir->size_bytes <= 4 && + !reg_low32_sufficient(emit_ir_index + 1, ph2_ir->dest, 0)) wrap_to_int(rd, ph2_ir->is_pointer); return; } @@ -1989,15 +1916,27 @@ void emit_bitwise(ph2_ir_t *ph2_ir, emit_byte(REX_W | REX_B); /* MOV r10, rcx */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, 1, 2)); - emit_rex(1, rs1, 11); /* MOV r11, rs1 */ - emit_byte(0x89); - emit_byte(modrm(MOD_DIRECT, reg_low3(rs1), 3)); + + /* As in the literal-count form above, the source is put in range first: + * an unsigned int subtraction leaves a borrow in the upper half, which + * SHR would bring down into the result, and a signed int one leaves a + * borrow SAR would. + */ + if (!ph2_ir->src0_is_unsigned && ph2_ir->size_bytes <= 4 && + !ph2_ir->src0_is_pointer) { + emit_narrow_move(11, rs1, 4, true); /* MOVSXD r11, rs1d */ + } else { + emit_rex(!(ph2_ir->src0_is_unsigned && ph2_ir->size_bytes <= 4), + rs1, 11); /* MOV r11{d}, rs1{d} */ + emit_byte(0x89); + emit_byte(modrm(MOD_DIRECT, reg_low3(rs1), 3)); + } emit_rex(1, rs2, -1); /* MOV rcx, rs2 */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), 1)); - emit_byte(REX_W | REX_B); /* SAR r11, cl */ + emit_byte(REX_W | REX_B); /* SAR/SHR r11, cl */ emit_byte(0xD3); - emit_byte(modrm(MOD_DIRECT, 7, 3)); + emit_byte(modrm(MOD_DIRECT, ph2_ir->src0_is_unsigned ? 5 : 7, 3)); emit_byte(REX_W | REX_R); /* MOV rcx, r10 */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, 2, 1)); @@ -2025,8 +1964,11 @@ void emit_compare_jump(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) * comparisons differ only in the condition, and SETcc is the matching * Jcc opcode plus 0x10, so branch_cc_for() supplies both forms. */ - emit_cmp(rs1, rs2); - emit_setcc_bool(rd, branch_cc_for(ph2_ir->op) + 0x10); + emit_cmp(ph2_ir->size_bytes, rs1, rs2); + emit_setcc_bool( + rd, branch_cc_for(ph2_ir->op, ph2_ir->src0_is_unsigned || + ph2_ir->src1_is_unsigned) + + 0x10); return; case OP_jump: { @@ -2113,10 +2055,11 @@ void emit_compare_jump(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) return; } - /* TEST rs1, rs1 */ - emit_rex(1, rs1, rs1); - emit_byte(0x85); - emit_byte(modrm(MOD_DIRECT, reg_low3(rs1), reg_low3(rs1))); + /* TEST rs1, rs1, over the width the branch records for its condition: + * an int one is decided by its low word alone. + */ + emit_test_self( + test_is_wide(ph2_ir->size_bytes, ph2_ir->src0_is_pointer), rs1); /* Emit both edges explicitly: JNZ then_bb, then JMP else_bb. * @@ -2154,7 +2097,12 @@ void emit_call_return(ph2_ir_t *ph2_ir, int rs1) { func_t *target_func = find_func(ph2_ir->func_name); - if (dynlink && target_func && !target_func->bbs) { + if (target_func && target_func->is_static && !target_func->bbs) { + printf("Error: Undefined static function called: %s\n", + ph2_ir->func_name); + fflush(stdout); /* see fatal() */ + abort(); + } else if (dynlink && target_func && !target_func->bbs) { /* An external symbol: call its PLT entry, which the loader * redirects to the real function on first use. */ @@ -2215,6 +2163,10 @@ void emit_call_return(ph2_ir_t *ph2_ir, int rs1) * ignore that redirection and return a stale register. */ int ret_reg = rs1; + if (ph2_ir->src0_is_unsigned && ph2_ir->size_bytes < PTR_SIZE) { + emit_zero_extend(0, ret_reg, ph2_ir->size_bytes); + ret_reg = 0; + } /* Debug output MOV rax, ret_reg - only if not already in RAX */ if (ret_reg != 0) { /* 0 is RAX, no move needed */ /* MOV RAX, ret_reg. @@ -2328,11 +2280,11 @@ void emit_memory(ph2_ir_t *ph2_ir, int rd, int rs1) * condition's own register: the condition dies at this instruction, so * the allocator is free to hand its register to the destination. * Reading the flags before overwriting it costs nothing -- MOV leaves - * them alone -- and testing after would have tested the wrong value. + * them alone -- and testing after would have tested the wrong value. As + * for OP_branch, size_bytes is the width of the condition. */ - emit_rex(1, cond, cond); /* TEST cond, cond */ - emit_byte(0x85); - emit_byte(modrm(MOD_DIRECT, reg_low3(cond), reg_low3(cond))); + emit_test_self( + test_is_wide(ph2_ir->size_bytes, ph2_ir->src0_is_pointer), cond); /* When the destination already holds the value the test selects, * copying the other over it would destroy it; keep what is there and @@ -2370,15 +2322,15 @@ void emit_memory(ph2_ir_t *ph2_ir, int rd, int rs1) */ emit_rex(1, rd, -1); emit_byte(0x0F); - emit_byte(0xBE); + emit_byte(ph2_ir->is_unsigned ? 0xB6 : 0xBE); } else if (eff_size == 2) { emit_rex(1, rd, -1); emit_byte(0x0F); - emit_byte(0xBF); + emit_byte(ph2_ir->is_unsigned ? 0xB7 : 0xBF); } else if (eff_size == 4) { - /* MOVSXD rd, DWORD PTR [rsp+ofs]: int is signed */ - emit_rex(1, rd, -1); - emit_byte(0x63); + /* A 32-bit MOV zero extends; signed int uses MOVSXD. */ + emit_rex(ph2_ir->is_unsigned ? 0 : 1, rd, -1); + emit_byte(ph2_ir->is_unsigned ? 0x8B : 0x63); } else { emit_rex(1, rd, -1); emit_byte(0x8B); @@ -2447,10 +2399,11 @@ void emit_read_write(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) have_disp = true; addr_fold = false; } + bool unsigned_word = ph2_ir->is_unsigned && rsize == 4; if (sib) - emit_rex_sib(1, rd, sib_base, sib_index); + emit_rex_sib(unsigned_word ? 0 : 1, rd, sib_base, sib_index); else - emit_rex(1, rd, rs1); + emit_rex(unsigned_word ? 0 : 1, rd, rs1); if (rsize == 1) { /* MOVSX r64, BYTE PTR [rs1]: char is signed here, as it is for * every other narrow type, and RISC-V already uses lb rather @@ -2458,7 +2411,7 @@ void emit_read_write(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) * through a pointer as a large positive value. */ emit_byte(0x0F); - emit_byte(0xBE); + emit_byte(ph2_ir->is_unsigned ? 0xB6 : 0xBE); } else if (rsize == 2) { /* MOVSX r64, WORD PTR [rs1]: short is signed, so the upper bits * must be a sign extension. ARM uses LDRSH and RISC-V uses lh @@ -2466,12 +2419,12 @@ void emit_read_write(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) * large positive value. */ emit_byte(0x0F); - emit_byte(0xBF); + emit_byte(ph2_ir->is_unsigned ? 0xB7 : 0xBF); } else if (rsize == 4) { /* MOVSXD r64, DWORD PTR [rs1]: int is signed, so the upper half * must be a sign extension, not zero. */ - emit_byte(0x63); + emit_byte(ph2_ir->is_unsigned ? 0x8B : 0x63); } else { /* MOV r64, [rs1] */ emit_byte(0x8B); @@ -2597,12 +2550,14 @@ void emit_global(ph2_ir_t *ph2_ir, int rd, int rs1) func_t *target = find_func(ph2_ir->func_name); emit_byte(REX_W | REX_B); emit_byte(0xB8 + 3); /* MOVABS r11, imm64 */ - if (target && target->bbs) { + if (target && (target->bbs || dynlink)) { if (funcaddr_ref_count >= FUNCADDR_REF_MAX) fatal("x64: too many function-address relocations"); funcaddr_refs[funcaddr_ref_count].patch_location = elf_code->size; funcaddr_refs[funcaddr_ref_count].target_bb = target->bbs; + funcaddr_refs[funcaddr_ref_count].plt_offset = + target->plt_offset; funcaddr_ref_count++; } emit_dword(0); @@ -2672,19 +2627,24 @@ void emit_global(ph2_ir_t *ph2_ir, int rd, int rs1) * only the declared width, so an unconditional 8-byte load would pick * up whatever sits in the rest of the slot. */ - emit_rex(1, dest_reg, 11); /* r/m is r11 */ + emit_rex( + ph2_ir->is_unsigned && eff_size == 4 && !ph2_ir->is_pointer ? 0 : 1, + dest_reg, 11); /* r/m is r11 */ if (eff_size == 1 && !ph2_ir->is_pointer) { /* MOVSX, not MOVZX: 'char' is signed here, exactly as OP_load * treats a one-byte local. Zero-extending made a global char * holding -1 compare as 255. */ emit_byte(0x0F); /* MOVSX dest, BYTE [r15 + ofs] */ - emit_byte(0xBE); + emit_byte(ph2_ir->is_unsigned ? 0xB6 : 0xBE); } else if (eff_size == 2 && !ph2_ir->is_pointer) { emit_byte(0x0F); /* MOVSX dest, WORD [r15 + ofs] */ - emit_byte(0xBF); + emit_byte(ph2_ir->is_unsigned ? 0xB7 : 0xBF); } else if (eff_size == 4 && !ph2_ir->is_pointer) { - emit_byte(0x63); /* MOVSXD dest, DWORD [r15 + ofs] */ + if (ph2_ir->is_unsigned) { + emit_byte(0x8B); /* MOV dest32, DWORD [r15 + ofs] */ + } else + emit_byte(0x63); /* MOVSXD dest, DWORD [r15 + ofs] */ } else { emit_byte(0x8B); /* MOV dest, QWORD [r15 + ofs] */ } @@ -2807,9 +2767,8 @@ void emit_logic_cast(ph2_ir_t *ph2_ir, int rd, int rs1) * The source is rs1, which is not always the same register as rd -- for * a global it never is. */ - emit_rex(1, rs1, rs1); - emit_byte(0x85); - emit_byte(modrm(MOD_DIRECT, reg_low3(rs1), reg_low3(rs1))); + emit_test_self( + test_is_wide(ph2_ir->size_bytes, ph2_ir->src0_is_pointer), rs1); emit_setcc_bool(rd, 0x94); /* SETE */ } @@ -2847,6 +2806,10 @@ void emit_logic_cast(ph2_ir_t *ph2_ir, int rd, int rs1) * + 1 to -32768 -- and a 4-byte truncation sign-extends the low word. */ int tsize = ph2_ir->src1; + if (ph2_ir->is_unsigned) { + emit_zero_extend(rd, rs1, tsize); + return; + } emit_rex(1, rd, rs1); if (tsize == 1) { emit_byte(0x0F); @@ -2881,17 +2844,32 @@ void emit_logic_cast(ph2_ir_t *ph2_ir, int rd, int rs1) * half and destroy every stack address. Copy it instead, which is also * what a source that is already full width wants. */ - if (dst_size == PTR_SIZE && PTR_SIZE == 8) + if (ph2_ir->is_pointer && dst_size == PTR_SIZE && PTR_SIZE == 8) width = 8; else if (width != 1 && width != 2 && width != 4) width = 8; - emit_narrow_move(rd, rs1, width, true); + if (ph2_ir->src0_is_unsigned) + emit_zero_extend(rd, rs1, width); + else + emit_narrow_move(rd, rs1, width, true); } return; case OP_cast: { - /* Usually just a register move */ - emit_mov_reg(rd, rs1); + /* A narrow scalar sits in its 64-bit register extended by its own + * signedness, and the instructions that read the whole register -- a + * right shift or a widening, for instance -- rely on that. An + * equal-width cast that changes the signedness must therefore redo the + * extension: an int cast from unsigned int 0xffffffff otherwise kept + * zeroes above it, and shifting it right arithmetically gave + * 0x7fffffff. + */ + if (ph2_ir->is_unsigned && ph2_ir->size_bytes < PTR_SIZE) + emit_zero_extend(rd, rs1, ph2_ir->size_bytes); + else if (ph2_ir->src0_is_unsigned && ph2_ir->size_bytes < PTR_SIZE) + emit_narrow_move(rd, rs1, ph2_ir->size_bytes, true); + else + emit_mov_reg(rd, rs1); } return; @@ -2946,7 +2924,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } if (ph2_ir->op != OP_branch) fused_cc_pending = false; - if (!branch_cc_for(ph2_ir->op)) { + if (!branch_cc_for(ph2_ir->op, + ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned)) { cmp_mem_slot = -1; cmp_imm_known = false; } else if (src1_const_known) { @@ -2959,13 +2938,19 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) * this value afterwards, so folding it costs no later reload. */ if (ph2_ir->op == OP_load && emit_ir_index >= 0 && emit_next_ir && - branch_cc_for(emit_next_ir->op) && emit_next_ir->src1 == ph2_ir->dest && + branch_cc_for(emit_next_ir->op, emit_next_ir->src0_is_unsigned || + emit_next_ir->src1_is_unsigned) && + emit_next_ir->src1 == ph2_ir->dest && emit_next_ir->src0 != ph2_ir->dest && reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { int w = load_width(ph2_ir); - if (w == 4 || w == 8) { + + /* A folded memory operand must have the same width as the comparison. + * In particular, comparing an int load after it has been promoted to + * long must not turn into an eight-byte read from its four-byte slot. + */ + if ((w == 4 || w == 8) && w == emit_next_ir->size_bytes) { cmp_mem_slot = ph2_ir->src0; - cmp_mem_width = w; return; } } @@ -2973,8 +2958,11 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) if (!op_keeps_frame_mirrors(ph2_ir->op)) { frame_mirror_reset(); } else { - if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { + /* A volatile slot is read however recently a register mirrored it: the + * read is an access the program performs. + */ + if (ph2_ir->op == OP_load && !ph2_ir->is_volatile && + ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) { int want = load_width(ph2_ir); if (reg_mirror_valid[ph2_ir->dest] && reg_mirror_slot[ph2_ir->dest] == ph2_ir->src0 && @@ -3043,7 +3031,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) * the same store. */ if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT && - reg_mirror_valid[ph2_ir->src0] && + !ph2_ir->is_volatile && reg_mirror_valid[ph2_ir->src0] && reg_mirror_slot[ph2_ir->src0] == ph2_ir->src1) { int keep = ph2_ir->size_bytes; if (ph2_ir->is_pointer && keep != PTR_SIZE) @@ -3086,10 +3074,11 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) * result that were live elsewhere would have been stored between the two * instructions -- and then this test would not fire. */ - int fuse_cc = branch_cc_for(ph2_ir->op); + int fuse_cc = branch_cc_for( + ph2_ir->op, ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned); if (fuse_cc && emit_next_ir && emit_next_ir->op == OP_branch && emit_next_ir->src0 == ph2_ir->dest) { - emit_cmp(rs1, rs2); + emit_cmp(ph2_ir->size_bytes, rs1, rs2); fused_cc = fuse_cc; fused_cc_pending = true; return; @@ -3104,13 +3093,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) if (ph2_ir->op == OP_bit_and && emit_next_ir && emit_next_ir->op == OP_branch && emit_next_ir->src0 == ph2_ir->dest && emit_ir_index >= 0 && reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + bool wide = test_is_wide(ph2_ir->size_bytes, ph2_ir->is_pointer); + if (src1_const_known) { - emit_rex(1, 0, rs1); + emit_rex(wide, 0, rs1); emit_byte(0xF7); /* TEST rs1, imm32 */ emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rs1))); emit_dword(src1_const); } else { - emit_rex(1, rs2, rs1); + emit_rex(wide, rs2, rs1); emit_byte(0x85); /* TEST rs1, rs2 */ emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), reg_low3(rs1))); } @@ -3121,6 +3112,14 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) switch (ph2_ir->op) { case OP_load_constant: { + if (ph2_ir->size_bytes == 8) { + emit_rex(1, -1, rd); + emit_byte(0xB8 + reg_low3(rd)); /* MOVABS r64, imm64 */ + emit_dword(ph2_ir->src0); + emit_dword(ph2_ir->src1); + return; + } + /* MOV r64, imm32 (sign-extended). The immediate is in src0, not a * register index. */ @@ -3134,6 +3133,18 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) if (const_load_dead(emit_ir_index + 1, ph2_ir->dest, ph2_ir->src0)) return; } + + /* A 32-bit register write zero-extends to 64 bits, which is exactly the + * representation an unsigned scalar needs before a logical shift or + * unsigned comparison. + */ + if (ph2_ir->is_unsigned && ph2_ir->size_bytes <= 4) { + emit_rex(0, -1, rd); + emit_byte(0xC7); + emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rd))); + emit_dword(ph2_ir->src0); + return; + } emit_rex(1, -1, rd); emit_byte(0xC7); emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rd))); @@ -3142,7 +3153,10 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } case OP_assign: { - emit_mov_reg(rd, rs1); + if (ph2_ir->is_unsigned && ph2_ir->size_bytes < PTR_SIZE) + emit_zero_extend(rd, rs1, ph2_ir->size_bytes); + else + emit_mov_reg(rd, rs1); return; } @@ -3208,6 +3222,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_logic_cast(ph2_ir, rd, rs1); break; case OP_define: + fatal_function_context = ph2_ir->func_name; /* Update the function's actual offset to current code position */ { func_t *func = find_func(ph2_ir->func_name); @@ -3482,6 +3497,20 @@ void code_generate(void) ph2_ir = ph2_ir->next) { if (x64_debug) fprintf(stderr, "[x64] emit global op=%d\n", ph2_ir->op); + + /* The initializer runs through a CALL with no frame of its own, and + * the slots it spills temporaries to are allocated from the global + * data area, which sits behind R15 on this target rather than on + * the stack as on the others. Addressing them from RSP wrote past + * the top of the stack once an initializer was large enough to + * spill. + */ + if (ph2_ir->op == OP_load) + ph2_ir->op = OP_global_load; + else if (ph2_ir->op == OP_store) + ph2_ir->op = OP_global_store; + else if (ph2_ir->op == OP_address_of) + ph2_ir->op = OP_global_address_of; emit_next_ir = NULL; emit_ph2_ir(ph2_ir); } @@ -3687,7 +3716,6 @@ void code_generate(void) if (ph2_ir->op == OP_define) { emit_func = find_func(ph2_ir->func_name); cur_saved_regs = emit_func ? emit_func->saved_regs : 1; - cur_pinned_regs = emit_func ? emit_func->pinned_regs : 0; if (emit_func && emit_func->bbs) { emit_bb = emit_func->bbs; @@ -4098,7 +4126,11 @@ void code_generate(void) */ for (int i = 0; i < funcaddr_ref_count; i++) { int at = funcaddr_refs[i].patch_location; - int addr = elf_code_start + funcaddr_refs[i].target_bb->elf_offset; + int addr; + if (funcaddr_refs[i].target_bb) + addr = elf_code_start + funcaddr_refs[i].target_bb->elf_offset; + else + addr = dynamic_sections.elf_plt_start + funcaddr_refs[i].plt_offset; patch_qword(at, addr); } diff --git a/src/x64.c b/src/x64.c index e5b5c77a..2b012b77 100644 --- a/src/x64.c +++ b/src/x64.c @@ -44,6 +44,7 @@ /* ModR/M opcode extensions selecting which shift 0xC1 encodes */ #define SHIFT_EXT_SHL 4 +#define SHIFT_EXT_SHR 5 #define SHIFT_EXT_SAR 7 /* ModR/M byte construction: mod (2 bits) | reg (3 bits) | r/m (3 bits) @@ -239,14 +240,39 @@ void emit_narrow_move(int dst, int src, int width, bool narrow) emit_byte(modrm(MOD_DIRECT, reg_low3(dst), reg_low3(src))); } +/* Move the low scalar width while clearing all high bits. x86-64's 32-bit + * register form does this for free; byte and halfword values need MOVZX. + */ +void emit_zero_extend(int dst, int src, int width) +{ + if (width >= 8) { + if (dst != src) { + emit_rex(1, src, dst); + emit_byte(0x89); + emit_byte(modrm(MOD_DIRECT, reg_low3(src), reg_low3(dst))); + } + return; + } + if (width == 4) { + emit_rex(0, src, dst); + emit_byte(0x89); /* MOV r32, r32 */ + emit_byte(modrm(MOD_DIRECT, reg_low3(src), reg_low3(dst))); + return; + } + emit_rex(1, dst, src); + emit_byte(0x0F); + emit_byte(width == 1 ? 0xB6 : 0xB7); + emit_byte(modrm(MOD_DIRECT, reg_low3(dst), reg_low3(src))); +} + /* MOV dst, src, or nothing when the value is already in the destination. */ void emit_mov_reg(int dst, int src) { emit_narrow_move(dst, src, 8, false); } -/* Shift @reg by an immediate count. @ext picks the shift: SHIFT_EXT_SHL or - * SHIFT_EXT_SAR. +/* Shift @reg by an immediate count. @ext picks the shift: SHIFT_EXT_SHL, + * SHIFT_EXT_SHR, or SHIFT_EXT_SAR. */ void emit_shift_imm(int reg, int ext, int imm) { diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index fd1b2771..263ccc19 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -281,6 +281,7 @@ test_eight_args() int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { return a + b + c + d + e + f + g + h; } + int main() { int result = sum8(1, 2, 3, 4, 5, 6, 7, 8); if (result == 36) { @@ -293,6 +294,115 @@ int main() { ' "PASS" } +# shecc currently represents long as a 32-bit signed scalar. Exercise that +# spelling through both register and stack argument slots and a return value. +test_long_args_and_return() +{ + run_abi_test "Long arguments and return" "Parameter Passing" ' +#include +long combine(long a, long b, long c, long d, long e, long f, long g) { + return a + b + c + d + e + f + g; +} +int main() { + long result = combine(1, 2, 3, 4, 5, 6, 21); + if (result == 42) { + printf("PASS\n"); + return 0; + } + printf("FAIL: got %d\n", result); + return 1; +} +' "PASS" +} + +# Exercise four register and four stack slots with narrow unsigned values. +# Callee-side loads must preserve the declared zero extension before promotion. +test_narrow_unsigned_args() +{ + run_abi_test "Narrow unsigned register and stack arguments" "Parameter Passing" ' +#include +int sum_narrow(unsigned char a, unsigned short b, unsigned char c, + unsigned short d, unsigned char e, unsigned short f, + unsigned char g, unsigned short h) { + return a + b + c + d + e + f + g + h; +} +int main(void) { + int result = sum_narrow(255U, 65535U, 255U, 65535U, + 255U, 65535U, 255U, 65535U); + if (result == 263160) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_mixed_narrow_args() +{ + run_abi_test "Mixed narrow register and stack arguments" "Parameter Passing" ' +#include +int sum_mixed(signed char a, unsigned char b, short c, unsigned short d, + signed char e, unsigned char f, short g, unsigned short h) { + return a + b + c + d + e + f + g + h; +} +int main(void) { + int result = sum_mixed(-128, 255U, -32768, 65535U, + -1, 128U, -2, 32768U); + if (result == 65787) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# A long long starts at an even core register, so after one int it takes r2 and +# r3. The callee reads the words as ints through a pointer of the caller's +# function type. +test_long_long_after_int() +{ + run_abi_test "Long long after one int (r2, r3)" "Parameter Passing" ' +#include +typedef int (*call_t)(int, long long); +int words(int a, int skipped, int low, int high) { return low == 3 && high == 2; } +int main() { + call_t call = (call_t) words; + if (call(1, 0x200000003LL)) { + printf("PASS\n"); + return 0; + } + printf("FAIL: long long not in r2 and r3\n"); + return 1; +} +' "PASS" +} + +# A long long that does not fit in the core registers goes to the stack whole, +# leaving r3 unused, and the next argument follows it on the stack. +test_long_long_on_stack() +{ + run_abi_test "Long long after three ints on the stack" "Parameter Passing" ' +#include +typedef int (*call_t)(int, int, int, long long, int); +int words(int a, int b, int c, int skipped, int low, int high, int e) { + return low == 3 && high == 2 && e == 9; +} +int main() { + call_t call = (call_t) words; + if (call(1, 2, 3, 0x200000003LL, 9)) { + printf("PASS\n"); + return 0; + } + printf("FAIL: long long not at the bottom of the stack\n"); + return 1; +} +' "PASS" +} + # Stack Alignment Tests test_stack_alignment_basic() @@ -360,6 +470,61 @@ int main() { ' "PASS" } +# Verify that AAPCS narrow unsigned return values are consumed with zero +# extension at the caller's integer-promotion boundary. +test_return_narrow_unsigned() +{ + run_abi_test "Return narrow unsigned values" "Return Values" ' +#include +unsigned char get_byte(void) { return 255U; } +unsigned short get_half(void) { return 65535U; } +int main(void) { + if (get_byte() == 255U && get_half() == 65535U) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_return_mixed_narrow() +{ + run_abi_test "Return mixed narrow signedness" "Return Values" ' +#include +signed char get_sbyte(void) { return -128; } +unsigned char get_ubyte(void) { return 255U; } +short get_shalf(void) { return -32768; } +unsigned short get_uhalf(void) { return 65535U; } +int main(void) { + if (get_sbyte() == -128 && get_ubyte() == 255U && + get_shalf() == -32768 && get_uhalf() == 65535U) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_bool_argument_and_return() +{ + run_abi_test "Bool argument and return normalization" "Return Values" ' +#include +_Bool echo_bool(_Bool value) { return value; } +int main(void) { + if (echo_bool(7) == 1 && echo_bool(0) == 0) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + test_return_int() { run_abi_test "Return int value" "Return Values" ' @@ -544,6 +709,11 @@ test_two_args test_four_args test_five_args test_eight_args +test_long_args_and_return +test_narrow_unsigned_args +test_mixed_narrow_args +test_long_long_after_int +test_long_long_on_stack echo "" echo -e "${CYAN}Running Stack Alignment Tests...${NC}" @@ -553,6 +723,9 @@ test_stack_alignment_extended echo "" echo -e "${CYAN}Running Return Value Tests...${NC}" test_return_char +test_return_narrow_unsigned +test_return_mixed_narrow +test_bool_argument_and_return test_return_int test_return_pointer diff --git a/tests/arm64-abi.sh b/tests/arm64-abi.sh index 02a552fb..9a986cb4 100755 --- a/tests/arm64-abi.sh +++ b/tests/arm64-abi.sh @@ -73,6 +73,23 @@ int sum10(int a,int b,int c,int d,int e,int f,int g,int h,int i,int j) { } int main(void) { return sum10(1,2,3,4,5,6,7,8,9,10); }' +# AAPCS64 gives each 64-bit integer one x-register slot. This crosses all eight +# register slots and two eight-byte stack slots, and retains nonzero high words +# so a W-register path cannot accidentally pass. +run_case 'wide register and stack arguments' 42 ' +unsigned long long sum10(unsigned long long a, unsigned long long b, + unsigned long long c, unsigned long long d, unsigned long long e, + unsigned long long f, unsigned long long g, unsigned long long h, + unsigned long long i, unsigned long long j) { + return a+b+c+d+e+f+g+h+i+j; +} +int main(void) { + unsigned long long total = sum10(0x100000001ULL, 0x100000002ULL, + 0x100000003ULL, 0x100000004ULL, 0x100000005ULL, 0x100000006ULL, + 0x100000007ULL, 0x100000008ULL, 0x100000009ULL, 0x10000000aULL); + return total == 0xa00000037ULL ? 42 : 1; +}' + # The callee reads stack arguments at a fixed offset above its own frame, so # that offset has to be rounded exactly as the prologue rounds the frame. A # frame whose size is 8 modulo 16 is what catches a disagreement: an @@ -102,8 +119,8 @@ int d2(int x) { int a,b,c; a=x; b=a+1; c=b+1; return d3(c) + 1; } int d1(int x) { int a; int *p = &a; *p = x; return d2(*p) + 1; } int main(void) { return d1(37); }' -# Narrow array elements must load sign-extended. Kept here rather than in -# tests/driver.sh because the Arm backend zero-extends them (see TODO.md). +# The shared driver covers narrow array loads on every target; retain this ABI +# case to exercise the same sign-extension path through the AArch64 harness. run_case 'narrow array load and store' 42 ' int main(void) { char b[4]; short h[4]; int i; diff --git a/tests/driver.sh b/tests/driver.sh index 0f3afd66..2ff6d437 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -262,11 +262,13 @@ function try() # Keep the compiler's diagnostic rather than discarding it: without it a # failure reports only an exit-code mismatch and never says why. $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2> "$tmp_err" - chmod +x $tmp_exe - - local output='' - output=$(${TARGET_EXEC:-} "$tmp_exe") local actual="$?" + local output='' + if [ "$actual" -eq 0 ]; then + chmod +x "$tmp_exe" + output=$(${TARGET_EXEC:-} "$tmp_exe") + actual="$?" + fi ((TOTAL_TESTS++)) ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) @@ -302,11 +304,49 @@ function try_output() try "$expected" "$expected_output" "$input" } +# Compile an inline program with one focused compiler-option set. Keep this +# beside try_ so feature regressions can exercise command-line behaviour without +# mutating the suite-wide linker configuration. +function try_flags() +{ + local expected="$1" + local extra_flags="$2" + local input=$(cat) + test_selected || return 0 + + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + local tmp_err="$(mktemp)" + echo "$input" > "$tmp_in" + $SHECC $SHECC_CFLAGS $extra_flags -o "$tmp_exe" "$tmp_in" 2> "$tmp_err" + local actual=$? + if [ "$actual" -eq 0 ]; then + chmod +x "$tmp_exe" + ${TARGET_EXEC:-} "$tmp_exe" + actual=$? + fi + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ "$actual" -ne "$expected" ]; then + report_test_failure "OPTION TEST" "$tmp_in" "$tmp_exe" "$expected" \ + "$actual" "$(< "$tmp_err")" "" "$tmp_err" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + # Compile and run a checked-in program through the same path as inline cases. # This keeps the small end-to-end programs in both stage-0 and stage-2 runs. function try_file() { - try "$1" "$2" "$(< "$3")" + if [ "$#" -eq 2 ]; then + try "$1" "$(< "$2")" + else + try "$1" "$2" "$(< "$3")" + fi } # try_compile_error - test shecc with invalid C program Usage: @@ -325,7 +365,8 @@ function try_compile_error() echo "$input" > "$tmp_in" # Suppress compiler error output and "Aborted" messages completely Run in a - # subshell with job control disabled + # subshell with job control disabled. A mistake in the input must end in a + # diagnostic exit, so a crash (an exit status above 128) fails the test. ( set +m 2> /dev/null # Disable job control messages $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>&1 @@ -337,6 +378,9 @@ function try_compile_error() if [ 0 == $exit_code ]; then report_test_failure "COMPILE ERROR TEST" "$tmp_in" "$tmp_exe" "non-zero" "0" "Compilation succeeded unexpectedly" + elif [ "$exit_code" -gt 128 ]; then + report_test_failure "COMPILE ERROR TEST" "$tmp_in" "$tmp_exe" \ + "diagnostic exit" "$exit_code" "Compiler crashed instead of reporting an error" else ((PASSED_TESTS++)) ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) @@ -347,6 +391,192 @@ function try_compile_error() fi } +# Compile invalid input with an explicit compiler option (for conformance-mode +# diagnostics) while preserving the normal test runner's accounting. +function try_compile_error_flag() +{ + local extra_flag="$1" + local input=$(cat) + test_selected || return 0 + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + echo "$input" > "$tmp_in" + + ( + set +m 2> /dev/null + $SHECC $SHECC_CFLAGS $extra_flag -o "$tmp_exe" "$tmp_in" 2>&1 + ) > /dev/null 2>&1 + local exit_code=$? + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ 0 == $exit_code ]; then + report_test_failure "CONFORMANCE ERROR TEST" "$tmp_in" "$tmp_exe" \ + "non-zero" "0" "Compilation succeeded unexpectedly" + elif [ "$exit_code" -gt 128 ]; then + report_test_failure "CONFORMANCE ERROR TEST" "$tmp_in" "$tmp_exe" \ + "diagnostic exit" "$exit_code" "Compiler crashed instead of reporting an error" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + +function try_compile_flag() +{ + local extra_flag="$1" + local input=$(cat) + test_selected || return 0 + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + local tmp_err="$(mktemp)" + echo "$input" > "$tmp_in" + $SHECC $SHECC_CFLAGS $extra_flag -o "$tmp_exe" "$tmp_in" 2> "$tmp_err" + local exit_code=$? + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ "$exit_code" -ne 0 ]; then + report_test_failure "CONFORMANCE COMPILE TEST" "$tmp_in" "$tmp_exe" \ + "0" "$exit_code" "$(< "$tmp_err")" "" "$tmp_err" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + +function try_failure_output() +{ + local expected_text="$1" + local input=$(cat) + test_selected || return 0 + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + local tmp_log="$(mktemp)" + echo "$input" > "$tmp_in" + $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" > "$tmp_log" 2>&1 + local compile_status=$? + local run_status=0 + if [ "$compile_status" -eq 0 ]; then + chmod +x "$tmp_exe" + ${TARGET_EXEC:-} "$tmp_exe" >> "$tmp_log" 2>&1 + run_status=$? + fi + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ "$compile_status" -ne 0 ] || [ "$run_status" -eq 0 ] \ + || ! grep -Fq -- "$expected_text" "$tmp_log"; then + report_test_failure "FAILURE OUTPUT TEST" "$tmp_in" "$tmp_exe" \ + "non-zero; $expected_text" "$run_status" "$(< "$tmp_log")" \ + "" "$tmp_log" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + +function try_compile_error_message() +{ + local expected="$1" + local input=$(cat) + test_selected || return 0 + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + local tmp_log="$(mktemp)" + echo "$input" > "$tmp_in" + + $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" > "$tmp_log" 2>&1 + local exit_code=$? + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ "$exit_code" -eq 0 ] || [ "$exit_code" -gt 128 ] \ + || ! grep -Fq -- "$expected" "$tmp_log"; then + report_test_failure "COMPILE ERROR MESSAGE TEST" "$tmp_in" "$tmp_exe" \ + "$expected" "$exit_code" "$(< "$tmp_log")" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + +# Verify a successful compilation emits a specific diagnostic. This is used for +# C constructs that are permitted but deserve a warning, such as casting away +# const qualification. +function try_compile_warning() +{ + local expected="$1" + local extra_flags="${2:-}" + local input=$(cat) + test_selected || return 0 + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + local tmp_log="$(mktemp)" + echo "$input" > "$tmp_in" + + $SHECC $SHECC_CFLAGS $extra_flags -o "$tmp_exe" "$tmp_in" > "$tmp_log" 2>&1 + local exit_code=$? + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ "$exit_code" -ne 0 ] || ! grep -Fq -- "$expected" "$tmp_log"; then + report_test_failure "COMPILE WARNING TEST" "$tmp_in" "$tmp_exe" \ + "$expected" "$exit_code" "$(< "$tmp_log")" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + +# Count the phase-2 instructions of one function that match an extended regular +# expression, in the --dump-ir output of an inline program. Some behaviour, such +# as each access to a volatile object reaching the generated code, cannot be +# observed by running the program. Usage: try_ir_count +# << EOF +function try_ir_count() +{ + local expected="$1" + local func="$2" + local pattern="$3" + local input=$(cat) + test_selected || return 0 + local tmp_in="$(mktemp --suffix .c)" + local tmp_exe="$(mktemp)" + local tmp_ir="$(mktemp)" + local tmp_err="$(mktemp)" + echo "$input" > "$tmp_in" + + $SHECC $SHECC_CFLAGS --dump-ir -o "$tmp_exe" "$tmp_in" > "$tmp_ir" \ + 2> "$tmp_err" + local actual=$? + if [ "$actual" -eq 0 ]; then + + # The phase-2 dump follows the phase-1 one; in it a function starts with + # its name in the first column and its instructions are indented. + actual=$(awk -v fn="$func:" ' + // { ph2 = 1; next } + ph2 && /^[^\t]/ { in_fn = ($0 == fn); next } + ph2 && in_fn' "$tmp_ir" | grep -cE -- "$pattern") + fi + + ((TOTAL_TESTS++)) + ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) + if [ "$actual" != "$expected" ]; then + report_test_failure "IR COUNT TEST ($func: $pattern)" "$tmp_in" \ + "$tmp_exe" "$expected" "$actual" "$(< "$tmp_ir")" "" "$tmp_err" + else + ((PASSED_TESTS++)) + ((CATEGORY_PASSED["$CURRENT_CATEGORY"]++)) + show_progress + fi +} + function items() { local expected="$1" @@ -405,7 +635,7 @@ function try_large() # Wrap the input to print the return value cat > "$tmp_in" << EOF -int printf(char *format, ...); +int printf(const char *format, ...); $input int main() { int result = test_function(); @@ -478,6012 +708,28896 @@ try_file 0 'F(10) = 55' "$TESTS_DIR/fib.c" try_file 0 $'1\nHello World' "$TESTS_DIR/hello.c" try_file 0 '' "$TESTS_DIR/strength-reduce.c" +# The section header table closes an ELF32 image, so e_shoff plus its extent +# must reach exactly the end of the file, including the page padding a static +# image inserts before .data. ELF64 output carries no section headers. +try_ 0 << EOF +#include +int main(int argc, char **argv) { + FILE *f = fopen(argv[0], "rb"); + char header[52]; + int size = 0; + int c; + if (!f) + return 2; + while ((c = fgetc(f)) != -1) { + if (size < 52) + header[size] = c; + size++; + } + fclose(f); + if (size < 52 || header[4] != 1) + return 0; + int shoff = (header[32] & 255) | ((header[33] & 255) << 8) | + ((header[34] & 255) << 16) | ((header[35] & 255) << 24); + int shentsize = (header[46] & 255) | ((header[47] & 255) << 8); + int shnum = (header[48] & 255) | ((header[49] & 255) << 8); + return shoff + shentsize * shnum != size; +} +EOF + # Category: Basic Literals and Constants begin_category "Literals and Constants" "Testing integer, character, and string literals" -# just a number -expr 0 0 -expr 42 42 - -# octal constant (satisfying re(0[0-7]+)) -expr 10 012 -expr 65 0101 - -# Category: Arithmetic Operations -begin_category "Arithmetic Operations" "Testing +, -, *, /, % operators" - -declare -a arithmetic_tests=( - "42 24+18" - "30 58-28" - "10 5*2" - "4 16>>2" - "20 8+3*4" - "54 (11-2)*6" - "10 9/3+7" - "8 8/(4-3)" - "35 8+3*5+2*6" - "55 1+2+3+4+5+6+7+8+9+10" - "55 ((((((((1+2)+3)+4)+5)+6)+7)+8)+9)+10" - "55 1+(2+(3+(4+(5+(6+(7+(8+(9+10))))))))" - "210 1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+(14+(15+(16+(17+(18+(19+20))))))))))))))))))" - "11 1+012" - "25 017+012" - "2 5%3" -) - -run_expr_tests arithmetic_tests -expr 6 "111 % 7" - -try_output 0 "1 1 -1 -1" << EOF -int v1 = 5 % 4; -int v2 = 5 % -4; -int v3 = -5 % 4; -int v4 = -5 % -4; -int main() { - printf("%d %d %d %d", v1, v2, v3, v4); - return 0; -} +try_compile_error << EOF +int main(void) { return 1.5; } EOF - try_compile_error << EOF -int value = 1 / 0; -int main() { return value; } +int main(void) { return 1e3; } EOF - try_compile_error << EOF -int value = 1 % 0; -int main() { return value; } +int main(void) { return .5; } +EOF +try_compile_error << EOF +int main(void) { return 0x1p3; } +EOF +try_compile_error << EOF +int main(void) { return 3.25F; } +EOF +try_compile_error << EOF +int main(void) { return 0x1.8p+2L; } +EOF +try_compile_error << EOF +int main(void) { return 0x.8p2; } +EOF +try_compile_error << EOF +int main(void) { return 0x.ABp2; } +EOF +try_compile_error << EOF +int main(void) { return 0x.p1; } EOF -# Category: Overflow Behavior -begin_category "Overflow Behavior" "Testing integer overflow handling" +# A hexadecimal integer needs at least one digit after its prefix, including +# where a constant expression would otherwise read the bare prefix as zero. +try_compile_error_message "expected hex digit after 0x" << EOF +enum { E = 0x }; +int main(void) { return E; } +EOF +try_compile_error_message "expected hex digit after 0x" << EOF +int global = 0X; +int main(void) { return global; } +EOF +try_compile_error_message "expected hex digit after 0x" << EOF +#if 0x == 0 +#endif +int main(void) { return 0; } +EOF +try_compile_error_message "expected hex digit after 0x" << EOF +int main(void) { return 0xg; } +EOF -try_output 0 "-2147483647" << EOF -int main() -{ - int a = 2147483647; - a += 2; - printf("%d\n", a); - return 0; -} +# A floating literal whose digits fill the token buffer must be diagnosed before +# its point and suffix are appended, not written past the buffer. +try_compile_error_message "Token too long" << EOF +int main(void) { return sizeof($(printf '1%.0s' {1..255}).f); } EOF -try_output 0 "-32767" << EOF -int main() { - short a = 32767; - a += 2; - printf("%d\n", a); - return 0; +# Record bodies nested past the type table are diagnosed, not aborted: each one +# takes a type entry before its members are read. +try_compile_error_message "Maximum number of types exceeded" << EOF +struct deep { $(printf 'struct { %.0s' {1..300}) int x; $(printf '} m;%.0s' {1..300}) }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { return 08.5; } +EOF +try_compile_error << EOF +#define FLOAT_MACRO 1.25e+1F +int main(void) { return FLOAT_MACRO; } +EOF +try_compile_error << EOF +#if 1.0 +#endif +int main(void) { return 0; } +EOF +try_compile_error << EOF +int float; +EOF +try_compile_error << EOF +double value; +EOF +try_compile_error << EOF +int main(void) { float value; return 0; } +EOF +try_compile_error << EOF +int main(void) { return (float)1; } +EOF +try_ "$((4 + 8 + (PTR_SZ == 8 ? 16 : 8) + PTR_SZ))" << EOF +int main(void) { + return sizeof(float) + sizeof(double) + sizeof(long double) + + sizeof(float *); } EOF - -try_output 0 "-127" << EOF -int main() { - char a = 127; - a += 2; - printf("%d\n", a); - return 0; +try_ "$((4 + 8 + (PTR_SZ == 8 ? 16 : 8)))" << EOF +typedef float float_alias; +typedef double double_alias; +typedef long double long_double_alias; +int main(void) { + return sizeof(float_alias) + sizeof(double_alias) + + sizeof(long_double_alias); } EOF +try_compile_error << EOF +int main(void) { return sizeof(unsigned double); } +EOF +try_compile_error << EOF +int main(void) { return sizeof(long float); } +EOF +try_compile_error << EOF +typedef float float_alias; +float_alias value; +EOF +try_compile_error << EOF +typedef double double_alias; +int identity(double_alias value) { return 0; } +EOF +try_compile_error << EOF +typedef float float_alias; +int main(void) { return (float_alias)1; } +EOF -# Category: Comparison Operations -begin_category "Comparison Operations" "Testing relational and equality operators" - -declare -a comparison_tests=( - "1 10>5" - "1 3+3>5" - "0 30==20" - "0 5>=10" - "1 5>=5" - "1 30!=20" - "1 010==8" - "1 011<11" - "0 021>=21" - "1 (012-5)==5" - "16 0100>>2" - "18 ~0355" -) - -run_expr_tests comparison_tests - -# Category: Logical Operations -begin_category "Logical Operations" "Testing logical AND, OR, NOT operators" - -declare -a logical_tests=( - "0 !237" - "18 ~237" - "0 0||0" - "1 1||0" - "1 1||1" - "0 0&&0" - "0 1&&0" - "1 1&&1" -) - -run_expr_tests logical_tests - -# Category: Bitwise Operations -begin_category "Bitwise Operations" "Testing bitwise shift, AND, OR, XOR operators" - -declare -a bitwise_tests=( - "16 2<<3" - "32 256>>3" -) - -run_expr_tests bitwise_tests -try_output 0 "128 59926 -6 -4 -500283" << EOF -int main() { - printf("%d %d %d %d %d", 32768 >> 8, 245458999 >> 12, -11 >> 1, -16 >> 2, -1000565 >> 1); - return 0; +try_ 0 << EOF +int main(void) { + return sizeof(2147483648U) != sizeof(unsigned int) || + 0xffffffff > -1 || (0xffffffff >> 31) != 1; } EOF -expr 239 "237 | 106" -declare -a more_bitwise_tests=( - "135 237^106" - "104 237&106" -) - -run_expr_tests more_bitwise_tests -# Category: Return Statements -begin_category "Return Statements" "Testing return statement functionality" - -declare -a return_tests=( - "1 return 1;" - "42 return 2*21;" -) +# A U-suffixed value above the one-word range selects unsigned long long. +try_ 0 << EOF +int main(void) { return sizeof(4294967296U) != 8; } +EOF -run_items_tests return_tests +try_ 0 << EOF +enum { global_unsigned_shift_count = 1 }; +unsigned int global_unsigned_shift = 0xffffffff >> 31; +int global_unsigned_compare = 0xffffffff > -1; +unsigned int global_unsigned_quotient = 0xffffffff / 2; +unsigned int global_unsigned_enum_shift = + 0xffffffff >> global_unsigned_shift_count; +int main(void) { + return global_unsigned_shift != 1U || global_unsigned_compare || + global_unsigned_quotient != 2147483647U || + global_unsigned_enum_shift != 2147483647U; +} +EOF -# Category: Variables and Assignments -begin_category "Variables and Assignments" "Testing variable declarations and assignments" +try_ 0 << EOF +int static_unsigned_initializer(void) { + enum { local_unsigned_shift_count = 1 }; + static unsigned int value = 0xffffffff >> local_unsigned_shift_count; + return value != 2147483647U; +} +int main(void) { return static_unsigned_initializer(); } +EOF -declare -a variable_tests=( - "10 int var; var = 10; return var;" - "42 int va; int vb; va = 11; vb = 31; int vc; vc = va + vb; return vc;" - "50 int v; v = 30; v = 50; return v;" - "25 short s; s = 25; return s;" - "50 short sa = 20; short sb = 30; short sc = sa + sb; return sc;" -) +try_ 0 << EOF +int main(void) { + return sizeof(int[2]) != 2 * sizeof(int) || + sizeof(int[2][3]) != 6 * sizeof(int); +} +EOF -run_items_tests variable_tests +# A sizeof result has type size_t in every scope, so it converts a signed +# operand to unsigned just as the file-scope constant does. +try_ 0 << EOF +int global_sizeof_wraps = (sizeof(int) - 5) > 0; +int main(void) { + int x = 3; + int arr[4]; + int n = -1; + long long wide = (long long) (sizeof(int) - 5); + return !((sizeof(int) - 5) > 0) || -1 < sizeof(int) || + !((sizeof x - 5) / 2 > 100) || + n < sizeof(arr) / sizeof(arr[0]) || !global_sizeof_wraps || + wide != (sizeof(size_t) == 8 ? -1LL : 4294967295LL); +} +EOF -# Narrow signed values must stay negative through promotion and through a store -# and reload. An LP64 backend holds them in a 64-bit register, so a load that -# zero-extends or a promotion that forgets to extend turns a small negative -# number into a large positive one. The array-element form belongs here too, but -# it fails on the Arm backend, whose char elements load zero-extended, so it -# stays in tests/arm64-abi.sh until that is fixed. -try_ 42 << EOF -int main() { - char c = -5; - short s = -1000; - int ci = c; - int si = s; - if (ci != -5) - return 1; - if (si != -1000) - return 2; - if (c >= 0) - return 3; - if (s >= 0) - return 4; - return 42; +# Unsigned and long long constant operands fold with their own conversions, so a +# zero-valued unsigned constant expression is still a null pointer constant. +try_ 0 << EOF +int three(void) { return 3; } +int main(void) { + int (*callback)(void) = three; + int zero = 0; + return (callback != (0UL - 0UL)) + (callback != (0ULL - 0ULL)) + + ((1 ? callback : sizeof(int) - sizeof(int))() == 3) != + 3 || + (4000000000U / 3U) != 1333333333U || (0x80000000U >> 31) != 1U || + (-1 < 0U) || (-1LL < 0U) != 1 || (-7LL / 2LL) != -3LL || + (0xFFFFFFFFFFFFFFFFULL / 3ULL) != 0x5555555555555555ULL || + (zero && 1U / (sizeof(int) - sizeof(int))); } EOF -# A pointer is wider than an int on an LP64 target, so testing one for truth has -# to consider the whole value, not just its low word. No fixture can force the -# case that separates the two, since pinning a pointer whose low word is zero -# needs a 64-bit literal and shecc has no integer constant that wide. What is -# testable is that every path which tests an address agrees: the backend emits a -# different width for a branch, for a logical negation and for a comparison, so -# each is reached here with a null and a non-null pointer. -try_ 42 << EOF -struct holder { - int *ptr; -}; +try_ 0 << EOF +int global_abstract_array_size = sizeof(int[2][3]); +int main(void) { return global_abstract_array_size != 6 * sizeof(int); } +EOF -int *pick(int *p, int take) -{ - if (take) - return p; - return 0; +try_ 0 << EOF +struct abstract_array_pair { int first; int second; }; +int global_abstract_record_array_size = + sizeof(struct abstract_array_pair[2]); +int main(void) { + return global_abstract_record_array_size != + 4 * sizeof(int); } +EOF -int main() { - int v = 42; - int *p = &v; - int *n = 0; - struct holder h; - int seen = 0; +try_ 0 << EOF +int global_pointer_to_array_size = sizeof(int (*)[2]); +int global_array_of_pointer_size = sizeof(int (*[2])); +int global_pointer_to_matrix_size = sizeof(int (*)[2][3]); +int global_matrix_of_pointer_size = sizeof(int (*[2][3])); +int main(void) { + return global_pointer_to_array_size != sizeof(int *) || + global_array_of_pointer_size != 2 * sizeof(int *) || + global_pointer_to_matrix_size != sizeof(int *) || + global_matrix_of_pointer_size != 6 * sizeof(int *); +} +EOF - if (!p) +try_ 0 << EOF +int global_function_pointer_size = sizeof(int (*)(void)); +int global_typed_function_pointer_size = sizeof(int (*)(int, const char *)); +int global_unprototyped_function_pointer_size = sizeof(int (*)()); +int global_variadic_function_pointer_size = sizeof(int (*)(int, ...)); +int global_float_function_pointer_size = + sizeof(int (*)(float, double, long double)); +int global_nested_function_pointer_size = sizeof(int (*(*)(int))[2]); +int global_returned_function_pointer_size = sizeof(int (*(*)(int))(int)); +int global_deep_returned_function_pointer_size = + sizeof(int (*(*(*)(int))(int))(int)); +int global_mixed_returned_function_pointer_size = + sizeof(int (*(*(*)(int))[2])(int)); +int global_pointer_to_callback_array_size = sizeof(int (*(*)[2])(int)); +int global_callback_array_returning_callback_size = + sizeof(int (*(*[2])(int))(int)); +int global_matrix_of_callbacks_size = sizeof(int (*[2][3])(int)); +int global_matrix_of_callbacks_returning_callbacks_size = + sizeof(int (*(*[2][3])(int))(int)); +int global_callback_array_returning_array_size = + sizeof(int (*(*[2])(int))[3]); +int global_array_of_callback_array_pointers_size = + sizeof(int (*(*[2])[3])(int)); +int global_matrix_of_callback_array_pointers_size = + sizeof(int (*(*[2][3])[4])(int)); +int main(void) { + return sizeof(int (*)(void)) != sizeof(int *) || + global_function_pointer_size != sizeof(int *) || + sizeof(int (*)(int, const char *)) != sizeof(int *) || + global_typed_function_pointer_size != sizeof(int *) || + sizeof(int (*)()) != sizeof(int *) || + global_unprototyped_function_pointer_size != sizeof(int *) || + sizeof(int (*)(int, ...)) != sizeof(int *) || + global_variadic_function_pointer_size != sizeof(int *) || + sizeof(int (*)(float, double, long double)) != sizeof(int *) || + global_float_function_pointer_size != sizeof(int *) || + sizeof(int (*(*)(int))[2]) != sizeof(int *) || + global_nested_function_pointer_size != sizeof(int *) || + sizeof(int (*(*)(int))(int)) != sizeof(int *) || + global_returned_function_pointer_size != sizeof(int *) || + sizeof(int (*(*(*)(int))(int))(int)) != sizeof(int *) || + global_deep_returned_function_pointer_size != sizeof(int *) || + sizeof(int (*(*(*)(int))[2])(int)) != sizeof(int *) || + global_mixed_returned_function_pointer_size != sizeof(int *) || + sizeof(int (*(*)[2])(int)) != sizeof(int *) || + global_pointer_to_callback_array_size != sizeof(int *) || + sizeof(int (*(*[2])(int))(int)) != 2 * sizeof(int *) || + global_callback_array_returning_callback_size != 2 * sizeof(int *) || + sizeof(int (*[2][3])(int)) != 6 * sizeof(int *) || + global_matrix_of_callbacks_size != 6 * sizeof(int *) || + sizeof(int (*(*[2][3])(int))(int)) != 6 * sizeof(int *) || + global_matrix_of_callbacks_returning_callbacks_size != + 6 * sizeof(int *) || + sizeof(int (*(*[2])(int))[3]) != 2 * sizeof(int *) || + global_callback_array_returning_array_size != 2 * sizeof(int *) || + sizeof(int (*(*[2])[3])(int)) != 2 * sizeof(int *) || + global_array_of_callback_array_pointers_size != + 2 * sizeof(int *) || + sizeof(int (*(*[2][3])[4])(int)) != 6 * sizeof(int *) || + global_matrix_of_callback_array_pointers_size != + 6 * sizeof(int *); +} +EOF + +# Pointers of a declarator level apply before its array suffix, and a grouped +# inner declarator applies after the suffix that follows it. A typedef array +# keeps its own bounds when an abstract declarator adds more. +try_ 0 << EOF +typedef int abstract_triple[3]; +int global_pointer_array_size = sizeof(int *[2]); +int global_array_of_array_pointers_size = sizeof(int (*[2])[3]); +int global_typedef_array_size = sizeof(abstract_triple[2]); +int pointer_array_bound[sizeof(char *const[3])]; +enum { array_of_array_pointers_size = sizeof(int (*[4])[3]) }; +int main(void) +{ + int local_bound[sizeof(int *[2][2])]; + switch (2 * sizeof(int *)) { + case sizeof(int *[2]): + break; + default: return 1; - if (n) - return 2; - if (p == 0) - return 3; - if (n != 0) - return 4; - - while (n) - return 5; - - seen = p ? 1 : 0; - if (!seen) - return 6; - seen = n ? 1 : 0; - if (seen) - return 7; - - if (p && !n) - seen = 2; - if (seen != 2) - return 8; - if (n || !p) - return 9; - - /* A pointer that reaches the test through a return value or a struct - * field has been through a store and a reload on the way. - */ - if (!pick(p, 1)) - return 10; - if (pick(p, 0)) - return 11; - - h.ptr = n; - if (h.ptr) - return 12; - h.ptr = p; - if (!h.ptr) - return 13; - - int *q = h.ptr; - return *q; + } + return global_pointer_array_size != 2 * sizeof(int *) || + global_array_of_array_pointers_size != 2 * sizeof(int *) || + global_typedef_array_size != 6 * sizeof(int) || + sizeof(pointer_array_bound) != 3 * sizeof(char *) * sizeof(int) || + array_of_array_pointers_size != 4 * sizeof(int *) || + sizeof(local_bound) != 4 * sizeof(int *) * sizeof(int) || + sizeof(int *[2]) != 2 * sizeof(int *) || + sizeof(int (*[2])[3]) != 2 * sizeof(int *) || + sizeof(char ([2])[3]) != 6 || + sizeof(abstract_triple[2]) != 6 * sizeof(int); } EOF -# Category: Compound Literals -begin_category "Compound Literals" "Testing C99 compound literal features" +try_compile_error << EOF +struct incomplete_sizeof_record; +int main(void) { return sizeof(struct incomplete_sizeof_record); } +EOF +try_compile_error << EOF +union incomplete_sizeof_union; +int main(void) { return sizeof(union incomplete_sizeof_union); } +EOF +try_compile_error << EOF +int main(void) { return sizeof(int (*)(int)[2]); } +EOF +try_compile_error << EOF +enum { invalid_callback_array = sizeof(int [2](int)) }; +int main(void) { return invalid_callback_array; } +EOF -# Compound literal support - C90/C99 compliant implementation Basic struct -# compound literals (verified working) -try_ 42 << EOF -typedef struct { int x; int y; } point_t; -int main() { - point_t p = {42, 100}; - return p.x; +try_ 0 << EOF +int global_nested_designated[2][2][2] = { [0] = { [1] = { 2 } }, 3, 4, 5 }; +int nested_designated(void) { + static int local[2][2][2] = { [0] = { [1] = { 2 } }, 3, 4, 5 }; + return local[0][1][0] != 2 || local[0][1][1] != 0 || + local[1][0][0] != 3 || local[1][0][1] != 4 || + local[1][1][0] != 5 || global_nested_designated[0][1][0] != 2 || + global_nested_designated[1][0][0] != 3 || + global_nested_designated[1][0][1] != 4 || + global_nested_designated[1][1][0] != 5; } +int main(void) { return nested_designated(); } EOF -try_ 42 << EOF -typedef struct { short x; short y; } point_t; -int main() { - point_t p = {42, 100}; - return p.x; +# A C99 designator may traverse a record member and then select an array element +# in a file-scope aggregate initializer. +try_ 0 << EOF +struct item { int values[3]; }; +struct outer { struct item member; }; +struct outer value = { + .member.values[2] = 7, + .member.values[0] = 3, +}; +int main(void) { + return value.member.values[0] != 3 || value.member.values[1] != 0 || + value.member.values[2] != 7; } EOF -try_ 100 << EOF -typedef struct { int x; int y; } point_t; -int main() { - point_t p = {42, 100}; - return p.y; +try_ 0 << EOF +int nested_hyperplane_designated(void) { + int values[2][2][2][2] = { [0] = { [1] = { [1] = { 2 } } }, 3, 4, 5 }; + return values[0][1][1][0] != 2 || values[0][1][1][1] != 0 || + values[1][0][0][0] != 3 || values[1][0][0][1] != 4 || + values[1][0][1][0] != 5; } +int main(void) { return nested_hyperplane_designated(); } EOF -try_ 5 << EOF -typedef struct { int x; } s_t; -int main() { - s_t s = {5}; - return s.x; +# A two-subscript designator of a four-dimensional array names a plane, so its +# braces hold rows. Parsing them as a whole hyperplane dropped the second row. +try_ 0 << EOF +int global_plane[2][2][2][2] = { [1][1] = { {1, 2}, {3, 4} }, [0][1][1] = { 5, 6 } }; +int main(void) { + int local_plane[2][2][2][2] = { [1][1] = { {1, 2}, {3, 4} }, [0][1][1] = { 5, 6 } }; + static int static_plane[2][2][2][2] = { [1][1] = { {1, 2}, {3, 4} }, [0][1][1] = { 5, 6 } }; + return global_plane[1][1][1][0] + global_plane[1][1][1][1] + + global_plane[0][1][1][1] + local_plane[1][1][1][0] + + local_plane[1][1][1][1] + local_plane[0][1][1][1] + + static_plane[1][1][1][0] + static_plane[1][1][1][1] + + static_plane[0][1][1][1] != 39 || local_plane[1][1][0][1] != 2 || + local_plane[1][0][1][1] != 0; } EOF -# Multi-field struct compound literals -try_ 30 << EOF -typedef struct { int a; int b; int c; } data_t; -int main() { - data_t d = {10, 20, 30}; - return d.c; -} +try_ 0 << EOF +int global_unbraced_designated[2][2][2] = { [0] = { [1] = 2, 3 }, 4, 5 }; +int nested_unbraced_designated(void) { + int automatic_values[2][2][2] = { [0] = { [1] = 2, 3 }, 4, 5 }; + static int static_values[2][2][2] = { [0] = { [1] = 2, 3 }, 4, 5 }; + return automatic_values[0][1][0] != 2 || + automatic_values[0][1][1] != 3 || + automatic_values[1][0][0] != 4 || + automatic_values[1][0][1] != 5 || + static_values[0][1][0] != 2 || static_values[0][1][1] != 3 || + static_values[1][0][0] != 4 || static_values[1][0][1] != 5 || + global_unbraced_designated[0][1][0] != 2 || + global_unbraced_designated[0][1][1] != 3 || + global_unbraced_designated[1][0][0] != 4 || + global_unbraced_designated[1][0][1] != 5; +} +int main(void) { return nested_unbraced_designated(); } EOF -# Array initialization -try_ 20 << EOF -int main() { - int arr[3] = {10, 20, 30}; - return arr[1]; -} +try_ 0 << EOF +int global_unbraced_hyperplane[2][2][2][2] = + { [0] = { [1] = 2, 3, 4, 5 }, 6, 7, 8 }; +int nested_unbraced_hyperplane(void) { + int automatic_values[2][2][2][2] = + { [0] = { [1] = 2, 3, 4, 5 }, 6, 7, 8 }; + static int static_values[2][2][2][2] = + { [0] = { [1] = 2, 3, 4, 5 }, 6, 7, 8 }; + return automatic_values[0][1][0][0] != 2 || + automatic_values[0][1][0][1] != 3 || + automatic_values[0][1][1][0] != 4 || + automatic_values[0][1][1][1] != 5 || + automatic_values[1][0][0][0] != 6 || + automatic_values[1][0][0][1] != 7 || + automatic_values[1][0][1][0] != 8 || + static_values[0][1][0][0] != 2 || + static_values[0][1][0][1] != 3 || + static_values[0][1][1][0] != 4 || + static_values[0][1][1][1] != 5 || + static_values[1][0][0][0] != 6 || + static_values[1][0][0][1] != 7 || + static_values[1][0][1][0] != 8 || + global_unbraced_hyperplane[0][1][0][0] != 2 || + global_unbraced_hyperplane[0][1][0][1] != 3 || + global_unbraced_hyperplane[0][1][1][0] != 4 || + global_unbraced_hyperplane[0][1][1][1] != 5 || + global_unbraced_hyperplane[1][0][0][0] != 6 || + global_unbraced_hyperplane[1][0][0][1] != 7 || + global_unbraced_hyperplane[1][0][1][0] != 8; +} +int main(void) { return nested_unbraced_hyperplane(); } EOF -# Extended compound literal tests (C99-style brace initialization) - -# Additional struct compound literals with different field counts -try_ 12 << EOF -typedef struct { int a; int b; int c; int d; } quad_t; -int main() { - quad_t q = {3, 4, 5, 0}; - return q.a + q.b + q.c; /* 3 + 4 + 5 = 12 */ -} +try_ 1 << EOF +int type_only_float_parameter = sizeof(int (*)(double)); +double still_an_unsupported_object; EOF -# Array of int initialization -try_ 35 << EOF -int main() { - int values[4] = {5, 10, 15, 5}; - return values[0] + values[1] + values[2] + values[3]; /* 5 + 10 + 15 + 5 = 35 */ +try_ 0 << EOF +typedef int (*float_callback_type)(double); +int global_float_callback_type_size = sizeof(float_callback_type); +int main(void) { + return sizeof(float_callback_type) != sizeof(int *) || + global_float_callback_type_size != sizeof(int *); } EOF -# Array initialization with struct compound literals - Advanced C99 features -# NOTE: These tests document the current implementation status - -# Test: Single element array of struct -try_ 10 << EOF -struct point { int x; int y; }; -int main() { - /* Single element struct arrays now work correctly */ - struct point pts[1] = { {10, 20} }; - return pts[0].x; /* Returns 10 correctly */ -} +try_ 1 << EOF +typedef int (*float_callback_type)(double); +double still_an_unsupported_object; EOF -# Test: Multi-element array of structs try_ 1 << EOF -struct point { int x; int y; }; -int main() { - /* Multi-element arrays: first element after index 0 may not initialize correctly */ - struct point pts[2] = { {1, 2}, {3, 4} }; - return pts[0].x; /* Expected: 1, Actual: 1 (may be coincidental) */ -} +typedef int (*float_callback_type)(double); +float_callback_type still_an_unsupported_callback_object; EOF -# Test: Mixed array and struct compound literals -try_ 40 << EOF -struct point { int x; int y; }; -int main() { - /* Verify that regular int arrays still work correctly */ - int arr[3] = {10, 15, 10}; - - /* Verify that individual struct initialization still works */ - struct point p = {5, 0}; - - return arr[0] + arr[1] + arr[2] + p.x; /* 10 + 15 + 10 + 5 = 40 */ +try_ 0 << EOF +int global_float_type_size = sizeof(float) + sizeof(double) + + sizeof(long double); +typedef double global_sizeof_real; +int global_typedef_float_array_size = sizeof(global_sizeof_real[2]); +int global_float_array_size = sizeof(float[2][3]); +int global_double_pointer_size = sizeof(double (*)[2]); +int main(void) { + return global_float_type_size != 4 + 8 + (sizeof(void *) == 8 ? 16 : 8) || + global_float_array_size != 6 * sizeof(float) || + global_typedef_float_array_size != 2 * sizeof(double) || + global_double_pointer_size != sizeof(double *); } EOF - -# Global arrays of structs with compound literals -try_ 7 << EOF -struct point { int x; int y; }; -struct point gpts1[] = { {3, 4} }; -int main() { - return gpts1[0].x + gpts1[0].y; /* 3 + 4 = 7 */ -} +try_compile_error << EOF +int invalid_global_float_type_size = sizeof(unsigned double); EOF - -try_ 7 << EOF -struct point { int x; int y; }; -struct point gpts2[2] = { {1, 2}, {3, 4}, }; -int main() { - return gpts2[1].x + gpts2[1].y; /* 3 + 4 = 7 */ -} +try_compile_error << EOF +int identity(float value) { return 0; } +EOF +try_compile_error << EOF +int identity(long double value) { return 0; } +EOF +try_compile_error << EOF +long double value; +EOF +try_compile_error << EOF +int _Complex; +EOF +try_compile_error << EOF +int _Imaginary; +EOF +try_compile_error << EOF +#define FLOAT_TYPE float +FLOAT_TYPE value; EOF -try_ 9 << EOF -typedef struct { int x; int y; } point_t; -point_t gpts3[] = { {4, 5} }; -int main() { - return gpts3[0].x + gpts3[0].y; /* 4 + 5 = 9 */ -} +# just a number +expr 0 0 + +# C99 6.4.4.4 requires at least one character between the quotes. +try_compile_error_message "Empty character constant" << EOF +int main(void) { return ''; } +EOF +try_compile_error_message "Empty character constant" << EOF +int main(void) { return L''; } +EOF +try_compile_error_message "Empty character constant" << EOF +#if '' == 0 +#endif +int main(void) { return 0; } EOF -# Enhanced compound literal tests - C99 features with non-standard extensions -# These tests validate both standard C99 compound literals and the non-standard -# behavior required by the test suite (array compound literals in scalar -# contexts) +# A newline cannot appear in a character constant or a string literal (C99 +# 6.4.4.4, 6.4.5); only a backslash-newline, which phase 2 removes first, may +# continue one onto the next line. +try_compile_error_message "Unenclosed character literal" << EOF +int main(void) { return 'a +'; } +EOF +try_compile_error_message "Unenclosed character literal" << EOF +int main(void) { return L' +'; } +EOF +try_compile_error_message "Unenclosed string literal" << EOF +int main(void) { return sizeof("a +b"); } +EOF +try_compile_error_message "Unenclosed string literal" << EOF +int main(void) { return sizeof(L"a +b"); } +EOF +try_ 3 << 'EOF' +int main(void) { return sizeof("a\ +b") + 'c\ +' - 'c'; } +EOF -# Test: Array compound literal assigned to scalar int (non-standard) -try_ 100 << EOF -int main() { - /* Non-standard: Assigns first element of array to scalar int */ - int x = (int[]){100, 200, 300}; - return x; -} +# The current execution wide-character representation is int. Wide character +# constants therefore share ordinary scalar expression and ICE lowering. +try_ 3 << EOF +enum { wide_a = L'A' }; +static int wide_b = L'\x42'; +int main(void) { return (wide_a == 65) + (wide_b == 66) + (L'C' == 67); } +EOF +try_ 1 << EOF +#define WIDE_A L'A' +int main(void) { return WIDE_A == 65; } EOF -# Test: Array compound literal assigned to scalar short (non-standard) -try_ 100 << EOF -int main() { - /* Non-standard: Assigns first element of array to scalar short */ - short x = (short[]){100, 200, 300}; - return x; +try_ 0 << EOF +int main(void) { + wchar_t values[] = L"ab"; + return sizeof L"ab" != 3 * sizeof(wchar_t) || + values[0] != 'a' || values[1] != 'b' || values[2] != 0; } EOF -# Test: Array compound literal in arithmetic expression -try_ 150 << EOF -int main() { - int a = 50; - /* Non-standard: Uses first element (100) in addition */ - int b = a + (int[]){100, 200}; - return b; +try_ 0 << EOF +struct item { wchar_t text[3]; }; +int main(void) { + struct item local = { L"ab" }; + static struct item saved = { .text = L"ok" }; + return local.text[1] != 'b' || saved.text[0] != 'o' || + saved.text[2] != 0; } EOF -# Test: Array compound literal in arithmetic expression -try_ 150 << EOF -int main() { - short a = 50; - /* Non-standard: Uses first element (100) in addition */ - short b = a + (short[]){100, 200}; - return b; +try_ 0 << EOF +typedef wchar_t wide_unit; +int main(void) { + wide_unit values[] = L"x"; + return values[0] != 'x' || values[1] != 0; } EOF -# Test: Mixed scalar and array compound literals -try_ 35 << EOF -int main() { - /* Scalar compound literals work normally */ - /* Array compound literal contributes its first element (5) */ - return (int){10} + (int){20} + (int[]){5, 15, 25}; +# A string literal initializes a whole innermost row of a multidimensional +# character array, zero-padded, and never stores its address into a char. The +# block-scope case dirties the stack first so that the padding is observable. +try_ 0 << 'EOF' +struct names { int id; char name[2][8]; }; +typedef char label[4]; +char g_s[2][4] = {"ab", "cd"}; +char g_t[][3] = {"ab", "c"}; +char g_u[2][4] = {[1] = "xy"}; +struct names g_n = {7, {"alpha", "beta"}}; +wchar_t g_w[2][3] = {L"a", L"bc"}; +char g_p[2][2][3] = {{"ab", "c"}, {{"d"}}}; +label g_l[2] = {"ab", {"cd"}}; +static const char want_s[8] = {'a', 'b', 0, 0, 'c', 'd', 0, 0}; +static const char want_t[6] = {'a', 'b', 0, 'c', 0, 0}; +static const char want_u[8] = {0, 0, 0, 0, 'x', 'y', 0, 0}; +static const char want_n[16] = {'a', 'l', 'p', 'h', 'a', 0, 0, 0, + 'b', 'e', 't', 'a', 0, 0, 0, 0}; +static const char want_p[12] = {'a', 'b', 0, 'c', 0, 0, 'd', 0, 0, 0, 0, 0}; +int same(const char *p, const char *want, int n) +{ + for (int i = 0; i < n; i++) + if (p[i] != want[i]) + return 0; + return 1; +} +int check(const char *s, const char *t, int tsize, const char *u, + const struct names *n, const wchar_t *w, const char *p) +{ + if (tsize != 6 || !same(s, want_s, 8) || !same(t, want_t, 6)) + return 1; + if (!same(u, want_u, 8)) + return 2; + if (n->id != 7 || !same(n->name[0], want_n, 16)) + return 3; + if (w[0] != 'a' || w[1] || w[2] || w[3] != 'b' || w[4] != 'c' || w[5]) + return 4; + if (!same(p, want_p, 12)) + return 5; + return 0; +} +void dirty(void) +{ + char junk[256]; + for (int i = 0; i < 256; i++) + junk[i] = 'Z'; +} +int block(void) +{ + char s[2][4] = {"ab", "cd"}; + char t[][3] = {"ab", "c"}; + char u[2][4] = {[1] = "xy"}; + struct names n = {7, {"alpha", "beta"}}; + wchar_t w[2][3] = {L"a", L"bc"}; + char p[2][2][3] = {{"ab", "c"}, {{"d"}}}; + label l[2] = {"ab", {"cd"}}; + if (!same(l[0], want_s, 8)) + return 6; + return check(s[0], t[0], sizeof t, u[0], &n, w[0], p[0][0]); +} +int block_static(void) +{ + static char s[2][4] = {"ab", "cd"}; + static char t[][3] = {"ab", "c"}; + static char u[2][4] = {[1] = "xy"}; + static struct names n = {7, {"alpha", "beta"}}; + static wchar_t w[2][3] = {L"a", L"bc"}; + static char p[2][2][3] = {{"ab", "c"}, {{"d"}}}; + return check(s[0], t[0], sizeof t, u[0], &n, w[0], p[0][0]); +} +int main(void) +{ + int rc = check(g_s[0], g_t[0], sizeof g_t, g_u[0], &g_n, g_w[0], + g_p[0][0]); + if (rc || !same(g_l[0], want_s, 8)) + return rc + 6; + dirty(); + rc = block(); + if (rc) + return rc + 10; + rc = block_static(); + return rc ? rc + 20 : 0; } EOF +try_compile_error_message "String literal initializer has incompatible array element type" << 'EOF' +int values[2][3] = {"ab"}; +int main(void) { return 0; } +EOF +try_compile_error_message "String literal cannot initialize a single array element" << 'EOF' +int main(void) { char text[2][4] = {1, "abc"}; return 0; } +EOF -# Test: Return statement with array compound literal -try_ 42 << EOF -int main() { - /* Non-standard: Returns first element of array */ - return (int[]){42, 84, 126}; -} +# Braces around a string literal are allowed only when it is the character +# array's whole initializer; it cannot initialize one element of any array. +try_compile_error_message "String literal cannot initialize a single array element" << 'EOF' +char text[3] = {"ab", "c"}; +int main(void) { return text[0]; } +EOF +try_compile_error_message "String literal initializer has incompatible array element type" << 'EOF' +int main(void) { int values[3] = {"ab"}; return values[0]; } EOF -# Test: Multiple array compound literals in expression -try_ 30 << EOF -int main() { - /* Both arrays contribute their first elements: 10 + 20 = 30 */ - int result = (int[]){10, 30, 50} + (int[]){20, 40, 60}; - return result; +# A string literal is an array lvalue and takes a subscript directly; sizeof +# then measures the element and & yields its address. +try_ 0 << 'EOF' +int id(int x) { return x; } +int main(void) { + char c = "ab"[1]; + char s[3] = {"ab"[1], 'c'}; + const char *p = &"xyz"[1]; + int i = 2; + if (c != 'b' || s[0] != 'b' || s[1] != 'c' || s[2]) + return 1; + if (p[0] != 'y' || p[1] != 'z' || "hello"[i] != 'l') + return 2; + if (sizeof "ab"[1] != 1 || sizeof("abc"[0]) != 1 || + sizeof L"ab"[0] != sizeof(wchar_t)) + return 3; + if (L"hi"[1] != 'i' || -"\x05"[0] != -5 || id("xyz"[2]) != 'z' || + "ab" "cd"[3] != 'd') + return 4; + return 0; } EOF -# Test: Array compound literal with single element -try_ 99 << EOF -int main() { - int val = (int[]){99}; - return val; +# In a static initializer an element of a narrow string literal is an arithmetic +# constant and &"ab"[1] an address constant, as gcc accepts; an element of a +# wide literal is not a constant there, nor in an enumerator. +try_ 0 << 'EOF' +struct s { char k; const char *s; int v; }; +#define DECLS(S) \ + S char v1 = "ab"[1]; \ + S int v2 = 1 + "abc"[2]; \ + S char v3[3] = {"xy"[0], "xy"[1], "xy"[2]}; \ + S const char *v4 = &"hello"[3]; \ + S const char *v5 = &"hi"[0]; \ + S int v6[2] = { 1 + "ab"[1], "cd"[0] * 2 }; \ + S const wchar_t *v7 = &L"wide"[1]; \ + S struct s v8 = { "pq"[1], &"pq"[1], "a" "b"[1] }; \ + S const char *v9[2] = { &"uv"[1], &"uv"[2] }; \ + S int v10[2] = { "\xff"[0], "\x80"[0] + 0 }; +#define CHECKS \ + int r = 0; \ + if (v1 != 'b' || v2 != 1 + 'c') \ + r |= 1; \ + if (v3[0] != 'x' || v3[1] != 'y' || v3[2]) \ + r |= 2; \ + if (*v4 != 'l' || *v5 != 'h') \ + r |= 4; \ + if (v6[0] != 1 + 'b' || v6[1] != 'c' * 2 || *v7 != 'i') \ + r |= 8; \ + if (v8.k != 'q' || *v8.s != 'q' || v8.v != 'b') \ + r |= 16; \ + if (*v9[0] != 'v' || *v9[1]) \ + r |= 32; \ + if (v10[0] != -1 || v10[1] != -128) \ + r |= 64; \ + return r; +DECLS(static) +int check_file(void) { CHECKS } +int check_block(void) { DECLS(static) CHECKS } +int main(void) { return check_file() | check_block() << 7; } +EOF +try_compile_error_message "Wide string literal element is not a constant" << 'EOF' +int main(void) { static int w = L"ab"[1]; return w; } +EOF +try_compile_error << 'EOF' +enum { E = "ab"[1] }; +int main(void) { return E; } +EOF + +# A string literal plus or minus an integer constant is an address constant (C99 +# 6.6p7), on either side of the '+', for a pointer, a record member and an array +# element, in static storage and in automatic storage alike. +try_ 0 << 'EOF' +struct s { int k; const char *s; const char *t; }; +enum { TWO = 2 }; +#define DECLS(S) \ + S const char *v1 = "abc" + 1; \ + S char *v2 = 2 + "abcd"; \ + S const char *v3 = "abcdef" + 4 - 2; \ + S const char *v4 = 1 * 2 + "wxyz" - 1; \ + S const wchar_t *v5 = L"wide" + TWO; \ + S struct s v6 = { 1, "pq" + 1, (1 + 1) + "rst" }; \ + S const char *v7[3] = { "uv" + 1, 1 + "uv", "ab" "cd" + 3 }; \ + S const char *v8 = "same" + 0; \ + S char *v9 = &"ab"[1]; +#define CHECKS \ + int r = 0; \ + if (*v1 != 'b' || *v2 != 'c') \ + r |= 1; \ + if (*v3 != 'c' || *v4 != 'x') \ + r |= 2; \ + if (*v5 != 'd') \ + r |= 4; \ + if (v6.k != 1 || *v6.s != 'q' || *v6.t != 't') \ + r |= 8; \ + if (*v7[0] != 'v' || *v7[1] != 'v' || *v7[2] != 'd') \ + r |= 16; \ + if (*v8 != 's' || *v9 != 'b') \ + r |= 32; \ + return r; +DECLS(static) +int check_file(void) { CHECKS } +int check_block(void) { DECLS(static) CHECKS } +int check_local(void) { DECLS() CHECKS } +int main(void) { + return check_file() | check_block() << 6 | check_local() << 12; } EOF -# Test: Array compound literal decay to pointer in initializer try_ 0 << EOF +enum { wide_count = sizeof L"ab" / sizeof(wchar_t) }; +wchar_t *global_pointer = L"xy"; int main(void) { - int *arr = (int[]){1, 2, 3, 4, 5}; - return arr[0] != 1 || arr[4] != 5; + L"statement"; + return wide_count != 3 || global_pointer[1] != 'y'; } EOF -# Test: Passing array compound literal as pointer argument try_ 0 << EOF -int sum(int *p, int n) { - int s = 0; - for (int i = 0; i < n; i++) - s += p[i]; - return s; -} int main(void) { - int s = sum((int[]){1, 2, 3, 0, 0}, 3); - return s != 6; + wchar_t values[] = L"\u00e9"; + return sizeof values != 2 * sizeof(wchar_t) || values[0] != 0xe9 || + values[1] != 0; } EOF +try_ 0 << EOF +enum { wide_ucn = L'\u00e9' }; +int main(void) { return wide_ucn != 0xe9 || L'\u00e9' != 0xe9; } +EOF +try_flags 0 "--no-libc" << EOF +#if L'\u00e9' != 0xe9 +#error wide UCN preprocessing value is incorrect +#endif +int main(void) { return 0; } +EOF -# Test: Complex expression with compound literals -try_ 77 << EOF -int main() { - int a = 7; - /* (7 * 10) + (100 / 10) - 3 = 70 + 10 - 3 = 77 */ - int b = (a * (int){10}) + ((int[]){100, 200} / 10) - (int[]){3}; - return b; +# A hexadecimal or octal escape in a wide constant keeps its whole value, which +# only a narrow constant must fit in a byte, on every path that evaluates one. +try_ 0 << 'EOF' +#if L'\x1234' != 0x1234 || L'\777' != 0777 +#error wide escape preprocessing value is incorrect +#endif +enum { wide_hex = L'\x1234' }; +int global_hex = L'\x12345678'; +int global_units[] = {L'\777', L'\x7fffffff'}; +int main(void) +{ + int local = L'\x1234'; + int units[] = L"\x1234\777"; + + switch (local) { + case L'\x1234': + break; + default: + return 1; + } + return wide_hex != 0x1234 || global_hex != 0x12345678 || + global_units[0] != 0777 || global_units[1] != 0x7fffffff || + units[0] != L'\x1234' || units[1] != L'\777'; } EOF +try_compile_error_message "Invalid wide character escape sequence" << 'EOF' +int main(void) { return L'\x100000000'; } +EOF +try_compile_error_message "Invalid wide character escape sequence" << 'EOF' +#if L'\x100000000' +#endif +int main(void) { return 0; } +EOF +try_ 0 << EOF +struct values { int code; }; +struct values global_values = {L'\u00e9'}; +int main(void) { return global_values.code != 0xe9; } +EOF +try_ 0 << EOF +int global_values[] = {L'\u00e9'}; +int main(void) { return global_values[0] != 0xe9; } +EOF +try_ 0 << EOF +int main(void) { return sizeof L'\u00e9' != sizeof(int); } +EOF +try_ 0 << EOF +int global_size = sizeof(L'\u00e9'); +int main(void) { return global_size != sizeof(int); } +EOF +try_ 0 << EOF +int main(void) { return L'a\u00e9' != 0x61c3a9; } +EOF -# Test: Compound literal in conditional expression -try_ 25 << EOF -int main() { - int flag = 1; - /* Ternary with compound literals */ - int result = flag ? (int[]){25, 50} : (int){15}; - return result; +try_ 0 << EOF +#define LEFT L"a" +#define RIGHT L"b" +int main(void) { + wchar_t values[] = LEFT RIGHT; + return sizeof(LEFT RIGHT) != 3 * sizeof(wchar_t) || values[1] != 'b'; } EOF -# Test: Nested compound literals in function calls -try_ 15 << EOF -int add(int a, int b) { - return a + b; +# A narrow literal next to a wide one joins it, and the result is wide. +try_ 0 << 'EOF' +#define NARROW "n" +wchar_t global_values[] = "g" L"h"; +int main(void) { + wchar_t left[] = L"a" "b"; + wchar_t right[] = "c" L"d" "e"; + wchar_t *macro = NARROW L"w"; + wchar_t *escape = "\x1" L"2"; + return (sizeof left != 3 * sizeof(wchar_t) || left[1] != 'b') + + (sizeof right != 4 * sizeof(wchar_t) || right[0] != 'c' || + right[2] != 'e' || right[3] != 0) * + 2 + + (sizeof("x" L"y") != 3 * sizeof(wchar_t)) * 4 + + (macro[0] != 'n' || macro[1] != 'w') * 8 + + (escape[0] != 1 || escape[1] != '2') * 16 + + (global_values[0] != 'g' || global_values[1] != 'h') * 32; } +EOF -int main() { - /* Function arguments with compound literals */ - return add((int){5}, (int[]){10, 20, 30}); -} +try_compile_error << EOF +int main(void) { char values[] = "a" L"b"; return 0; } EOF -# Test: Array compound literal with variable initialization -try_ 60 << EOF -int main() { - int x = (int[]){10, 20, 30}; /* x = 10 */ - int y = (int[]){20, 40}; /* y = 20 */ - int z = (int[]){30}; /* z = 30 */ - return x + y + z; -} +try_compile_error << EOF +int main(void) { char values[] = L"x"; return 0; } EOF -# Test: Compound assignment with array compound literal -try_ 125 << EOF -int main() { - int sum = 25; - sum += (int[]){100, 200}; /* sum += 100 */ - return sum; -} +try_compile_error << EOF +int main(void) { wchar_t values[] = "x"; return 0; } EOF -# Test: Array compound literal in loop -try_ 55 << EOF -int main() { - int sum = 0; - for (int i = 0; i < 5; i++) { - /* Each iteration adds 10 (first element) to sum */ - sum += (int[]){10, 20, 30}; - } - return sum + (int[]){5}; /* 50 + 5 = 55 */ -} +try_compile_error << EOF +int main(void) { wchar_t values[] = L"\U00110000"; return 0; } EOF -# Test: Scalar compound literals (standard C99) -try_ 42 << EOF -int main() { - /* Standard scalar compound literals */ - int a = (int){42}; - return a; -} +try_compile_error << EOF +int main(void) { wchar_t values[] = L"\xFFFFFFFF"; return 0; } EOF -# Test: Char compound literals -try_ 65 << EOF -int main() { - char c = (char){'A'}; /* 'A' = 65 */ - return c; -} +try_compile_error << EOF +int main(void) { wchar_t values[2][3] = L"ab"; return 0; } EOF -# Test: Empty array compound literal (edge case) -try_ 0 << EOF -int main() { - /* Empty compound literal defaults to 0 */ - int x = (int[]){}; - return x; -} +try_compile_error << EOF +struct item { wchar_t values[2][3]; }; +int main(void) { struct item value = { L"ab" }; return 0; } EOF -# variable with octal literals -items 10 "int var; var = 012; return var;" -items 100 "int var; var = 10 * 012; return var;" -items 32 "int var; var = 0100 / 2; return var;" -items 65 "int var; var = 010 << 3; var += 1; return var;" - -# Category: Conditional Statements -begin_category "Conditional Statements" "Testing if/else control flow" - -# if -items 5 "if (1) return 5; else return 20;" -items 10 "if (0) return 5; else if (0) return 20; else return 10;" -items 10 "int a; a = 0; int b; b = 0; if (a) b = 10; else if (0) return a; else if (a) return b; else return 10;" -items 27 "int a; a = 15; int b; b = 2; if(a - 15) b = 10; else if (b) return a + b + 10; else if (a) return b; else return 10;" - -items 8 "if (1) return 010; else return 11;" -items 10 "int a; a = 012 - 10; int b; b = 0100 - 64; if (a) b = 10; else if (0) return a; else if (a) return b; else return 10;" - -# The values on both sides of the select, its condition, and unrelated values -# are all used after the join. This keeps the register file full when the -# allocator has to choose the select result's register. -try_ 30 << EOF -int pick(int a, int b, int c, int d, int e, int f, int g) { - int selected; - int hold = g; - if (a) - selected = b; - else - selected = c; - return a + b + c + d + e + f + hold + selected; +try_ 0 << EOF +wchar_t global_values[] = L"go"; +int main(void) { + static wchar_t local_values[] = L"ok"; + return global_values[1] != 'o' || global_values[2] != 0 || + local_values[0] != 'o' || local_values[1] != 'k'; } +EOF -int main() { - return pick(1, 2, 3, 4, 5, 6, 7); +try_ 0 << EOF +int main(void) { + wchar_t fixed[5] = L"A\0B"; + return fixed[0] != 'A' || fixed[1] != 0 || fixed[2] != 'B' || + fixed[3] != 0 || fixed[4] != 0; } EOF -# Category: Compound Statements -begin_category "Compound Statements" "Testing block scoping and compound statements" - -# compound -items 5 "{ return 5; }" -items 10 "{ int a; a = 5; { a = 5 + a; } return a; }" -items 20 "int a; a = 10; if (1) { a = 20; } else { a = 10; } return a;" -items 30 "int a; a = 10; if (a) { if (a - 10) { a = a + 1; } else { a = a + 20; } a = a - 10; } else { a = a + 5; } return a + 10;" - -# Category: Loop Constructs -begin_category "Loop Constructs" "Testing while, do-while, and for loops" - -# loop -items 55 "int acc; int p; acc = 0; p = 10; while (p) { acc = acc + p; p = p - 1; } return acc;" -items 60 "int acc; acc = 15; do { acc = acc * -2; } while (acc < 0); return acc;" -items 45 "int i; int acc; acc = 0; for (i = 0; i < 10; ++i) { acc = acc + i; } return acc;" -items 45 "int i; int j; i=0; j=0; while (i<10) { j=j+i; i=i+1; } return j;" -items 1 "int x; x=0; do {x = x + 1; break;} while (1); return x;" -items 2 "int x; x=0; do {x++; continue; abort();} while (x < 2); return x;" -items 2 "int x; x=0; while(x < 2){x++; continue; abort();} return x;" -items 7 "int i; i=0; int j; for (j = 0; j < 10; j++) { if (j < 3) continue; i = i + 1; } return i;" -items 10 "while(0); return 10;" -items 10 "while(1) break; return 10;" -items 10 "for(;;) break; return 10;" -items 0 "int x; for(x = 10; x > 0; x--); return x;" -items 30 "int i; int acc; i = 0; acc = 0; do { i = i + 1; if (i - 1 < 5) continue; acc = acc + i; if (i == 9) break; } while (i < 10); return acc;" -items 26 "int acc; acc = 0; int i; for (i = 0; i < 100; i++) { if (i < 5) continue; if (i == 9) break; acc = acc + i; } return acc;" -items 1 "int i = 0; for (;;) { i++; if (i < 4) { continue; } break; } return i == 4;" -items 14 "int n = 0; for (int i = 0;;) { i++; if (i < 14) { continue; } n = i; break; } return n;" -items 14 "int i = 0; for (; i < 20;) { i++; if (i < 14) { continue; } break; } return i;" -items 14 "int n = 0; for (int i = 0; i < 20;) { i++; if (i < 14) { continue; } n = i; break; } return n;" -items 14 "int i = 0; for (; i < 14;) { i++; } return i;" -items 0 "int i = 0; for (;; i++) { break; } return i;" - -# Category: Comments -begin_category "Comments" "Testing C-style and C++-style comment parsing" - -# C-style comments / C++-style comments Start +# A narrow string literal keeps every byte after an embedded null character, +# including across adjacent literals, in array, member and pointer uses. +try_ 10 << EOF +struct holder { char text[6]; int tag; }; +struct holder global_member = { "a\0" "bc", 7 }; +char global_array[] = "a\0bc"; +int main(void) { + struct holder local_member = { "a\0bc", 9 }; + char local_array[6] = "x\0" "yz"; + char *pointer = "p\0q"; + return (global_member.text[2] == 'b') + (global_member.text[3] == 'c') + + (global_member.tag == 7) + (sizeof(global_array) == 5) + + (global_array[3] == 'c') + (local_member.text[3] == 'c') + + (local_member.tag == 9) + (local_array[2] == 'y') + + (local_array[3] == 'z') + (pointer[2] == 'q'); +} +EOF + +# An array with room for the characters of its string literal but not the +# terminating null takes the characters alone (C99 6.7.8p14), for arrays, record +# members and rows at every storage duration. The literal may also be enclosed +# in braces, including in a compound literal. The neighbour must stay intact, +# and a literal with more characters than elements is still an error. try_ 0 << EOF -/* This is a test C-style comments */ -int main() { return 0; } +struct R { char n[2]; int k; }; +char gs[2] = "ab"; +char guard = 'q'; +static char gss[2] = {"cd"}; +char grows[2][2] = {"ef", "g"}; +struct R gr = {"hi", 5}; +wchar_t gw[2] = L"jk"; +int main(void) { + char s[3] = "lm", t[2] = "no"; + static char ss[2] = "pq"; + struct R r = {"rs", 7}, rw[2] = {"tu", 1, "vw", 2}; + char rows[][2] = {"xy", "z"}; + wchar_t w[2] = {L"AB"}; + char *cl = (char[2]){"CD"}; + if (gs[0] != 'a' || gs[1] != 'b' || guard != 'q' || gss[1] != 'd' || + grows[0][1] != 'f' || grows[1][0] != 'g' || grows[1][1] || + gr.n[1] != 'i' || gr.k != 5 || gw[0] != 'j' || gw[1] != 'k') + return 1; + if (t[0] != 'n' || t[1] != 'o' || s[1] != 'm' || s[2] || ss[1] != 'q' || + r.n[0] != 'r' || r.n[1] != 's' || r.k != 7 || rw[1].n[1] != 'w' || + rw[1].k != 2) + return 2; + return sizeof rows != 4 || rows[0][1] != 'y' || rows[1][1] || + w[1] != 'B' || cl[0] != 'C' || cl[1] != 'D'; +} EOF -try_ 0 << EOF -// This is a test C++-style comments -int main() { return 0; } +try_compile_error << EOF +char s[1] = "ab"; +int main(void) { return 0; } EOF -# Middle -try_ 0 << EOF -int main() { - /* This is a test C-style comments */ - return 0; -} +try_compile_error << EOF +int main(void) { char s[1] = {"ab"}; return s[0]; } EOF -try_ 0 << EOF -int main() { - // This is a test C++-style comments - return 0; -} +try_compile_error << EOF +int main(void) { static char s[1] = "ab"; return s[0]; } EOF -# End -try_ 0 << EOF -int main() { return 0; } -/* This is a test C-style comments */ +try_compile_error << EOF +struct R { char n[1]; } r = {"ab"}; +int main(void) { return 0; } EOF -try_ 0 << EOF -int main() { return 0; } -// This is a test C++-style comments +try_compile_error << EOF +int main(void) { char r[2][1] = {"a", "cd"}; return r[0][0]; } +EOF +try_compile_error << EOF +int main(void) { wchar_t w[1] = L"ab"; return w[0]; } EOF -# Category: Functions -begin_category "Functions" "Testing function definitions, calls, and recursion" - -# functions +# Without braces, string literals meeting a record member that is an array of +# pointers initialize its elements in turn. They are values for the pointers, +# not the contents of a character array, so the list continues to the next +# member instead of reporting too many initializers or an element type error. try_ 0 << EOF +typedef char *str; +struct T { char *n[2]; int k; } gt = {"s", "t", 4}; +struct T2 { str n[2]; int k; }; +struct M { str n[2][2]; int k; } gm = {"a", "b", "c", "d", 3}; +struct T2 gt2 = {"u", "v", 5}, gta[2] = {"a", "b", 1, "c", "d", 2}; int main(void) { - return 0; + struct T t = {"s", "t", 4}; + static struct T2 st2 = {"y", "z", 6}; + struct T2 ta[2] = {"a", "b", 1, "c", "d", 2}; + struct M m = {"e", "f", "g", "h", 7}; + if (*gt.n[1] != 't' || gt.k != 4 || *gt2.n[1] != 'v' || gt2.k != 5 || + *gta[1].n[0] != 'c' || gta[1].k != 2 || *gm.n[1][0] != 'c' || + gm.k != 3) + return 1; + return *t.n[0] != 's' || t.k != 4 || *st2.n[1] != 'z' || st2.k != 6 || + *ta[1].n[1] != 'd' || ta[1].k != 2 || *m.n[1][1] != 'h' || + m.k != 7; } EOF -try_ 55 << EOF -int sum(int m, int n) { - int acc; - acc = 0; - int i; - for (i = m; i <= n; i = i + 1) - acc = acc + i; - return acc; -} - -int main() { - return sum(1, 10); +# C99 _Bool conversions store the truth value, not a truncated source byte. +try_ 4 << EOF +_Bool echo_bool(_Bool value) { return value; } +int main(void) { + _Bool positive = 2; + _Bool negative = -7; + _Bool zero = 0; + positive = 42; + return (positive == 1) + (negative == 1) + (zero == 0) + + (echo_bool(-3) == 1); } EOF -try_ 120 << EOF -int fact(int x) { - if (x == 0) { - return 1; - } else { - return x * fact(x - 1); - } -} - -int main() { - return fact(5); +try_ 2 << EOF +_Bool global_true = 2; +int main(void) { + static _Bool local_true = -3; + return (global_true == 1) + (local_true == 1); } EOF -try_ 55 << EOF -int fib(int n, int a, int b) +try_ 31 << EOF +int bool_pointer_object; +_Bool pointer_to_bool(int *value) { return value; } +_Bool bool_identity(_Bool value) { return value; } +int main(void) { + _Bool direct = &bool_pointer_object; + int *null_value = 0; + _Bool passed = pointer_to_bool(&bool_pointer_object); + _Bool null_result = pointer_to_bool(null_value); + return direct + 2 * passed + 4 * (!null_result) + + 8 * (bool_identity(&bool_pointer_object) == 1) + + 16 * (bool_identity(null_value) == 0); +} +EOF + +# A cast to _Bool compares with zero rather than truncating, for narrow, long +# long and pointer operands alike, as do _Bool arguments and returns. +try_output 0 "1 1 1 1 1 0 0 2 1 1 1 1 1 1 1 1" << EOF +_Bool take(_Bool value) { return value; } +_Bool from_int(int value) { return value & 0x100; } +_Bool from_wide(long long value) { return value; } +_Bool from_pointer(char *value) { return value; } +_Bool (*callback)(_Bool) = take; +int main(void) { - if (n == 0) - return a; - else if (n == 1) - return b; - return fib(n - 1, b, a + b); + int x = 0x1c1; + short half = 0x100; + long long wide = 0x100000000LL; + unsigned long long top = 0x8000000000000000ULL; + char *pointer = (char *) &x, *null = 0; + printf("%d %d %d %d %d %d %d ", (_Bool) (x & 0x100), (_Bool) half, + (_Bool) wide, (_Bool) top, (_Bool) pointer, (_Bool) null, + (_Bool) (x & 0x200)); + printf("%d %d ", (_Bool) wide + (_Bool) 256, (_Bool) 0x100000000LL); + printf("%d %d %d %d ", take(x & 0x100), take(wide), take(pointer), + callback(wide)); + printf("%d %d %d", from_int(x), from_wide(wide), from_pointer(pointer)); + return 0; } +EOF -int main() { - return fib(012, 0, 1); /* octal(12) = dec(10) */ +# A function passed for, assigned to, or initializing a pointer to a function +# returning _Bool stays an address: only _Bool objects convert to 0 or 1. +try_ 0 << EOF +_Bool odd(int v) { return v & 1; } +_Bool call(_Bool (*m)(int), int v) { if (m(v)) return 1; return 0; } +_Bool (*global_ptr)(int) = odd; +int main(void) +{ + _Bool (*local_ptr)(int) = odd; + _Bool (*assigned)(int); + assigned = odd; + return !call(odd, 3) | !call(local_ptr, 5) << 1 | !assigned(7) << 2 | + !global_ptr(9) << 3 | call(odd, 2) << 4; } EOF -# Test large fibonacci values using the new try_large function -try_large 987 << EOF -int fib(int n, int a, int b) +# Stores into _Bool objects reached through subscripts, pointers and members, +# and objects declared through a _Bool typedef, keep only the truth value. +try_output 0 "1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1" << EOF +typedef _Bool flag; +struct holder { + int pad; + _Bool plain; + flag alias; +}; +struct holder global_holder = {1, 256, 512}; +flag global_flag = 256; +int main(void) { - if (n == 0) - return a; - if (n == 1) - return b; - return fib(n - 1, b, a + b); + int x = 0x1c1; + long long wide = 0x100000000LL; + char *pointer = (char *) &x; + _Bool values[3] = {0, 0, 0}, *p = values, grid[2][2]; + _Bool (*row)[3] = &values; + struct holder h = {1, x & 0x100, wide}, *hp = &h; + flag local = x & 0x100; + printf("%d %d %d %d %d ", global_holder.alias, global_flag, h.plain, + h.alias, local); + local = wide; + printf("%d %d %d ", local, (flag) (x & 0x100), (flag) pointer); + values[1] = x & 0x100; + printf("%d ", values[1]); + p[2] = wide; + printf("%d ", values[2]); + values[2] = 0; + *(p + 2) = x & 0x100; + printf("%d ", values[2]); + grid[1][1] = x & 0x100; + printf("%d ", grid[1][1]); + (*row)[0] = x & 0x100; + printf("%d ", values[0]); + h.plain = 0; + hp->alias = pointer; + printf("%d %d ", hp->alias, (h.plain = wide)); + h.plain = x & 0x100 ? x & 0x100 : 0; + printf("%d ", h.plain); + values[0] = 0; + *p++ = x & 0x100; + printf("%d", values[0]); + return 0; } +EOF -int test_function() { - return fib(16, 0, 1); /* fib(16) = 987 */ +# C99 integer promotions convert _Bool to int before arithmetic. This also +# exercises the common unsigned type when that promoted result meets unsigned +# int, rather than treating a one-byte _Bool as an arithmetic byte. +try_ 31 << EOF +int main(void) { + _Bool one = 1; + _Bool zero = 0; + unsigned int unsigned_one = 1U; + return ((one + one) == 2) + + 2 * ((one - zero) == 1) + + 4 * ((one << 3) == 8) + + 8 * ((one + unsigned_one) == 2U) + + 16 * ((zero - one) < 0); +} +EOF + +# Volatile accesses must remain observable through qualified pointers and must +# not be replaced with a cached direct-object value across a call boundary. +try_ 31 << EOF +volatile int global_slot; +void write_volatile(volatile int *slot, int value) { *slot = value; } +int bump_volatile(volatile int *slot) { + int before = *slot; + *slot = before + 1; + return *slot; +} +int main(void) { + volatile int local_slot = 3; + write_volatile(&local_slot, 9); + write_volatile(&global_slot, 20); + return (bump_volatile(&local_slot) == 10) + + 2 * (local_slot == 10) + + 4 * (bump_volatile(&global_slot) == 21) + + 8 * (global_slot == 21) + + 16 * (((volatile int *) &local_slot) == &local_slot); +} +EOF + +# Accessing a volatile object is a side effect (C99 5.1.2.3p2, 6.7.3p6), so an +# expression evaluated only for its side effects still reads it: a name used as +# a statement, the left operand of a comma, a cast to void, a for clause. Each +# evaluated read reaches the generated code, and no load of the same object +# stands in for it. +try_ir_count 5 f 'load %x[0-9]+, -?[0-9]+\(gp\)' << EOF +volatile int status; +int f(void) +{ + status; + status; + (void) status; + status, 0; + for (status;;) + break; + return 1; } +int main(void) { return f() - 1; } EOF -# Test function with short parameters and return type -try_ 35 << EOF -short add_shorts(short a, short b) { - return a + b; +# A postfix update of a volatile object reads it once, where it is evaluated. +try_ir_count 2 f 'load %x[0-9]+, -?[0-9]+\(gp\)' << EOF +volatile int status; +int f(void) +{ + status++; + status--, 0; + return 1; } +int main(void) { return f() - 1; } +EOF -int main() { - return add_shorts(15, 20); +# A volatile local lives in its slot: storing it does not let the next read +# reuse the stored register. +try_ir_count 2 f 'load %x[0-9]+, -?[0-9]+\(sp\)' << EOF +int f(void) +{ + volatile int local = 1; + local; + local; + return 0; } +int main(void) { return f(); } EOF -# Test other large values -try_large 1000 << EOF -int test_function() { - return 1000; +# Reads through a pointer to volatile, of a member of a volatile record and of +# an element of a volatile array are kept too, even when nothing uses them. +try_ir_count 4 f '= \(%x[0-9]+\)' << EOF +struct S { int a; int b; }; +volatile int *p; +volatile struct S s; +volatile int a[4]; +volatile struct S *sp; +int f(void) +{ + *p; + s.b; + a[2]; + sp->a; + return 7; } +int main(void) { return f() - 7; } EOF -try_large 65536 << EOF -int test_function() { - return 1 << 16; /* 2^16 = 65536 */ +# A volatile parameter lives in its slot as a volatile local does: it is stored +# there on entry, each read reloads it and each assignment stores it, the +# register the value was just computed in notwithstanding. +try_ir_count 2 f 'load %x[0-9]+, -?[0-9]+\(sp\)' << EOF +int f(volatile int x) +{ + x; + x; + return 0; } +int main(void) { return f(1); } EOF - -try_large 999999 << EOF -int test_function() { - return 999999; +try_ir_count 2 f 'store %x[0-9]+, -?[0-9]+\(sp\)' << EOF +int f(void) +{ + volatile int local = 4; + local = local + 1; + return local; } +int main(void) { return f() - 5; } EOF -try_compile_error << EOF -int main() { - int a = 03, b = 01118, c = 091; - printf("%d %d %d\n", a, b, c); +# A 32-bit target holds a long long in two registers, and a volatile one is read +# and written a whole pair at a time. +if [ "$PTR_SZ" = 4 ]; then + LL_REG='%x[0-9]+:%x[0-9]+' +else + LL_REG='%x[0-9]+' +fi +try_ir_count 1 f "load $LL_REG, -?[0-9]+\\(gp\\)" << EOF +volatile long long wide; +int f(void) +{ + wide; return 0; } +int main(void) { return f(); } EOF - -try_compile_error << EOF -int main(void v) {} +try_ir_count 3 f "load $LL_REG, -?[0-9]+\\(sp\\)" << EOF +int f(volatile long long y) +{ + y; + y = y + 1; + return (int) y; +} +int main(void) { return f(3) - 4; } EOF - -try_compile_error << EOF -int main(void, int i) {} +try_ir_count 2 f "store $LL_REG, -?[0-9]+\\(sp\\)" << EOF +int f(volatile long long y) +{ + y; + y = y + 1; + return (int) y; +} +int main(void) { return f(3) - 4; } EOF - -# Unreachable declaration should not cause prog segmentation fault (prog should -# leave normally with exit code 0) -try_ 0 << EOF -int main() +try_ 10 << EOF +volatile long long vw; +int f1(volatile int x) { x; x; return x; } +int f2(volatile int x) { x = x + 1; return x; } +int f3(void) { volatile int lv = 4; lv = lv + 1; return lv; } +int f4(volatile long long y) { y; y = y + 0x100000000LL; return (int) (y >> 32); } +int main(void) { - return 0; - int a = 5; + vw = 0x200000001LL; + return f1(1) + f2(2) + f3() + f4(0x100000003LL) - (int) (vw >> 32) + 1; } EOF -try_ 1 << EOF -int is_odd(int x); - -int is_even(int x) { - if (x == 0) { - return 1; - } else { - return is_odd(x - 1); - } +# Every write to a volatile object reaches memory, the same value written again +# and an object nothing reads by name included. +try_ir_count 3 f 'store %x[0-9]+, -?[0-9]+\(gp\)' << EOF +volatile int control; +int f(void) +{ + control = 1; + control = 1; + control = 1; + return 0; } - -int is_odd(int x) { - if (x == 0) { - return 0; - } else { - return is_even(x - 1); - } +int main(void) { return f(); } +EOF +try_ir_count 3 f 'store %x[0-9]+, -?[0-9]+\(sp\)' << EOF +int f(void) +{ + volatile int local; + local = 1; + local = 1; + local = 1; + return 0; } - -int main() { - return is_even(20); +int main(void) { return f(); } +EOF +try_ir_count 4 f 'store %x[0-9]+, -?[0-9]+\(sp\)' << EOF +int f(volatile int x) +{ + x = 1; + x = 1; + x = 1; + return 0; } +int main(void) { return f(0); } EOF - -try_ 253 << EOF -int ack(int m, int n) { - if (m == 0) { - return n + 1; - } else if (n == 0) { - return ack(m - 1, 1); - } else { - return ack(m - 1, ack(m, n - 1)); - } +try_ir_count 5 f '\(%x[0-9]+\) = %x[0-9]+' << EOF +struct S { int a; int b; }; +volatile int *p; +volatile struct S s; +int f(void) +{ + *p = 1; + *p = 1; + *p = 1; + s.b = 1; + s.b = 1; + return 0; } - -int main() { - return ack(3, 5); +int main(void) +{ + static int backing; + p = &backing; + return f(); } EOF - -# Category: Pointer Operations -begin_category "Pointer Operations" "Testing pointer declarations, dereferencing, and arithmetic" - -# pointers -items 3 "int x; int *y; x = 3; y = &x; return y[0];" -items 5 "int b; int *a; b = 10; a = &b; a[0] = 5; return b;" -items 2 "int x[2]; int y; x[1] = 2; y = *(x + 1); return y;" -items 2 "int x; int *y; int z; z = 2; y = &z; x = *y; return x;" -items 2 "short x; short *y; short z; z = 2; y = &z; x = *y; return x;" - -# pointer dereference immediately after declaration -items 42 "int x; x = 10; int *p; p = &x; p[0] = 42; exit(x);" -items 10 "int val; val = 5; int *ptr; ptr = &val; ptr[0] = 10; exit(val);" -items 7 "int a; a = 3; int *b; b = &a; b[0] = 7; exit(a);" - -# asterisk dereference for reading after declaration -items 42 "int x; x = 42; int *p; p = &x; int y; y = *p; exit(y);" -items 15 "int val; val = 15; int *ptr; ptr = &val; exit(*ptr);" -items 100 "int a; a = 100; int *b; b = &a; int c; c = *b; exit(c);" - -# complex pointer dereference patterns after declaration -try_ 25 << EOF -int main() { - int x; - int *p; - x = 10; - p = &x; /* pointer declaration and assignment */ - p[0] = 25; /* array-style assignment immediately after */ - return x; +try_ir_count 3 f "store $LL_REG, -?[0-9]+\\(gp\\)" << EOF +volatile long long wide; +int f(void) +{ + wide = 1; + wide = 1; + wide = 1; + return 0; } +int main(void) { return f(); } EOF - -try_ 50 << EOF -int main() { - int arr[3]; - int *ptr; - arr[0] = 10; arr[1] = 20; arr[2] = 30; - ptr = arr; - ptr[0] = 50; /* should modify arr[0] */ - return arr[0]; +try_ir_count 3 f "store $LL_REG, -?[0-9]+\\(sp\\)" << EOF +int f(void) +{ + volatile long long local; + local = 1; + local = 1; + local = 1; + return 0; } +int main(void) { return f(); } EOF - -try_ 50 << EOF -int main() { - int a, b; - int *p1, *p2; - a = 5; b = 15; - p1 = &a; - p2 = &b; - p1[0] = 100; /* multiple pointer assignments in same block */ - p2[0] = 200; - return p1[0] / 2; /* 100 / 2 = 50 */ +try_ 7 << EOF +volatile long long wide; +void f(void) +{ + wide = 0x500000006LL; + wide = 0x100000002LL; + wide = 0x300000004LL; +} +int main(void) +{ + f(); + return (int) (wide >> 32) + (int) wide; } EOF -try_ 10 << EOF -void change_it(int *p) { - if (p[0] == 0) { - p[0] = 10; - } else { - p[0] = p[0] - 1; - } +# A discarded assignment stores and does not read the object back: C11 6.5.16p3 +# permits the read but does not require it. Storing a bit-field still reads the +# unit once, for the bits it keeps. +try_ir_count 0 f 'load %x[0-9]+, -?[0-9]+\((gp|sp)\)' << EOF +volatile int v; +int f(void) +{ + volatile int local; + v = 3; + local = 3; + v = 4, local = 4; + return 0; } - -int main() { - int v; - v = 2; - change_it(&v); - change_it(&v); - change_it(&v); - return v; +int main(void) { return f(); } +EOF +try_ir_count 1 f '= \(%x[0-9]+\)' << EOF +volatile struct S { int a; int b : 3; } s; +int f(void) +{ + s.a = 3; + s.b = 1; + return 0; } +int main(void) { return f(); } EOF - -# typedef pointer tests - testing fixes for typedef pointer compilation issues -# These tests verify typedef pointer functionality after: -# 1. Removing incorrect pointer level inheritance in read_full_var_decl() -# 2. Adding typedef pointer recognition in array indexing operations -# 3. Implementing proper pointer arithmetic scaling for typedef pointers - -# Test 1: Basic typedef pointer declaration and dereference -try_ 42 << EOF -typedef int *int_ptr; -int main() { - int x = 42; - int_ptr p = &x; - return *p; /* Basic dereference - WORKING */ +try_ 2 << EOF +struct S { int a; int b : 3; }; +volatile struct S s; +int main(void) +{ + int x = (s.b = 5); + s.a = 7; + return (x == -3) + (s.b == -3) - (s.a != 7); } EOF -# Test 2: Multiple typedef pointer variables -try_ 55 << EOF -typedef int *int_ptr; -int main() { - int a = 55, b = 100; - int_ptr p1 = &a; - int_ptr p2 = &b; - return *p1; /* Should return 55 - WORKING */ +# Only evaluated operands are read, each once. The value of "x = v" is not a +# second read of v, the operand of sizeof is not read at all, and assigning +# through a pointer to volatile need not read the object back. +try_ir_count 1 f 'load %x[0-9]+, -?[0-9]+\(gp\)' << EOF +volatile int v; +int x; +int f(void) +{ + x = v; + sizeof v; + return 0; } +int main(void) { return f(); } EOF - -# Test 3: Typedef pointer in function parameters -try_ 30 << EOF -typedef int *int_ptr; -int add_via_ptr(int_ptr a, int_ptr b) { - return *a + *b; +try_ir_count 0 f '= \(%x[0-9]+\)' << EOF +volatile int *p; +int f(void) +{ + *p = 1; + return 0; } -int main() { - int x = 10, y = 20; - return add_via_ptr(&x, &y); /* Function call with typedef pointers - WORKING */ +int main(void) { return f(); } +EOF + +# Reading those objects still yields their values. +try_ 36 << EOF +volatile int vg; +volatile int *vp; +struct S { int a; int b; }; +volatile struct S vs; +volatile struct S *vsp; +volatile char varr[4]; +int f1(void) { vg; return 1; } +int f2(void) { vg; vg; return 2; } +int f3(void) { volatile int lv = 3; lv; lv; return lv; } +int f4(int c) { int x = 0; if (c) x = vg; return x + 4; } +int f5(void) { vsp->b; varr[1]; (void) vg; return (vg, 5); } +int f6(void) { for (vg; vg; vg) break; return 6; } +int main(void) +{ + static volatile int backing[2]; + vp = &backing[1]; + vsp = &vs; + *vp; + vg = 7; + vs.a = 1; + return f1() + f2() + f3() + f4(1) + f5() + f6() + vs.a + vg; } EOF -# Test 4: Multiple typedef declarations -try_ 7 << EOF -typedef int *int_ptr; -typedef char *char_ptr; -int main() { - int x = 7; - char c = 'A'; - int_ptr ip = &x; - char_ptr cp = &c; - return *ip; /* Different typedef pointer types - WORKING */ +# A file-scope record keeps its volatile qualifier like a scalar does, so every +# redeclaration of either must repeat it. +try_ 8 << EOF +struct S { int a; }; +union U { int a; char c; }; +extern volatile struct S s; +volatile struct S s = { 3 }; +extern volatile union U u; +volatile union U u, u2; +volatile struct R { int a; } r1, r2; +extern volatile struct R r2; +volatile int *p, q; +extern volatile int *p; +extern volatile int q; +int main(void) { + s.a++; + u.a = 4; + r2.a = s.a; + return r2.a + u.a; } EOF - -# Test 5: Global typedef pointer -try_ 88 << EOF -typedef int *int_ptr; -int global_value = 88; -int_ptr global_ptr; -int main() { - global_ptr = &global_value; - return *global_ptr; /* Global typedef pointer - WORKING */ -} +try_compile_error << EOF +struct S { int a; }; +volatile struct S s; +extern struct S s; +int main(void) { return 0; } EOF - -# Test 6: Typedef pointer initialization -try_ 100 << EOF -typedef int *int_ptr; -int main() { - int val = 100; - int_ptr p = &val; /* Initialize at declaration */ - int result = *p; - return result; /* Indirect usage - WORKING */ -} +try_compile_error << EOF +volatile union U { int a; } u; +extern union U u; +int main(void) { return 0; } EOF - -# Test 7: Nested typedef pointer usage in expressions -try_ 15 << EOF -typedef int *int_ptr; -int main() { - int x = 5, y = 10; - int_ptr px = &x; - int_ptr py = &y; - return *px + *py; /* Expression with multiple derefs - WORKING */ -} +try_compile_error << EOF +volatile int value; +extern int value; +int main(void) { return 0; } EOF -# Test 8: Typedef pointer assignment after declaration -try_ 25 << EOF -typedef int *int_ptr; -int main() { - int value = 25; - int_ptr ptr; - ptr = &value; /* Assignment after declaration */ - return *ptr; /* WORKING */ +# An omitted outer array bound is compatible with any bound on the same element +# type, in either order, and the bound completes the shared object. +try_ 0 << EOF +extern int a[]; +int a[3] = {1, 2, 3}; +int b[]; +int guard_b = 5; +int b[4]; +int c[3] = {7, 8, 9}; +extern int c[]; +int guard_c = 6; +extern int m[][2]; +int m[3][2] = {{1, 2}, {3, 4}, {5, 6}}; +int main(void) { + for (int i = 0; i < 4; i++) + b[i] = 11; + return sizeof(a) != 3 * sizeof(int) || a[2] != 3 || + sizeof(b) != 4 * sizeof(int) || guard_b != 5 || b[3] != 11 || + sizeof(c) != 3 * sizeof(int) || c[2] != 9 || guard_c != 6 || + sizeof(m) != 6 * sizeof(int) || m[2][1] != 6; } EOF +try_compile_error << EOF +int d[3]; +int d[4]; +int main(void) { return 0; } +EOF +try_compile_error << EOF +extern int m[][2]; +int m[3][3]; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int e; +extern int e[]; +int main(void) { return 0; } +EOF +try_compile_error << EOF +extern int f[]; +int *f; +int main(void) { return 0; } +EOF -# Test 9: Typedef pointer array indexing -try_ 100 << EOF -typedef int *int_ptr; -int main() { - int values[3] = {42, 100, 200}; - int_ptr p = values; - return p[1]; /* Array indexing - NOW WORKING with fix */ +# Qualifiers may also follow the type specifier, in typedefs and at both scopes, +# and a typedef passes them to every object it declares. The redeclarations +# below only match when each spelling recorded the volatile qualifier. +try_ 60 << EOF +struct S { int a; }; +enum E { E2 = 2 }; +typedef volatile int VI; +typedef int volatile VI2; +typedef const volatile int CVI; +typedef VI VI3; +typedef struct S volatile VS; +typedef volatile struct S VS2; +typedef struct { int a; } const CR; +typedef struct T { int a; } volatile VT; +typedef enum { E4 = 4 } volatile VE; +typedef int *P; +typedef P restrict RP; +struct S volatile vs; +extern volatile struct S vs; +union U { int a; } volatile vu; +extern volatile union U vu; +enum E volatile ve = E2; +extern volatile enum E ve; +int volatile vi = 1; +extern VI vi; +VI2 vi2 = 2; +extern volatile int vi2; +VI3 vi3 = 3; +extern volatile int vi3; +VS vrec; +extern struct S volatile vrec; +VS2 vrec2; +extern VS vrec2; +struct S const cs = {5}; +int main(void) +{ + extern struct S volatile vrec; + struct S volatile ls, *lp = &ls; + struct R { int a; } volatile const lr = {6}; + enum E const le = E2; + CR cr = {7}; + struct T t = {1}; + VT vt = {8}; + VE e = E4; + CVI cv = 9; + int x = 3; + RP rp = &x; + typedef struct S const LCS; + LCS lcs = {2}; + t.a = 2; + vs.a = 1; + vu.a = 2; + vrec.a = 3; + vrec2.a = 4; + ls.a = 1; + return vs.a + vu.a + ve + vi + vi2 + vi3 + vrec.a + vrec2.a + cs.a + + lp->a + lr.a + le + cr.a + t.a + vt.a + e + cv + *rp + lcs.a - 7; } EOF +try_compile_error << EOF +struct S { int a; }; +struct S volatile s; +extern struct S s; +int main(void) { return 0; } +EOF +try_compile_error << EOF +enum E { E1 }; +enum E volatile e; +extern enum E e; +int main(void) { return 0; } +EOF +try_compile_error << EOF +typedef int volatile VI; +VI value; +extern int value; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct S { int a; }; +typedef struct S volatile VS; +VS value; +extern struct S value; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct S { int a; }; +struct S value; +int main(void) { extern struct S volatile value; return 0; } +EOF +try_compile_error << EOF +typedef int const CI; +int main(void) { CI value = 1; value = 2; return value; } +EOF +try_compile_error << EOF +struct S { int a; }; +typedef struct S const CS; +int main(void) { CS value = {1}; value.a = 2; return value.a; } +EOF +try_compile_error << EOF +struct S { int a; }; +struct S const value = {1}; +int main(void) { value.a = 2; return value.a; } +EOF +try_compile_error << EOF +enum E { E1 }; +int main(void) { enum E const value = E1; value = E1; return value; } +EOF +try_compile_error_message "restrict requires a pointer type" << EOF +struct S { int a; }; +struct S restrict value; +int main(void) { return 0; } +EOF +try_compile_error_message "restrict requires a pointer type" << EOF +typedef int restrict R; +int main(void) { return 0; } +EOF +try_ 3 << EOF +struct S { int a; }; +struct S g = {3}; +int main(void) { typedef struct S ST; extern ST g; return g.a; } +EOF -# Test 10: Complex array indexing with typedef pointer -try_ 90 << EOF -typedef int *int_ptr; -int main() { - int arr[5] = {10, 20, 30, 40, 50}; - int_ptr p = arr; - return p[0] + p[2] + p[4]; /* Multiple array accesses */ +# A storage class, typedef included, may follow the type as well (C99 6.7p2; +# obsolescent per 6.11.5 but valid), at file scope, in a block, in a for +# initializer and on a parameter. It still admits no second storage class, and +# an identifier after the type is the declarator, not a specifier. +try_ 16 << EOF +struct S { int a; }; +struct S static gs = { 2 }; +int typedef T; +T const static k = 1; +long extern y; +long y = 1; +int static f(void) { return 3; } +int g(int register a, const register int b) { return a + b; } +int main(void) { + T typedef U; + U u = 1; + struct S register rs = gs; + long unsigned static int q = 4; + long extern int y; + for (int register i = 0; i < 1; i++) + u += i; + struct Q { int b; } static r = {5}; + return u + k + f() + rs.a + q + r.b + g(1, -1) - y + 1; } EOF -# Test 11: Typedef pointer arithmetic - increment -try_ 20 << EOF -typedef int *int_ptr; -int main() { - int values[3] = {10, 20, 30}; - int_ptr p = values; - p++; /* Move to next element */ - return *p; /* Should return 20 */ -} +try_compile_error << EOF +int main(void) { int x static; return 0; } EOF -# Test 12: Typedef pointer arithmetic - addition -try_ 40 << EOF -typedef int *int_ptr; -int main() { - int values[5] = {10, 20, 30, 40, 50}; - int_ptr p = values; - p = p + 3; /* Move forward by 3 elements */ - return *p; /* Should return 40 */ -} +try_compile_error_message "duplicate static storage class specifier" << EOF +int main(void) { int static static x; return x; } EOF -# Test 13: Typedef pointer arithmetic - subtraction -try_ 30 << EOF -typedef int *int_ptr; -int main() { - int values[5] = {10, 20, 30, 40, 50}; - int_ptr p = values + 4; /* Point to last element */ - p = p - 2; /* Move back by 2 elements */ - return *p; /* Should return 30 */ -} +try_compile_error_message "incompatible storage class specifiers" << EOF +int main(void) { unsigned static extern x; return 0; } EOF -# Test 14: Typedef pointer arithmetic - prefix increment -try_ 20 << EOF -typedef int *int_ptr; -int main() { - int values[3] = {10, 20, 30}; - int_ptr p = values; - ++p; /* Prefix increment */ - return *p; /* Should return 20 */ -} +try_compile_error_message "typedef cannot be combined" << EOF +extern int typedef T; +int main(void) { return 0; } EOF -# Test 15: Typedef pointer arithmetic - postfix increment -try_ 10 << EOF -typedef int *int_ptr; -int main() { - int values[3] = {10, 20, 30}; - int_ptr p = values; - int val = *p++; /* Get value, then increment */ - return val; /* Should return 10 */ -} +try_compile_error << EOF +int f(int static a) { return a; } +int main(void) { return f(1); } EOF -# Test 16: Typedef pointer arithmetic - decrement -try_ 20 << EOF -typedef int *int_ptr; -int main() { - int values[3] = {10, 20, 30}; - int_ptr p = values + 2; /* Point to values[2] */ - p--; /* Move back one element */ - return *p; /* Should return 20 */ +# restrict is a C99 pointer qualifier. These are non-aliasing calls by contract; +# the test covers parser/type-name acceptance and ordinary accesses without +# requiring an alias-sensitive optimization. +try_ 31 << EOF +int accumulate(int *restrict destination, const int *restrict source) { + *destination += *source; + return *destination; +} +int main(void) { + int left = 4; + int right = 5; + int *restrict local = &left; + const int *restrict input = &right; + int *cast_local = (int *restrict) local; + return (accumulate(local, input) == 9) + + 2 * (left == 9) + + 4 * (accumulate(cast_local, input) == 14) + + 8 * (*input == 5) + + 16 * (cast_local == &left); } EOF +expr 42 42 -# Test 17: Typedef char pointer arithmetic -try_ 98 << EOF -typedef char *char_ptr; -int main() { - char chars[5] = {'a', 'b', 'c', 'd', 'e'}; - char_ptr p = chars; - p = p + 1; /* Move forward by 1 byte */ - return *p; /* Should return 'b' = 98 */ +# octal constant (satisfying re(0[0-7]+)) +expr 10 012 +expr 65 0101 + +# Category: C99 universal character names +begin_category "Universal Character Names" "Testing UCN literals and identifiers" + +# C99 universal character names in narrow literals are encoded in shecc's UTF-8 +# execution character set. Exercise two-, three-, and four-byte sequences. +try_ 4 << EOF +int main(void) { + char *s = "\\u00a9\\u20ac\\U0001f600"; + return ((unsigned char)s[0] == 0xc2) + ((unsigned char)s[1] == 0xa9) + + ((unsigned char)s[2] == 0xe2) + ((unsigned char)s[5] == 0xf0); } EOF -# Test 18: Mixed typedef pointer operations -try_ 35 << EOF -typedef int *int_ptr; -int main() { - int values[10] = {5, 10, 15, 20, 25, 30, 35, 40, 45, 50}; - int_ptr p = values; - p = p + 2; /* Move to values[2] = 15 */ - p++; /* Move to values[3] = 20 */ - p = p + 3; /* Move to values[6] = 35 */ - return *p; -} +# Multi-character constants retain the implementation-defined left-to-right +# UTF-8 byte packing used by ordinary escaped constants. +try_ 1 << EOF +int main(void) { return '\\u00a9' == 0xc2a9; } EOF -# Pointer difference calculations Test basic pointer subtraction returning -# element count -try_ 5 << EOF -int main() { - char arr[10]; - char *p = arr; - char *q = arr + 5; - int diff = q - p; /* Should return 5 (5 elements) */ - return diff; -} +# Identifier UCNs are decoded to a stable UTF-8 spelling before keyword, +# typedef, object, and macro lookup. Cover a continuation UCN and a four-byte +# scalar in a macro name as well as a UCN at the start of a typedef name. +try_ 42 << EOF +#define \U0001f600 40 +typedef int \u03b1; +\u03b1 value\u00e9 = \U0001f600; +int main(void) { return value\u00e9 + 2; } EOF -try_ 3 << EOF -int main() { - char str[20]; - char *start = str + 2; - char *end = str + 5; - return end - start; /* Should return 3 */ -} +# C99's three exceptions to the basic-source UCN restriction remain UCN +# nondigits, despite their direct spellings not being ordinary identifiers. +try_ 42 << EOF +int \u0024 = 42; +int main(void) { return \u0024; } EOF -# Test pointer difference with char pointers (element size = 1) -try_ 7 << EOF -int main() { - char buffer[100]; - char *p1 = buffer; - char *p2 = buffer + 7; - return p2 - p1; /* Should return 7 */ -} +try_compile_error << EOF +int main(void) { return "\\u0041"[0]; } +EOF +try_compile_error << EOF +int main(void) { return "\\uD800"[0]; } +EOF +try_compile_error << EOF +int main(void) { return "\\U00110000"[0]; } +EOF +try_compile_error << EOF +int \u0041 = 0; +EOF +try_compile_error << EOF +int \u12 = 0; EOF -# Test reverse pointer difference -try_ 5 << EOF -int main() { - char data[50]; - char *high = data + 10; - char *low = data + 5; - return high - low; /* Should return 5 */ +# Category: Arithmetic Operations +begin_category "Arithmetic Operations" "Testing +, -, *, /, % operators" + +# Unary minus negates a grouped or cast operand, not only a name or a literal. +try_ 0 << EOF +int negate_argument(int value) { return value; } +int main(void) +{ + int x = 5; + char c = 2; + + if (-(x) != -5) return 1; + if (-(x + 1) != -6) return 2; + if (negate_argument(-(x)) != -5) return 3; + if (-(int) c != -2) return 4; + if (1 + -(x) != -4) return 5; + return 0; } EOF -# Pointer arithmetic tests - -# Basic integer pointer difference -try_ 7 << EOF -int main() { - int arr[10]; - int *p = arr; - int *q = arr + 7; - return q - p; +# Any unary expression may follow a unary minus (C99 6.5.3), another unary +# operator among them: these were rejected as an unexpected token. +try_ 1 << EOF +int arr[3] = {1, 2, 3}; +int main(void) { + int x = 5, *p = &x; + long long w = 3; + int r = 0; + r += (- - x == 5); + r += (- -x == 5) * 2; + r += (~ - x == 4) * 4; + r += (! - x == 0) * 8; + r += (- ~ x == 6) * 16; + r += (-*p == -5) * 32; + r += (-'a' == -97) * 64; + r += (-(int) sizeof(int) == -4 && -sizeof(char) + 1 == 0) * 128; + r += (- + x == -5) * 256; + r += (- ++x == -6 && x == 6) * 512; + r += (-2[arr] == -3 && -(x) - -1 == -5) * 1024; + r += (- - - -w == 3 && - - -1 == -1) * 2048; + r += (-(int) 7 == -7 && -(char) 300 == -44) * 4096; + r += (-arr[1] == -2 && -x-- == -6) * 8192; + r += (x == 5) * 16384; + return r == 32767; +} +EOF + +# A postfix update is complete at the next sequence point (C99 6.5.2.4, 6.5.15, +# 6.5.13, 6.5.14, 6.5.2.2p10), and happens only when its operand is evaluated: +# deferring it to the end of the statement ran skipped updates and let the +# second operand of && see the old value. +try_ 0 << EOF +int g; +int seen(int v) { return g * 10 + v; } +int main(void) { + int x = 7, y = 0, z = 3, w; + if (!((x-- == 7 && x == 6) ? 1 : 0)) return 1; + 0 && y++; + 1 || y++; + w = y ? y++ : 5; + if (y != 0 || w != 5) return 2; + if ((z++ || z) != 1 || (z++ ? z : 0) != 5) return 3; + g = 1; + if (seen(g++) != 21) return 4; + return 0; } EOF -# Char pointer differences -try_ 10 << EOF -int main() { - char text[50]; - char *start = text; - char *end = text + 10; - return end - start; +# ++ and -- on a selected element or member step by the pointee of the value it +# holds: a pointer member or element stepped by one byte in postfix form, and an +# int element of a pointer, ++p[0], stepped by sizeof(int) in prefix form. +try_ 0 << EOF +struct rec { char c; int v; }; +struct holder { int *ip; struct rec *rp; char **cpp; }; +int main(void) { + int arr[4] = {1, 2, 3, 4}; + struct rec recs[2]; + char *strs[2] = {"a", "b"}; + struct holder h = {arr, recs, strs}, *hp = &h; + int *ips[2] = {arr, arr}; + int *old = ips[0]++; + h.ip++; + hp->rp++; + h.cpp++; + int *p = arr; + ++p[0]; + p[1]--; + ++h.ip; + if (old != arr || ips[0] != arr + 1 || *h.ip != 3) return 1; + if (arr[0] != 2 || arr[1] != 1) return 2; + return h.rp != recs + 1 || hp->cpp != strs + 1; } EOF +# An element of an array of pointers to arrays, or of a pointer to such +# pointers, still points to a whole row, so *pas[0] designates the row; and +# arithmetic on the array or pointer steps over pointers. The dereference was +# rejected, and pas + 1 stepped by a row. try_ 0 << EOF -int main() { - char buffer[100]; - char *p1 = buffer + 25; - char *p2 = buffer + 25; - return p2 - p1; /* Same position = 0 */ +struct holder { int (*row)[3]; }; +int m[2][3] = {{1, 2, 3}, {4, 5, 6}}; +int (*gpas[2])[3] = {m, m + 1}; +int main(void) +{ + int (*pas[2])[3] = {m, m + 1}; + int (**pr)[3] = pas; + int (**q)[3] = pas + 1; + struct holder h = {m + 1}, *hp = &h; + int *e = *pas[1]; + if ((*pas[0])[2] != 3 || (*pas[1])[0] != 4 || pas[1][0][1] != 5) return 1; + if ((*gpas[1])[2] != 6 || (*hp->row)[1] != 5 || (*h.row)[0] != 4) return 2; + (*pas[1])[2] = 9; + if (m[1][2] != 9 || sizeof(*pas[0]) != 3 * sizeof(int)) return 3; + if ((*pr[1])[1] != 5 || (**pr)[2] != 3 || e[2] != 9) return 4; + if ((**(pas + 1))[1] != 5 || (**q)[0] != 4 || q - pas != 1) return 5; + if ((**(pr + 1))[2] != 9 || *(*pas[0] + 1) != 2) return 6; + return 0; } EOF -# More complex char pointer arithmetic -try_ 15 << EOF -int main() { - char str[100]; - char *p = str + 5; - char *q = str + 20; - return q - p; /* 20 - 5 = 15 */ +# Compound assignment scales a pointer operand as ++ and -- step it, also when +# the pointer is selected by a member or subscript; there it was not scaled. +try_ 0 << EOF +struct h { int *ip; char **cpp; int (*row)[3]; }; +int main(void) +{ + int arr[8] = {0, 1, 2, 3, 4, 5, 6, 7}; + int m[3][3]; + char *strs[4]; + int *ps[2] = {arr, arr}; + int (*pas[1])[3] = {m}; + struct h s = {arr, strs, m}, *sp = &s; + int *p = arr; + s.ip += 2; + sp->cpp += 3; + ps[1] -= -3; + s.row += 1; + pas[0] += 2; + p += 5; + p[0] += 10; + ps[0][1] -= 1; + if (s.ip != arr + 2 || s.cpp != strs + 3 || ps[1] != arr + 3) return 1; + if (s.row != m + 1 || pas[0] != m + 2 || p != arr + 5) return 2; + return arr[5] != 15 || arr[1] != 0; +} +EOF + +# The element or member step also covers a typedef'd pointer, which steps by its +# pointee, and a pointer to an array, which steps by a whole row; the latter +# stepped by one element. +try_ 0 << EOF +typedef int *iptr; +typedef struct rec { char c; int v; } *rptr; +typedef iptr *ipptr; +struct rows { int (*row)[3]; iptr ip; }; +int main(void) +{ + int arr[4] = {1, 2, 3, 4}; + struct rec recs[2]; + int m[2][3] = {{1, 2, 3}, {4, 5, 6}}; + iptr ips[2] = {arr, arr}; + rptr rps[2]; + rps[0] = recs; + rps[1] = recs; + ipptr ipps[1] = {ips}; + int (*pas[2])[3] = {m, m}; + struct rows r = {m, arr}, *rp = &r; + iptr *old = ipps[0]++; + ips[0]++; + ++ips[1]; + ips[1]--; + rps[1]++; + pas[0]++; + --pas[1]; + ++pas[1]; + int (*prev)[3] = pas[1]++; + r.row++; + rp->ip++; + ++r.ip; + if (ips[0] != arr + 1 || ips[1] != arr || *ips[0] != 2) return 1; + if (rps[1] != recs + 1 || ipps[0] != ips + 1 || old != ips) return 2; + if (pas[0] != m + 1 || prev != m || pas[1] != m + 1) + return 3; + if (r.row != m + 1 || (*r.row)[0] != 4 || r.ip != arr + 2) return 4; + return 0; } EOF +try_compile_error << EOF +int main(void) { int *p = 0; return - -p != 0; } +EOF +try_compile_error << EOF +int main(void) { return - ; } +EOF -# Test with void* cast (treated as char*) -try_ 8 << EOF -int main() { - char array[20]; - void *vp1 = array; - void *vp2 = array + 8; - return (char*)vp2 - (char*)vp1; +# C99 integer promotions and signed/unsigned common-type selection must retain +# the promoted arithmetic result across character, short, int, and long ranks. +try_ 0 << EOF +int main(void) { + signed char signed_byte = -1; + unsigned char unsigned_byte = 255; + short signed_short = -2; + unsigned short unsigned_short = 65535; + unsigned int unsigned_int = 1; + long signed_long = -1; + unsigned long unsigned_long = 1; + return signed_byte + unsigned_byte != 254 || + signed_short + unsigned_short != 65533 || + signed_byte < unsigned_int || signed_long < unsigned_long; +} +EOF + +# Unsigned int operations must use modular arithmetic, logical right shift, +# unsigned division, and unsigned relational comparisons after the value's high +# bit is set at run time. +try_ 4 << EOF +int main(void) +{ + unsigned int bits = 2147483647; + bits = bits + bits + 1; + return (bits >> 31) + (bits / 2 == 2147483647) + + (bits / 3 == 1431655765) + (bits > 0); } EOF -# Integer pointer with array indexing -try_ 3 << EOF -int main() { - int nums[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - int *first = &nums[2]; - int *second = &nums[5]; - return second - first; /* Direct subtraction: (5-2) = 3 */ +try_ 1 << EOF +int main(void) { + unsigned int bits = 2147483647; + bits = bits + bits + 1; + return bits >> 31; } EOF -# Larger integer pointer difference -try_ 10 << EOF -int main() { - int values[20]; - int *p = values; - int *q = values + 10; - return q - p; /* Direct pointer arithmetic */ +# The shift count's unsignedness must not turn a signed right shift into a +# logical one. The left operand alone determines the right-shift opcode. +try_ 0 << EOF +int main(void) { + int value = -4; + volatile unsigned int count = 1; + return (value >> count) != -2; } EOF -# Negative pointer difference -try_ 251 << EOF -int main() { - int arr[10]; - int *p = arr + 8; - int *q = arr + 3; - return q - p; /* 3 - 8 = -5, wraps to 251 in exit code */ +try_ 1 << EOF +int main(void) { + unsigned int bits = 2147483647; + bits = bits + bits + 1; + return bits / 2 == 2147483647; } EOF - -# Zero pointer difference -try_ 0 << EOF -int main() { - int data[10]; - int *p1 = data + 5; - int *p2 = data + 5; - return p2 - p1; /* Same position = 0 */ +try_ 2 << EOF +int main(void) { + int negative = -1; + unsigned int divisor = 2U; + return (negative / divisor == 2147483647U) + + (negative % divisor == 1U); } EOF - -# Struct pointer arithmetic -try_ 4 << EOF -struct point { - int x; - int y; - int z; -}; - -int main() { - struct point pts[10]; - struct point *p1 = pts; - struct point *p2 = pts + 4; - return p2 - p1; /* Struct pointer difference */ +try_ 1 << EOF +int main(void) { + unsigned int all_bits = 4294967295U; + return all_bits >> 31; } EOF - -# Mixed pointer arithmetic operations -try_ 16 << EOF -int main() { - int arr[20]; - int *start = arr; - int *mid = arr + 10; - int *end = arr + 18; - return (end - mid) + (mid - start) - 2; /* (18-10) + (10-0) - 2 = 8 + 10 - 2 = 16 */ +try_ 2 << EOF +int main(void) { + unsigned int value = 1UL; + return (value + 1L == 2U) + (sizeof(long) == 4); } EOF - -# Pointer arithmetic with typedef -try_ 6 << EOF -typedef int* int_ptr; -int main() { - int data[15]; - int_ptr p1 = data + 2; - int_ptr p2 = data + 8; - return p2 - p1; /* Typedef pointer difference: 8 - 2 = 6 */ +try_ 2 << EOF +int main(void) { + return (0xffffffff >> 31) + (-2147483648 < 0); } EOF - -# Complex expression with pointer differences -try_ 13 << EOF -int main() { - int vals[30]; - int *a = vals; - int *b = vals + 5; - int *c = vals + 9; - int *d = vals + 15; - return (d - a) - (c - b) + 2; /* (15-0) - (9-5) + 2 = 15 - 4 + 2 = 13 */ +try_ 2 << EOF +int main(void) { + unsigned int one = 1U; + return ((~one) >> 31) + ((1 ? one - 2 : -1) >> 31); } EOF - -# Test negative pointer difference (converted to exit code) -try_ 253 << EOF -int main() { - char data[20]; - char *high = data + 5; - char *low = data + 8; - int diff = high - low; /* -3 */ - /* Convert negative to positive for exit code */ - return diff < 0 ? 256 + diff : diff; /* Returns 253 (256-3) */ +try_ 2 << EOF +int main(void) { + unsigned char byte = 1; + unsigned short half = 1; + return (-byte == -1) + (-half == -1); } EOF - -# Test short pointer -try_ 150 << EOF -int main() { - short value = 150; - short *ptr = &value; - return *ptr; +try_ 2 << EOF +int main(void) { + unsigned char byte = 255U; + unsigned short half = 65535U; + byte++; + half++; + return (byte == 0U) + (half == 0U); } EOF -# Test short pointer arithmetic -try_ 20 << EOF -int main() { - short arr[3] = {10, 20, 30}; - short *p = arr; - p++; - return *p; +# An int-width result is zero when its low word is, whatever an LP64 backend +# leaves above it, so a truth test, a ternary, a ! and an if all agree. +try_ 0 << EOF +unsigned uid(unsigned x) { return x; } +int iid(int x) { return x; } +int main(void) { + unsigned u = uid(0xffffffffU); + unsigned one = uid(1U); + unsigned top = uid(0x80000000U); + int s = iid(-1); + int fails = 0; + if (~u) + fails = fails + 1; + if ((~u) ? 1 : 0) + fails = fails + 2; + if (!(~u) != 1) + fails = fails + 4; + if (u + one) + fails = fails + 8; + if (top - top) + fails = fails + 16; + if (top * uid(2U)) + fails = fails + 32; + if (top << uid(1U)) + fails = fails + 64; + if ((~u) & u) + fails = fails + 128; + if ((~u) | (u + one)) + fails = fails + 256; + if (u ^ uid(0xffffffffU)) + fails = fails + 512; + if (~s + 1) + fails = fails + 1024; + if ((iid(3) - iid(3)) ? 1 : 0) + fails = fails + 2048; + if (!(iid(0) * iid(5)) != 1) + fails = fails + 4096; + if (((u + one) >> uid(1U)) != 0) + fails = fails + 8192; + if (((iid(-8) + iid(0)) >> 1) != -4) + fails = fails + 16384; + return fails; +} +EOF +# A folded ~ or unary minus of an unsigned int constant has a zero high word. +try_ 0 << EOF +int main(void) { + unsigned long long flipped = (unsigned long long) (~4294967294U); + unsigned long long negated = (unsigned long long) (-9U); + unsigned long long grouped = (unsigned long long) -(9U); + long long signed_flip = (long long) (~5); + return flipped != 1 || negated != 0xfffffff7ULL || + grouped != 0xfffffff7ULL || signed_flip != -6; } EOF -# Test short pointer difference -try_ 2 << EOF -int main() { - short data[5] = {1, 2, 3, 4, 5}; - short *start = data + 1; - short *end = data + 3; - return end - start; +# A folded negative int is sign-extended when converted to long long, and a +# folded unsigned int result is not. +try_ 0 << EOF +long long g = (long long) (0 - 42); +int main(void) { + static unsigned long long s = (unsigned long long) (2 * -21); + long long a = (long long) (0 - 42); + unsigned long long b = (unsigned long long) (-45 - 32); + long long c = (long long) (35 | -38); + unsigned long long d = (unsigned long long) (-13 ^ 55); + unsigned long long e = (unsigned long long) ((22 | -3) >> 28); + unsigned long long u = (unsigned long long) (0U - 42); + return a != -42 || b != 0xffffffffffffffb3ULL || c != -5 || + d != 0xffffffffffffffc4ULL || e != ~0ULL || g != -42 || + s != 0xffffffffffffffd6ULL || u != 0xffffffd6ULL || + (long long) (0 - 42) >= 0; +} +EOF +# A constant narrowed to an unsigned type folds zero-extended, not to -2. +try_ 0 << EOF +int main(void) { + unsigned short half = 0xfffe; + unsigned short wide = 0xfffffffeULL; + unsigned char byte = 0xfe; + return (int) half != 65534 || (int) wide != 65534 || (int) byte != 254; } EOF -# Category: Function Pointers -begin_category "Function Pointers" "Testing function pointer declarations and calls" - -# function pointers -try_ 18 << EOF -typedef struct { - int (*ta)(); - int (*tb)(int); -} fptrs; -int t1() { return 7; } -int t2(int x) { return x + 1; } -int main() { - fptrs fb; - fptrs *fs = &fb; - fs->ta = t1; - fs->tb = t2; - return fs->ta() + fs->tb(10); +# A conversion between narrow types that changes signedness leaves the result +# extended by its own type, not by its source's. +try_ 0 << EOF +unsigned short widen(signed char c) { unsigned short v = c; return v; } +int main(void) { + signed char c = -17; + unsigned char u = 255; + unsigned short v = c; + unsigned short z = (unsigned short) ((unsigned char) c); + unsigned short m = (unsigned short) ((signed char) u); + unsigned char b = (unsigned char) c; + signed char s = (signed char) u; + short h = (signed char) u; + return (unsigned) v != 0xffef || (unsigned) z != 0xef || + (m >> 8) != 0xff || (long long) b != 239 || s != -1 || h != -1 || + 1000 / (unsigned short) ((unsigned char) c) != 4 || + (int) widen(c) != 0xffef; +} +EOF + +# A shift takes the type of its left operand alone; a long long count does not +# make an int shift 64 bits wide. +try_ 0 << EOF +int shift_not(unsigned char x, long long n) { return (~x) >> (n & 31); } +int shift_xor(int x, int y, unsigned long long n) { return (x ^ y) >> (n & 31); } +unsigned int shift_out(unsigned int x, unsigned int y, long long n) { + return (x | y) << n; +} +int main(void) { + return shift_not(0, 15) != -1 || shift_xor(-8, 0, 2) != -2 || + shift_out(0x80000000U, 1, 1) != 2; } EOF -# Local function pointer, direct struct member, and pointer-to-struct member. -# The first path must use the pointer value directly; the latter two must load -# the pointer from the member slot. -try_ 6 << EOF -typedef struct { - int (*fn)(int); -} holder_t; - -int suc(int x) { return x + 1; } - -int main() { - int (*local)(int); - holder_t h; - holder_t *p = &h; - - local = suc; - h.fn = suc; - p->fn = suc; - - return local(1) + h.fn(1) + p->fn(1); +# An unsigned int shifted right by a variable count ignores the borrow a 64-bit +# register can hold above an unsigned int subtraction. +try_ 0 << EOF +unsigned int borrow_shift(unsigned int a, unsigned int b, unsigned long long n) { + return (a - b) >> (n & 15); +} +int main(void) { + return borrow_shift(466218516U, 1243335322U, 3) != 0x1a35c46fU; } EOF -# Assignment between function-pointer variables copies the stored function -# address; it must not treat the RHS variable name as a function symbol. -try_ 5 << EOF -int suc(int x) { return x + 1; } - -int main() { - int (*first)(int); - int (*second)(int); - - first = suc; - second = first; - return second(4); +# An int division saves and restores the registers it borrows at full width, so +# a long long living in one of them keeps its high word. +try_ 0 << EOF +unsigned long long mix(unsigned long long a, unsigned long long b, + unsigned long long c, unsigned long long d, + unsigned long long e, unsigned long long f, int n) { + unsigned long long w0 = a % 0x7fffffffffffffffULL; + unsigned long long w1 = b % 0x7fffffffffffffffULL; + unsigned long long w2 = c % 0x7fffffffffffffffULL; + unsigned long long w3 = d % 0x7fffffffffffffffULL; + unsigned long long w4 = e % 0x7fffffffffffffffULL; + unsigned long long w5 = f % 0x7fffffffffffffffULL; + unsigned long long w6 = a % 0x7ffffffffffffffeULL; + int q = 10 % (n | 1); + return w0 + w1 + w2 + w3 + w4 + w5 + w6 + q; +} +int main(void) { + unsigned long long k = 0x100000000ULL; + return mix(k, k, k, k, k, k, 112) != 0x700000000ULL + 10; } EOF -# A local function pointer shadows a global function. Copying it must load the -# local variable's stored target, rather than materializing the global -# function's address. -try_ 9 << EOF -int target(int x) { return x + 3; } -int replacement(int x) { return x + 8; } - -int main() { - int (*target)(int); - int (*copy)(int); - - target = replacement; - copy = target; - return copy(1); +# A wide constant narrowed to an int argument keeps the sign of its low word, +# even though a 64-bit target loads the argument at full width. +try_ 0 << EOF +int top_two(int x) { return x >> 30; } +unsigned int top_two_unsigned(unsigned int x) { return x >> 30; } +int main(void) { + return top_two(0xe231fffab478ULL) != -1 || + top_two_unsigned(0xe231fffab478ULL) != 3; } EOF -# An indirect call with two arguments must not leave stale argument-register -# mappings visible to a later one-argument call. -try_ 155 << EOF -typedef struct { - int (*add)(int, int); -} pair_holder_t; - -int add(int a, int b) { return a + b; } -int one(int x) { return x + 100; } -int get_right() { return 20; } - -int main() { - pair_holder_t h; - int left = 10; - int right = get_right(); - h.add = add; - return h.add(left, right) + one(5) + right; +# A right shift reads the whole register of the value it shifts, so a negative +# int quotient or remainder must reach it sign-extended. +try_ 0 << EOF +int half_remainder(int x, int d) { return (x % d) >> 1; } +int shifted_quotient(int x, int d) { return (x / d) >> 1; } +int main(void) { + return half_remainder(-24122, 9) != -1 || + shifted_quotient(-24122, 9) != -1340; } EOF - -# Addressing a pointer to a function-pointer aggregate must return the pointer -# variable's address, not backing storage for its pointee. -try_ 5 << EOF -typedef struct { - int (*fn)(int); -} holder_t; - -int suc(int x) { return x + 1; } - -int call(holder_t *direct) { - holder_t **indirect = &direct; - return direct == *indirect ? direct->fn(4) : 1; +try_ 3 << EOF +long long identity(long long value) { return value; } +unsigned long long uidentity(unsigned long long value) { return value; } +int main(void) { + long long signed_value = 1000; + unsigned long long unsigned_value = 2000U; + return (sizeof(signed_value) == 8) + + (identity(signed_value) == 1000) + + (uidentity(unsigned_value) == 2000U); } - -int main() { - holder_t h; - h.fn = suc; - return call(&h); +EOF +try_ 4 << EOF +long int identity_long_int(long int value) { return value; } +signed long long int identity_signed_wide(signed long long int value) { + return value; +} +unsigned long long int identity_unsigned_wide(unsigned long long int value) { + return value; +} +int main(void) { + long int narrow = -7L; + signed long long int negative = -0x100000000LL; + unsigned long long int positive = 0x100000000ULL; + return (identity_long_int(narrow) == -7L) + + (identity_signed_wide(negative) == -0x100000000LL) + + (identity_unsigned_wide(positive) == 0x100000000ULL) + + (sizeof(unsigned long long int) == 8); } EOF - -# struct with multiple pointer declarations in same line -try_ 42 << EOF -typedef struct chunk { - struct chunk *next, *prev; - int size; -} chunk_t; - -int main() { - chunk_t c; - c.size = 42; - return c.size; +try_ 2 << EOF +typedef long long signed_wide; +typedef unsigned long long unsigned_wide; +int main(void) { + signed_wide signed_value = -1LL; + unsigned_wide unsigned_value = 1ULL << 32; + return (signed_value < 0LL) + ((unsigned_value >> 32) == 1ULL); } EOF - -# Category: Arrays -begin_category "Arrays" "Testing array declarations, indexing, and operations" - -# a parameter whose first dimension is omitted is still a 2-D array: "int -# a[][4]" must index exactly like "int a[3][4]", not like "int **" -try_ 66 << EOF -int sum2(int a[][4], int rows) +try_ 2 << EOF +typedef long unsigned long reordered_unsigned_wide; +typedef const long long signed_wide_const; +int main(void) { + reordered_unsigned_wide value = 1ULL << 32; + signed_wide_const negative = -1LL; + return ((value >> 32) == 1ULL) + (negative < 0LL); +} +EOF +try_compile_error << EOF +unsigned unsigned int invalid; +int main(void) { return invalid; } +EOF +try_compile_error << EOF +signed unsigned int invalid; +int main(void) { return invalid; } +EOF +try_compile_error << EOF +signed signed int invalid; +int main(void) { return invalid; } +EOF +try_compile_error << EOF +long long long invalid; +int main(void) { return invalid; } +EOF +try_compile_error << EOF +typedef unsigned unsigned int invalid; +int main(void) { return 0; } +EOF +try_compile_error << EOF +typedef signed unsigned int invalid; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { return (signed unsigned int) 1; } +EOF +try_compile_error << EOF +int main(void) { return (unsigned unsigned int) 1; } +EOF +try_compile_error << EOF +enum invalid_enum { invalid_value }; +unsigned enum invalid_enum invalid; +int main(void) { return invalid; } +EOF +try_compile_error << EOF +enum invalid_enum { invalid_value }; +int main(void) { return (long enum invalid_enum) invalid_value; } +EOF +try_compile_error << EOF +enum invalid_enum { invalid_value }; +int main(void) { return sizeof(signed enum invalid_enum); } +EOF +try_compile_error << EOF +int main(void) { return sizeof(signed unsigned int); } +EOF +try_compile_error << EOF +int main(void) { return sizeof(unsigned unsigned int); } +EOF +try_compile_error << EOF +int signed char invalid; +int main(void) { return invalid; } +EOF +try_compile_error << EOF +typedef int unsigned char invalid; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { return (int signed char) 1; } +EOF +try_compile_error << EOF +int main(void) { return sizeof(int unsigned char); } +EOF +try_compile_error << EOF +typedef long long long invalid; +int main(void) { return 0; } +EOF +try_compile_error << EOF +long incompatible_object; +int incompatible_object; +int main(void) { return 0; } +EOF +try_compile_error << EOF +unsigned long incompatible_function(unsigned long value); +unsigned int incompatible_function(unsigned int value); +int main(void) { return 0; } +EOF +try_compile_error << EOF +static int object_then_function; +static int object_then_function(void) { return 1; } +int main(void) { return object_then_function(); } +EOF +try_compile_error << EOF +static int function_then_object(void) { return 1; } +static int function_then_object; +int main(void) { return function_then_object(); } +EOF +try_ 3 << EOF +int main(void) { + long signed_long = -1L; + unsigned int unsigned_int = 1U; + unsigned long unsigned_long = 1UL; + int signed_int = -2; + return (sizeof(1L) == sizeof(long)) + + ((signed_long + unsigned_int) == 0UL) + + ((unsigned_long + signed_int) == 0xffffffffUL); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned long value = 0xffffffffUL; + return (sizeof(long unsigned) == 4) + + ((long unsigned) value == 0xffffffffUL); +} +EOF +try_ 8 << EOF +typedef short unsigned int base_first_ushort; +short unsigned int preserve_half(short unsigned int value) +{ + return value; +} +int main(void) { + short unsigned int half = 65535U; + char unsigned byte = 255U; + int signed whole = -1; + int unsigned short reordered_half = 65535U; + base_first_ushort typedef_half = 65535U; + return (sizeof(half) == 2) + (sizeof(byte) == 1) + + (half == 65535U && byte == 255U && whole < 0) + + (preserve_half(half) == 65535U) + + (reordered_half == 65535U) + + ((short unsigned int)-1 == 65535U) + + (sizeof(short unsigned int) == 2) + + (sizeof(typedef_half) == 2 && typedef_half == 65535U); +} +EOF + +# The int spelling may accompany short and long in any specifier order, in every +# context that reads a type (C99 6.7.2p2). +try_ 12 << EOF +short int file_short = 3; +static int short file_static = 1; +typedef unsigned short int file_ushort; +short int keep_short(int long value) { return value; } +struct mixed { short int s; int short unsigned u; long int l; }; +int main(void) { + short int y = 1; + int short w = -2; + signed short int s = -1; + short int signed t = -1; + unsigned short int u = 65535; + int long unsigned lu = 5; + long long int ll = 1; + long int long il = 2; + int long long li = 3; + short volatile unsigned int vu = 65535; + const int short cs = 4; + typedef short int block_short; + block_short b = 7; + struct mixed m; + short int pair, *pp = &pair; + *pp = 1; + m.u = 65535; + for (short int i = 0; i < 2; i++) + y += i; + return (sizeof(y) == 2) + (w == -2 && s == -1 && t == -1) + + (u == 65535 && vu == 65535 && m.u == 65535) + + (sizeof(file_ushort) == 2) + (sizeof(short int) == 2) + + (sizeof(int long long) == 8) + ((short int) 65539 == 3) + + (keep_short(file_short + file_static) == 4) + + (ll + il + li == 6 && lu == 5) + (cs == 4 && b == 7) + + (sizeof(b) == 2 && sizeof(m.s) == 2) + (y == 2 && pair == 1); +} +EOF +try_compile_error << EOF +int main(void) { short int int value = 1; return value; } +EOF +try_compile_error << EOF +int main(void) { long char int value = 1; return value; } +EOF +try_compile_error << EOF +int main(void) { short long int value = 1; return value; } +EOF + +# A cast type name reads its specifiers with the declaration reader, so a +# qualifier may stand anywhere among the scalar words. A qualifier after the +# base word used to spin the cast reader forever. The name is looked up in the +# scope of the cast: a block typedef names a type, and a variable hides one. +try_ 64 << EOF +typedef int T; +struct S { int a; }; +enum E { E0, E1 }; +typedef int *P; +int main(void) { + typedef short B; + int x = 5, *p = &x; + struct S s = { 3 }; + int r = ((char volatile) 1 - 1 == 0) + + ((int volatile signed const) -1 == -1) * 2 + + ((short const int unsigned) -1 == 65535) * 4 + + ((B) 65539 == 3) * 8 + + ((struct S const *) &s)->a; + { + int T = 3; + r += ((T) - 1 == 2) * 16; + } + r += (*(P restrict) p == 5) * 32 + ((enum E volatile) 1 == E1) - 3; + return r + (sizeof(enum E const) == sizeof(int)) - 1; +} +EOF +try_compile_error << EOF +int main(void) { return (int char volatile) 1; } +EOF +try_compile_error << EOF +int main(void) { return (signed volatile unsigned) 1; } +EOF +try_compile_error_message "restrict requires a pointer type" << EOF +int main(void) { return (int restrict){7}; } +EOF + +# A storage class may follow any scalar word, not only a trailing int. +try_ 7 << EOF +int const volatile short unsigned typedef U; +U u = 65535; +int main(void) { + const volatile const int short auto s = 259; + int char_ok = 0; + char unsigned static c = 255; + char_ok = c == 255; + return (u == 65535) + (s == 259) * 2 + char_ok * 4; +} +EOF +try_ 1 << EOF +int main(void) { return (unsigned long long) 1 == 1ULL; } +EOF +try_ 4 << EOF +int main(void) { + long long decimal = 4294967296; + long long hexadecimal = 0x100000000; + unsigned long long all_bits = 0xffffffffffffffff; + return ((decimal >> 32) == 1LL) + + ((hexadecimal >> 32) == 1LL) + + ((all_bits >> 63) == 1ULL) + + (all_bits > 0ULL); +} +EOF +try_ 1 << EOF +int main(void) { + unsigned long long maximum = 0xffffffffffffffffULL; + return maximum + 1ULL == 0ULL; +} +EOF +try_ 4 << EOF +int main(void) { + return (sizeof(2147483647) == 4) + + (sizeof(2147483648) == 8) + + (sizeof(0xffffffff) == 4) + + (sizeof(0x100000000) == 8); +} +EOF + +# A 32-bit target deliberately types -2147483648 as int, so that INT_MIN spelled +# that way stays usable; with an L suffix it is still a long long. +try_ "$((PTR_SZ >= 8 ? 2 : 1))" << EOF +int main(void) { + return (sizeof(-2147483648) == 8) + + (sizeof(-2147483648L) == 8); +} +EOF +try_flags 6 "--no-libc" << EOF +#include +#include +int main(void) { + return (sizeof(INT_MIN) == 4) + (sizeof(LONG_MIN) == 4) + + (sizeof(INT32_MIN) == 4) + (sizeof(INT64_MIN) == 8) + + (sizeof(LLONG_MIN) == 8) + (sizeof(INTMAX_MIN) == 8); +} +EOF +try_ 6 << EOF +int main(void) { + /* C99 chooses candidates from the suffix-specific list: the current + * ABI has 32-bit long, so overflow moves to the 64-bit long-long tier. */ + return (sizeof(2147483647L) == 4) + + (sizeof(2147483648L) == 8) + + (sizeof(0x80000000L) == 4) + + (sizeof(0x80000000LL) == 8) + + (sizeof(4294967295U) == 4) + + (sizeof(4294967296U) == 8); +} +EOF +try_ 1 << EOF +int main(void) { return 4294967296U >> 32; } +EOF + +# A 32-bit target admits long long literals, objects, returns and callbacks as +# well as sizeof and pointers. +try_ 1 << EOF +long long wide_value; +long long wide_return(void) { return 1LL; } +long long (*wide_callback)(void) = wide_return; +int main(void) { return (int)wide_callback() + (int)wide_value; } +EOF +try_ 2 << EOF +int main(void) { + return (sizeof(long long) == 8) + + (sizeof(unsigned long long) == 8); +} +EOF +try_ 1 << EOF +typedef long long *wide_pointer; +long long *identity_wide_pointer(long long *value) { return value; } +int main(void) { + int storage = 0; + wide_pointer alias = (wide_pointer)&storage; + return identity_wide_pointer(alias) == alias; +} +EOF +try_ 3 << EOF +int main(void) { + return (sizeof(1LL) == 8) + ((1LL << 32) != 0) + + (((1ULL << 32) >> 32) == 1U); +} +EOF +try_ 3 << EOF +int main(void) { + unsigned long long value = 0x100000000ULL; + unsigned long long pattern = 0x123456789abcdef0ULL; + return ((value >> 32) == 1ULL) + + ((pattern >> 32) == 0x12345678ULL) + + ((value + 7ULL) == 0x100000007ULL); +} +EOF +try_ 3 << EOF +int main(void) { + unsigned long long value = 4294967296ULL; + unsigned long long pattern = 1311768467463790320ULL; + return ((value >> 32) == 1ULL) + + ((pattern >> 32) == 305419896ULL) + + ((value + 7ULL) == 4294967303ULL); +} +EOF +try_ 3 << EOF +int main(void) { + unsigned long long octal = 040000000000ULL; + unsigned long long binary = 0b100000000000000000000000000000000ULL; + return ((octal >> 32) == 1ULL) + + ((binary >> 32) == 1ULL) + + ((octal + binary) == 0x200000000ULL); +} +EOF +try_ 4 << EOF +int main(void) { + unsigned long long all = 18446744073709551615ULL; + long long min = -9223372036854775808LL; + return (all == 0xffffffffffffffffULL) + + ((all >> 63) == 1ULL) + + (min < 0LL) + + ((min >> 63) == -1LL); +} +EOF +try_ 2 << EOF +unsigned long long global_value = 0x123456789abcdef0ULL; +int main(void) { + return ((global_value >> 32) == 0x12345678ULL) + + (((global_value + 1ULL) >> 32) == 0x12345678ULL); +} +EOF +try_ 2 << EOF +long long global_min = -9223372036854775808LL; +int main(void) { + return (global_min < 0LL) + ((global_min >> 63) == -1LL); +} +EOF + +# An integer cast inside a scalar file-scope initializer converts its constant +# operand, including a grouped wide operand, and the result keeps its high word. +try_ 0 << EOF +typedef long long cast_wide_t; +typedef unsigned char cast_byte_t; +enum { cast_negative = -1 }; +long long cast_sum = (long long)0x100000000LL + 1; +unsigned long long cast_shift = (unsigned long long)1LL << 40; +long long cast_grouped = (long long)(0x100000000LL + 1); +cast_wide_t cast_typedef_shift = (cast_wide_t)1 << 40; +long long cast_sign_extended = (long long)(int)0xffffffffU; +_Bool cast_bool = (_Bool)0x100000000LL; +cast_byte_t cast_byte = (cast_byte_t)cast_negative; +int cast_leading = (int)3 + (char)300; +int main(void) +{ + return cast_sum != 0x100000001LL || cast_shift != 0x10000000000ULL || + cast_grouped != 0x100000001LL || + cast_typedef_shift != 0x10000000000LL || + cast_sign_extended != -1LL || cast_bool != 1 || + cast_byte != 255 || cast_leading != 47; +} +EOF +try_compile_error << EOF +int cast_object; +long long cast_non_constant = (long long)cast_object + 1; +int main(void) { return 0; } +EOF + +# A cast, !, ~ and unary + bind tighter than a following binary operator. The +# operand of each is only the pointer, so `(char *) p + 1` advances by one byte +# rather than being read as the int pointer arithmetic p + 1, and `!p + 1` is 1. +try_ 0 << EOF +int id(int value) { return value; } +int main(void) { + int arr[4] = {1, 2, 3, 4}, *p = arr, *q = arr, x = 3; + char c = 100, *cp = (char *) p + 1; + if (cp - (char *) p != 1 || (char *) p + 1 - (char *) p != 1) return 1; + if (id((char *) arr + 3 - (char *) arr) != 3) return 2; + if ((char *) (int *) p + 2 != (char *) arr + 2) return 3; + if ((int) (char *) p + 1 - (int) (char *) p != 1) return 4; + if (!p + 1 != 1 || ~x + 5 != 1 || +x + 1 != 4) return 5; + if ((long) x * 2 != 6 || ((unsigned char) c << 1) != 200) return 6; + if ((int *) (p + 2) - q != 2 || (int) arr[1] + 2 != 4) return 7; + return *(char *) p + 1 != 2; +} +EOF +try_ 2 << EOF +unsigned long long global_sum = 0x100000000ULL + 7ULL; +int main(void) { + return ((global_sum >> 32) == 1ULL) + + ((global_sum - 7ULL) == 0x100000000ULL); +} +EOF +try_ 8 << EOF +unsigned long long ternary_wide_true = 1 ? 0x100000000ULL : 1U; +unsigned long long ternary_wide_false = 0 ? 0x100000000ULL : 1U; +unsigned long long ternary_signed_rank = + -1LL < 1U ? 0x100000000ULL : 1U; +unsigned long long ternary_signed_value = + 0xffffffffU > -1LL ? 0x100000000ULL : 1U; +unsigned long long ternary_unsigned_rank = + 0ULL < -1LL ? 0x100000000ULL : 1U; +unsigned long long ternary_signed_word = + -1 < 0 ? 0x100000000ULL : 1U; +int main(void) { + return ((ternary_wide_true >> 32) == 1ULL) + + ((unsigned int)ternary_wide_true == 0U) + + ((ternary_wide_false >> 32) == 0ULL) + + ((unsigned int)ternary_wide_false == 1U) + + ((ternary_signed_rank >> 32) == 1ULL) + + ((ternary_signed_value >> 32) == 1ULL) + + ((ternary_unsigned_rank >> 32) == 1ULL) + + ((ternary_signed_word >> 32) == 1ULL); +} +EOF +try_ 8 << EOF +unsigned long long logical_and_true = + 0x100000000ULL && 1 ? 0x100000000ULL : 1U; +unsigned long long logical_and_false = + 0 && 0x100000000ULL ? 0x100000000ULL : 1U; +unsigned long long logical_or_true = + 0 || 0x100000000ULL ? 0x100000000ULL : 1U; +unsigned long long logical_or_false = + 0 || 0 ? 0x100000000ULL : 1U; +unsigned long long logical_precedence = + 1 || 0 && 0 ? 0x100000000ULL : 1U; +unsigned long long logical_grouped_precedence = + (1 || 0) && 0 ? 0x100000000ULL : 1U; +unsigned long long logical_unary_not = + !0 && 0x100000000ULL ? 0x100000000ULL : 1U; +unsigned long long logical_nested = + 0 ? 1U : 1 && 0x100000000ULL ? 0x100000000ULL : 1U; +int main(void) { + return (logical_and_true == 0x100000000ULL) + + (logical_and_false == 1ULL) + + (logical_or_true == 0x100000000ULL) + + (logical_or_false == 1ULL) + + (logical_precedence == 0x100000000ULL) + + (logical_grouped_precedence == 1ULL) + + (logical_unary_not == 0x100000000ULL) + + (logical_nested == 0x100000000ULL); +} +EOF +try_ 7 << EOF +unsigned long long logical_protected_and = + 0 && (1 / 0) ? 0x100000000ULL : 1U; +unsigned long long logical_protected_or = + 1 || (1 / 0) ? 0x100000000ULL : 1U; +unsigned long long logical_direct_protected_and = + 0 && 1 / 0 ? 0x100000000ULL : 1U; +unsigned long long logical_direct_protected_or = + 1 || 1 / 0 ? 0x100000000ULL : 1U; +unsigned long long logical_protected_chain = + 0 && 1 / 0 + 1 ? 0x100000000ULL : 1U; +unsigned long long logical_nested_protected_and = + 0 && (1 || 1 / 0) ? 0x100000000ULL : 1U; +unsigned long long logical_nested_protected_or = + 1 || (0 && 1 / 0) ? 0x100000000ULL : 1U; +int main(void) { + return (logical_protected_and == 1ULL) + + (logical_protected_or == 0x100000000ULL) + + (logical_direct_protected_and == 1ULL) + + (logical_direct_protected_or == 0x100000000ULL) + + (logical_protected_chain == 1ULL) + + (logical_nested_protected_and == 1ULL) + + (logical_nested_protected_or == 0x100000000ULL); +} +EOF +try_ 4 << EOF +unsigned long long ternary_protected_true = + 0 ? 1 / 0 : 1U; +unsigned long long ternary_protected_false = + 1 ? 0x100000000ULL : 1 / 0; +unsigned long long ternary_protected_nested = + 0 ? 1 / 0 : 1 ? 0x100000000ULL : 1 / 0; +unsigned long long ternary_protected_logical_condition = + (0 && 1 / 0) ? 1 / 0 : 0x100000000ULL; +int main(void) { + return (ternary_protected_true == 1ULL) + + (ternary_protected_false == 0x100000000ULL) + + (ternary_protected_nested == 0x100000000ULL) + + (ternary_protected_logical_condition == 0x100000000ULL); +} +EOF +try_compile_error << EOF +unsigned long long invalid_wide_active_ternary_true = + 1 ? 1 / 0 : 0x100000000ULL; +EOF +try_compile_error << EOF +unsigned long long invalid_wide_active_ternary_false = + 0 ? 0x100000000ULL : 1 / 0; +EOF +try_compile_error << EOF +unsigned long long invalid_wide_ternary_condition = + 1 / 0 ? 0x100000000ULL : 1U; +EOF + +# The signed minimum divided by -1 overflows its common type, as INT_MIN / -1 +# does in the word-sized evaluator; the unsigned and discarded forms are valid. +try_compile_error << EOF +long long invalid_wide_min_quotient = (-9223372036854775807LL - 1) / -1; +EOF +try_compile_error << EOF +long long invalid_wide_min_remainder = (-9223372036854775807LL - 1) % -1; +EOF +try_compile_error << EOF +long long invalid_int_min_quotient_wide = (-2147483647 - 1) / -1 + 0LL; +EOF +try_ 3 << EOF +unsigned long long wide_unsigned_min_quotient = 0x8000000000000000ULL / -1; +long long wide_protected_min_quotient = + 0 ? (-9223372036854775807LL - 1) / -1 : 1LL; +long long wide_min_quotient_by_uint = (-9223372036854775807LL - 1) / -1U; +int main(void) { + return (wide_unsigned_min_quotient == 0ULL) + + (wide_protected_min_quotient == 1LL) + + (wide_min_quotient_by_uint == -2147483648LL); +} +EOF +try_compile_error << EOF +int invalid_wide_discarded_ternary_object; +unsigned long long invalid_wide_discarded_ternary = + 0 ? invalid_wide_discarded_ternary_object : 1U; +EOF + +# The word-sized evaluator skips a discarded arm up to its matching ':', or to +# the end of the declarator, past grouped and nested conditionals inside it. +try_ 8 << EOF +int int_ternary_grouped_true_arm = 0 ? (1 ? 2 : 3) : 4; +int int_ternary_nested_true_arm = 0 ? 1 ? 2 : 3 : 4; +int int_ternary_nested_false_arm = 1 ? 5 : 0 ? 6 : 7; +int int_ternary_nested_selected = 1 ? 1 ? 19 : 20 : 21; +int int_ternary_list = 1 ? 22 : 23, int_ternary_list_next = 24; +int int_ternary_deep = 0 ? 1 ? 2 : 3 ? 4 : 5 : 1 ? 25 : 26; +int main(void) { + static int grouped_static = 0 ? (1 ? 2 : 3) : 18; + static int nested_static = 1 ? 1 ? 19 : 20 : 21; + return (int_ternary_grouped_true_arm == 4) + + (int_ternary_nested_true_arm == 4) + + (int_ternary_nested_false_arm == 5) + + (int_ternary_nested_selected == 19) + + (int_ternary_list == 22 && int_ternary_list_next == 24) + + (int_ternary_deep == 25) + (grouped_static == 18) + + (nested_static == 19); +} +EOF + +# A conditional or logical element of a static brace initializer is folded as a +# constant. Lowered as a runtime branch, it replaced the global setup entry and +# every earlier global store read back as zero. +try_ 0 << EOF +enum { brace_constant = 3 }; +int brace_before = 7; +int brace_array[] = {1 ? 2 : 3, 4}; +int brace_logical[3] = {0 ? 5 : 6, 1 && 2, 0 || brace_constant}; +int brace_operands[] = {sizeof(int) == 4 ? 8 : 9, -1 ? 10 : 11, + 'a' ? 12 : 13, brace_constant ? 14 : 15}; +int brace_nested[2][2] = {{1 ? 16 : 17, 18}, {0 ? 19 : 20, 21 && 0}}; +struct brace_pair { int x, y; } brace_record = {1 ? 22 : 23, 0 && 1}; +struct brace_pair brace_records[] = {{1 ? 24 : 25, 26}, {27, 0 ? 28 : 29}}; +long long brace_wide[] = {1 ? 0x100000000LL : 0, 0 || -1}; +int brace_after = 30; +int main(void) { + static int brace_static[] = {1 ? 31 : 32, 1 && 1}; + return (brace_before != 7) + (brace_array[0] != 2) + + (brace_array[1] != 4) + (sizeof(brace_array) != 2 * sizeof(int)) + + (brace_logical[0] != 6) + (brace_logical[1] != 1) + + (brace_logical[2] != 1) + (brace_operands[0] != 8) + + (brace_operands[1] != 10) + (brace_operands[2] != 12) + + (brace_operands[3] != 14) + (brace_nested[0][0] != 16) + + (brace_nested[0][1] != 18) + (brace_nested[1][0] != 20) + + (brace_nested[1][1] != 0) + (brace_record.x != 22) + + (brace_record.y != 0) + (brace_records[0].x != 24) + + (brace_records[1].y != 29) + (brace_wide[0] != 0x100000000LL) + + (brace_wide[1] != 1) + (brace_after != 30) + + (brace_static[0] != 31) + (brace_static[1] != 1); +} +EOF +try_compile_error << EOF +int brace_object; +int brace_logical_object[] = {1 && brace_object}; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int brace_comma[] = {(1, 2)}; +int main(void) { return 0; } +EOF + +# An address constant is a valid operand of a conditional or a logical operator +# in a static initializer: the conditional selects it, and the logical operators +# only test it, since it is never null. +try_ 0 << EOF +int selected_objects[3] = {4, 5, 6}; +int selected_function(void) { return 9; } +char *selected_true_arm[] = {1 ? "ab" : "cd", "ef"}; +char *selected_false_arm[] = {0 ? "ab" : "cd"}; +char *selected_scalar = 0 ? "ab" : "cd"; +struct selected_holder { int tag; char *text; int *element; }; +struct selected_holder selected_record = {1, 0 ? "ab" : "cd", + 1 ? selected_objects : 0}; +int *selected_addresses[] = {0 ? selected_objects : 0, + 1 ? &selected_objects[2] : 0}; +int (*selected_callbacks[])(void) = {1 ? selected_function : 0}; +char *selected_nested[] = {0 ? "ab" : 1 ? "gh" : "ij"}; +char *selected_null[] = {1 ? (char *) 0 : "ab"}; +int selected_truth[] = {"ab" && 1, selected_objects || 0, + selected_function ? 3 : 4, !"ab"}; +int selected_scalar_truth = "ab" && 2; +int main(void) +{ + static char *selected_static[] = {0 ? "ab" : "kl"}; + static char *selected_static_scalar = 1 ? "mn" : "op"; + + return (selected_true_arm[0][0] != 'a') + (selected_true_arm[1][0] != 'e') + + (selected_false_arm[0][0] != 'c') + (selected_scalar[0] != 'c') + + (selected_record.tag != 1) + (selected_record.text[0] != 'c') + + (selected_record.element != selected_objects) + + (selected_addresses[0] != 0) + + (selected_addresses[1] != &selected_objects[2]) + + (selected_callbacks[0]() != 9) + (selected_nested[0][0] != 'g') + + (selected_null[0] != 0) + (selected_truth[0] != 1) + + (selected_truth[1] != 1) + (selected_truth[2] != 3) + + (selected_truth[3] != 0) + (selected_scalar_truth != 1) + + (selected_static[0][0] != 'k') + (selected_static_scalar[0] != 'm'); +} +EOF +try_compile_error << EOF +int selected_object; +char *selected_non_constant[] = {selected_object ? "ab" : "cd"}; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int selected_object; +char *selected_non_constant_arm[] = {1 ? "ab" : selected_object}; +int main(void) { return 0; } +EOF +try_compile_error << EOF +char *selected_comma[] = {1 ? ("ab", "cd") : "ef"}; +int main(void) { return 0; } +EOF + +# An address constant offset by an integer is still one inside a conditional or +# a logical operator, with the integer on either side of the sum; the step is +# the element the address points to, a whole row for a decayed matrix. A tested +# address is true, so the right operand of || is never evaluated. +try_ 0 << EOF +enum { offset_back_one = -1 }; +char offset_text[] = "abc"; +int offset_values[4] = {1, 2, 3, 4}; +int offset_rows[3][2] = {{1, 2}, {3, 4}, {5, 6}}; +char *offset_first[] = {"x" ? 1 + offset_text : 0, "x" ? 2 + "xyz" : 0}; +char *offset_grouped = offset_text ? (offset_text) + 2 : 0; +int *offset_both = offset_values ? 3 + offset_values - 2 : 0; +int *offset_back = "x" ? -1 + (offset_values + 3) : 0; +int *offset_enum = "x" ? offset_back_one + (offset_values + 2) : 0; +int *offset_wide = "x" ? 1LL + offset_values : 0; +int (*offset_row)[2] = "x" ? 2 + offset_rows : 0; +int offset_truth = "x" && 1 + offset_text; +int offset_skipped = offset_values || 1 / 0; +int main(void) +{ + return (*offset_first[0] != 'b') + (*offset_first[1] != 'z') + + (*offset_grouped != 'c') + (*offset_both != 2) + + (*offset_back != 3) + (*offset_enum != 2) + (*offset_wide != 2) + + ((*offset_row)[1] != 6) + + (offset_truth != 1) + (offset_skipped != 1); +} +EOF +try_compile_error << EOF +int offset_values[4]; +int *offset_negated = "x" ? 1 - offset_values : 0; +int main(void) { return 0; } +EOF + +# An offset the word-sized address relocation cannot hold is refused rather than +# losing its high word or overflowing once scaled. +try_compile_error_message "Global address offset exceeds supported integer range" << EOF +int offset_values[4]; +int *offset_high = "x" ? 0x100000000LL + offset_values : 0; +int main(void) { return 0; } +EOF +try_compile_error_message "Global address offset exceeds supported integer range" << EOF +int offset_values[4]; +int *offset_scaled = "x" ? 0x40000000 + offset_values : 0; +int main(void) { return 0; } +EOF +try_ 18 << EOF +unsigned long long global_ternary_true = + (1 ? 0x100000000ULL : 0ULL) + 1ULL; +unsigned long long global_ternary_false = + 0 ? 0x100000000ULL : 1U; +unsigned long long global_ternary_precedence = + 1 - 1 ? 0x100000000ULL : 1U; +unsigned long long global_ternary_high_condition = + 0x100000000ULL ? 7U : 1U; +unsigned long long global_ternary_nested = + 0 ? 1U : 1 ? 0x100000000ULL : 2U; +unsigned long long global_ternary_sizeof_true = + sizeof(int) == 4 ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_false = + sizeof(int) != 4 ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_string = + sizeof "abc" == 4 ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_grouped_string = + sizeof("abc") != 4 ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_adjacent_string = + sizeof("a" "bc") == 4 ? 7U : 1U; +unsigned long long global_ternary_sizeof_wstring = + sizeof L"ab" == 3 * sizeof(wchar_t) ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_grouped_wstring = + sizeof(L"ab") != 3 * sizeof(wchar_t) ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_adjacent_wstring = + sizeof(L"a" L"b") == 3 * sizeof(wchar_t) ? 7U : 1U; +unsigned long long global_ternary_sizeof_scalar = + sizeof 1 == sizeof(int) ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_grouped_scalar = + sizeof(1 + 2) != sizeof(int) ? 0x100000000ULL : 1U; +int global_ternary_object; +int global_ternary_array[3]; +unsigned long long global_ternary_sizeof_object = + sizeof global_ternary_object == sizeof(int) ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_grouped_object = + sizeof(global_ternary_object) != sizeof(int) ? 0x100000000ULL : 1U; +unsigned long long global_ternary_sizeof_array = + sizeof global_ternary_array == 3 * sizeof(int) ? 7U : 1U; +int main(void) { + return (global_ternary_true == 0x100000001ULL) + + (global_ternary_false == 1ULL) + + (global_ternary_precedence == 1ULL) + + (global_ternary_high_condition == 7ULL) + + (global_ternary_nested == 0x100000000ULL) + + (global_ternary_sizeof_true == 0x100000000ULL) + + (global_ternary_sizeof_false == 1ULL) + + (global_ternary_sizeof_string == 0x100000000ULL) + + (global_ternary_sizeof_grouped_string == 1ULL) + + (global_ternary_sizeof_adjacent_string == 7ULL) + + (global_ternary_sizeof_wstring == 0x100000000ULL) + + (global_ternary_sizeof_grouped_wstring == 1ULL) + + (global_ternary_sizeof_adjacent_wstring == 7ULL) + + (global_ternary_sizeof_scalar == 0x100000000ULL) + + (global_ternary_sizeof_grouped_scalar == 1ULL) + + (global_ternary_sizeof_object == 0x100000000ULL) + + (global_ternary_sizeof_grouped_object == 1ULL) + + (global_ternary_sizeof_array == 7ULL); +} +EOF +try_ 6 << EOF +int postfix_object; +typedef char cast_byte; +struct wide_inc_rec; +typedef struct wide_inc_rec wide_inc_t; +struct wide_inc_rec *wide_inc_ptr; +wide_inc_t *wide_inc_typedef_ptr; +unsigned long long postfix_value = + sizeof postfix_object++ == sizeof(int) ? 0x100000000ULL : 1U; +unsigned long long cast_true = + sizeof((cast_byte)postfix_object) == 1 ? 0x100000000ULL : 1U; +unsigned long long cast_false = + sizeof((cast_byte)postfix_object) != 1 ? 0x100000000ULL : 1U; +unsigned long long incomplete_pointer = + sizeof wide_inc_ptr == sizeof(void *) ? 0x100000000ULL : 1U; +unsigned long long incomplete_typedef_pointer = + sizeof wide_inc_typedef_ptr != sizeof(void *) ? 0x100000000ULL : 1U; +int main(void) { + return (postfix_value == 0x100000000ULL) + (postfix_object == 0) + + (cast_true == 0x100000000ULL) + (cast_false == 1ULL) + + (incomplete_pointer == 0x100000000ULL) + + (incomplete_typedef_pointer == 1ULL); +} +EOF +try_compile_error << EOF +int invalid_wide_sizeof_function(void) { return 0; } +unsigned long long invalid_wide_sizeof_function_value = + sizeof invalid_wide_sizeof_function ? 0x100000000ULL : 1U; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int invalid_wide_logical_operand; +unsigned long long invalid_wide_logical_value = + 0 && invalid_wide_logical_operand ? 0x100000000ULL : 1U; +int main(void) { return 0; } +EOF +try_compile_error << EOF +unsigned long long invalid_wide_active_and = + 1 && (1 / 0) ? 0x100000000ULL : 1U; +int main(void) { return 0; } +EOF +try_compile_error << EOF +unsigned long long invalid_wide_active_or = + 0 || (1 / 0) ? 0x100000000ULL : 1U; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int invalid_wide_sizeof_grouped_function(void) { return 0; } +unsigned long long invalid_wide_sizeof_grouped_function_value = + sizeof(invalid_wide_sizeof_grouped_function) ? 0x100000000ULL : 1U; +int main(void) { return 0; } +EOF +try_compile_error << EOF +unsigned long long invalid_wide_sizeof_void = sizeof(void) ? 1ULL : 0ULL; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct invalid_wide_sizeof_record; +unsigned long long invalid_wide_sizeof_record_value = + sizeof(struct invalid_wide_sizeof_record) ? 1ULL : 0ULL; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct invalid_wide_sizeof_record_array; +unsigned long long invalid_wide_sizeof_record_array_value = + sizeof(struct invalid_wide_sizeof_record_array[2]) ? 1ULL : 0ULL; +int main(void) { return 0; } +EOF +try_compile_error << EOF +unsigned long long invalid_wide_sizeof_void_expression = + sizeof((void)1) ? 1ULL : 0ULL; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct bad_expr_rec; +struct bad_expr_rec *bad_expr_ptr; +unsigned long long invalid_wide_sizeof_expression_record_value = + sizeof((*bad_expr_ptr)) ? 1ULL : 0ULL; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct bad_expr_trec; +typedef struct bad_expr_trec bad_expr_t; +bad_expr_t *bad_expr_tptr; +unsigned long long invalid_wide_sizeof_expression_typedef_value = + sizeof((*bad_expr_tptr)) ? 1ULL : 0ULL; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int invalid_wide_sizeof_trailing_object; +unsigned long long invalid_wide_sizeof_trailing_value = + sizeof (char)invalid_wide_sizeof_trailing_object ? 0x100000000ULL : 1U; +int main(void) { return 0; } +EOF +try_ 2 << EOF +unsigned long long global_parenthesized = (0x100000000ULL + 7ULL); +int main(void) { + return ((global_parenthesized >> 32) == 1ULL) + + ((unsigned int)global_parenthesized == 7U); +} +EOF + +try_ 2 << EOF +unsigned long long global_nested_parenthesized = ((0x100000000ULL + 7ULL)); +int main(void) { + return ((global_nested_parenthesized >> 32) == 1ULL) + + ((unsigned int)global_nested_parenthesized == 7U); +} +EOF + +try_ 2 << EOF +unsigned long long global_grouped_outer = (0x100000000ULL + 7ULL) * 2ULL; +int main(void) { + return ((global_grouped_outer >> 32) == 2ULL) + + ((unsigned int)global_grouped_outer == 14U); +} +EOF + +try_ 2 << EOF +unsigned long long global_wide_late = (3 + 0x100000000ULL) * 2ULL; +int main(void) { + return ((global_wide_late >> 32) == 2ULL) + + ((unsigned int)global_wide_late == 6U); +} +EOF + +try_ 2 << EOF +unsigned long long global_nested_grouped = + (0x100000000ULL + (3ULL * 4ULL)) - 5ULL; +int main(void) { + return ((global_nested_grouped >> 32) == 1ULL) + + ((unsigned int)global_nested_grouped == 7U); +} +EOF + +try_ 2 << EOF +long long global_negated_grouped = -(0x100000000LL + 7LL); +int main(void) { + return ((global_negated_grouped >> 32) == -2LL) + + ((unsigned int) global_negated_grouped == 0xfffffff9U); +} +EOF + +try_ 4 << EOF +unsigned long long global_wide_complement = ~0ULL; +long long global_wide_double_negation = -(~0LL); +unsigned long long global_wide_unary_plus = +0x100000000ULL; +unsigned long long global_wide_logical_not = !0ULL; +int main(void) { + return (global_wide_complement == 0xffffffffffffffffULL) + + (global_wide_double_negation == 1LL) + + (global_wide_unary_plus == 0x100000000ULL) + + (global_wide_logical_not == 1ULL); +} +EOF + +# A narrow operand of a unary operator or a shift is promoted to int first, so +# the folded result extends as an int rather than as the cast type. +try_ 6 << EOF +long long global_promoted_complement = ~(unsigned char) 0; +long long global_promoted_negation = -(unsigned short) 5; +long long global_promoted_shift = ~((unsigned char) 1 << 1); +long long global_promoted_shift_neg = -((unsigned short) 1 << 1); +int main(void) { + static long long promoted_static = ~(unsigned char) 0; + static long long promoted_static_shift = ~(unsigned short) 0 >> 1; + return (global_promoted_complement == -1LL) + + (global_promoted_negation == -5LL) + + (global_promoted_shift == -3LL) + + (global_promoted_shift_neg == -2LL) + + (promoted_static == -1LL) + (promoted_static_shift == -1LL); +} +EOF + +try_ 2 << EOF +int main(void) { + unsigned int all = 0xffffffffU; + return (all == 0xffffffffU) + (all > 1U); +} +EOF + +try_ 2 << EOF +long long global_negative_wide = -0x100000000LL; +int main(void) { + return ((global_negative_wide >> 32) == -1LL) + + ((unsigned int)global_negative_wide == 0U); +} +EOF + +try_ 2 << EOF +unsigned long long global_unsigned_negative_wide = -0x100000000LL; +int main(void) { + return ((global_unsigned_negative_wide >> 32) == 0xffffffffULL) + + ((unsigned int)global_unsigned_negative_wide == 0U); +} +EOF +try_ 3 << EOF +unsigned long long global_unsuffixed = 0x100000000; +unsigned long long global_all_bits = 0xffffffffffffffff; +long long global_decimal = 2147483648; +int main(void) { + return ((global_unsuffixed >> 32) == 1ULL) + + ((global_all_bits >> 63) == 1ULL) + + (global_decimal == 2147483648LL); +} +EOF +try_ 2 << EOF +unsigned int global_octal_max = 037777777777; +unsigned long long global_octal_wide = 040000000000; +int main(void) { + return (global_octal_max >> 31) + + ((global_octal_wide >> 32) == 1ULL); +} +EOF +try_ 3 << EOF +unsigned long long global_quotient = + 0x123456789abcdef0ULL / 0x100000000ULL; +unsigned long long global_remainder = + 0x123456789abcdef0ULL % 0x100000000ULL; +int main(void) { + return (global_quotient == 0x12345678ULL) + + (global_remainder == 0x9abcdef0ULL) + + ((global_quotient << 32) == 0x1234567800000000ULL); +} +EOF +try_ 2 << EOF +unsigned long long global_precedence = + 0x100000000ULL + 3ULL * 4ULL - 5ULL; +int main(void) { + return ((global_precedence >> 32) == 1ULL) + + ((unsigned int)global_precedence == 7U); +} +EOF +try_ 2 << EOF +unsigned long long identity_wide(unsigned long long value) { return value; } +int main(void) { + unsigned long long value = identity_wide(0x123456789abcdef0ULL); + return ((value >> 32) == 0x12345678ULL) + + ((value - 0x1234567800000000ULL) == 0x9abcdef0ULL); +} +EOF +try_ 3 << EOF +int main(void) { + unsigned long long high = 0x100000000ULL; + unsigned long long mask = 0xffffffffffffffffULL; + return (((1ULL | high) >> 32) == 1ULL) + + (((high & mask) >> 32) == 1ULL) + + ((high ^ high) == 0ULL); +} +EOF +try_ 3 << EOF +int main(void) { + unsigned long long value = 0x123456789abcdef0ULL; + return ((value / 0x100000000ULL) == 0x12345678ULL) + + ((value % 0x100000000ULL) == 0x9abcdef0ULL) + + ((0x100000000ULL / 3ULL) == 1431655765ULL); +} +EOF +try_compile_error << EOF +int main(void) { return 18446744073709551616ULL != 0ULL; } +EOF +try_compile_error << EOF +int main(void) { return 9223372036854775808LL != 0LL; } +EOF +try_compile_error << EOF +int main(void) { return 9223372036854775808 != 0LL; } +EOF +try_ 2 << EOF +int main(void) { + unsigned long long first = 0x100000000lLu; + unsigned long long second = 0x100000000Ull; + return ((first >> 32) == 1ULL) + ((second >> 32) == 1ULL); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned long long all_bits = ~0ULL; + return ((all_bits >> 63) == 1ULL) + + (!0x100000000ULL == 0); +} +EOF +try_compile_error << EOF +int main(void) { return 1UU; } +EOF +try_compile_error << EOF +int main(void) { return 1LLL; } +EOF +try_compile_error << EOF +int main(void) { return 1LUL; } +EOF +try_ 2 << EOF +int main(void) { + int value = -6; + return (value / 4 == -1) + (value % 4 == -2); +} +EOF +try_ 2 << EOF +int main(void) { + return (sizeof(long long) == 8) + (sizeof(unsigned long long) == 8); +} +EOF +try_ 5 << EOF +int main(void) { + unsigned int high = 0xffffffffU; + unsigned long long widened_unsigned = (unsigned long long) high; + long long widened_signed = (long long) -1; + unsigned long long shifted = (unsigned long long) 1U << 32; + return ((widened_unsigned >> 32) == 0ULL) + + ((widened_unsigned >> 31) == 1ULL) + + (widened_signed == -1LL) + + ((shifted >> 32) == 1ULL) + + ((unsigned int) shifted == 0U); +} +EOF + +# A cast to long long extends by the source signedness, even when the source is +# the result of 32-bit arithmetic whose register holds no extension. +try_output 0 "ffffffff.fffffffb;0.ee6b2800;ffffffff.fffffffd;0.c8;0.ea60;ffffffff.fffffffb;ffffffff.f4143e00;ffffffff.ffffec78;0.7fb;ffffffff.fffffffa;" << EOF +void show(long long value) +{ + printf("%x.%x;", (unsigned) (value >> 32), (unsigned) value); +} +int main(void) +{ + int negative = -5; + unsigned int large = 4000000000U; + short half = -3; + unsigned char byte = 200; + unsigned short word = 60000; + int product = -1000000; + show((long long) negative); + show((long long) large); + show((long long) half); + show((long long) byte); + show((long long) word); + show((unsigned long long) negative); + product *= byte; + show((long long) product); + product = -1000000; + product /= byte; + show((long long) product); + show((long long) (negative & 0x7ff)); + show((long long) (negative ^ 1)); + return 0; +} +EOF +try_ 2 << EOF +long long bump(long long value) { return value + 1LL; } +unsigned long long twice(unsigned long long value) { return value * 2ULL; } +int main(void) { + long long signed_value = 1LL << 32; + unsigned long long unsigned_value = 1ULL << 32; + return ((bump(signed_value) >> 32) == 1LL) + + ((twice(unsigned_value) >> 33) == 1ULL); +} +EOF +try_ 4 << EOF +int main(void) { + long long signed_value = 1LL << 33; + unsigned long long unsigned_value = 1ULL << 33; + return (((signed_value / 2LL) >> 32) == 1LL) + + ((signed_value % 3LL) == 2LL) + + (((unsigned_value / 2ULL) >> 32) == 1ULL) + + ((unsigned_value % 3ULL) == 2ULL); +} +EOF +try_ 2 << EOF +long long eighth(long long a, long long b, long long c, long long d, + long long e, long long f, long long g, long long h) { + return h; +} +unsigned long long ueighth(unsigned long long a, unsigned long long b, + unsigned long long c, unsigned long long d, + unsigned long long e, unsigned long long f, + unsigned long long g, unsigned long long h) { + return h; +} +int main(void) { + long long signed_value = 1LL << 32; + unsigned long long unsigned_value = 1ULL << 32; + return ((eighth(1LL, 2LL, 3LL, 4LL, 5LL, 6LL, 7LL, signed_value) >> 32) == 1LL) + + ((ueighth(1ULL, 2ULL, 3ULL, 4ULL, 5ULL, 6ULL, 7ULL, unsigned_value) >> 32) == 1ULL); +} +EOF +try_ 1 << EOF +int main(void) { + unsigned long long value = 0xffffffffU; + value = value * 16 + 0; + return (value >> 32) == 15ULL; +} +EOF +try_ 1 << EOF +unsigned long long scale(unsigned long long value, int factor) { + unsigned long long product = value * factor; + return product; +} +int main(void) { + unsigned long long value = 0xffffffffU; + return (scale(value, 16) >> 32) == 15ULL; +} +EOF +try_ 4 << EOF +int main(void) { + unsigned int high = 0xffffffffU; + long long one = 1LL; + return ((high + one) == 4294967296LL) + + ((high * one) == 4294967295LL) + + ((-1 + 1ULL) == 0ULL) + + ((-1 > 1ULL) == 1); +} +EOF + +# A wide global reduction must rebuild the high word of every int-sized operand +# from its type: an enumeration constant never stores one, and an int-sized +# intermediate such as 2U - 3U must not keep its borrow. +try_ 8 << EOF +enum { NEGATIVE_ONE = -1 }; +long long wide_enum_sum = 0x100000000LL + NEGATIVE_ONE; +long long wide_enum_product = 1LL * NEGATIVE_ONE; +long long wide_unsigned_borrow = 0LL + (2U - 3U); +long long wide_unsigned_carry = 0LL + (0x7fffffffU + 1U) * 2U; +long long wide_mixed_quotient = 0LL + (-2) / 2U; +long long wide_narrow_divisor = 0x100000000LL / (2U - 3U); +long long wide_narrow_truth = 0LL + !(0x80000000U + 0x80000000U); +long long wide_enum_shift = (0LL + NEGATIVE_ONE) >> 1; +int main(void) { + return (wide_enum_sum == 0xffffffffLL) + (wide_enum_product == -1LL) + + (wide_unsigned_borrow == 0xffffffffLL) + + (wide_unsigned_carry == 0LL) + + (wide_mixed_quotient == 0x7fffffffLL) + + (wide_narrow_divisor == 1LL) + (wide_narrow_truth == 1LL) + + (wide_enum_shift == -1LL); +} +EOF + +# A signed long long divided by an unsigned int keeps the signed long long +# common type, so the quotient and remainder follow signed rules. +try_ 4 << EOF +long long wide_signed_quotient = -2LL / 2U; +long long wide_signed_truncation = -7LL / 2U; +long long wide_signed_remainder = -7LL % 2U; +unsigned long long wide_unsigned_quotient = -2LL / 2ULL; +int main(void) { + return (wide_signed_quotient == -1LL) + + (wide_signed_truncation == -3LL) + + (wide_signed_remainder == -1LL) + + (wide_unsigned_quotient == 0x7fffffffffffffffULL); +} +EOF + +# Character constants are integer constant expression operands, so a wide global +# initializer accepts them next to a long long literal. +try_ 4 << EOF +long long wide_char_sum = 0x100000000LL + 'a'; +long long wide_char_first = 'a' + 1LL; +long long wide_wchar_product = L'b' * 0x100000000LL; +long long wide_char_condition = '\0' ? 1LL : 0x100000000LL; +int main(void) { + return (wide_char_sum == 0x100000061LL) + (wide_char_first == 98LL) + + (wide_wchar_product == 0x6200000000LL) + + (wide_char_condition == 0x100000000LL); +} +EOF + +# A constant copied into its caller by inlining keeps its high word: ~0ULL +# returned from a helper is not 0xffffffff. +try_ 0 << EOF +unsigned long long all_ones(void) { return ~0ULL; } +long long high_only(void) { return 0x100000000LL; } +int main(void) { + return all_ones() != 0xffffffffffffffffULL || high_only() != 0x100000000LL; +} +EOF + +# A post-allocation all-ones rewrite needs the constant's high word as well: an +# eight-byte OR or AND with 0xffffffffULL is no identity. +try_ 0 << EOF +int main(void) { + unsigned long long low = 0xffffffffULL; + unsigned long long high = 0x100000000ULL; + low |= ~0; + high &= 0xffffffffULL; + return low != 0xffffffffffffffffULL || high != 0; +} +EOF + +# A long long read or written through a pointer moves both words: array elements +# and a member of a record reached through a pointer. +try_ 0 << EOF +struct wide_member { char c; long long v; int i; }; +int main(void) { + long long a[4]; + long long sum = 0; + struct wide_member s; + struct wide_member *p = &s; + for (int i = 0; i < 4; i++) + a[i] = (long long)i << 33; + for (int i = 0; i < 4; i++) + sum += a[i]; + p->v = -3LL << 40; + p->i = 7; + return sum != (6LL << 33) || s.v != (-3LL << 40) || s.i != 7; +} +EOF +# Taking the address of a long long held in registers stores both words first. +try_ 0 << EOF +void bump(long long *p) { *p += 1; } +int main(void) { + long long v = 0x1122334455667788LL; + long long *p = &v; + *p += 1; + bump(&v); + return v != 0x112233445566778aLL; +} +EOF + +# A long long carried round a loop in a register pair keeps its high word when +# the phi copy stores it back to the variable's slot. +try_ 0 << EOF +long long countdown(long long a) { + int r = 0; + while (r < 3) { + a = a - 0x80000000LL; + r++; + } + return a; +} +int main(void) { return countdown(0x100000000LL) != -0x80000000LL; } +EOF + +# A long long whose low word is zero is still true in a condition, a logical +# operator and a loop test. +try_ 61 << EOF +int is_set(long long a) { if (a) return 1; return 0; } +int is_clear(long long a) { return !a; } +int both(long long a) { return a && 1; } +int either(long long a, long long b) { return b || a; } +int drain(long long a) { + int r = 0; + while (a && r < 9) { + a = a - 0x80000000LL; + r++; + } + return r; +} +int choose(long long a) { return a ? 1 : 0; } +int main(void) { + long long a = 0x100000000LL; + return is_set(a) + 2 * is_clear(a) + 4 * both(a) + 8 * either(a, 0) + + 16 * (drain(a) == 2) + 32 * choose(a); +} +EOF + +# Ordering two long longs compares their low words unsigned whatever the type, +# and the high words by the type's signedness. +try_ 0 << EOF +int order(long long a, long long b) { + return (a < b) + 2 * (a <= b) + 4 * (a > b) + 8 * (a >= b) + + 16 * (a == b) + 32 * (a != b); +} +int uorder(unsigned long long a, unsigned long long b) { + return (a < b) + 2 * (a <= b) + 4 * (a > b) + 8 * (a >= b) + + 16 * (a == b) + 32 * (a != b); +} +int main(void) { + return order(0, 0x80000000LL) != 35 || order(0x80000000LL, 0) != 44 || + order(-1, 0) != 35 || order(0x100000000LL, 0xffffffffLL) != 44 || + order(-0x80000000LL, 1) != 35 || order(7, 7) != 26 || + uorder(0, 0x80000000ULL) != 35 || uorder(~0ULL, 1) != 44 || + uorder(0x100000000ULL, 0xffffffffULL) != 44; +} +EOF + +# A signed right shift of a long long takes the sign from the high word; the low +# word's own top bit is data and must not be replicated. +try_ 0 << EOF +long long sar(long long v, int n) { return v >> n; } +int main(void) { + return sar(0x80000000LL, 1) != 0x40000000LL || + sar(0x1ffffffffLL, 4) != 0x1fffffffLL || + sar(-0x100000000LL, 33) != -1 || + sar(-0x80000000LL, 4) != -0x8000000LL; +} +EOF +# Shifting a long long by a run-time amount of zero leaves it unchanged. +try_ 0 << EOF +int amounts[2] = {0, 63}; +long long shl(long long v, int n) { return v << n; } +unsigned long long shr(unsigned long long v, int n) { return v >> n; } +long long sar(long long v, int n) { return v >> n; } +int main(void) { + int zero = amounts[0]; + return shl(0x80000001LL, zero) != 0x80000001LL || + shr(0xffffffffULL, zero) != 0xffffffffULL || + sar(-0x80000000LL, zero) != -0x80000000LL || + shl(1, amounts[1]) != (long long)0x8000000000000000ULL; +} +EOF + +# Issue 312: a long long argument after four ints is found where the caller put +# it, with both of its words. +try_ 0 << EOF +int test_ll(int a, int b, int c, int d, long long e) { + return e == 1000LL && a == 1 && b == 2 && c == 3 && d == 4; +} +int main(void) { return !test_ll(1, 2, 3, 4, 1000); } +EOF + +# A negative signed char widened to a short keeps its sign, whether the short is +# a local or a parameter the call converts it to. +try_ 0 << EOF +int to_ushort(signed char c) { unsigned short s = c; return s; } +int high_word(short s) { unsigned long long x = 8; x |= s; return x >> 32; } +int main(void) { + signed char c = -3; + return to_ushort(-108) != 65428 || high_word(c) != -1; +} +EOF + +# A repeated wide subexpression becomes a copy of the first result, and fusing +# that copy into the operation must not leave the first result unwritten. +try_ 0 << EOF +void sink(unsigned long long v) {} +unsigned long long r; +void same_twice(unsigned long long a0, long long a1, unsigned long long a2, + unsigned long long a3) { + unsigned long long l0 = 6942535506704294056ULL; + long long l4 = a0; + l0 /= l4; + sink(l0); + r = 1 ? ((a0 & l0) ^ (l0 & a0)) : 7; +} +int main(void) { + same_twice(18, 0x7fffffff, 0xf3a9221e0ce272ccULL, 4); + return r != 0; +} +EOF + +# One long long passed twice keeps both copies: staging the second pair must not +# release the first argument's registers to the constants loaded after it. +try_ 0 << EOF +long long g = 0x123456789LL; +int f(long long a0, long long a1, unsigned int a2, long long a3) { + return a0 != 0x123456789LL || a1 != 0x123456789LL || a2 != 0x89f20000U || + a3 != 0x25085689f936b9a0LL; +} +int main(void) { return f(g, g, 0x89f20000U, 0x25085689f936b9a0LL); } +EOF + +# Post-allocation rewrites of a register pair keep its high word: the move after +# a pair operation and the load of a slot written just before it. +try_ 0 << EOF +long long total(long long a, long long b, long long c) { + long long s = 0; + for (int i = 0; i < 3; i++) { + long long v = (i == 0 ? a : i == 1 ? b : c) * 1; + s = s + v; + } + return s; +} +int main(void) { return total(0, 0x100000000LL, -1) != 0xffffffffLL; } +EOF + +# Moving a register pair one register over writes the high word before the low +# register that holds it is overwritten. +try_ 0 << EOF +long long mix(long long a, int b, long long c, int d, int e, long long f) { + return a * 1 + b * 10 + c * 100 + d * 1000 + e * 10000 + f * 100000; +} +int main(void) { + return mix(1, 2, 3, 4, 5, 6) != 654321 || + mix(-1LL << 40, 2, 3, 4, 5, 6) != (-1LL << 40) + 654320; +} +EOF + +# Spilling a register pair to free one register for a word stores both of its +# words. +try_ 0 << EOF +#include +int main(void) { + long long vals[3]; + int r = 0; + vals[0] = 0LL; + vals[1] = -1LL; + vals[2] = 5LL; + for (int j = 0; j < 3; j++) { + long long a = vals[0], b = vals[j]; + int sum = (a < b); + sum = sum * 5 + (a > b); + r = r * 10 + sum; + printf("%d", sum); + } + printf("\n"); + return r != 15; +} +EOF + +# A long long global keeps both words when a function stores a constant in it +# and when its initializer has an int-sized type. +try_ 0 << EOF +unsigned long long wide_scalar; +unsigned long long wide_narrow_init = 1 ? 7U : 1U; +long long wide_negative_init = 1 ? -7 : 1; +void set(void) { wide_scalar = 0x100000001ULL; } +int main(void) { + set(); + return wide_scalar != 0x100000001ULL || wide_narrow_init != 7 || + wide_negative_init != -7; +} +EOF + +# A long long operation extends an int-sized operand the parser left narrow, +# including through a pointer store and a narrow quotient of a wide dividend. +try_ 0 << EOF +int main(void) { + int i = -1; + unsigned u = 1; + long long l = -2; + long long cell = 5; + long long *p = &cell; + unsigned long long q = 0x300000000ULL; + int small; + *p = i; + small = (int)(q / 3); + return !(l < u) || (l + u) != -1 || cell != -1 || + small != 0 || (u << 31) != 0x80000000U; +} +EOF + +# Pair division in a loop holds two operand pairs and a result pair at once, and +# must still find registers beside the ones kept for the loop counters. +try_ 92 << EOF +int main(void) { + long long v[5], d[4]; + unsigned r = 0; + v[0] = 7LL; v[1] = -7LL; v[2] = 0x123456789LL; v[3] = -0x123456789LL; + v[4] = 0x7fffffffffffffffLL; + d[0] = 3LL; d[1] = -3LL; d[2] = 0x10000LL; d[3] = -0x100000001LL; + for (int i = 0; i < 5; i++) + for (int j = 0; j < 4; j++) { + long long q = v[i] / d[j], m = v[i] % d[j]; + unsigned long long uq = (unsigned long long)v[i] / (unsigned long long)d[j]; + unsigned long long um = (unsigned long long)v[i] % (unsigned long long)d[j]; + r = r * 33 + (unsigned)q + (unsigned)(q >> 32); + r = r * 33 + (unsigned)m + (unsigned)(m >> 32); + r = r * 33 + (unsigned)uq + (unsigned)(uq >> 32); + r = r * 33 + (unsigned)um + (unsigned)(um >> 32); + } + return r % 251; +} +EOF + +# Aggregate initializers keep the high word of a long long member or element: a +# file-scope record, a nested array member and a block-scope static array. +try_ 0 << EOF +struct wide_record { int a; long long b; int c; unsigned long long d[2]; }; +struct wide_record wide_global = {1, 0x1122334455667788LL, 3, + {0x100000000ULL, 9}}; +long long wide_static(int i) { + static long long table[3] = {0x100000001LL, -0x200000000LL, 5}; + return table[i]; +} +int main(void) { + return wide_global.a != 1 || wide_global.b != 0x1122334455667788LL || + wide_global.c != 3 || wide_global.d[0] != 0x100000000ULL || + wide_global.d[1] != 9 || wide_static(0) != 0x100000001LL || + wide_static(1) != -0x200000000LL || wide_static(2) != 5; +} +EOF + +# A file-scope array element computed by a wide operator is lowered in global +# setup, which must name the high registers of the operands and the result. +try_ 0 << EOF +long long wide_ops[] = {1LL << 40, -2, (1LL << 40) | 3, ~(1LL << 40), + -(1LL << 40), (1LL << 40) / 3, 0x100000000LL - 1}; +int main(void) { + return wide_ops[0] != 0x10000000000LL || wide_ops[1] != -2 || + wide_ops[2] != 0x10000000003LL || wide_ops[3] != -0x10000000001LL || + wide_ops[4] != -0x10000000000LL || wide_ops[5] != 0x5555555555LL || + wide_ops[6] != 0xffffffffLL; +} +EOF + +# ++ and -- on a long long carry into and borrow from the high word, whether the +# object is a variable, an array element or a member reached through a pointer. +try_ 0 << EOF +struct wide_counter { long long count; }; +int main(void) { + unsigned long long u = 0xffffffffULL; + long long s = 0x100000000LL; + long long cells[2] = {0xffffffffLL, 0x100000000LL}; + struct wide_counter counter = {0xffffffffLL}; + struct wide_counter *p = &counter; + unsigned long long old = u++; + long long before = --s; + cells[0]++; + --cells[1]; + p->count++; + return u != 0x100000000ULL || old != 0xffffffffULL || + s != 0xffffffffLL || before != 0xffffffffLL || + cells[0] != 0x100000000LL || cells[1] != 0xffffffffLL || + counter.count != 0x100000000LL; +} +EOF + +# A narrow global is loaded and stored at its own width. Its data slot is no +# wider than its type on LP64, so a full-width store to a clobbered b and e. +try_ 0 << EOF +unsigned int a = 1; +unsigned int b = 2; +signed char c = 3; +unsigned short d = 4; +int e = 7; +unsigned long long wide = 0x678d2f31; +void set(unsigned int v) { a = v; c = -1; d = 65535; } +int main(void) { + set(0xffffffff); + return b != 2 || e != 7 || c != -1 || d != 65535 || a != 0xffffffff || + (b >> (wide & 31)) != 0 || (a >> (wide & 31)) != 0x7fff; +} +EOF + +# A global is not renamed, so "g = g" is dropped as redundant; a later read of g +# must still see the store before it rather than the dropped assignment. +try_ 0 << EOF +int g = 1; +int read_g(void) { return g; } +int main(void) { + g = g; + if (g != 1) + return 1; + g = 5; + g = g; + return g != 5 || read_g() != 5; +} +EOF + +# Algebraic and strength-reduction folds read a constant's low word only. +# 0xffffffffULL is no all-ones mask for a long long, and 0x100000004ULL is not +# the power of two its low word is. +try_ 0 << EOF +unsigned long long mask(unsigned long long x) { return x & 0xffffffffULL; } +unsigned long long fill(unsigned long long x) { return x | 0xffffffffULL; } +long long times(long long x) { return x * 0xffffffffLL; } +unsigned long long scale(unsigned long long x) { return x * 0x100000004ULL; } +unsigned long long part(unsigned long long x) { return x / 0x100000004ULL; } +long long all_ones(long long x) { return x | -1; } +int main(void) { + return mask(0x123456789ULL) != 0x23456789ULL || + fill(0x123456789ULL) != 0x1ffffffffULL || + times(2) != 0x1fffffffeLL || scale(3) != 0x30000000cULL || + part(0x300000010ULL) != 3 || all_ones(5) != -1LL; +} +EOF + +# A wide product needs all four words of its operands, including when the result +# replaces one of them or squares it in a loop. +try_ 0 << EOF +long long cell = 0x100000003LL; +int main(void) { + long long x = 0x100000003LL, y = 0x200000005LL, z = -0x123456789LL; + for (int i = 0; i < 3; i++) { + x = x * y; + z *= z; + cell = cell * y; + } + return x != 0x23f00000177LL || z != 0xaabea71870b3341LL || + cell != 0x23f00000177LL; +} +EOF + +# A branch threaded on a constant condition tests the whole constant: +# 0x227044f500000000ULL has a zero low word but is true. +try_ 0 << EOF +int main(void) { + long long flag = 0xafc6260dLL; + unsigned long long other = 0xffffffffULL; + unsigned long long picked = + ((unsigned long long)flag ? 0x227044f500000000ULL : 0) ? 0 : other; + return picked != 0; +} +EOF + +# Stack arguments of long longs go past the outgoing area a frame reserves for +# one word per argument, and on RISC-V, which passes eight arguments in +# registers, that area is empty. The caller's locals must survive the call. +try_ 0 << EOF +long long pick(long long a, long long b, long long c, long long d, long long e, + long long f, long long g, int h) { + long long r = a + b + c + d + e + f; + for (int i = 0; i < h; i++) + r += g; + return r; +} +int main(void) { + int keep = 77; + int *p = &keep; + long long r = pick(1, 2, 3, 4, 5, 6, 0x100000000LL, 2); + return r != 0x200000015LL || *p != 77; +} +EOF + +# A variadic function saves its named parameters from the argument words, +# including those passed on the stack, before va_start walks past them. +try_ 0 << EOF +#include +int named(int a, int b, int c, int d, int e, int f, int g, ...) { + va_list ap; + int x; + va_start(ap, g); + x = va_arg(ap, int); + va_end(ap); + return a + b + c + d + e + f * g + x; +} +int main(void) { return named(1, 2, 3, 4, 5, 6, 7, 100) != 157; } +EOF + +# va_arg reads a long long from the pair of argument words that holds it, which +# starts at an even word on a 32-bit target, and a variadic function saves +# enough words for arguments of two words each. +try_ 0 << EOF +#include +long long sum(int n, ...) { + va_list ap; + long long s = 0; + va_start(ap, n); + for (int i = 0; i < n; i++) { + if (i % 2) + s = s * 3 + va_arg(ap, int); + else + s = s * 3 + va_arg(ap, long long); + } + va_end(ap); + return s; +} +/* Eight named words fill the RISC-V argument registers, so saving the words + * passed on the stack goes through a7 after h has been saved from it. + */ +int eighth(int a, int b, int c, int d, int e, int f, int g, int h, ...) { + va_list ap; + int cells[1]; + va_start(ap, h); + cells[0] = h; + va_end(ap); + return cells[0]; +} +int main(void) { + return eighth(1, 2, 3, 4, 5, 6, 7, 8) != 8 || + sum(4, 0x100000000LL, 2, 0x300000000LL, 4) != + ((0x100000000LL * 3 + 2) * 3 + 0x300000000LL) * 3 + 4 || + sum(7, 1LL, 2, -3LL, 4, 0x500000000LL, 6, 7LL) != + (((((1LL * 3 + 2) * 3 - 3) * 3 + 4) * 3 + 0x500000000LL) * 3 + 6) * 3 + 7; +} +EOF + +# RV32 passes a named long long in the next two argument words, splitting it +# between a7 and the stack, and aligns only a variadic one; AAPCS32 aligns every +# one. Either way the callee must find what the caller placed. +try_ 0 << EOF +#include +long long split(int a, int b, int c, int d, int e, int f, int g, long long h) { + return (a + b + c + d + e + f + g) * 1000 + h * 10; +} +long long after(long long a, int b, int c, int d, int e, int f, long long g, + int h) { + return a * 3 + (b + c + d + e + f) * 1000 + g * 10 + h; +} +long long odd(int a, long long b, int c) { return a * 100 + b * 10 + c; } +long long unnamed(int a, int b, int c, int d, int e, int f, int g, ...) { + va_list ap; + long long x; + va_start(ap, g); + x = va_arg(ap, long long); + va_end(ap); + return (a + b + c + d + e + f + g) + x * 5; +} +typedef long long (*after_t)(long long, int, int, int, int, int, long long, int); +int main(void) { + after_t callback = after; + return split(1, 2, 3, 4, 5, 6, 7, 0x800000009LL) != + 28000 + 0x800000009LL * 10 || + after(0x100000000LL, 1, 2, 3, 4, 5, -0x800000009LL, 11) != + 0x300000000LL + 15000 - 0x800000009LL * 10 + 11 || + callback(0x100000000LL, 1, 2, 3, 4, 5, -0x800000009LL, 11) != + 0x300000000LL + 15000 - 0x800000009LL * 10 + 11 || + odd(1, 0x100000000LL, 2) != 100 + 0xa00000000LL + 2 || + unnamed(1, 2, 3, 4, 5, 6, 7, -0x300000000LL) != 28 - 0xf00000000LL; +} +EOF +try_ 1 << EOF +unsigned int identity(unsigned int value) { return value; } +int main(void) { return identity(4294967295U) >> 31; } +EOF +try_ 2 << EOF +unsigned char byte_identity(unsigned char value) { return value; } +unsigned short half_identity(unsigned short value) { return value; } +int main(void) { + return (byte_identity(255) == 255) + (half_identity(65535) == 65535); +} +EOF +try_ 1 << EOF +unsigned int eighth(unsigned int a, unsigned int b, unsigned int c, + unsigned int d, unsigned int e, unsigned int f, + unsigned int g, unsigned int h) { return h; } +int main(void) { + return eighth(1U, 2U, 3U, 4U, 5U, 6U, 7U, 4294967295U) >> 31; +} +EOF +try_ 2 << EOF +int main(void) { + return (((unsigned int)-1) >> 31) + + (((unsigned short)-1) >> 15); +} +EOF +try_ 5 << EOF +/* C99 integer promotions preserve the magnitude of narrow unsigned values: + * unary operators promote to int, and the promoted operands then participate + * in ordinary binary arithmetic. Sign-extending either source would make + * these comparisons false. */ +int main(void) { + unsigned char byte = 255; + unsigned short half = 65535; + return (+byte == 255) + (~byte == -256) + (-byte == -255) + + (+half == 65535) + (~half == -65536); +} +EOF +try_ 3 << EOF +typedef signed char signed_byte; +signed_byte echo_signed_byte(signed_byte value); +signed char echo_signed_byte(signed char value) { return value; } +signed_byte compatible_signed_byte_object; +signed char compatible_signed_byte_object; +int main(void) { + signed_byte value = -1; + return (sizeof(signed char) == 1) + (echo_signed_byte(value) == -1) + + (compatible_signed_byte_object == 0); +} +EOF +try_compile_error << EOF +char incompatible_character_object; +signed char incompatible_character_object; +int main(void) { return 0; } +EOF +try_compile_error << EOF +char incompatible_character_function(char value); +signed char incompatible_character_function(signed char value); +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { + char plain = 0; + signed char signed_value = 0; + char *plain_pointer = &plain; + signed char *signed_pointer = &signed_value; + plain_pointer = signed_pointer; + return *plain_pointer; +} +EOF +try_compile_error << EOF +void take_plain(char *value) { } +int main(void) { + signed char value = 0; + take_plain(&value); + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + char plain = 0; + signed char signed_value = 0; + return &plain == &signed_value; +} +EOF +try_ 0 << EOF +int main(void) { + signed char value = 0; + void *bridge = &value; + signed char *round_trip = bridge; + return *round_trip; +} +EOF +try_ 0 << EOF +int main(void) { + unsigned char byte = 255; + unsigned short half = 65535; + byte++; + half++; + return byte + half; +} +EOF +try_ 2 << EOF +int main(void) { + unsigned char byte = 255; + unsigned short half = 65535; + byte /= 2; + half /= 2; + return (byte == 127) + (half == 32767); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned char bytes[1] = {255}; + unsigned short halves[1] = {65535}; + bytes[0] /= 2; + halves[0] /= 2; + return (bytes[0] == 127) + (halves[0] == 32767); +} +EOF +try_ 2 << EOF +typedef unsigned char *uchar_pointer; +typedef unsigned short *ushort_pointer; +int main(void) { + unsigned char bytes[1] = {255}; + unsigned short halves[1] = {65535}; + uchar_pointer byte_ptr = bytes; + ushort_pointer half_ptr = halves; + return (byte_ptr[0] / 2 == 127) + (half_ptr[0] / 2 == 32767); +} +EOF +try_ 1 << EOF +typedef unsigned char *uchar_pointer; +typedef uchar_pointer *uchar_pointer_pointer; +int main(void) { + unsigned char bytes[1] = {255}; + uchar_pointer pointer = bytes; + uchar_pointer_pointer pointer_to_pointer = &pointer; + uchar_pointer loaded = *pointer_to_pointer; + return loaded[0] / 2 == 127; +} +EOF +try_ 1 << EOF +typedef unsigned char *uchar_pointer; +typedef uchar_pointer *uchar_pointer_pointer; +typedef uchar_pointer_pointer *uchar_pointer_pointer_pointer; +int main(void) { + unsigned char bytes[1] = {255}; + uchar_pointer pointer = bytes; + uchar_pointer_pointer pointer_to_pointer = &pointer; + uchar_pointer_pointer_pointer pointer_to_pointer_to_pointer = + &pointer_to_pointer; + uchar_pointer_pointer loaded_pointer_to_pointer = + *pointer_to_pointer_to_pointer; + uchar_pointer loaded_pointer = *loaded_pointer_to_pointer; + return loaded_pointer[0] / 2 == 127; +} +EOF +try_ 1 << EOF +int main(void) { return '\x0041' == 'A'; } +EOF +try_compile_error_message "Hexadecimal escape sequence out of range" << EOF +int main(void) { return '\x123'; } +EOF +try_ 3 << EOF +#if __STDC__ != 1 +#error __STDC__ must be one +#endif +#if __STDC_VERSION__ != 199901L +#error expected C99 version macro +#endif +int main(void) { return __STDC__ + !__STDC_HOSTED__ + + (__STDC_VERSION__ == 199901L); } +EOF +try_ 2 << EOF +struct unsigned_members { unsigned char byte; unsigned short half; }; +int main(void) { + struct unsigned_members value = {255, 65535}; + return (value.byte / 2 == 127) + (value.half / 2 == 32767); +} +EOF + +# A narrow unsigned value loaded from memory must not arrive sign-extended: on +# an LP64 target an index of 200 read from a member or an element went into the +# address as -56. +try_ 0 << EOF +struct unsigned_members { unsigned char byte; unsigned short half; }; +char table[60000]; +int main(void) { + struct unsigned_members value = {200, 50000}; + struct unsigned_members *p = &value; + unsigned char bytes[1] = {200}; + table[200] = 7; + table[50000] = 9; + return table[p->byte] != 7 || table[p->half] != 9 || + table[bytes[0]] != 7 || *(table + p->byte) != 7; +} +EOF +try_ 6 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + return p[1][0]; +} +EOF + +# An array member of a const record decays to a pointer to const elements, +# through arithmetic too, as any pointer to const does; a function pointer +# member of it is read-only. +try_ 0 << EOF +struct S { int arr[2]; }; struct S s = {{3, 4}}; int main(void) { const struct S *p = &s; const int *q = p->arr; struct S *m = &s; *(m->arr) = 5; *((*m).arr + 1) = 6; return *q + *(p->arr + 1) + (*p).arr[1] - 17; } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } struct S { int (*cb)(int); }; struct S s = {inc}; int main(void) { const struct S *p = &s; return p->cb(1) + (*p).cb(1) - 4; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct S { int arr[2]; }; struct S s; int main(void) { const struct S *p = &s; *(p->arr) = 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct S { int arr[2]; }; struct S s; int main(void) { const struct S *p = &s; *((*p).arr + 1) = 2; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct S { int arr[2]; }; const struct S cs; int main(void) { *(cs.arr) = 1; return 0; } +EOF +try_compile_error_message "discarding const qualifier" << EOF +struct S { int arr[2]; }; struct S s; int main(void) { const struct S *p = &s; int *q = p->arr; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct S { int arr[2]; }; struct S s; int main(void) { const struct S *p = &s; *(p->arr + 1) += 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a[2]; +int main(void) { const int *q = a; *(q + 1) = 2; return 0; } +EOF +try_compile_error_message "discarding const qualifier" << EOF +struct S { int arr[2]; }; struct S s; int main(void) { const struct S *p = &s; int *q = (*p).arr + 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } struct S { int (*cb)(int); int n; }; struct S s = {inc, 0}; int main(void) { const struct S *p = &s; p->cb = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } struct S { int (*cb)(int); int n; }; struct S s = {inc, 0}; int main(void) { const struct S *p = &s; (*p).cb = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } struct S { int (*cb)(int); }; struct S a[1] = {{inc}}; int main(void) { const struct S *p = a; p[0].cb = dec; return 0; } +EOF + +# A member reached through an explicit dereference of a pointer to a const +# record is not a modifiable lvalue, whatever the grouping or update, while its +# pointee and the members of a modifiable record stay writable. +try_ 0 << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; struct S *m = &s; (*m).a = 5; (*m).p++; (m[0]).arr[1] += 1; (*m).in.x = 7; ++(*m).a; *(*ps).p = 9; return (*ps).a + (*ps).arr[1] + (*ps).in.x + (ps[0]).arr[0] + *(*ps).p + (int) ((*ps).p - v) - (6 + 4 + 7 + 2 + 9 + 1); } +EOF +try_ 0 << EOF +struct In { int x; }; +struct S { struct In *q; const struct In *cq; int a; struct In in; }; +struct In i1 = {1}, i2 = {2}; +struct S s = {&i1, &i2, 3, {4}}; +int take(struct In in) { return in.x; } +int main(void) +{ + const struct S *ps = &s; + struct S *m = &s; + (*ps).q->x = 5; + ps[0].q->x += 1; + (*m).cq = &i1; + (*m).in.x = 8; + return i1.x != 6 || (*ps).cq->x != 6 || take((*ps).in) != 8 || + (ps[0]).a != 3; +} +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*ps).p++; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*ps).a = 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*ps).arr[0] = 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (ps[0]).a += 2; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*ps).in.x = 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; ++(*ps).a; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*ps).a--; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { cs_t *ps = &s; (*ps).a = 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*ps).in = s.in; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; struct S { int *p; int a; int arr[2]; struct In in; }; int v[2]; struct S s = {v, 1, {2, 3}, {4}}; typedef const struct S cs_t; int main(void) { const struct S *ps = &s; (*(ps)).arr[1]++; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; +struct S { const struct In *cq; }; +struct In i1 = {1}; +struct S s = {&i1}; +int main(void) +{ + struct S *m = &s; + (*m).cq->x = 2; + return 0; +} +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; +struct S { const struct In cin; int *const cp; }; +int v; +struct S s = {{1}, &v}; +int main(void) +{ + struct S *m = &s; + (*m).cin.x = 2; + return 0; +} +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct In { int x; }; +struct S { const struct In cin; int *const cp; }; +int v, w; +struct S s = {{1}, &v}; +int main(void) +{ + struct S *m = &s; + (*m).cp = &w; + return 0; +} +EOF + +# A const pointer element of a row pointer is read-only through every store, +# while the row pointer itself and non-const elements stay modifiable. +try_ 0 << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; *(*r)[0] = 9; *r[0][1] = 8; crow_t t = &arr; *(*t)[0] += 1; int (*q)[2] = &c; (*q)[0] = 7; q[0][1] += 1; (*q)[1]++; ++q[0][0]; int *(*m)[2]; int *mm[2] = {&a, &b}; m = &mm; (*m)[0] = &b; m[0][1] = &a; return a + b + (*r)[1][0] + c[0] + c[1] - (11 + 8 + 8 + 8 + 5); } +EOF +try_ 0 << EOF +int a = 1, b = 2; +const int ca = 3, cb = 4; +int *const arr[2] = {&a, &b}; +int *const arr2[2] = {&b, &a}; +const int *carr[2] = {&ca, &cb}; +typedef int *const cip; +typedef cip (*crow_t)[2]; +int main(void) +{ + int *const (*r)[2] = &arr; + crow_t t = &arr; + const int *(*q)[2] = &carr; + r = &arr2; + t = &arr2; + (*q)[0] = &cb; + q[0][1] = &ca; + (*q)[1]++; + q[0][1]--; + *(*r)[0] = 5; + *t[0][1] += 1; + return b != 5 || a != 2 || *(*q)[0] != 4 || *q[0][1] != 3 || + *(*t)[0] != 5; +} +EOF +try_ 0 << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int *const arr2[2] = {&b, &a}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; r = &arr2; return 0; } +EOF +try_ 0 << EOF +int a[2]; +const int *p = a; +const int **pp = &p; +struct S { int *p; }; +struct S s = {a}; +int main(void) +{ + struct S *ps = &s; + (*pp)++; + (*ps).p++; + return p != a + 1 || s.p != a + 1; +} +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; (*r)[0] = &b; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; r[0][1] = &b; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { crow_t r = &arr; (*r)[0] = &b; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { crow_t r = &arr; r[0][1] = &a; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; (*r)[0] += 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; (*r)[1]++; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; ++r[0][1]; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a = 1, b = 2; int *const arr[2] = {&a, &b}; int c[2] = {3, 4}; const int d[2] = {5, 6}; typedef int *const cip; typedef cip (*crow_t)[2]; int main(void) { int *const (*r)[2] = &arr; --(*r)[1]; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +const int ca = 3, cb = 4; +const int *carr[2] = {&ca, &cb}; +int main(void) { const int *(*q)[2] = &carr; *(*q)[0] = 1; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int a[2]; +int *const cp = a; +int *const *pcp = &cp; +int main(void) +{ + (*pcp)++; + return 0; +} +EOF + +# A typedef of a pointer to an array typedef of callbacks points to the whole +# row, whose elements are called and stored as through fn_t (*rows_t)[2]. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*fn_t)(int); +typedef fn_t arr_t[2]; +typedef arr_t *rows_t; +fn_t fa[2] = {inc, dec}; +rows_t g = &fa; +int main(void) +{ + rows_t r = &fa; + arr_t *q = &fa; + (*r)[0] = dec; + int v = fa[0](1); + (*r)[0] = inc; + return (*r)[0](1) - 2 + (*g)[1](1) + r[0][1](3) - 2 + (*q)[1](5) - 4 + v + + (sizeof(*r) != 2 * sizeof(fn_t)) + (**q)(7) - 8; +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*fn_t)(int); +typedef fn_t arr_t[2]; +typedef arr_t *rows_t; +fn_t fa[2] = {inc, dec}; +arr_t *gq = &fa; +int main(void) +{ + arr_t *q = &fa; + rows_t r = &fa; + int a = (*q)[1](5); + int b = (*gq)[1](5); + int c = q[0][0](1); + int d = r[0][0](1); + int e = (*r)[0](1); + return a + b + c + d + e - 14; +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } +typedef int (*const cfn_t)(int); +typedef cfn_t carr_t[2]; +typedef carr_t *crows_t; +const cfn_t fa[2] = {inc, inc}; +int main(void) { crows_t r = &fa; return 0; } +EOF +try_compile_error_message "assignment of read-only variable" << EOF +int inc(int x) { return x + 1; } +typedef int (*fn_t)(int); +typedef fn_t arr_t[2]; +typedef arr_t *const crows_t; +fn_t fa[2] = {inc, inc}; +fn_t fb[2] = {inc, inc}; +int main(void) { crows_t r = &fa; (*r)[0] = inc; r = &fb; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +typedef int (*const cfn_t)(int); +typedef cfn_t carr_t[2]; +typedef carr_t *crows_t; +const cfn_t fa[2] = {inc, inc}; +int main(void) { crows_t r = &fa; (*r)[0] = inc; return 0; } +EOF + +# A const specifier on the callback typedef, not only on the callback's own +# pointer, makes the row const. Such a pointer was taken for a pointer to +# modifiable callbacks and could not point to a const array of them. +try_ 0 << EOF +int inc(int x) { return x + 1; } +typedef int (*fn_t)(int); +typedef const fn_t (*crows_t)[2]; +const fn_t fa[2] = {inc, inc}; +int main(void) { crows_t c = &fa; return (*c)[1](5) - 6 + c[0][0](1) - 2; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +typedef int (*fn_t)(int); +typedef const fn_t (*crows_t)[2]; +const fn_t fa[2] = {inc, inc}; +int main(void) { crows_t c = &fa; (*c)[0] = inc; return 0; } +EOF + +# The same pointers to rows of callbacks may be block-scope typedefs, and a row +# of const callbacks takes the address of a const array of them. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*fn_t)(int); +fn_t fa[2] = {inc, dec}; +int main(void) +{ + typedef fn_t (*rows_t)[2]; + typedef int (*(*spelled_t)[2])(int); + rows_t r = &fa; + spelled_t s = &fa; + (*r)[0] = dec; + int v = fa[0](1); + r[0][0] = inc; + return v + (*r)[0](1) - 2 + (*s)[1](3) - 2 + r[0][1](5) - 4 + s[0][0](0) - 1 + + (sizeof(*r) != 2 * sizeof(fn_t)) + (sizeof(rows_t) != sizeof(void *)); +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*const cfn_t)(int); +typedef int (*fn_t)(int); +const cfn_t cfa[2] = {inc, dec}; +fn_t fa[2] = {inc, dec}; +int main(void) +{ + typedef cfn_t (*crows_t)[2]; + typedef fn_t (*rows_t)[2], (*other_t)[2]; + crows_t c = &cfa; + rows_t r = &fa; + other_t o = r; + o[0][0] = dec; + return (*c)[1](3) - 2 + c[0][0](1) - 2 + fa[0](1); +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*const cfn_t)(int); +typedef cfn_t (*crows_t)[2]; +const cfn_t cfa[2] = {inc, dec}; +int main(void) +{ + crows_t c = &cfa; + return (*c)[1](3) - 2 + c[0][0](1) - 2; +} +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +typedef int (*fn_t)(int); +const fn_t fa[2] = {inc, inc}; +int main(void) { typedef const fn_t (*crows_t)[2]; crows_t c = &fa; (*c)[0] = inc; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +typedef int (*const cfn_t)(int); +const cfn_t cfa[2] = {inc, inc}; +int main(void) { typedef cfn_t (*crows_t)[2]; crows_t c = &cfa; c[0][1] = inc; return 0; } +EOF + +# So may a pointer to a block-scope array typedef of callbacks, as at file +# scope; its row was taken for a callback and no element could be called. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*fn_t)(int); +typedef fn_t garr_t[2]; +int main(void) +{ + typedef fn_t arr_t[2]; + typedef arr_t *rows_t; + typedef garr_t *grows_t; + fn_t fa[2] = {inc, dec}; + rows_t r = &fa; + grows_t g = &fa; + r[0][0] = dec; + return (*r)[1](5) - 4 + r[0][1](5) - 4 + (*g)[0](1) + g[0][1](3) - 2 + + (sizeof(*r) != 2 * sizeof(fn_t)); +} +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +typedef int (*const cfn_t)(int); +const cfn_t fa[2] = {inc, inc}; +int main(void) { typedef cfn_t carr_t[2]; typedef carr_t *crows_t; crows_t r = &fa; r[0][1] = inc; return 0; } +EOF + +# A pointer to a row of callbacks, through a callback typedef or spelled out, +# calls and stores each element, and keeps a const callback read-only. +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*fn_t)(int); typedef fn_t (*rows_t)[2]; fn_t fa[2] = {inc, dec}; rows_t g = &fa; int main(void) { rows_t r = &fa; return (*r)[0](1) - 2 + (*g)[1](1) + r[0][1](3) - 2 + (sizeof(*r) != 2 * sizeof(fn_t)); } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*(*rows_t)[2])(int); int (*fa[2])(int) = {inc, dec}; rows_t g = &fa; int main(void) { rows_t r = &fa; return (*r)[0](1) - 2 + (*g)[1](1) + r[0][1](3) - 2; } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int (*fa[2])(int) = {inc, dec}; int main(void) { int (*(*r)[2])(int) = &fa; return (*r)[0](1) - 2 + r[0][1](3) - 2; } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*fn_t)(int); typedef fn_t (*rows_t)[2]; fn_t fa[2] = {inc, dec}; int main(void) { rows_t r = &fa; (*r)[0] = dec; return fa[0](1); } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*const cfn_t)(int); typedef cfn_t (*crows_t)[2]; cfn_t cfa[2] = {inc, dec}; int main(void) { crows_t r = &cfa; return (*r)[1](3) - 2 + r[0][0](1) - 2; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*const cfn_t)(int); typedef cfn_t (*crows_t)[2]; cfn_t cfa[2] = {inc, dec}; int main(void) { crows_t r = &cfa; (*r)[0] = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*const cfn_t)(int); typedef cfn_t (*crows_t)[2]; cfn_t cfa[2] = {inc, dec}; int main(void) { crows_t r = &cfa; r[0][1] = inc; return 0; } +EOF + +# A file-scope pointer-to-array typedef may take a pointer typedef as its base, +# which only makes the row's element a pointer. +try_ 0 << EOF +typedef int *ip; +typedef ip (*rows_t)[2]; +typedef ip (*grid_t)[2][3]; +typedef int *(*direct_t)[2]; +int a = 1, b = 2; +int *arr[2] = {&a, &b}; +int *grid[2][3] = {{&a, &b, &a}, {&b, &a, &b}}; +rows_t global_rows = &arr; +int main(void) +{ + rows_t r = &arr; + grid_t g = &grid; + direct_t d = &arr; + (*r)[0] = &b; + return *(*r)[1] != 2 || *r[0][0] != 2 || *(*global_rows)[0] != 2 || + *(*g)[1][2] != 2 || *(*d)[1] != 2 || sizeof(*r) != 2 * sizeof(ip) || + sizeof(*g) != 6 * sizeof(ip) || sizeof(rows_t) != sizeof(void *); +} +EOF +try_ 0 << EOF +struct P { int x; }; +typedef struct P *pp; +typedef pp (*prow_t)[2]; +struct P p1 = {5}, p2 = {6}; +struct P *parr[2] = {&p1, &p2}; +int main(void) +{ + prow_t r = &parr; + return (*r)[1]->x != 6 || r[0][0]->x != 5 || sizeof(*r) != 2 * sizeof(pp); +} +EOF +try_ 0 << EOF +typedef int *const cip; +typedef cip (*crow_t)[2]; +int a = 1, b = 2; +cip arr[2] = {&a, &b}; +int main(void) +{ + crow_t r = &arr; + return 0; +} +EOF +# The same pointer-to-array aliases declared at file scope. +try_ 0 << EOF +typedef int (*row_pointer)[2]; +typedef int (*plane_pointer)[2][3]; +typedef char *(*string_row_pointer)[3]; +int row[2] = {3, 4}; +int plane[2][3] = {{1, 2, 3}, {4, 5, 6}}; +char *strings[3] = {"ab", "cd", "ef"}; +row_pointer global_row = &row; +int first(row_pointer p) { return (*p)[1] + p[0][0]; } +int main(void) { + plane_pointer q = &plane; + string_row_pointer r = &strings; + return first(&row) != 7 || (*global_row)[1] != 4 || (*q)[1][2] != 6 || + r[0][1][1] != 'd' || sizeof(row_pointer) != sizeof(int *) || + sizeof(*global_row) != 2 * sizeof(int) || + sizeof(*q) != 6 * sizeof(int) || sizeof(*r) != 3 * sizeof(char *); +} +EOF + +# A pointer to a pointer to an array still points to the array after one +# dereference, and a pointer typedef as the element type stays pointer-sized. +try_ 0 << EOF +typedef int row[3]; +typedef int (*row_pointer)[3]; +typedef int *(*slot_row_pointer)[3]; +typedef char *str; +int x = 9; +row r = {4, 5, 6}; +int *slots[3] = {&x, 0, 0}; +str strings[3] = {"ab", "cd", "ef"}; +int main(void) { + row *g2 = &r; + row_pointer t = &r; + row_pointer *tt = &t; + int (**direct)[3] = &t; + slot_row_pointer u = &slots; + slot_row_pointer *uu = &u; + str (*p)[3] = &strings; + p[0][2] = "gh"; + return (*g2)[2] != 6 || (*tt)[0][1] != 5 || (*direct)[0][2] != 6 || + sizeof(**tt) != 3 * sizeof(int) || **uu != slots || + ***uu != &x || ****uu != 9 || sizeof(*p) != 3 * sizeof(str) || + sizeof(p[0][1]) != sizeof(str) || p[0][1][1] != 'd' || + strings[2][1] != 'h'; +} +EOF +# A pointer-to-array member keeps its row once loaded: `*s.rows` is the row. +try_ 0 << EOF +typedef int (*row_pointer)[3]; +int row[3] = {1, 2, 3}; +int plane[2][3] = {{1, 2, 3}, {4, 5, 6}}; +char *strings[2] = {"ab", "cd"}; +struct S { + int (*row)[3]; + row_pointer alias; + int (*plane)[2][3]; + char *(*strings)[2]; + int (**slot)[3]; +}; +int main(void) { + struct S s; + struct S *ps = &s; + int *first; + s.row = &row; + s.alias = &row; + s.plane = &plane; + s.strings = &strings; + s.slot = &s.row; + first = *s.row; + (*s.row)[0] = 7; + return (*s.row)[1] != 2 || (*ps->row)[2] != 3 || first[1] != 2 || + (*s.alias)[0] != 7 || (*ps->plane)[1][2] != 6 || + (*s.strings)[1][1] != 'd' || sizeof(*s.row) != 3 * sizeof(int) || + (**s.slot)[2] != 3 || row[0] != 7; +} +EOF +try_compile_error << EOF +typedef const int (*plane_pointer)[2][3]; +int planes[1][2][3]; +int main(void) { + plane_pointer p = &planes[0]; + return ((*p)[1][2] = 15); +} +EOF +try_ 7 << EOF +struct typedef_pair { int left; int right; }; +typedef struct typedef_pair *pair_pointer; +int main(void) { + struct typedef_pair value = {3, 4}; + pair_pointer pointer = &value; + return pointer[0].left + pointer[0].right; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int r = 0, c = 0; + int value = ((*p)[r++][c++] = 15); + return value != 15 || r != 1 || c != 1 || (*p)[0][0] != 15; +} +EOF +try_compile_error << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef const int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + return ((*p)[1][2] = 15); +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = ((*p)[1][2] += 9); + return value != 15 || (*p)[1][2] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int r = 0, c = 0; + int value = ((*p)[r++][c++] += 9); + return value != 10 || r != 1 || c != 1 || (*p)[0][0] != 10; +} +EOF +try_ 9 << EOF +union typedef_value { int left; int right; }; +typedef union typedef_value *value_pointer; +int main(void) { + union typedef_value value; + value.right = 9; + value_pointer pointer = &value; + return pointer[0].right; +} +EOF +try_ 42 << EOF +struct packet { int value; char data[]; }; +int main(void) { + char storage[sizeof(struct packet) + sizeof(long unsigned int) - 1]; + struct packet *packet = (struct packet *)storage; + packet->value = 39; + packet->data[0] = 3; + return (sizeof(struct packet) == 4 ? packet->value : 0) + packet->data[0]; +} +EOF +try_ 42 << EOF +struct matrix { int tag; int cells[][2]; }; +int main(void) { + char storage[sizeof(struct matrix) + 16]; + struct matrix *matrix = (struct matrix *)storage; + matrix->cells[1][1] = 42; + return matrix->cells[1][1]; +} +EOF +try_compile_error << EOF +struct invalid { char data[]; int value; }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct invalid { int value, data[]; int after; }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct invalid { char data[]; }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +union invalid { char data[]; int value; }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct packet { int value; char data[]; }; +struct invalid { int header; struct packet packet; }; +int main(void) { return 0; } +EOF +try_ 1 << EOF +struct packet { int value; char data[]; }; +union holder { struct packet packet; int value; }; +int main(void) { return sizeof(union holder) == sizeof(int); } +EOF +try_compile_error << EOF +struct packet { int value; char data[]; }; +union holder { struct packet packet; int value; }; +struct invalid { union holder holder; }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct packet { int value; char data[]; }; +typedef struct packet packet_t; +struct invalid { packet_t packet; }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct packet { int value; char data[]; }; +struct packet packets[2]; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct packet { int value; char data[]; }; +typedef struct packet packet_t; +packet_t packets[2]; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct packet { int value; char data[]; }; +union holder { struct packet packet; int value; }; +union holder holders[2]; +int main(void) { return 0; } +EOF +try_ 4 << EOF +struct packet { int value; char data[]; }; +struct packet *packets[2]; +typedef struct packet *packet_ptr; +packet_ptr typedef_packets[2]; +int main(void) { + return (sizeof(packets) == 2 * sizeof(struct packet *)) + + (sizeof packets == 2 * sizeof(struct packet *)) + + (sizeof(typedef_packets) == 2 * sizeof(packet_ptr)) + + (sizeof typedef_packets == 2 * sizeof(packet_ptr)); +} +EOF +try_ 7 << EOF +typedef struct { int left; int right; } *anonymous_pair_pointer; +int main(void) { + int values[4] = {1, 2, 3, 4}; + anonymous_pair_pointer pointer = (anonymous_pair_pointer)values; + return pointer[1].left + pointer[1].right; +} +EOF + +# Such a typedef names a pointer, not the record it defines: an array or record +# of them is initialized with pointers, at file and block scope. The elements +# were initialized as records, storing garbage. +try_ 0 << EOF +typedef struct rec { char c; int v; } *rptr; +struct rec grecs[2]; +rptr grps[2] = {grecs, grecs + 1}; +rptr gp = grecs + 1; +struct holder { rptr p; int n; } gh = {grecs, 5}; +int main(void) +{ + struct rec recs[2]; + rptr rps[2] = {recs, recs + 1}; + rptr p = recs; + struct holder h = {recs + 1, 7}; + rptr one[] = {0, recs}; + p->v = 3; + rps[1]->v = 4; + if (grps[0] != grecs || grps[1] != grecs + 1 || gp != grecs + 1) return 1; + if (rps[0] != recs || rps[1] != recs + 1) return 2; + if (gh.p != grecs || gh.n != 5 || h.p != recs + 1 || h.n != 7) return 3; + if (sizeof one != 2 * sizeof(rptr) || one[1] != recs) return 4; + if (sizeof(*p) != sizeof(struct rec) || recs[0].v != 3) return 5; + return h.p->v != 4; +} +EOF + +# A pointer typedef that defines its record, tagged or not, reaches the record +# through base_struct: sizeof(*p) and sizeof p[0] are the record's size, not a +# pointer's. Its step is the padded record size, for postfix as well as prefix +# updates, and the alias is a pointer operand, never a record one. +try_ 1 << EOF +typedef struct { int x, y; char c; } *PA, A; +typedef PA *PPA; +typedef struct T { int x, y; char c; } *TP; +typedef struct { int x, y; char c; } B, *PB; +typedef int **PP; +A arr[3]; +B brr[3]; +struct T trr[3]; +int main(void) { + typedef struct { char b[5]; int z; } *LP; + LP lp = 0; + PA pa = arr, pa2 = arr; + PPA ppa = &pa; + TP tp = trr; + PB pb = brr; + int v = 0, *vp = &v, *vq = &v; + PP pp = &vp; + int r = (sizeof(*pa) == sizeof(A)) + (sizeof pa[0] == sizeof(A)) * 2 + + (sizeof(*pa).c == 1) * 4 + (sizeof **ppa == sizeof(A)) * 8 + + (sizeof *lp == 3 * sizeof(int)) * 16; + pa++; + ++pa2; + tp++; + pb--; + pb += 2; + pp++; + r += ((pa - arr) == 1 && pa2 == pa) * 32; + r += ((char *) pa - (char *) arr == sizeof(A)) * 64; + r += (tp - trr == 1 && pb - brr == 1) * 128; + r += ((char *) (lp + 1) - (char *) lp == 3 * sizeof(int)) * 256; + r += ((char *) pp - (char *) &vp == sizeof(int *)) * 512; + (void) vq; + return r == 1023; +} +EOF + +# An array operand of a cast is its address: an element wider than a pointer +# made the cast a truncation of the array, which read back garbage once the +# address had to be reloaded around a call. +try_output 0 "12 12 24" << EOF +struct S { int x, y; char c; }; +struct S srr[3]; +struct S trr[3]; +int main(void) { + struct S *p = srr + 1; + struct S *q = trr + 2; + printf("%d ", (int) ((char *) p - (char *) srr)); + printf("%d %d", (int) ((char *) p - (char *) srr), + (int) ((char *) q - (char *) trr)); + return 0; +} +EOF + +# A typedef that defines a tagged record through a pointer declarator names the +# pointer, not the record: the tag keeps the record's own layout. +try_ 6 << EOF +typedef struct wide { char bytes[100]; int count; } *wide_ptr, wide_t; +typedef union mixed { char bytes[50]; int value; } *mixed_ptr; +int main(void) { + struct wide w; + union mixed m; + wide_ptr p = &w; + p->bytes[99] = 3; + p->count = 4; + return (sizeof(struct wide) == 104) + (sizeof(w) == 104) + + (sizeof(wide_t) == 104) + (sizeof(union mixed) == 52) + + (sizeof(m) == 52) + (w.bytes[99] + w.count == 7); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned char bytes[1] = {255}; + unsigned short halves[1] = {65535}; + return (bytes[0] / 2 == 127) + (halves[0] / 2 == 32767); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned int one = 1U; + int minus_two = -2; + int minus_one = -1; + return ((one + minus_two) >> 31) + (minus_one > one); +} +EOF +try_ 4 << EOF +typedef unsigned short ushort; +int main(void) { + ushort small = 65535; + return (small > 0) + (sizeof(unsigned char) == 1) + + (sizeof(unsigned short) == 2) + (sizeof(unsigned long) == 4); +} +EOF + +declare -a arithmetic_tests=( + "42 24+18" + "30 58-28" + "10 5*2" + "4 16>>2" + "20 8+3*4" + "54 (11-2)*6" + "10 9/3+7" + "8 8/(4-3)" + "35 8+3*5+2*6" + "55 1+2+3+4+5+6+7+8+9+10" + "55 ((((((((1+2)+3)+4)+5)+6)+7)+8)+9)+10" + "55 1+(2+(3+(4+(5+(6+(7+(8+(9+10))))))))" + "210 1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+(14+(15+(16+(17+(18+(19+20))))))))))))))))))" + "11 1+012" + "25 017+012" + "2 5%3" +) + +run_expr_tests arithmetic_tests +expr 6 "111 % 7" + +try_output 0 "1 1 -1 -1" << EOF +int v1 = 5 % 4; +int v2 = 5 % -4; +int v3 = -5 % 4; +int v4 = -5 % -4; +int main() { + printf("%d %d %d %d", v1, v2, v3, v4); + return 0; +} +EOF + +try_compile_error << EOF +int value = 1 / 0; +int main() { return value; } +EOF + +try_compile_error << EOF +int value = 1 % 0; +int main() { return value; } +EOF + +# A value named twice is computed once and copied, then read through both names. +# Folding the copy into the operation must not leave the first name unwritten +# while something still reads it. +try_ 12 << EOF +int f(int a, int b) +{ + int r = a * b; + int s = a * b; + return r + s; +} + +int main() +{ + return f(2, 3); +} +EOF + +try_ 0 << EOF +int fold_add(int a, int b) { int r = a + b; int s = a + b; return r + s; } +int fold_sub(int a, int b) { int r = a - b; int s = a - b; return r + s; } +int fold_mul(int a, int b) { int r = a * b; int s = a * b; return r + s; } +int fold_div(int a, int b) { int r = a / b; int s = a / b; return r + s; } +int fold_mod(int a, int b) { int r = a % b; int s = a % b; return r + s; } +int fold_and(int a, int b) { int r = a & b; int s = a & b; return r + s; } +int fold_or(int a, int b) { int r = a | b; int s = a | b; return r + s; } +int fold_xor(int a, int b) { int r = a ^ b; int s = a ^ b; return r + s; } +int fold_shl(int a, int b) { int r = a << b; int s = a << b; return r + s; } +int fold_shr(int a, int b) { int r = a >> b; int s = a >> b; return r + s; } +int main() +{ + int bad = 0; + if (fold_add(29, 3) != 64) + bad |= 1; + if (fold_sub(29, 3) != 52) + bad |= 2; + if (fold_mul(29, 3) != 174) + bad |= 4; + if (fold_div(29, 3) != 18) + bad |= 8; + if (fold_mod(29, 3) != 4) + bad |= 16; + if (fold_and(29, 3) != 2) + bad |= 32; + if (fold_or(29, 3) != 62) + bad |= 64; + if (fold_xor(29, 3) != 60) + bad |= 128; + if (fold_shl(29, 3) != 464) + bad |= 256; + if (fold_shr(29, 3) != 6) + bad |= 512; + return bad != 0; +} +EOF + +try_ 38 << EOF +int f(int a, int b) +{ + int r = a * b; + int s = a * b; + while (b-- > 0) + a += r + s; + return a; +} + +int main() +{ + return f(2, 3); +} +EOF + +# Folding a constant load into the operation after it, as in 0 + b, leaves the +# constant's register without it, which is wrong while something else still +# reads that register. The third argument is unused, so the register holds 64. +try_ 0 << EOF +int fold_add(int a, int b, int c) +{ + int k = 0; + int r = k + b; + return r + (k >> a); +} + +int fold_neg(int a, int b, int c) +{ + int k = 0; + int r = k - b; + return r + (k >> a); +} + +int fold_zero(int a, int b, int c) +{ + int k = 0; + int r = k * b; + return r + (k >> a); +} + +int fold_one(int a, int b, int c) +{ + int k = 1; + int r = k * b; + return r + (k >> a); +} + +int main() +{ + int bad = 0; + if (fold_add(1, 5, 64) != 5) + bad |= 1; + if (fold_neg(1, 5, 64) != -5) + bad |= 2; + if (fold_zero(1, 5, 64) != 0) + bad |= 4; + if (fold_one(1, 5, 64) != 5) + bad |= 8; + return bad; +} +EOF + +# Strength reduction loads the shift count 2 over the constant 4 it replaces, so +# the constant has to be unread afterwards. +try_ 22 << EOF +int f(int a, int b, int c) +{ + int k = 4; + int r = k * b; + return r + (k >> a); +} + +int main() +{ + return f(1, 5, 64); +} +EOF + +# x & 0 becomes a load of zero into the result. The constant's register loses +# the zero, and the operation it replaces must not simply disappear. +try_ 0 << EOF +int f(int a, int b, int c) +{ + int k = 0; + int r = k & b; + return r + (k >> a); +} + +int main() +{ + return f(1, 5, 64); +} +EOF + +# Category: Overflow Behavior +begin_category "Overflow Behavior" "Testing integer overflow handling" + +try_output 0 "-2147483647" << EOF +int main() +{ + int a = 2147483647; + a += 2; + printf("%d\n", a); + return 0; +} +EOF + +try_output 0 "-32767" << EOF +int main() { + short a = 32767; + a += 2; + printf("%d\n", a); + return 0; +} +EOF + +try_output 0 "-127" << EOF +int main() { + char a = 127; + a += 2; + printf("%d\n", a); + return 0; +} +EOF + +# Category: Comparison Operations +begin_category "Comparison Operations" "Testing relational and equality operators" + +declare -a comparison_tests=( + "1 10>5" + "1 3+3>5" + "0 30==20" + "0 5>=10" + "1 5>=5" + "1 30!=20" + "1 010==8" + "1 011<11" + "0 021>=21" + "1 (012-5)==5" + "16 0100>>2" + "18 ~0355" +) + +run_expr_tests comparison_tests + +# Category: Logical Operations +begin_category "Logical Operations" "Testing logical AND, OR, NOT operators" + +declare -a logical_tests=( + "0 !237" + "18 ~237" + "0 0||0" + "1 1||0" + "1 1||1" + "0 0&&0" + "0 1&&0" + "1 1&&1" +) + +run_expr_tests logical_tests + +# Logical negation of a pointer yields int, irrespective of the pointed-to type. +# A record pointer used to carry its record size into a following comparison, +# producing an invalid truncation on 32-bit targets. +try_ 2 << EOF +struct pair { int first; int second; }; +int main(void) { + struct pair value; + struct pair *present = &value; + struct pair *absent = 0; + return (!present == 0) + (!absent == 1); +} +EOF + +# Category: Bitwise Operations +begin_category "Bitwise Operations" "Testing bitwise shift, AND, OR, XOR operators" + +declare -a bitwise_tests=( + "16 2<<3" + "32 256>>3" +) + +run_expr_tests bitwise_tests + +# A shift count outside the width of int has no defined result. The compiler +# must not fold one with the host's shift, which on x86 reduced "1 << 40" to 256 +# while a count known only at run time gave the target's result; leaving the +# shift to the target makes both spellings agree. +try_ 1 << EOF +int shift_count(int count) { return count; } +int main(void) +{ + int wide = shift_count(40); + int negative = shift_count(-1); + + return (1 << 40) == (1 << wide) && (8 >> 33) == (8 >> (wide - 7)) && + (1 << -1) == (1 << negative) && (-1 << 3) == -8; +} +EOF +try_output 0 "128 59926 -6 -4 -500283" << EOF +int main() { + printf("%d %d %d %d %d", 32768 >> 8, 245458999 >> 12, -11 >> 1, -16 >> 2, -1000565 >> 1); + return 0; +} +EOF +expr 239 "237 | 106" +declare -a more_bitwise_tests=( + "135 237^106" + "104 237&106" +) + +run_expr_tests more_bitwise_tests + +# Category: Return Statements +begin_category "Return Statements" "Testing return statement functionality" + +declare -a return_tests=( + "1 return 1;" + "42 return 2*21;" + "4 int value = 1; return value++, value + 2;" + "4 int value = 0; return 1 ? value++, value + 3 : 0;" + "5 int value = 0; return 1 ? value = 2, value + 3 : 0;" + "3 return 0 ? 1 : 0 ? 2 : 3;" + "3 return 1 ? 0 ? 2 : 3 : 4;" +) + +run_items_tests return_tests + +# Category: Variables and Assignments +begin_category "Variables and Assignments" "Testing variable declarations and assignments" + +try_ 5 << EOF +int main(void) { + typedef int local_count; + local_count value = 5; + return value; +} +EOF +try_ 7 << EOF +int main(void) { + typedef int *local_pointer; + int value = 7; + local_pointer pointer = &value; + return *pointer; +} +EOF + +# A block pointer typedef names the same type as a file-scope one: its +# dereference is the pointee, not the pointer, so arithmetic on it must not +# scale, and a subscript through a hidden pointer-to-pointer steps by slots. +try_ 8 << EOF +int main(void) { int e = 7; typedef int *EP; EP q = &e; return *q + 1; } +EOF +try_ 65 << EOF +int main(void) { + int a[3] = {5, 6, 7}; + typedef int *EP; + EP q = a; + return q[0] + *(q + 1) * 10 + (q + 2)[0] * 100 - 700; +} +EOF +try_ 13 << EOF +int main(void) { + char c[2] = {3, 9}; + typedef char *CP; + CP q = c; + return *q + *(q + 1) + 1; +} +EOF +try_output 0 "4 6 5 10 30 20 10 20 2 1 250 3 12 2 8 1 2 2 3 4 3" << EOF +struct S { int m; char c; int n; }; +int main(void) { + struct S s[2] = {{1, 2, 3}, {4, 5, 6}}; + typedef struct S *SP; + SP p = s; + SP p1 = p + 1; + short sh[3] = {10, 20, 30}; + typedef short *SHP; + SHP h = sh; + typedef SHP *SHPP; + SHPP hh = &h; + long long ll[2] = {100, 200}; + typedef long long *LLP; + LLP l = ll; + typedef unsigned char *UCP; + unsigned char uc[2] = {250, 3}; + UCP u = uc; + typedef int *IP; + typedef IP IP2; + int arr[4] = {1, 2, 3, 4}; + IP2 ip = arr; + printf("%d %d %d ", p[1].m, p1->n, p1[0].c); + printf("%d %d %d %d %d ", *h, h[2], *(h + 1), **hh, hh[0][1]); + printf("%d %d ", (int) (l[1] / 100), (int) (*l / 100)); + printf("%d %d ", *u, u[1]); + printf("%d %d %d %d %d ", (int) sizeof(*p), (int) sizeof(*h), + (int) sizeof(*l), (int) sizeof(*u), (int) sizeof(**hh)); + ip++; + printf("%d %d %d %d", *ip, ip[1], *(ip + 2), (int) ((arr + 4) - ip)); + return 0; +} +EOF +try_ 61 << EOF +typedef int **GPP; +typedef int *GP; +struct T { GP p; GPP gp; }; +int main(void) { + int ia[3] = {10, 20, 30}; + int *ip = ia; + typedef int **PP; + typedef int *IP; + PP pp = &ip; + GPP gp = &ip; + IP rows[2] = {ia, ia + 1}; + struct T t; + struct T *tp = &t; + t.p = ia; + t.gp = &ip; + /* 20 + 10 + 10 + 1 + 20 = 61 */ + return pp[0][1] + *gp[0] + (rows[1][1] - rows[0][1]) + + (t.gp[0][2] == 30) + tp->p[1]; +} +EOF +try_ 11 << EOF +struct S { int m; int n; }; +int main(void) { + typedef struct S *SP; + typedef struct S **SPP; + struct S s[2] = {{1, 2}, {3, 4}}; + SP sp = s; + SP sps[2] = {s + 1, s}; + SPP spp = &sp; + SPP spp2 = sps; + /* 4 + 3 + 2 + 2 = 11 */ + return spp2[1][1].n + spp2[0]->m + spp[0]->n + sps[1]->n; +} +EOF +try_ 9 << EOF +typedef int outer_type; +int main(void) { + outer_type outer = 9; + { typedef int outer_type; outer_type inner = outer; return inner; } +} +EOF +try_ 8 << EOF +int main(void) { + typedef int *(*rows_t)[2]; + int first = 3, second = 7; + int *data[1][2] = { { &first, &second } }; + rows_t p = data; + return *p[0][1] + 1; +} +EOF +try_ 8 << EOF +int main(void) { + typedef int *pointer_row[2]; + typedef pointer_row pointer_row_alias; + int first = 3, second = 7; + pointer_row_alias values = { &first, &second }; + return *values[1] + 1; +} +EOF +try_ 8 << EOF +typedef int *global_pointer_row[2]; +int main(void) { + int first = 3, second = 7; + global_pointer_row values = { &first, &second }; + return *values[1] + 1; +} +EOF +try_ 8 << EOF +typedef int *pointer_alias; +typedef pointer_alias global_pointer_row[2]; +int main(void) { + int first = 3, second = 7; + global_pointer_row values = { &first, &second }; + return *values[1] + 1; +} +EOF + +# A file-scope typedef reads a declarator list too, and its specifier with its +# qualifiers applies to each declarator. +try_compile_error_message "assignment of read-only location" << EOF +typedef const int CI, *CP; +int main(void) { int x = 1; CP p = &x; *p = 2; return 0; } +EOF +try_compile_error_message "assignment of read-only variable" << EOF +typedef int *IP; +typedef const IP CP, CQ[2]; +int main(void) { IP x = 0; CP p = 0; p = x; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +typedef const struct { int x, y; } CR, *CRP; +int main(void) { CR c = {1, 2}; CRP q = &c; q->x = 2; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +typedef const enum { A1, B1 } CE, *CEP; +int main(void) { CE e = A1; CEP p = &e; *p = B1; return 0; } +EOF +try_ 42 << EOF +typedef struct { int m; int n; } S, *SP, SA[2]; +typedef struct tagged { int v; } T, *TP; +typedef struct later L, *LP; +typedef union { int i; char c; } U, *UP; +typedef int I, *IP, IA[3], (*FP)(int); +typedef enum { E0, E1, E2 } E, *EP; +typedef const int *CIP; +typedef IP const CP; +struct later { int w; }; +int twice(int v) { return 2 * v; } +int main(void) { + int x = 3; + S s[2] = {{1, 2}, {3, 4}}; + SP sp = s + 1; + SA sa; + T t = {5}; + TP tp = &t; + struct tagged *raw = tp; + L l; + LP lp = &l; + U u; + UP up = &u; + IA ia = {1, 2, 3}; + IP ip = ia; + FP fp = twice; + E e = E2; + EP ep = &e; + CIP cip = &x; + CP cp = &x; + lp->w = 6; + up->i = 7; + sa[1] = s[0]; + *cp += 1; + /* 4 + 5 + 6 + 7 + 3 + 2 + 2 + 4 + 1 + 8 */ + return sp->n + raw->v + l.w + u.i + ip[2] + *ep + sa[1].n + *cip + + (sizeof(sa) == 2 * sizeof(S)) + fp(4); +} +EOF + +# A record typedef may derive an array at file scope as in a block, whether it +# names a tag or defines the record, and an array parameter of it is a pointer. +try_ 64 << EOF +struct P { int a, b; }; +typedef struct P pair_row[2]; +typedef struct { int a; } untagged_row[3], untagged_one; +typedef union { int i; char c; } union_row[2], *union_ptr; +typedef struct Q { int x; } q_grid[2][3], q_one; +typedef struct P *pair_ptr_row[2]; +typedef struct P pair_alias; +typedef pair_alias alias_row[2]; +pair_row global_pairs; +int second_b(pair_row row) { return row[1].b; } +int third_a(untagged_row row) { return row[2].a; } +int main(void) { + typedef struct P local_row[4]; + typedef struct { char c; } local_untagged[5]; + pair_row pairs; + untagged_row ur; + untagged_one one; + union_row un; + union_ptr up = un; + q_grid grid; + q_one q; + struct Q *qp = &grid[1][2]; + pair_ptr_row ptrs; + alias_row aliases; + local_row lr; + local_untagged lu; + pairs[1].b = 1; + ur[2].a = 2; + one.a = 3; + up[1].c = 4; + grid[1][2].x = 5; + q.x = 6; + ptrs[1] = &pairs[1]; + aliases[0].a = 7; + global_pairs[1].a = 8; + lr[3].b = 9; + lu[4].c = 10; + return second_b(pairs) + third_a(ur) + one.a + un[1].c + qp->x + q.x + + ptrs[1]->b + aliases[0].a + global_pairs[1].a + lr[3].b + lu[4].c + + (sizeof(pair_row) == 2 * sizeof(struct P)) + + (sizeof(untagged_row) == 3 * sizeof(int)) + + (sizeof(union_row) == 2 * sizeof(int)) + + (sizeof(q_grid) == 6 * sizeof(int)) + + (sizeof(pair_ptr_row) == 2 * sizeof(struct P *)) + + (sizeof(local_row) == 4 * sizeof(struct P)) + + (sizeof(local_untagged) == 5) + (sizeof(global_pairs) == 16); +} +EOF +try_compile_error_message "Typedef array element has incomplete record type" << EOF +struct incomplete; +typedef struct incomplete incomplete_row[2]; +int main(void) { return 0; } +EOF +try_ 14 << EOF +typedef struct { int a; int b; } *AP, A; +typedef union { int i; char c[8]; } *UP, U; +typedef struct later *LP, L; +struct later { int w; int z; }; +int main(void) { + A a = {1, 2}; + AP ap = &a; + U u; + UP up = &u; + L l; + LP lp = &l; + lp->z = 3; + up->i = 4; + /* 2 + 4 + 3 + 1 + 1 + 1 + 2 */ + return ap->b + u.i + l.z + (sizeof(A) == 8) + + (sizeof(U) == 8) + (sizeof(L) == 8) + ap[0].a * 2; +} +EOF +try_compile_error << EOF +int main(void) { typedef int invalid_local = 1; return 0; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int inner_only; inner_only value = 1; } + inner_only expired = 2; + return expired; +} +EOF +try_compile_error << EOF +typedef int hidden_type; +int main(void) { + { int hidden_type = 0; hidden_type value = 1; return value; } +} +EOF +try_ 11 << EOF +typedef int shadowed_type; +int main(void) { + { typedef int shadowed_type; shadowed_type value = 11; return value; } +} +EOF +try_compile_error << EOF +int main(void) { int collision; typedef int collision; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int collision; int collision; return 0; } +EOF +try_compile_error << EOF +typedef int parameter_type; +int parameter_hides_type(int parameter_type) { + parameter_type value = 1; + return value; +} +EOF +try_ 12 << EOF +struct block_record { int value; }; +int main(void) { + typedef struct block_record record_alias, *record_pointer; + record_alias value = { 5 }; + record_pointer pointer = &value; + pointer->value += 7; + return value.value; +} +EOF +try_ 9 << EOF +union block_union { int value; char byte; }; +int main(void) { + typedef union block_union union_alias; + union_alias value; + value.value = 9; + return value.value; +} +EOF + +# A block typedef may define the record or enum it names, tagged or not, and +# give it several declarators; the tag belongs to the block. +try_ 7 << EOF +int main(void) { + typedef struct Q { int b; } R; + R r; + struct Q q2; + r.b = 3; + q2.b = 4; + return r.b + q2.b; +} +EOF +try_ 56 << EOF +struct Q { char outer[8]; }; +int bump(void *p); +int main(void) { + typedef struct { int b; } R, *RP; + typedef struct node { struct node *next; int v; } node_t, *node_ptr; + typedef union { int x; char c[8]; } U; + typedef enum { AA = 5, BB } E; + R r = {2}; + RP p = &r; + node_t n1, n2; + node_ptr np = &n1; + U u; + E e = BB; + n1.next = &n2; + n2.v = 9; + p->b += 1; + u.x = 2; + { + typedef struct Q { short s; } R; + R inner; + struct Q tagged; + inner.s = 4; + tagged.s = 5; + if (sizeof(R) != 2 || sizeof(struct Q) != 2) + return 1; + r.b += inner.s + tagged.s; + } + /* 12 + 8 + 6 + 9 + 1 + 4 + 16 = 56 */ + return r.b + sizeof(U) + e + np->next->v + + (sizeof(node_t) == 2 * sizeof(node_ptr)) + sizeof(R) + + (sizeof(struct Q) == 8) * 16; +} +EOF +try_ 12 << EOF +int main(void) { + typedef struct { int a; } I; + typedef struct { I in; int b; } N; + typedef struct { int x, y; } const CP, *CPP; + typedef const union { int x; } CU; + N n = {{5}, 7}; + CP c = {1, 2}; + CPP q = &c; + CU cu = {9}; + return n.in.a + n.b + (q->y == 2) + (cu.x == 9) - 2; +} +EOF +try_compile_error << EOF +int main(void) { + typedef const struct { int x; } CR; + CR c = {1}; + c.x = 2; + return c.x; +} +EOF +try_compile_error << EOF +int main(void) { + typedef struct { int x; } const CR; + CR c = {1}; + c.x = 2; + return c.x; +} +EOF +try_compile_error << EOF +int main(void) { + { typedef struct scoped { int x; } S; } + struct scoped s; + s.x = 1; + return s.x; +} +EOF + +# The specifiers of a typedef, with their qualifiers, belong to every declarator +# of its list; const on a pointer typedef qualifies that pointer. +try_compile_error_message "assignment of read-only location" << EOF +int main(void) { int x = 1; typedef const int CI, *CP; CP p = &x; *p = 2; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int main(void) { int x = 1; typedef int const CI, *CP; CP p = &x; p[0] = 2; return 0; } +EOF +try_compile_error_message "assignment of read-only variable" << EOF +int main(void) { typedef int *IP; IP x = 0; typedef const IP CP, CQ; CQ p = 0; p = x; return 0; } +EOF +try_compile_error_message "assignment of read-only variable" << EOF +int main(void) { typedef int *IP; IP x = 0; typedef IP const CP; CP p = 0; p = x; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +struct S { int x; }; +int main(void) { typedef const struct S CR, *CRP; struct S s; CRP q = &s; q->x = 2; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int main(void) { typedef const enum { A1, B1 } CE, *CEP; CE e = A1; CEP p = &e; *p = B1; return 0; } +EOF +try_ 3 << EOF +int main(void) { + int x = 1; + typedef int *IP, **IPP; + typedef int *const CPP, *NP; + typedef const IP CIP, *PCIP; + IP p = &x; + IPP q = &p; + NP n = &x; + CIP c = &x; + PCIP pc = &c; + **q = 2; + n = p; + *c += 1; + return **pc; +} +EOF +try_compile_error << EOF +struct record_expiry { int value; }; +int main(void) { + { typedef struct record_expiry inner_record; inner_record value = { 1 }; } + inner_record expired; + return 0; +} +EOF +try_compile_error << EOF +struct tag_namespace { int value; }; +int main(void) { + typedef struct tag_namespace ordinary_alias; + struct ordinary_alias not_a_tag; + return 0; +} +EOF + +# A typedef naming an undeclared tag declares that tag incomplete: the alias +# serves for pointers, completes with the later definition, and still cannot +# define an object before then. +try_compile_error_message "Incomplete struct/union type cannot define an object" << EOF +int main(void) { typedef struct missing_tag missing_alias; missing_alias object; return 0; } +EOF +try_ 4 << EOF +int main(void) { + typedef struct missing_tag missing_alias; + missing_alias *p = 0; + struct missing_tag { int v; } o; + o.v = 4; + p = &o; + return p->v; +} +EOF +try_compile_error << EOF +union kind_check { int value; }; +int main(void) { typedef struct kind_check wrong_kind; return 0; } +EOF +try_ 4 << EOF +struct forward_record; +int main(void) { + typedef struct forward_record *forward_pointer; + forward_pointer pointer = 0; + return pointer == 0 ? 4 : 0; +} +EOF + +# A file-scope tag declaration without declarators may repeat a tag that is +# already forward declared or complete; it refers to the same type. +try_ 12 << EOF +struct redeclared_record; +struct redeclared_record; +struct redeclared_record { int value; }; +struct redeclared_record; +union redeclared_union { int value; }; +union redeclared_union; +union redeclared_union; +int main(void) +{ + struct redeclared_record record; + union redeclared_union alias; + record.value = 5; + alias.value = 7; + return record.value + alias.value; +} +EOF + +# A record tag declared inside a function belongs to that block: it neither +# clashes with a later file-scope tag of the other kind nor hides one from +# another function, and a local definition shadows the file-scope one. +try_ 14 << EOF +void block_union_owner(void) { union scoped_tag { int value; } u; u.value = 1; } +struct scoped_tag { int field; }; +struct shadowed_tag { int a; int b; }; +int shadowing_function(void) { + struct shadowed_tag { char c; } local; + local.c = 5; + return local.c + sizeof(local); +} +int main(void) { + struct scoped_tag s; + struct shadowed_tag t; + s.field = 3; + t.b = 2; + block_union_owner(); + return s.field + shadowing_function() + t.b - 3 + sizeof(t) - 2; +} +EOF + +# sizeof, casts and other lookups that name a tag also see the innermost one, +# and two functions may each declare their own record under one tag. +try_ 18 << EOF +struct outer_shadow { int a, b; }; +int local_sizeof(void) { + struct outer_shadow { char c; } l; + l.c = 0; + return sizeof(struct outer_shadow) + l.c; +} +int first_owner(void) { struct own_tag { int a, b; } x; x.a = 0; return sizeof(struct own_tag) + x.a; } +int second_owner(void) { struct own_tag { char c; } y; y.c = 0; return sizeof(struct own_tag) + y.c; } +int main(void) { return local_sizeof() + first_owner() + second_owner() + 8; } +EOF +# Every declaration of a tag names the same kind of tag. +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +union mismatched_tag { int value; }; +struct mismatched_tag; +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +struct mismatched_definition; +union mismatched_definition { int value; }; +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +union mismatched_use { int value; }; +struct mismatched_use object; +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +union mismatched_alias { int value; }; +typedef struct mismatched_alias mismatched_alias_t; +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +enum mismatched_enum { MISMATCHED_ENUM }; +int main(void) { struct mismatched_enum *p = 0; return p != 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +int main(void) { struct block_kind; union block_kind u; return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +int main(void) { struct implicit_kind *p = 0; union implicit_kind *q = 0; return p != 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +struct offsetof_kind { int a; }; +int main(void) { return __builtin_offsetof(union offsetof_kind, a); } +EOF +# An enum tag is checked against record tags in the same name space too. +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +struct enum_reuse { int a; }; +enum enum_reuse { ENUM_REUSE }; +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +union enum_use { int a; }; +enum enum_use object; +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +int main(void) { struct block_enum { int a; }; enum block_enum { BLOCK_ENUM }; return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +struct outer_enum_use { int a; }; +int main(void) { enum outer_enum_use value; return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +struct param_enum_use { int a; }; +int use(enum param_enum_use value) { return 0; } +int main(void) { return 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +union cast_enum_use { int a; }; +int main(void) { return (enum cast_enum_use) 0; } +EOF +try_compile_error_message "tag was previously declared as a different kind of tag" << EOF +int main(void) { enum block_record { BLOCK_RECORD }; struct block_record { int a; }; return 0; } +EOF + +# A scope defines the content of a tag only once (C99 6.7.2.3p1). Forward +# declarations and an inner block's own definition remain valid. +try_ 9 << EOF +struct T; +struct T; +typedef struct T T_alias; +struct T { int a; }; +struct T; +union U { int a; }; +enum E { E_OUTER = 3 }; +int main(void) { + struct T; + struct T { char c; } t; + struct T; + t.c = 1; + { + struct T { int x, y; }; + union U { char z; }; + enum E { E_INNER = 4 }; + T_alias outer; + outer.a = 5; + return sizeof(struct T) + sizeof(union U) + t.c + E_OUTER + E_INNER + + outer.a - 13; + } +} +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +struct twice { int a; }; +struct twice { int b; }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +union twice { int a; }; +union twice { int b; }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +struct twice { int a; }; +typedef struct twice { int b; } twice_t; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +typedef union twice { int a; } twice_t; +union twice { int b; }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +struct outer { struct nested { int a; } in; }; +struct nested { int b; }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +int main(void) { struct twice { int a; }; struct twice { int b; }; return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +int main(void) { union twice { int a; } u; typedef union twice { int b; } t; return 0; } +EOF + +# A member list opens no scope, so defining the tag again inside its own member +# list is a redefinition, although the tag is still incomplete there. +try_compile_error_message "redefinition of struct or union tag" << EOF +struct self { struct self { int x; } inner; }; +int main(void) { return sizeof(struct self); } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +union self { int a; union self { int x; } inner; }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +struct self { struct middle { struct self { int y; } deepest; } inner; }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +typedef struct self { struct self { int x; } inner; } self_t; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +typedef union self { union self { int x; } inner; } self_t; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +int main(void) { struct self { struct self { int x; } inner; } s; return 0; } +EOF +try_compile_error_message "redefinition of struct or union tag" << EOF +int main(void) { union self { int a; union self { int x; } inner; } u; return 0; } +EOF +try_compile_error << EOF +enum self { SELF_A = sizeof(struct { enum self { SELF_B } e; }) }; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { enum self { SELF_A = sizeof(struct { enum self { SELF_B } e; }) }; return 0; } +EOF +# The tag may still be named, by pointer, and redefined in an inner block. +try_ 5 << EOF +int main(void) { + struct self { struct self *next; struct back { struct self *owner; } b; } s; + s.b.owner = &s; + return s.b.owner == &s ? 5 : 0; +} +EOF +try_ 4 << EOF +int main(void) { + struct self { int x; }; + { + struct self { struct self *p; int y; } s; + s.p = &s; + s.y = 4; + return s.p->y; + } +} +EOF +try_compile_error_message "redefinition of enum tag" << EOF +enum twice { TWICE_A }; +enum twice { TWICE_B }; +int main(void) { return 0; } +EOF +try_compile_error_message "redefinition of enum tag" << EOF +int main(void) { enum twice { TWICE_A }; enum twice { TWICE_B }; return 0; } +EOF + +# An inner scope may reuse the name for another kind of tag, and an enum tag +# never names, or redefines, an ordinary typedef of the same spelling. +try_ 0 << EOF +typedef char enum_typedef; +enum enum_typedef { ENUM_TYPEDEF = 1 }; +union outer_union { int a; }; +enum_typedef c; +int main(void) { + struct shadowed { char x; }; + { + enum shadowed { SHADOWED = 2 }; + enum shadowed s = SHADOWED; + enum enum_typedef t = ENUM_TYPEDEF; + if (sizeof(s) != sizeof(int) || sizeof(t) != sizeof(int)) + return 1; + } + { + enum outer_union { OUTER_UNION = 3 }; + if (OUTER_UNION != 3) + return 2; + } + if (sizeof(struct shadowed) != 1) + return 3; + return sizeof(enum_typedef) + sizeof(c) - 2; +} +EOF + +# A tag defined in one function is not visible from another: naming it there +# declares a new incomplete tag, so a pointer is fine and an object is not. +try_compile_error_message "Incomplete struct/union type cannot define an object" << EOF +int owner(void) { struct foreign_tag { int a; } x; x.a = 1; return x.a; } +int main(void) { struct foreign_tag y; return 0; } +EOF + +# A specifier naming an undeclared tag declares it incomplete in the current +# scope, and members of a block-scope record see the block's own tags. +try_ 21 << EOF +struct file_later *file_ptr; +struct file_later { int v; }; +int owner(void) { struct foreign_ptr { int a, b; } x; x.a = 1; x.b = 2; return x.a + x.b; } +int user(void) { struct foreign_ptr *p = 0; return p == 0; } +int members(void) { + struct inner { int x; }; + struct outer { struct inner in; struct unseen *link; int y; } o; + o.in.x = 3; + o.link = 0; + o.y = 4; + return o.in.x + o.y; +} +int late(void) { + struct late_tag *lp; + struct late_tag { int q; } l; + l.q = 6; + lp = &l; + return lp->q; +} +int main(void) { + struct file_later f; + f.v = 7; + file_ptr = &f; + return owner() + user() + members() + late() + file_ptr->v - 3; +} +EOF +try_compile_error << EOF +struct incomplete_record; +int main(void) { + typedef struct incomplete_record incomplete_alias; + incomplete_alias value; + return 0; +} +EOF +try_ 8 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + row_pointer q = p + 1; + return q[0][1]; +} +EOF +try_ 3 << EOF +int main(void) { + int src[2][2] = { { 1, 2 }, { 3, 4 } }; + int (*q)[2] = src + 1; + return q[0][0]; +} +EOF + +# A row of a multidimensional array decays to a pointer to its first element +# instead of being loaded as though it were a scalar element. +try_ 0 << EOF +int global_rows[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; +int global_planes[2][2][3] = { { { 1, 2, 3 }, { 4, 5, 6 } }, + { { 7, 8, 9 }, { 10, 11, 12 } } }; +int second(int *row) { return row[1]; } +int main(void) { + int v[2][2] = { { 1, 2 }, { 3, 0 } }; + int *p; + p = v[1]; + int *q = v[0]; + int *g = global_rows[2]; + int *r = global_planes[1][0]; + int (*plane)[3] = global_planes[1]; + int (*next)[3]; + next = global_planes[0] + 1; + int i = 1; + return p[0] != 3 || q[1] != 2 || g[0] != 5 || r[2] != 9 || + plane[1][1] != 11 || next[0][2] != 6 || second(v[1]) != 0 || + *(v[1] + 1) != 0 || *v[0] != 1 || *(global_rows[i] + 1) != 4 || + v[1] - v[0] != 2 || global_rows[2] - global_rows[0] != 4 || + sizeof(v[1]) != 2 * sizeof(int) || !v[1] || + sizeof(*global_planes[1]) != 3 * sizeof(int); +} +EOF + +# An array name compared against a pointer decays to the address of its first +# element. A narrow element type must not make the comparison an integer one, +# which used to sign-extend the address from its low byte. +ans="1100 +1111 +1110 +1111 +1100 +0011 +0111 +3" +try_output 0 "$ans" << EOF +struct holder { + char field[4]; + int numbers[2]; +}; + +int by_param(char param[], void *raw) +{ + return (raw == param) + 2 * (param == raw) + 4 * (raw != param); +} + +int main(void) +{ + char buf[8]; + char grid[2][3]; + struct holder box; + int values[3]; + char *p = buf; + int *ip = values; + void *r = p; + void *rg = grid; + void *rg1 = grid[1]; + void *rf = box.field; + void *rv = ip; + + printf("%d%d%d%d\n", r == buf, buf == r, r != buf, buf != r); + printf("%d%d%d%d\n", r == &buf[0], &buf[0] == r, r == &buf, &buf == r); + printf("%d%d%d%d\n", rg == grid, grid == rg, rg1 == grid[1], + grid[0] != rg); + printf("%d%d%d%d\n", rf == box.field, box.field == rf, rv == values, + values == rv); + printf("%d%d%d%d\n", rv == ip, ip == values, rv != values, + rf != box.field); + printf("%d%d%d%d\n", (char *) r < buf, buf < (char *) r, + (char *) r >= buf, buf >= (char *) r); + printf("%d%d%d%d\n", (char *) r > buf, (int *) rv <= values, + (int *) rv >= values, (char *) rg1 > grid[0]); + printf("%d\n", by_param(buf, r)); + return 0; +} +EOF +try_ 0 << EOF +struct point { int x, y; }; +struct grid { int tag; int cells[2][3]; struct point points[2][2]; }; +struct grid global_grid = { 7, { { 1, 2, 3 }, { 4, 5, 6 } }, + { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } } }; +int third(const int *row) { return row[2]; } +int main(void) { + struct grid local = global_grid; + struct grid *pointer = &local; + int *cells = local.cells[1]; + struct point *points = pointer->points[1]; + struct point matrix[2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; + struct point *row = matrix[1]; + struct point *second = matrix[0] + 1; + return cells[0] != 4 || third(pointer->cells[0]) != 3 || + *(global_grid.cells[1] + 2) != 6 || points[1].x != 7 || + pointer->points[1]->y != 6 || row[1].y != 8 || matrix[1]->x != 5 || + second->x != 3; +} +EOF +try_ 7 << EOF +int x = 5, y = 7; +int *slots[2][2]; +int main(void) { + slots[1][0] = &x; + slots[1][1] = &y; + int **row = slots[1]; + return *row[1] == *slots[1][1] && row + 1 == slots[1] + 1 ? *slots[1][1] : 0; +} +EOF +try_ 0 << EOF +int rows[2][2] = { { 1, 2 }, { 3, 4 } }; +int planes[2][2][3] = { { { 1, 2, 3 }, { 4, 5, 6 } }, + { { 7, 8, 9 }, { 10, 11, 12 } } }; +int *row = rows[1]; +int *after = rows[0] + 1; +int (*plane_row)[3] = planes[1] + 1; +int *slots[3] = { rows[1], rows[0] + 1, planes[1][1] + 2 }; +int main(void) { + static int *local = rows[1] + 1; + return row[1] != 4 || *after != 2 || plane_row[0][0] != 10 || + *slots[0] != 3 || *slots[1] != 2 || *slots[2] != 12 || *local != 4; +} +EOF +try_compile_error_message "Global initializer requires a constant address" << EOF +int rows[2][2]; +int *element = rows[1][0]; +int main(void) { return 0; } +EOF +try_ 3 << EOF +int main(void) { + int src[2][2] = { { 1, 2 }, { 3, 4 } }; + int (*slots[2])[2] = { src, src + 1 }; + int (**p)[2] = slots; + return p[1][0][0]; +} +EOF +try_ 0 << EOF +int main(void) { + int rows[2][2] = { { 2, 5 }, { 3, 7 } }; + int (*planes[])[2] = { rows, rows }; + return planes[1][1][0] != 3; +} +EOF +try_ 6 << EOF +int main(void) { + short src[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + short (*q)[3] = src + 1; + return q[0][2]; +} +EOF +try_ 6 << EOF +int main(void) { + short src[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + short (*q)[3] = 1 + src; + return q[0][2]; +} +EOF +try_ 5 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[0]; + row_pointer q = p + 1; + return q[0][1]; +} +EOF +try_ 1 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[1]; + row_pointer q = p - 1; + return q[0][1] == 2 && q == &rows[0]; +} +EOF +try_ 1 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[1]; + return sizeof(*(p - 1)) == 2 * sizeof(int); +} +EOF +try_ 9 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + int value = (*p)[1]; + return value; +} +EOF +try_ 12 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + return p[1][1][2]; +} +EOF +try_ 15 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + p[1][1][2] += 3; + return p[1][1][2]; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = ++p[1][1][2]; + return value != 13 || p[1][1][2] != 13; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = p[1][1][2]++; + return value != 12 || p[1][1][2] != 13; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = --p[1][1][2]; + return value != 11 || p[1][1][2] != 11; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = p[1][1][2]--; + return value != 12 || p[1][1][2] != 11; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[2][2][3] = {{{1, 2, 3}, {4, 5, 6}}, + {{7, 8, 9}, {10, 11, 12}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = (p[1][1][2] = 15); + return value != 15 || p[1][1][2] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = ((*p)[1][2] = 15); + return value != 15 || (*p)[1][2] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] = 15); + return value != 15 || (*p)[1][1][1] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int i = 0; + int j = 0; + int k = 0; + int value = ((*p)[i++][j++][k++] = 15); + return value != 15 || i != 1 || j != 1 || k != 1 || (*p)[0][0][0] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] += 9); + return value != 17 || (*p)[1][1][1] != 17; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int i = 0; + int j = 0; + int k = 0; + int value = ((*p)[i++][j++][k++] ^= 9); + return value != 8 || i != 1 || j != 1 || k != 1 || (*p)[0][0][0] != 8; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + int (*p)[2][2][2] = &cubes[0]; + int value = ((*p)[1][1][1] -= 3); + return value != 5 || (*p)[1][1][1] != 5; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] *= 3); + return value != 24 || (*p)[1][1][1] != 24; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] /= 2); + return value != 4 || (*p)[1][1][1] != 4; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] %= 3); + return value != 2 || (*p)[1][1][1] != 2; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] <<= 1); + return value != 16 || (*p)[1][1][1] != 16; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] >>= 1); + return value != 4 || (*p)[1][1][1] != 4; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] &= 6); + return value != 0 || (*p)[1][1][1] != 0; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ((*p)[1][1][1] |= 2); + return value != 10 || (*p)[1][1][1] != 10; +} +EOF +try_compile_error << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef const int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + return ((*p)[1][1][1] = 15); +} +EOF +try_compile_error << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef const int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + return ((*p)[1][1][1] |= 2); +} +EOF +try_compile_error << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int *q = &cubes[0][0][0]; + return ((*p)[1][1][1] += q); +} +EOF +try_compile_error << EOF +struct box { int value; }; +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + struct box value = {9}; + return ((*p)[1][1][1] ^= value); +} +EOF +try_compile_error << EOF +struct box { int value; }; +int main(void) { + struct box cubes[1][2][2][2] = {{{{{1}, {2}}, {{3}, {4}}}, + {{{5}, {6}}, {{7}, {8}}}}}; + typedef struct box (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + return ((*p)[1][1][1] += 1); +} +EOF +try_compile_error << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef const int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + return ((*p)[1][1][1][1] ^= 1); +} +EOF +try_compile_error << EOF +struct box { int value; }; +int main(void) { + struct box hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef struct box (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + return ++(*p)[1][1][1][1]; +} +EOF +try_compile_error << EOF +struct box { int value; }; +int main(void) { + struct box hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef struct box (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + return ((*p)[1][1][1][1] += 1); +} +EOF +try_compile_error << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + return (*p)[1][1][1][1][0]++; +} +EOF +try_compile_error << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + return ++(*p)[1][1][1][1][0]; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + int value = ((*p)[1][1][1][1] = 15); + return value != 15 || (*p)[1][1][1][1] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + int (*p)[2][2][2][2] = &hyper; + int value = ((*p)[1][1][1][1] <<= 1); + return value != 32 || (*p)[1][1][1][1] != 32; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + int i = 0, j = 0, k = 0, l = 0; + int value = ((*p)[i++][j++][k++][l++] |= 8); + return value != 9 || i != 1 || j != 1 || k != 1 || l != 1 || + (*p)[0][0][0][0] != 9; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = (*p)[1][2]++; + return value != 6 || (*p)[1][2] != 7; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int r = 0, c = 0; + int value = (*p)[r++][c++]++; + return value != 1 || r != 1 || c != 1 || (*p)[0][0] != 2; +} +EOF +try_compile_error << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef const int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + return (*p)[1][2]++; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = (*p)[1][1][1]++; + return value != 8 || (*p)[1][1][1] != 9; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = (*p)[1][2]--; + return value != 6 || (*p)[1][2] != 5; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int r = 0, c = 0; + int value = (*p)[r++][c++]--; + return value != 1 || r != 1 || c != 1 || (*p)[0][0] != 0; +} +EOF +try_compile_error << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef const int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + return (*p)[1][2]--; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = (*p)[1][1][1]--; + return value != 8 || (*p)[1][1][1] != 7; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = ++(*p)[1][2]; + return value != 7 || (*p)[1][2] != 7; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int r = 0, c = 0; + int value = ++(*p)[r++][c++]; + return value != 2 || r != 1 || c != 1 || (*p)[0][0] != 2; +} +EOF +try_compile_error << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef const int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + return ++(*p)[1][2]; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = ++(*p)[1][1][1]; + return value != 9 || (*p)[1][1][1] != 9; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int value = --(*p)[1][2]; + return value != 5 || (*p)[1][2] != 5; +} +EOF +try_ 0 << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + int r = 0, c = 0; + int value = --(*p)[r++][c++]; + return value != 0 || r != 1 || c != 1 || (*p)[0][0] != 0; +} +EOF +try_compile_error << EOF +int main(void) { + int planes[1][2][3] = {{{1, 2, 3}, {4, 5, 6}}}; + typedef const int (*plane_pointer)[2][3]; + plane_pointer p = &planes[0]; + return --(*p)[1][2]; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int value = --(*p)[1][1][1]; + return value != 7 || (*p)[1][1][1] != 7; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int i = 0, j = 0, k = 0; + int value = (*p)[i++][j++][k++]++; + return value != 1 || i != 1 || j != 1 || k != 1 || (*p)[0][0][0] != 2; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + int i = 0, j = 0, k = 0; + int value = --(*p)[i++][j++][k++]; + return value != 0 || i != 1 || j != 1 || k != 1 || (*p)[0][0][0] != 0; +} +EOF +try_ 0 << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + int (*p)[2][2][2] = &cubes[0]; + int value = ++(*p)[1][1][1]; + return value != 9 || (*p)[1][1][1] != 9; +} +EOF +try_compile_error << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef const int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + return (*p)[1][1][1]++; +} +EOF +try_compile_error << EOF +int main(void) { + int cubes[1][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + typedef const int (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + return --(*p)[1][1][1]; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + int value = (*p)[1][1][1][1]++; + return value != 16 || (*p)[1][1][1][1] != 17; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + int value = ++(*p)[1][1][1][1]; + return value != 17 || (*p)[1][1][1][1] != 17; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + int value = (*p)[1][1][1][1]--; + return value != 16 || (*p)[1][1][1][1] != 15; +} +EOF +try_ 0 << EOF +int main(void) { + int hyper[2][2][2][2] = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}; + typedef int (*hyper_pointer)[2][2][2][2]; + hyper_pointer p = &hyper; + int value = --(*p)[1][1][1][1]; + return value != 15 || (*p)[1][1][1][1] != 15; +} +EOF +try_compile_error << EOF +struct box { int value; }; +int main(void) { + struct box cubes[1][2][2][2] = {{{{{1}, {2}}, {{3}, {4}}}, + {{{5}, {6}}, {{7}, {8}}}}}; + typedef struct box (*cube_pointer)[2][2][2]; + cube_pointer p = &cubes[0]; + return ++(*p)[1][1][1]; +} +EOF +try_ 0 << EOF +int main(void) { + int row[3] = {1, 2, 3}; + typedef int (*row_pointer)[3]; + row_pointer p = &row; + int i = 0; + int value = ((*p)[i++] += 4); + return value != 5 || i != 1 || (*p)[0] != 5; +} +EOF +try_ 9 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[1]; + int (*q)[2] = p - 1; + return q[0][1]; +} +EOF +try_ 6 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[0]; + p += 1; + return p[0][2]; +} +EOF +try_ 0 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[1]; + row_pointer q = &rows[0]; + return (p - q) + (q - p); +} +EOF +try_ 1 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[0]; + return (p + 1) - p; +} +EOF +try_ 1 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[1]; + int (*q)[2] = &rows[0]; + return p - q; +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*p)[2] = rows; + int *q = rows[0]; + return p - q; +} +EOF +try_compile_error << EOF +int main(void) { + int rows2[1][2] = { { 4, 9 } }; + int rows3[1][3] = { { 1, 2, 3 } }; + int (*p)[2] = rows2; + int (*q)[3] = rows3; + return p - q; +} +EOF +try_ 11 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[1]; + int step = 1; + p -= step++; + return step + p[0][1]; +} +EOF +try_ 6 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[0]; + row_pointer q = ++p; + return q[0][2]; +} +EOF +try_ 9 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[1]; + int (*q)[2] = --p; + return q[0][1]; +} +EOF +try_ 1 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + return sizeof(*(++p)) == 2 * sizeof(int); +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*p)[2] = rows; + (*p)[0] += p; + return 0; +} +EOF +try_ 8 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[0]; + row_pointer old = p++; + return old[0][1] + p[0][2]; +} +EOF +try_ 17 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[1]; + int (*old)[2] = p--; + return old[0][1] + p[0][1]; +} +EOF +try_ 6 << EOF +int main(void) { + short rows[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } }; + typedef short (*row_pointer)[3]; + row_pointer p = &rows[0]; + p++; + return p[0][2]; +} +EOF +try_compile_error << EOF +int main(void) { + short rows[1][3] = { { 1, 2, 3 } }; + typedef short (*row_pointer)[3]; + row_pointer const p = &rows[0]; + p += 1; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*const p)[2] = &rows[0]; + ++p; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*const p)[2] = &rows[0]; + p++; + return 0; +} +EOF +try_ 1 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + return p + 1 == &rows[1]; +} +EOF +try_ 8 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + int (*q)[2] = p + 1; + return q[0][1]; +} +EOF +try_ 6 << EOF +enum block_enum_tag { block_enum_value = 6 }; +int main(void) { + enum block_enum_tag value = block_enum_value; + return value; +} +EOF +try_ 13 << EOF +struct separate_tag_namespace { int value; }; +typedef int separate_tag_namespace; +int main(void) { + typedef struct separate_tag_namespace record_alias; + record_alias value = { 13 }; + return value.value; +} +EOF +try_ 8 << EOF +int main(void) { + for (typedef int loop_count, *loop_pointer; + sizeof(loop_count) == sizeof(int); sizeof(loop_pointer)) { + loop_count value = 8; + loop_pointer pointer = &value; + return *pointer; + } +} +EOF + +# A for initializer resolves its type name in block scope, like any other +# declaration: a block typedef names a type there, and a local object hides a +# file-scope typedef. +try_ 7 << EOF +int main(void) { + typedef int T; + typedef int *P; + int values[2] = { 3, 4 }; + int s = 0; + for (T i = 0; i < 2; i++) + s += i; + for (P p = values; p != values + 2; p++) + s += *p; + return s - 1; +} +EOF +try_flags 3 --std=c99 << EOF +int T = 9; +int main(void) { + typedef char T; + int s = 0; + for (T i = 0; i < 3; i++) + s += i; + return s + sizeof(T) - 1; +} +EOF +try_ 3 << EOF +typedef int T; +int main(void) { + int T = 2; + int s = 0; + for (T = 0; T < 3; T++) + s += T; + return s; +} +EOF +try_ 17 << EOF +int main(void) { + typedef unsigned long count_t, *count_p; + count_t value = 17; + count_p pointer = &value; + return *pointer; +} +EOF +try_ 19 << EOF +int main(void) { + typedef long unsigned reordered_t; + typedef unsigned short short_t; + reordered_t a = 12; + short_t b = 7; + return a + b; +} +EOF +try_ 6 << EOF +int main(void) { + typedef int pair_t[2]; + pair_t pair = { 6, 7 }; + pair[0] = pair[0] + 1; + return pair[0] + pair[1] - sizeof(pair_t); +} +EOF +try_ 30 << EOF +int main(void) { + typedef int table_t[2][3]; + table_t table = { { 1, 2, 3 }, { 4, 5, 6 } }; + return table[1][2] + sizeof(table_t); +} +EOF +try_ 30 << EOF +int main(void) { + typedef int row_t[2]; + typedef row_t matrix_t[3]; + matrix_t matrix = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; + return matrix[2][1] + sizeof(matrix_t); +} +EOF +try_ 120 << EOF +int main(void) { + typedef int row_t[2]; + typedef row_t plane_t[3]; + typedef plane_t cube_t[2]; + typedef cube_t hyper_t[2]; + typedef hyper_t hyper_alias_t; + hyper_alias_t values = {0}; + values[1][1][2][1] = 24; + return sizeof(hyper_alias_t) + values[1][1][2][1]; +} +EOF +try_ 120 << EOF +int main(void) { + int values[2][2][3][2] = {0}; + typedef int hyper_t[2][2][3][2]; + hyper_t *pointer = &values; + (*pointer)[1][1][2][1] = 24; + return sizeof(*pointer) + (*pointer)[1][1][2][1]; +} +EOF +try_ 120 << EOF +int main(void) { + int values[2][2][3][2] = {0}; + typedef int (*hyper_pointer_t)[2][2][3][2]; + hyper_pointer_t pointer = &values; + (*pointer)[1][1][2][1] = 24; + return sizeof(*pointer) + (*pointer)[1][1][2][1]; +} +EOF +try_ 24 << EOF +int main(void) { + int values[2][2][3][2] = {0}; + int (*pointer)[2][2][3][2] = &values; + pointer[0][1][1][2][1] = 24; + return pointer[0][1][1][2][1]; +} +EOF +try_ 6 << EOF +int main(void) { + int values[3][2][3] = {0}; + values[1][1][2] = 6; + return (values + 1)[0][1][2]; +} +EOF +try_ 6 << EOF +int main(void) { + int values[3][2][3] = {0}; + values[1][1][2] = 6; + return (1 + values)[0][1][2]; +} +EOF +try_ 6 << EOF +int main(void) { + int values[3][2][3] = {0}; + values[1][1][2] = 6; + return (values + 2 - 1)[0][1][2]; +} +EOF +try_ 24 << EOF +int main(void) { + int values[3][2][2][2] = {0}; + values[1][1][1][1] = 24; + return (values + 1)[0][1][1][1]; +} +EOF +try_ 8 << EOF +int main(void) { + typedef int row_t[2]; + typedef row_t row_alias_t; + return sizeof(row_alias_t); +} +EOF +try_ 24 << EOF +int main(void) { + for (typedef int row_t[2]; sizeof(row_t) == 8; ) { + typedef row_t matrix_t[3]; + matrix_t matrix = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; + return sizeof(matrix_t); + } +} +EOF +try_compile_error << EOF +int main(void) { + typedef int a[1][1][1][1]; + typedef a b[1]; + return 0; +} +EOF +try_ 9 << EOF +int main(void) { + for (typedef int row_t[2]; sizeof(row_t) == 8; ) { + row_t row = { 4, 5 }; + return row[0] + row[1]; + } +} +EOF +try_compile_error << EOF +int main(void) { typedef int unsized_row[]; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*row_pointer)[]; return 0; } +EOF +try_ 1 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + typedef row_pointer row_pointer_alias; + row_pointer p = &rows[0]; + row_pointer_alias q = p; + return p == q; +} +EOF +try_ 7 << EOF +int main(void) { + typedef int *pointer_row[2]; + int first = 3, second = 7; + pointer_row values = { &first, &second }; + return *values[1]; +} +EOF +try_ 8 << EOF +int main(void) { + typedef int *pointer_row[2]; + int first = 3, second = 7; + pointer_row values = { &first, &second }; + return *values[1] + 1; +} +EOF +try_compile_error << EOF +int main(void) { + typedef const int *pointer_row[2]; + int value = 3; + pointer_row values = { &value, &value }; + *values[0] = 4; + return 0; +} +EOF +try_ "$((2 * PTR_SZ))" << EOF +int main(void) { + typedef int *pointer_row[2]; + return sizeof(pointer_row); +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int *pointer_row[2]; } + pointer_row values = { 0, 0 }; + return values[0] != 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int **unsupported[2]; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int *unsupported[]; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int *unsupported[2][2]; return 0; } +EOF + +# An element of a pointer to function pointers is a function pointer: it is +# loaded as a whole pointer, compares with a function, and is callable with or +# without a unary *. It was read as an int and could not be called. +try_ 0 << EOF +int plus1(int v) { return v + 1; } +int twice(int v) { return v * 2; } +int (*fps[2])(int) = {plus1, twice}; +int (**fpp)(int) = fps; +struct holder { int (**slots)(int); } h = {fps}; +int call_at(int (**cb)(int), int i, int v) { return cb[i](v) + (*cb[i])(v); } +int main(void) +{ + int (*one)(int) = fpp[1]; + if (fpp[1] == 0) return 1; + if (fpp[1] != twice) return 2; + if (fpp[0] != plus1) return 3; + if (fpp[0](1) != 2 || (*fpp[1])(6) != 12 || one(4) != 8) return 4; + if (h.slots[1](2) != 4 || (*h.slots[0])(2) != 3) return 5; + return call_at(fpp, 1, 3) != 12; +} +EOF + +# A function pointer loaded from an element or member keeps its prototype as a +# value, so a parenthesized selection is still callable, with or without a unary +# *, and compares with a function. These were rejected. +try_ 0 << EOF +int plus1(int v) { return v + 1; } +int twice(int v) { return v * 2; } +int (*fps[2])(int) = {plus1, twice}; +int (**fpp)(int) = fps; +struct holder { int (*fp)(int); } s = {twice}; +int main(void) +{ + int (*local[2])(int) = {twice, plus1}; + if ((fps[1])(4) != 8) return 1; + if ((*(fps[1]))(4) != 8) return 2; + if (fps[1] != twice) return 3; + if ((*(fpp[0]))(1) != 2) return 4; + if ((*(s.fp))(2) != 4 || (s.fp)(3) != 6) return 5; + if ((local[1])(1) != 2 || (*(local[0]))(1) != 2) return 6; + if ((*(*fpp))(1) != 2) return 7; + return 0; +} +EOF + +# A parameter declared as an array of function pointers is adjusted to a pointer +# to function pointers (C99 6.7.5.3p7): it can be subscripted, called through, +# measured, assigned and passed on. Unsized, it could not be subscripted, and a +# dereference of it crashed. +try_ 0 << EOF +int plus1(int v) { return v + 1; } +int twice(int v) { return v * 2; } +int (*fps[2])(int) = {plus1, twice}; +int inner(int (**cb)(int), int v) { return cb[1](v); } +int unsized(int (*cb[])(int), int v); +int unsized(int (**cb)(int), int v) { return (*cb[1])(v) + (**cb)(v); } +int sized(int (*cb[2])(int), int v) +{ + int (*first)(int) = cb[0]; + if (sizeof cb != sizeof(void *) || cb[0] != plus1) return 100; + cb = fps; + return first(v) + cb[1](v) + inner(cb, v); +} +int main(void) +{ + if (unsized(fps, 5) != 16) return 1; + return sized(fps, 3) != 16; +} +EOF + +# A pointer to function pointers points to objects, so it takes pointer +# arithmetic and relational comparison (C99 6.5.6, 6.5.8), and so does the array +# of function pointers that decays to one, at file and block scope. The array +# was rejected, and the pointer stepped by an int on LP64. +try_ 0 << EOF +int plus1(int v) { return v + 1; } +int twice(int v) { return v * 2; } +int (*fps[3])(int) = {plus1, twice, plus1}; +int (**gend)(int) = fps + 3; +int (**gmid)(int) = fps + 1; +struct box { int (**slots)(int); } gbox = {fps}; +int main(void) +{ + int (*local[2])(int) = {twice, plus1}; + int (**p)(int) = fps + 1; + int (**q)(int) = 1 + fps; + int (**r)(int) = local + 1; + int (**e)(int) = fps; + int n = 0; + if ((**p)(3) != 6 || (*q)(1) != 2 || gmid != p) return 1; + if (gend - fps != 3 || p - fps != 1 || fps + 2 - p != 1) return 2; + p = p + 1; + p -= 2; + ++p; + p++; + gbox.slots += 2; + if (p != fps + 2 || gbox.slots != gend - 1) return 3; + if ((*(fps + 2))(4) != 5 || (*(r - 1))(1) != 2) return 4; + if (*(fps + 1) != twice || p[-1] != twice || !(p > fps)) return 5; + while (e != gend) + n += (*e++)(1); + return n != 6; +} +EOF + +# An array of function pointers converts to a pointer to function pointers, and +# so does the address of one of its elements, in an initializer, an assignment +# and an argument. Both were rejected, and the array was loaded as if it named a +# single function pointer. +try_ 0 << EOF +int plus1(int v) { return v + 1; } +int twice(int v) { return v * 2; } +int (*fps[2])(int) = {plus1, twice}; +int apply(int (**cb)(int), int v) { return (**cb)(v) + (*cb)(v); } +int main(void) +{ + int (*local[2])(int) = {twice, plus1}; + int (**pp)(int) = fps; + int (**qq)(int) = &fps[1]; + int (**rr)(int); + rr = local; + if ((**pp)(1) != 2 || pp[1](3) != 6) return 1; + if ((**qq)(3) != 6 || qq != &fps[1]) return 2; + if (apply(fps, 2) != 6 || apply(local, 2) != 8) return 3; + if (apply(&fps[1], 1) != 4) return 4; + if ((*rr)(1) != 2 || rr[1](1) != 2) return 5; + return 0; +} +EOF + +# Equality with a function designator reduced before a following operator of +# lower precedence, as in a != twice || b != plus1, compared the raw symbol and +# the pointer object's slot instead of the two addresses. +try_ 0 << EOF +int plus1(int v) { return v + 1; } +int twice(int v) { return v * 2; } +int main(void) +{ + int (*a)(int) = twice, (*b)(int) = plus1; + if (a != twice || b != plus1) return 1; + if (!(a == twice && plus1 == b)) return 2; + if ((a == plus1 | b == twice) != 0) return 3; + return a == twice ? 0 : 4; +} +EOF + +# Unary * on a function pointer selected by a subscript or member, or reached +# through a pointer to function pointers, designates the function (C99 +# 6.5.3.2p4), and further asterisks change nothing. These calls were rejected, +# and (*fps)(4) and *s.fp read the code as a pointer. +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int twice(int value) { return value * 2; } +int (*fps[2])(int) = {plus1, twice}; +int (**fpp)(int) = fps; +struct holder { int (*fp)(int); } s = {twice}; +int main(void) { + struct holder *sp = &s; + int (*local[2])(int) = {twice, plus1}; + int (*copy)(int) = *s.fp; + if ((*fps[1])(4) != 8 || (**fpp)(4) != 5 || (*s.fp)(4) != 8) return 1; + if ((*fps)(4) != 5 || (**fps)(4) != 5 || (*sp->fp)(1) != 2) return 2; + if ((*local[1])(1) != 2 || (**local)(3) != 6 || copy(5) != 10) return 3; + if ((***fpp)(2) != 3) return 4; + return 0; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2])(int); + int (*first)(int) = plus1; + int (*second)(int) = plus1; + slots_t slots = {&first, &second}; + return (*slots[1])(7); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2])(int); + typedef slots_t slots_alias_t; + int (*callback)(int) = plus1; + slots_alias_t slots = {0, &callback}; + return sizeof(slots_t) != 2 * sizeof(void *) || (*slots[1])(4) != 5; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2])(int); + int (*callback)(int) = plus1; + slots_t slots = {&callback, &callback}; + return slots[0](4); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (**slots_t[2])(int); + void (*callback)(int) = set_target; + slots_t slots = {&callback, &callback}; + (*slots[1])(7); + return target; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2])(int); + int (*first)(int) = plus1; + int (*second)(int) = plus2; + slots_t slots = {&first, 0}; + slots[0] = &second; + if ((*slots[0])(5) != 7) + return 1; + slots[0] = 0; + return slots[0] != 0; +} +EOF +try_ 9 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (**slots_t[2][2])(int); + void (*callback)(int) = set_target; + slots_t slots = {{0, 0}, {&callback, 0}}; + (*slots[1][0])(9); + return target; +} +EOF +try_ 9 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (**slots_t[2][2][2])(int); + void (*callback)(int) = set_target; + slots_t slots = {{{0, 0}, {0, 0}}, {{0, &callback}, {0, 0}}}; + (*slots[1][0][1])(9); + return target; +} +EOF +try_ 9 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (**slots_t[2][2][2][2])(int); + void (*callback)(int) = set_target; + slots_t slots = {0}; + slots[1][0][1][1] = &callback; + (*slots[1][0][1][1])(9); + return target; +} +EOF +try_compile_error << EOF +long incompatible(long value) { return value; } +int main(void) { + typedef int (**slots_t[2])(int); + long (*wrong)(long) = incompatible; + slots_t slots = {0, 0}; + slots[0] = &wrong; + return 0; +} +EOF +try_compile_error << EOF +long incompatible(long value) { return value; } +int main(void) { + typedef int (**slots_t[2])(int); + long (*callback)(long) = incompatible; + slots_t slots = {&callback, &callback}; + return 0; +} +EOF +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**const slots_t[2])(int); + int (*first)(int) = plus1; + slots_t slots = {&first, 0}; + *slots[0] = plus2; + return (*slots[0])(5); +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**const slots_t[2])(int); + int (*first)(int) = plus1; + slots_t slots = {&first, 0}; + slots[0] = &first; + return 0; +} +EOF +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**volatile slots_t[2])(int); + int (*first)(int) = plus1; + int (*second)(int) = plus2; + slots_t slots = {&first, 0}; + slots[0] = &second; + return (*slots[0])(5); +} +EOF +try_ 6 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**restrict slots_t[2])(int); + int (*first)(int) = plus1; + slots_t slots = {&first, 0}; + return (*slots[0])(5); +} +EOF + +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**const row_t[2])(int); + typedef row_t grid_t[2]; + int (*callback)(int) = plus1; + grid_t slots = {{0, 0}, {0, &callback}}; + slots[1][1] = &callback; + return 0; +} +EOF + +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**volatile row_t[2])(int); + typedef row_t grid_t[2]; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + grid_t slots = {{&first, 0}, {0, 0}}; + slots[1][1] = &second; + return (*slots[1][1])(5); +} +EOF +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2])(int); + typedef slots_t const cslots_t; + int (*callback)(int) = plus1; + cslots_t slots = {&callback, 0}; + *slots[0] = plus2; + return (*slots[0])(5); +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2])(int); + typedef slots_t const cslots_t; + int (*callback)(int) = plus1; + cslots_t slots = {&callback, 0}; + slots[0] = &callback; + return 0; +} +EOF +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2])(int); + typedef slots_t volatile vslots_t; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + vslots_t slots = {&first, 0}; + slots[0] = &second; + return (*slots[0])(5); +} +EOF +try_ 6 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2])(int); + typedef slots_t restrict rslots_t; + int (*callback)(int) = plus1; + rslots_t slots = {&callback, 0}; + return (*slots[0])(5); +} +EOF +try_ 0 << EOF +int main(void) { + typedef int *values_t[2]; + typedef values_t restrict restricted_values_t; + int value = 7; + restricted_values_t values = {&value, 0}; + return *values[0] != 7; +} +EOF +try_compile_error << EOF +int main(void) { + typedef int values_t[2]; + typedef values_t restrict restricted_values_t; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + typedef int (*callbacks_t[2])(int); + typedef callbacks_t restrict restricted_callbacks_t; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (**slots_t[2])(int); } + slots_t slots = {0, 0}; + return 0; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2][2])(int); + int (*first)(int) = plus1; + int (*second)(int) = plus2; + slots_t slots = {{&first, &first}, {&second, &first}}; + return (*slots[1][0])(6); +} +EOF +try_ 9 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (**row_t[2])(int); + typedef row_t grid_t[2]; + void (*callback)(int) = set_target; + grid_t slots = {{0, 0}, {0, &callback}}; + (*slots[1][1])(9); + return target; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2][2])(int); + typedef slots_t slots_alias_t; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + slots_alias_t slots = {{&first, 0}, {0, &second}}; + slots[0][0] = &second; + return sizeof(slots_t) != 4 * sizeof(void *) || (*slots[0][0])(5) != 7; +} +EOF +try_compile_error << EOF +long incompatible(long value) { return value; } +int main(void) { + typedef int (**slots_t[2][2])(int); + long (*wrong)(long) = incompatible; + slots_t slots = {{0, 0}, {0, 0}}; + slots[1][0] = &wrong; + return 0; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2][2])(int); + int (*callback)(int) = plus1; + slots_t slots = {{&callback, &callback}, {&callback, &callback}}; + return slots[0][0](4); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2][2][2])(int); + typedef slots_t slots_alias_t; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + slots_alias_t slots = {{{&first, 0}, {0, 0}}, {{0, 0}, {0, &second}}}; + slots[0][0][0] = &second; + return sizeof(slots_t) != 8 * sizeof(void *) || (*slots[0][0][0])(5) != 7; +} +EOF +try_compile_error << EOF +long incompatible(long value) { return value; } +int main(void) { + typedef int (**slots_t[2][2][2])(int); + long (*wrong)(long) = incompatible; + slots_t slots = {{{0, 0}, {0, 0}}, {{0, 0}, {0, 0}}}; + slots[1][0][1] = &wrong; + return 0; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2][2][2])(int); + int (*callback)(int) = plus1; + slots_t slots = {{{&callback, 0}, {0, 0}}, {{0, 0}, {0, 0}}}; + return slots[0][0][0](4); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**slots_t[2][2][2][2])(int); + typedef slots_t slots_alias_t; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + slots_alias_t slots = { + {{{&first, 0}, {0, 0}}, {{0, 0}, {0, 0}}}, + {{{0, 0}, {0, 0}}, {{0, 0}, {0, &second}}} + }; + slots[0][0][0][0] = &second; + return sizeof(slots_t) != 16 * sizeof(void *) || + (*slots[0][0][0][0])(5) != 7; +} +EOF +try_compile_error << EOF +long incompatible(long value) { return value; } +int main(void) { + typedef int (**slots_t[2][2][2][2])(int); + long (*wrong)(long) = incompatible; + slots_t slots = {0}; + slots[1][0][1][0] = &wrong; + return 0; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slots_t[2][2][2][2])(int); + int (*callback)(int) = plus1; + slots_t slots = {0}; + slots[0][0][0][0] = &callback; + return slots[0][0][0][0](4); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**row_t[2])(int); + typedef row_t grid_t[2]; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + grid_t slots = {{&first, 0}, {0, &second}}; + return sizeof(grid_t) != 4 * sizeof(void *) || (*slots[1][1])(6) != 8; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (**row_t[2])(int); + typedef row_t cube_t[2][2]; + int (*first)(int) = plus1; + int (*second)(int) = plus2; + cube_t slots = {{{&first, 0}, {0, 0}}, {{0, 0}, {0, &second}}}; + slots[0][0][0] = &second; + return sizeof(cube_t) != 8 * sizeof(void *) || (*slots[0][0][0])(5) != 7; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (**slots_t[2][2][2][2][2])(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { + typedef int (**slots_t[2][2][2][2])(int); + typedef slots_t wrapped_t[2]; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (**slots_t[])(int); return 0; } +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int unary_t(int); + unary_t *callback = plus1; + return callback(7); +} +EOF + +# A file-scope function typedef names the same function type: pointers to it, +# parameters, and a declarator that declares a function. A definition cannot +# take its type from the typedef. +try_ 0 << EOF +typedef int unary_t(int), count_t; +typedef void setter_t(int *); +int plus1(int value) { return value + 1; } +void set3(int *p) { *p = 3; } +unary_t plus2, *global_callback = plus1; +static unary_t times2; +count_t three = 3; +int apply(unary_t *callback, int value) { return callback(value); } +int main(void) { + unary_t *callback = plus1; + setter_t *setter = set3; + int x = 0; + setter(&x); + return callback(7) != 8 || global_callback(1) != 2 || plus2(1) != 3 || + times2(4) != 8 || apply(plus1, 2) != 3 || x != three || + sizeof(unary_t *) != sizeof(void *); +} +int plus2(int value) { return value + 2; } +static int times2(int value) { return value * 2; } +EOF + +# A record's first member survives a spill of the register that held the +# record's allocation address, here forced by an indirect call. +try_ 0 << EOF +typedef int thunk_t(void); +int five(void) { return 5; } +int (*global_thunk)(void) = five; +struct holder { thunk_t *callback; }; +int main(void) { + typedef int local_thunk_t(void); + struct local_holder { local_thunk_t *callback; } local; + struct holder h; + h.callback = five; + local.callback = five; + return global_thunk() + h.callback() + local.callback() != 15; +} +EOF + +# Function typedefs may be variadic or return a pointer, at both scopes. +try_ 0 << EOF +typedef int count_t(int, ...); +typedef char *name_t(void); +int count(int n, ...) { return n; } +char *name(void) { return "abc"; } +count_t *global_count = count; +name_t *global_name = name; +count_t count2; +name_t name2; +int call_count(count_t *f) { return f(2, 5, 6); } +int main(void) { + typedef int local_count_t(int, ...); + typedef char *local_name_t(void); + local_count_t *lc = count; + local_name_t *ln = name2; + name_t *nn = name; + return lc(3, 1, 2, 3) != 3 || ln()[0] != 'x' || global_count(1, 4) != 1 || + global_name()[2] != 'c' || count2(7) != 7 || name2()[1] != 'y' || + call_count(count) != 2 || nn()[1] != 'b' || + sizeof(name_t *) != sizeof(void *); +} +int count2(int n, ...) { return n; } +char *name2(void) { return "xyz"; } +EOF + +# Arrays of pointers to a function typedef, and pointers to one such pointer, at +# both scopes. +try_ 0 << EOF +typedef int thunk_t(void); +typedef int unary_t(int); +typedef void setter_t(int *); +int one(void) { return 1; } +int two(void) { return 2; } +int twice(int x) { return 2 * x; } +int inc(int x) { return x + 1; } +void set4(int *p) { *p = 4; } +thunk_t *global_table[2] = {one, two}; +unary_t *global_ops[] = {twice, inc}; +thunk_t *global_one = one; +thunk_t **global_slot = &global_one; +int call_slot(thunk_t **slot) { return (*slot)(); } +int main(void) { + typedef int local_t(void); + local_t *local_table[2] = {two, one}; + unary_t *ops[2]; + setter_t *setters[1] = {set4}; + thunk_t *first = one; + thunk_t **slot = &first; + int x = 0; + ops[0] = inc; + ops[1] = twice; + setters[0](&x); + return global_table[0]() + global_table[1]() + local_table[0]() + + global_ops[0](3) + global_ops[1](3) + ops[0](1) + ops[1](5) + x + + (*slot)() + (*global_slot)() + call_slot(&first) != 34 || + sizeof(local_table) != 2 * sizeof(local_t *) || + sizeof(global_ops) != 2 * sizeof(unary_t *); +} +EOF +try_compile_error << EOF +typedef int unary_t(int); +unary_t plus1 { return 1; } +int main(void) { return plus1(0); } +EOF +try_compile_error << EOF +typedef int unary_t(int); +unary_t plus1; +int plus1(void) { return 1; } +int main(void) { return plus1(); } +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (*callback_t)(int); + typedef callback_t const const_callback_t; + const_callback_t callback = plus1; + return callback(7); +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (*volatile callback_t)(int); + callback_t callback = plus1; + return callback(7); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (*setter_t)(int); + setter_t setter = set_target; + setter(7); + return target; +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void setter_t(int); + setter_t *callback = set_target; + callback(7); + return target; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (*const callback_t)(int); + callback_t callback = plus1; + return callback(7); +} +EOF +try_ 8 << EOF +int main(void) { + typedef int unary_t(int); + unary_t plus1; + return plus1(7); +} +int plus1(int value) { return value + 1; } +EOF +try_ 9 << EOF +int main(void) { + typedef int unary_t(int); + unary_t plus1, plus2; + return plus1(3) + plus2(3); +} +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +EOF +try_ 9 << EOF +int plus1(int); +int main(void) { + typedef int unary_t(int); + extern unary_t plus1, plus2; + return plus1(3) + plus2(3); +} +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +EOF +try_compile_error << EOF +long plus2(long); +int main(void) { + typedef int unary_t(int); + unary_t plus1, plus2; + return 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int unary_t(int); + unary_t declared_function, *slot = plus1; + return slot(4) != 5; +} +EOF +try_compile_error << EOF +int main(void) { + typedef int unary_t(int); + unary_t plus1, plus2 = 0; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); unary_t plus1, plus2; } + return plus1(1) + plus2(1); +} +int plus1(int value) { return value; } +int plus2(int value) { return value; } +EOF +try_ 2 << EOF +int plus1(int); +int plus2(int); +int main(void) { + { typedef int unary_t(int); unary_t plus1, plus2; } + return plus1(1) + plus2(1); +} +int plus1(int value) { return value; } +int plus2(int value) { return value; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static int (*callback)(int) = hidden; + return callback(1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +int main(void) { + { extern int hidden(int); } + return hidden(1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static int (*callback)(int) = &hidden; + return callback(1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static int (*callback)(int) = (hidden); + return callback(1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static int (*callbacks[1])(int) = { &*hidden }; + return callbacks[0](1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static int (*callbacks[1])(int) = { (*&hidden) }; + return callbacks[0](1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +struct holder { int (*callback)(int); }; +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static struct holder saved = { &*hidden }; + return saved.callback(1); +} +int hidden(int value) { return value; } +EOF +try_compile_error << EOF +struct holder { int (*callback)(int); }; +int main(void) { + { typedef int unary_t(int); unary_t hidden; } + static struct holder saved = { (*&hidden) }; + return saved.callback(1); +} +int hidden(int value) { return value; } +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int unary_t(int); + extern unary_t plus1; + return plus1(7); +} +EOF +try_compile_error << EOF +long plus1(long); +int main(void) { + typedef int unary_t(int); + unary_t plus1; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + typedef int unary_t(int); + unary_t plus1 = 0; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int); static unary_t plus1; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int); auto unary_t plus1; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int); register unary_t plus1; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int); const unary_t plus1; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int); volatile unary_t plus1; return 0; } +EOF +try_compile_error << EOF +int main(void) { + { typedef int unary_t(int); } + unary_t *callback = 0; + return callback != 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int) = 0; return 0; } +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (*callback_t)(int); + callback_t callback = plus1; + return callback(7); +} +EOF +try_ 9 << EOF +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callback_t)(int); + typedef callback_t callback_alias_t; + callback_alias_t callback = plus2; + return callback(7); +} +EOF +try_ 9 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callback_t)(int); + callback_t callback = plus1; + callback = plus2; + return callback(7); +} +EOF +try_ "$PTR_SZ" << EOF +int main(void) { + typedef int (*callback_t)(int); + return sizeof(callback_t); +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int apply(int (*callback)(int), int value) { return callback(value); } +int main(void) { + typedef int (*callback_t)(int); + extern int apply(callback_t, int); + return apply(plus1, 7); +} +EOF +try_ "$PTR_SZ" << EOF +int main(void) { + typedef int (*callback_t)(int); + extern callback_t choose(void); + return sizeof(callback_t); +} +EOF +try_compile_error << EOF +int main(void) { + typedef int (*const callback_t)(int); + callback_t callback = 0; + callback = 0; + return 0; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (*callback_t)(int); + typedef callback_t const const_callback_t; + const_callback_t callback = plus1; + callback = plus1; + return callback(7); +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (*callback_t)(int); } + callback_t callback = 0; + return callback != 0; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + return (*slot)(7); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t slot_alias_t; + int (*callback)(int) = plus1; + slot_alias_t slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_ 0 << EOF +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t slot_alias_t; + return sizeof(slot_t) != sizeof(void *) || + sizeof(slot_alias_t) != sizeof(void *); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (**slot_t)(int); + void (*callback)(int) = set_target; + slot_t slot = &callback; + (*slot)(7); + return target; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + return slot(7); +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (**slot_t)(int); } + slot_t slot = 0; + return slot != 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**const slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**const slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + slot = &callback; + return 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**volatile slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**const slot)(int) = &callback; + slot = &callback; + return 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**restrict slot)(int) = &callback; + slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**restrict slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t restrict rslot_t; + int (*callback)(int) = plus1; + rslot_t slot = &callback; + return (*slot)(4) != 5 || sizeof(rslot_t) != sizeof(void *); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**const restrict slot_t)(int); + int (*callback)(int) = plus1; + slot_t slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (*restrict *slot_t)(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*restrict callback_t)(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*const *slot_t)(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*volatile *slot_t)(int); return 0; } +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t const const_slot_t; + int (*callback)(int) = plus1; + const_slot_t slot = &callback; + return (*slot)(4) != 5 || sizeof(const_slot_t) != sizeof(void *); +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t const const_slot_t; + int (*callback)(int) = plus1; + const_slot_t slot = &callback; + slot = &callback; + return 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t volatile vslot_t; + int (*callback)(int) = plus1; + vslot_t slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +int main(void) { + typedef int (**slot_t)(int); + typedef slot_t *deeper_t; + return 0; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**slot)(int) = &callback; + return (*slot)(7); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + void (*callback)(int) = set_target; + void (**slot)(int) = &callback; + (*slot)(7); + return target; +} +EOF +try_compile_error << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**slot)(int) = &callback; + return slot(7); +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**other)(int) = &callback; + int (**slot)(int) = other; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +long callback(long value) { return value; } +int main(void) { + long (*other_callback)(long) = callback; + long (**other)(long) = &other_callback; + int (**slot)(int) = other; + return 0; +} +EOF +try_ 0 << EOF +int main(void) { + int (**slot)(int) = 0; + slot = 0; + return slot != 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int (*callback)(int) = plus1; +int (**slot)(int) = &callback; +int main(void) { return (*slot)(4) != 5; } +EOF +try_compile_error << EOF +long callback(long value) { return value; } +long (*other)(long) = callback; +int (**slot)(int) = &other; +int main(void) { return 0; } +EOF +try_ 0 << EOF +int (**slot)(int) = 0; +int main(void) { return slot != 0; } +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**a)(int) = &callback; + int (**b)(int) = &callback; + int flag = 0; + int (**slot)(int) = flag ? a : b; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +int main(void) { + int (**a)(int) = 0; + int (**b)(long) = 0; + int flag = 0; + int (**slot)(int) = flag ? a : b; + return 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int (*callback)(int) = plus1; + int (**slot)(int) = &callback; + int (**other)(int) = slot; + slot = other; + return (*slot)(4) != 5; +} +EOF +try_compile_error << EOF +int main(void) { + int (**slot)(int); + int (**other)(long); + slot = other; + return 0; +} +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int result; +void use(int (**slot)(int)); +void use(int (**slot)(int)) { result = (*slot)(4); } +int main(void) { int (*callback)(int) = plus1; use(&callback); return result != 5; } +EOF +try_compile_error << EOF +void use(int (**slot)(int)) {} +int main(void) { + int (**slot)(long) = 0; + use(slot); + return 0; +} +EOF +try_compile_error << EOF +void use(int (**slot)(int)); +void use(int (**slot)(long)); +int main(void) { return 0; } +EOF +try_compile_error << EOF +extern int (**slot)(int); +extern int (**slot)(long); +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*restrict callback_t)(int); return 0; } +EOF +try_compile_error << EOF +void use(int (*callback)(int)); +void use(int (**callback)(int)); +int main(void) { return 0; } +EOF +try_compile_error << EOF +typedef int (*callback_t)(int); +typedef int (*const const_callback_t)(int); +callback_t choose(void); +const_callback_t choose(void); +int main(void) { return 0; } +EOF +try_compile_error << EOF +typedef int (*callback_t)(int); +typedef int (*volatile volatile_callback_t)(int); +callback_t choose(void); +volatile_callback_t choose(void); +int main(void) { return 0; } +EOF +try_ 8 << EOF +typedef int (*global_callback_t)(int); +int plus1(int value) { return value + 1; } +global_callback_t choose(void) { return plus1; } +int main(void) { + typedef int (*callback_t)(int); + extern callback_t choose(void); + return choose()(7); +} +EOF +try_compile_error << EOF +int main(void) { typedef int unary_t(int); return sizeof(unary_t); } +EOF +try_compile_error << EOF +int main(void) { + typedef int unary_t(int); + switch (0) { case sizeof(unary_t): return 1; } + return 0; +} +EOF +try_ "$PTR_SZ" << EOF +int main(void) { typedef int unary_t(int); return sizeof(unary_t *); } +EOF +try_ 9 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2])(int); + callbacks_t callbacks = {plus1, plus2}; + return callbacks[0](3) + callbacks[1](3); +} +EOF +try_ "$((2 * PTR_SZ))" << EOF +int main(void) { + typedef int (*callbacks_t[2])(int); + return sizeof(callbacks_t); +} +EOF +try_ 9 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2][2])(int); + callbacks_t callbacks = {{plus1, plus2}, {plus2, plus1}}; + return callbacks[1][0](3) + callbacks[1][1](3); +} +EOF +try_ "$((4 * PTR_SZ))" << EOF +int main(void) { + typedef int (*callbacks_t[2][2])(int); + return sizeof(callbacks_t); +} +EOF +try_ 9 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2][2][2])(int); + callbacks_t callbacks = { + {{plus1, plus2}, {plus2, plus1}}, + {{plus2, plus1}, {plus1, plus2}} + }; + return callbacks[1][1][0](3) + callbacks[1][0][0](3); +} +EOF +try_ "$((8 * PTR_SZ))" << EOF +int main(void) { + typedef int (*callbacks_t[2][2][2])(int); + return sizeof(callbacks_t); +} +EOF +try_ 9 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2][2][2][2])(int); + callbacks_t callbacks = { + {{{plus1, plus2}, {plus2, plus1}}, {{plus2, plus1}, {plus1, plus2}}}, + {{{plus2, plus1}, {plus1, plus2}}, {{plus1, plus2}, {plus2, plus1}}} + }; + return callbacks[1][1][1][1](3) + callbacks[1][0][0][0](3); +} +EOF +try_ "$((16 * PTR_SZ))" << EOF +int main(void) { + typedef int (*callbacks_t[2][2][2][2])(int); + return sizeof(callbacks_t); +} +EOF +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2][2][2][2])(int); + typedef callbacks_t callbacks_alias_t; + callbacks_alias_t callbacks = { + {{{plus1, plus2}, {plus2, plus1}}, {{plus2, plus1}, {plus1, plus2}}}, + {{{plus2, plus1}, {plus1, plus2}}, {{plus1, plus2}, {plus2, plus1}}} + }; + return callbacks[0][1][0][1](6) + (sizeof(callbacks_alias_t) != 16 * sizeof(void *)); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (*setters_t[1][1][1][1])(int); + setters_t setters = {{{{set_target}}}}; + setters[0][0][0][0](7); + return target; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + for (typedef int (*callbacks_t[1][1][1][1])(int); ; ) { + callbacks_t callbacks = {{{{plus1}}}}; + return callbacks[0][0][0][0](7); + } +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (*callbacks_t[1][1][1][1])(int); } + callbacks_t callbacks = {{{{0}}}}; + return callbacks[0][0][0][0] != 0; +} +EOF +try_ 7 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2][2][2])(int); + typedef callbacks_t callbacks_alias_t; + callbacks_alias_t callbacks = { + {{plus1, plus2}, {plus2, plus1}}, + {{plus2, plus1}, {plus1, plus2}} + }; + return callbacks[1][0][1](6) + (sizeof(callbacks_alias_t) != 8 * sizeof(void *)); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (*setters_t[1][1][1])(int); + setters_t setters = {{{set_target}}}; + setters[0][0][0](7); + return target; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + for (typedef int (*callbacks_t[1][1][1])(int); ; ) { + callbacks_t callbacks = {{{plus1}}}; + return callbacks[0][0][0](7); + } +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (*callbacks_t[1][1][1])(int); } + callbacks_t callbacks = {{{0}}}; + return callbacks[0][0][0] != 0; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2][2])(int); + typedef callbacks_t callbacks_alias_t; + callbacks_alias_t callbacks = {{plus1, plus2}, {plus2, plus1}}; + return callbacks[0][1](6); +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (*setters_t[1][1])(int); + setters_t setters = {{set_target}}; + setters[0][0](7); + return target; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + for (typedef int (*callbacks_t[1][1])(int); ; ) { + callbacks_t callbacks = {{plus1}}; + return callbacks[0][0](7); + } +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (*callbacks_t[1][1])(int); } + callbacks_t callbacks = {{0}}; + return callbacks[0][0] != 0; +} +EOF +try_ 7 << EOF +int target; +void set_target(int value) { target = value; } +int main(void) { + typedef void (*setters_t[1])(int); + setters_t setters = {set_target}; + setters[0](7); + return target; +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int (*callbacks_t[2])(int); + typedef callbacks_t callbacks_alias_t; + callbacks_alias_t callbacks = {plus1, plus2}; + return callbacks[0](7); +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + for (typedef int (*callbacks_t[1])(int); ; ) { + callbacks_t callbacks = {plus1}; + return callbacks[0](7); + } +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int (*callbacks_t[1])(int); } + callbacks_t callbacks = {0}; + return callbacks[0] != 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (*callbacks_t[])(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*callbacks_t[2][2][2][2][2])(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { + typedef int (*callbacks_t[2][2][2][2])(int); + typedef callbacks_t wrapped_t[2]; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (*callbacks_t[2])(int) = 0; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*callbacks_t[2])(int, ...); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*const callbacks_t[2])(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*volatile callbacks_t[2])(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int (*restrict callbacks_t[2])(int); return 0; } +EOF +try_compile_error << EOF +int main(void) { + typedef int (*callback_t)(int); + typedef callback_t const callbacks_t[2]; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef float (*callbacks_t[2])(int); return 0; } +EOF +try_compile_error << EOF +struct pair { int value; }; +int main(void) { typedef struct pair (*callbacks_t[2])(int); return 0; } +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int *int_ptr, unary_t(int); + int value = 7; + int_ptr pointer = &value; + unary_t *callback = plus1; + return callback(*pointer); +} +EOF +try_ 9 << EOF +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + typedef int first_t(int), second_t(int); + first_t *first = plus1; + second_t *second = plus2; + return first(3) + second(3); +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int (*callback_t)(int), unary_t(int); + callback_t callback = plus1; + unary_t *alias_callback = callback; + return alias_callback(7); +} +EOF +try_ 5 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int row_t[2], unary_t(int); + row_t row = {3, 4}; + unary_t *callback = plus1; + return callback(row[1]); +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + typedef int *const pointer_t, unary_t(int); + unary_t *callback = plus1; + return callback(7); +} +EOF +try_compile_error << EOF +int main(void) { + typedef int *const pointer_t, unary_t(int); + int value = 0; + pointer_t pointer = &value; + pointer = &value; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + typedef int scalar_t, unary_t(int) = 0; + return 0; +} +EOF +try_ 3 << EOF +int count(int n, ...) { return n; } +int main(void) { + typedef int scalar_t, variadic_t(int, ...); + variadic_t *callback = count; + scalar_t value = callback(3, 1, 2); + return value; +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int scalar_t, unary_t(int); } + unary_t *callback = 0; + return callback != 0; +} +EOF +try_ 8 << EOF +int plus1(int); +int main(void) { + typedef int unary_t(int); + unary_t plus1; + return plus1(7); +} +int plus1(int value) { return value + 1; } +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + int plus1 = 0; + { + typedef int unary_t(int); + unary_t plus1; + return plus1(7); + } +} +EOF +try_ 8 << EOF +int plus1(int value) { return value + 1; } +int main(void) { + for (typedef int unary_t(int); ; ) { + unary_t *callback = plus1; + return callback(7); + } +} +EOF +try_ "$((2 * PTR_SZ))" << EOF +int main(void) { + typedef int *(*rows_t)[2]; + int first = 3, second = 7; + int *data[1][2] = { { &first, &second } }; + rows_t p = data; + return sizeof(*p); +} +EOF +try_ 7 << EOF +int main(void) { + for (typedef int *(*rows_t)[2]; sizeof(rows_t) == sizeof(int *); ) { + int first = 3, second = 7; + int *data[1][2] = { { &first, &second } }; + rows_t p = data; + return *p[0][1]; + } +} +EOF +try_compile_error << EOF +int main(void) { + { typedef int *(*rows_t)[2]; } + rows_t p = 0; + return p != 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int **(*bad)[2]; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef int *(*bad)[]; return 0; } +EOF +try_ "$((8 + PTR_SZ))" << EOF +int main(void) { + int rows[2][2] = { { 1, 2 }, { 3, 4 } }; + for (typedef int (*row_pointer)[2]; sizeof(row_pointer) == sizeof(int *); ) { + row_pointer p = &rows[0]; + return sizeof(*p) + sizeof(row_pointer); + } +} +EOF +try_ 10 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + return (*p)[1] + (sizeof(*p) == 2 * sizeof(int)); +} +EOF +try_ 9 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + return (*(p))[1]; +} +EOF +try_ 7 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + (*p)[1] = 7; + return rows[0][1]; +} +EOF +try_ 7 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + (*p)[1] = 7; + return rows[0][1]; +} +EOF +try_ 6 << EOF +int main(void) { + short rows[1][3] = { { 1, 2, 3 } }; + typedef short (*row_pointer)[3]; + row_pointer p = rows; + (*p)[1] += 4; + return rows[0][1]; +} +EOF +try_ 5 << EOF +int main(void) { short rows[1][3]={{1,2,3}}; typedef short (*R)[3]; R p=rows; short old=(*p)[1]++; return old+rows[0][1]; } +EOF +try_ 13 << EOF +int main(void) { int rows[1][2]={{4,9}}; int (*p)[2]=rows; int old=(*p)[1]--; return old+rows[0][1]-4; } +EOF +try_ 9 << EOF +int main(void) { int rows[1][2]={{4,9}}; int (*p)[2]=rows; int i=0; (*p)[i++]++; return i+rows[0][0]+3; } +EOF +try_ 10 << EOF +int main(void) { int rows[1][2]={{4,9}}; int (*p)[2]=rows; int choice=1; (*p)[choice ? 1 : 0]++; return rows[0][1]; } +EOF +try_compile_error << EOF +int main(void) { int rows[1][2]={{4,9}}; typedef const int (*R)[2]; R p=rows; (*p)[1]++; return 0; } +EOF +try_ 6 << EOF +int main(void) { + short rows[1][3] = { { 1, 2, 3 } }; + typedef short (*row_pointer)[3]; + row_pointer p = rows; + short value = ++(*p)[1]; + return value + rows[0][1]; +} +EOF +try_ 16 << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*p)[2] = rows; + int value = --(*p)[1]; + return value + rows[0][1]; +} +EOF +try_ 11 << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*p)[2] = rows; + int index = 0; + int value = ++(*p)[index++]; + return value + index + rows[0][0]; +} +EOF +try_ 2 << EOF +int main(void) { + _Bool rows[1][1] = { { 1 } }; + _Bool (*p)[1] = rows; + _Bool value = ++(*p)[0]; + return value + rows[0][0]; +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + typedef const int (*row_pointer)[2]; + row_pointer p = rows; + return ++(*p)[1]; +} +EOF +try_ 2 << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*p)[2] = rows; + (*p)[0] -= 2; + return rows[0][0]; +} +EOF +try_ 1 << EOF +int main(void) { + _Bool rows[1][1] = { { 1 } }; + _Bool (*p)[1] = rows; + (*p)[0] += 1; + return rows[0][0]; +} +EOF + +# C99 defines ++ and -- as += 1 and -= 1, so on _Bool the result converts back +# to 0 or 1: b++ on 1 leaves 1, --b on 0 leaves 1 and b-- on 1 leaves 0. The +# same holds for members, elements, pointers and compound assignment. +try_output 0 "1 1 1 1 0 | 1 1 1 1 0 1 1 1 1 0 | 1 1 1 | 1 1 1 0 1 | 1 1 1 1 1 1 | 1 1 1 1 1 1 1 1 1 1" << EOF +typedef _Bool flag; +struct holder { + int pad; + _Bool plain; + flag alias; +}; +_Bool global_flag; +int main(void) +{ + _Bool b, values[3] = {0, 0, 0}, *p = values; + flag alias; + struct holder h, *hp = &h; + int v; + b = 1; + b++; + printf("%d ", b); + b = 1; + ++b; + printf("%d ", b); + b = 0; + b--; + printf("%d ", b); + b = 0; + --b; + printf("%d ", b); + b = 1; + --b; + printf("%d | ", b); + b = 1; + v = b++; + printf("%d %d ", v, b); + b = 1; + v = ++b; + printf("%d %d ", v, b); + b = 0; + v = b--; + printf("%d %d ", v, b); + b = 0; + v = --b; + printf("%d %d ", v, b); + b = 1; + v = b--; + printf("%d %d | ", v, b); + alias = 1; + alias++; + global_flag = 0; + v = --global_flag; + printf("%d %d %d | ", alias, v, global_flag); + h.plain = 1; + h.plain++; + hp->alias = 0; + v = --hp->alias; + printf("%d %d %d ", h.plain, v, h.alias); + h.plain = 0; + v = h.plain--; + printf("%d %d | ", v, h.plain); + values[1] = 1; + values[1]++; + values[2] = 0; + v = --values[2]; + printf("%d %d %d ", values[1], v, values[2]); + p[0] = 0; + p[0]--; + p[1] = 1; + v = ++p[1]; + printf("%d %d %d | ", values[0], v, values[1]); + b = 0; + b += 5; + printf("%d ", b); + b = 0; + b -= 1; + printf("%d ", b); + b = 1; + b <<= 8; + printf("%d ", b); + b = 0; + v = (b += 2); + printf("%d %d ", v, b); + h.plain = 0; + h.plain += 5; + values[1] = 0; + values[1] += 256; + printf("%d %d ", h.plain, values[1]); + *p = 0; + v = (*p += 2); + printf("%d %d ", v, values[0]); + b = 0; + for (v = 0; v < 3; v++) + b++; + printf("%d", b); + return 0; +} +EOF +try_ 8 << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + int (*p)[2] = rows; + int column = 0; + (*p)[column++] += 4; + return column + rows[0][0] - 1; +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + typedef const int (*row_pointer)[2]; + row_pointer p = rows; + (*p)[1] += 1; + return 0; +} +EOF +try_ 8 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + int column = 0; + (*p)[column++] = 7; + return column + rows[0][0]; +} +EOF +try_compile_error << EOF +int main(void) { + int rows[1][2] = { { 4, 9 } }; + typedef const int (*row_pointer)[2]; + row_pointer p = &rows[0]; + (*p)[1] = 7; + return 0; +} +EOF +try_ 7 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + p[1][0] = 7; + return p[1][0]; +} +EOF +try_ 6 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + return p[1][0]; +} +EOF +try_ 7 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + p[1][0] = 7; + return rows[1][0]; +} +EOF +try_ 1 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + return p == &rows[0]; +} +EOF +try_ 7 << EOF +int main(void) { + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + p[1][0] = 7; + return rows[1][0]; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (*row_pointer)[0]; return 0; } +EOF +try_ 0 << EOF +int main(void) { + typedef int (*row_pointer)[2]; + typedef row_pointer table[2]; + int rows[2][2] = { { 3, 5 }, { 7, 8 } }; + table pointers = { rows, rows + 1 }; + return pointers[1][0][1] != 8 || + sizeof(*pointers[1]) != 2 * sizeof(int); +} +EOF +try_compile_error << EOF +int main(void) { + typedef int (*row_pointer)[2]; + typedef row_pointer matrix[2][2]; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { typedef int (*row_pointer)[2]; typedef row_pointer *rows; return 0; } +EOF +try_compile_error << EOF +int main(void) { typedef const int readonly_t; readonly_t value = 1; value = 2; } +EOF +try_compile_error << EOF +int main(void) { + typedef int *inner_pointer; + typedef inner_pointer * const fixed_pointer; + fixed_pointer value = 0; + value = 0; +} +EOF +try_ 14 << EOF +struct loop_record { int value; }; +int main(void) { + for (typedef struct loop_record loop_alias; 1; ) { + loop_alias value = { 14 }; + return value.value; + } +} +EOF +try_compile_error << EOF +int main(void) { + for (typedef int loop_only; 0; ) {} + loop_only expired; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { for (typedef int invalid = 1; 0; ) {} return 0; } +EOF +try_ 3 << EOF +int main(void) { + for (int value = 3; value; value = 0) + return value; + return 0; +} +EOF +try_compile_error << EOF +void invalid_global_void_object; +EOF +try_compile_error << EOF +int main(void) { void invalid_local_void_object; return 0; } +EOF +try_compile_error << EOF +int main(void) { for (void invalid_for_void_object; 0; ) {} return 0; } +EOF +try_compile_error << EOF +struct invalid_void_member { void member; }; +EOF +try_compile_error << EOF +typedef void invalid_void_alias; +invalid_void_alias invalid_hidden_global_void_object; +EOF +try_compile_error << EOF +typedef void invalid_void_alias; +int main(void) { invalid_void_alias invalid_hidden_local_void_object; return 0; } +EOF +try_compile_error << EOF +typedef void invalid_void_alias; +struct invalid_hidden_void_member { invalid_void_alias member; }; +EOF +try_compile_error << EOF +typedef void invalid_void_alias; +int invalid_void_parameter(invalid_void_alias value); +EOF +try_compile_error << EOF +int invalid_void_array_parameter(void values[]); +EOF +try_compile_error << EOF +typedef void invalid_void_alias; +int invalid_hidden_void_array_parameter(invalid_void_alias values[]); +EOF +try_compile_error << EOF +void (*invalid_void_pointer_to_array)[2]; +EOF +try_ 0 << EOF +void valid_void_function(void) {} +typedef void valid_void_alias; +typedef void *valid_void_pointer_alias; +int valid_void_pointer_parameter(void *value) { return value != 0; } +int main(void) { + void *pointer = 0; + void *pointer_array[1] = { pointer }; + valid_void_pointer_alias alias_pointer_array[1] = { pointer }; + valid_void_alias *alias_pointer = pointer; + valid_void_function(); + return pointer != 0 || alias_pointer != 0 || + pointer_array[0] != 0 || alias_pointer_array[0] != 0 || + valid_void_pointer_parameter(pointer); +} +EOF + +items 7 "auto int value = 7; return value;" +items 3 "int total = 0; for (auto int value = 0; value < 3; value++) total += value; return total;" +items 6 "int first = 1, second = 2; first++, second += 2; return first + second;" +items 6 "int first = 0; int second = first = 3; return first + second;" +try_ 2 << EOF +int main(void) { + int value = 0; + value ? value = 1 : value = 2; + return value; +} +EOF +try_ 3 << EOF +int main(void) { + int first = 0, second = 0; + (first = 1, second = 2); + return first + second; +} +EOF +try_ 1 << EOF +int main(void) { + int value = 1; + value + 4; + return value; +} +EOF +try_ 7 << EOF +struct pair { int left; int right; }; +int main(void) { + struct pair first = {1, 2}; + struct pair second = {3, 4}; + (first = second); + return first.left + first.right; +} +EOF +try_ 7 << EOF +struct pair { int left; int right; }; +int main(void) { + struct pair first = {1, 2}; + struct pair second = {3, 4}; + struct pair copied = (first = second); + return copied.left + copied.right; +} +EOF +try_compile_error << EOF +int main(void) { auto auto int value = 0; return value; } +EOF +try_compile_error << EOF +int main(void) { static auto int value = 0; return value; } +EOF +try_compile_error << EOF +auto int file_scope; +EOF + +declare -a variable_tests=( + "10 int var; var = 10; return var;" + "42 int va; int vb; va = 11; vb = 31; int vc; vc = va + vb; return vc;" + "50 int v; v = 30; v = 50; return v;" + "25 short s; s = 25; return s;" + "50 short sa = 20; short sb = 30; short sc = sa + sb; return sc;" +) + +run_items_tests variable_tests + +# A block-scope register declaration has ordinary automatic storage behavior. +try_ 9 << EOF +int main(void) { + register int value = 4; + value += 5; + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + register int value = 4; + int *pointer = &value; + return *pointer; +} +EOF + +try_ 12 << EOF +int identity(register int value) { return value; } +int main(void) { return identity(12); } +EOF + +try_ 13 << EOF +int increment(int *restrict value) { *value += 1; return *value; } +int main(void) { + int value = 12; + int *restrict pointer = &value; + return increment(pointer); +} +EOF + +try_ 2 << EOF +typedef int *int_pointer; +int main(void) { + int value = 1; + int_pointer restrict pointer = &value; + *pointer += 1; + return value; +} +EOF + +try_ 2 << EOF +typedef int *int_pointer; +int increment(int_pointer restrict pointer) { *pointer += 1; return *pointer; } +int main(void) { int value = 1; return increment(&value); } +EOF + +try_compile_error << EOF +int restrict value; +EOF + +try_compile_error << EOF +typedef int (*callback)(void); +callback restrict function; +EOF + +try_ 14 << EOF +inline int increment_inline(int value) { return value + 1; } +static inline int twice_inline(int value) { return value * 2; } +int inline trailing_inline(int value) { return value - 1; } +int main(void) { + return twice_inline(increment_inline(6)) + trailing_inline(1); +} +EOF + +# C99 6.7.4 forbids an external-linkage inline definition from containing a +# modifiable static definition or naming an internal-linkage object/function. +try_compile_error << EOF +inline int invalid_external_inline_static(void) { + static int value; + return value; +} +EOF + +try_compile_error << EOF +static int hidden_external_inline_object; +inline int invalid_external_inline_object(void) { + return hidden_external_inline_object; +} +EOF + +try_compile_error << EOF +static int hidden_external_inline_function(void) { return 1; } +inline int invalid_external_inline_function(void) { + return hidden_external_inline_function(); +} +EOF + +try_compile_error << EOF +static int hidden_external_inline_address(void) { return 1; } +inline int invalid_external_inline_address(void) { + return (&hidden_external_inline_address) != 0; +} +EOF + +try_compile_error << EOF +static int hidden_external_inline_sizeof; +inline int invalid_external_inline_sizeof(void) { + return sizeof hidden_external_inline_sizeof; +} +EOF + +try_ 3 << EOF +static inline int permitted_internal_inline(void) { + static int value; + return ++value; +} +int main(void) { + return permitted_internal_inline() + permitted_internal_inline(); +} +EOF + +try_ 2 << EOF +inline int permitted_external_inline_const(void) { + static int const value = 1; + static int * const pointer = 0; + return value + (pointer == 0); +} +int main(void) { return permitted_external_inline_const(); } +EOF + +try_ 13 << EOF +int third(int values[static 3]) { return values[2]; } +int sum_pair(int values[restrict static 2]) { return values[0] + values[1]; } +int main(void) { + int values[3] = { 3, 4, 6 }; + return third(values) + sum_pair(values); +} +EOF + +try_ 0 << EOF +int increment(int value) { return value + 1; } +int apply(int (*callbacks[static 1])(int)) { return callbacks[0](2) != 3; } +int main(void) { int (*callback)(int) = increment; return apply(&callback); } +EOF + +try_compile_error << EOF +int invalid(int values[static]); +EOF + +try_compile_error << EOF +int invalid(int values[2][static 2]); +EOF + +try_compile_error << EOF +int invalid(int values[2][const 2]); +EOF + +try_compile_error << EOF +int invalid(int values[2][volatile 2]); +EOF + +try_compile_error << EOF +int invalid(int values[2][restrict 2]); +EOF + +try_compile_error << EOF +int invalid(int (*callbacks[2][static 1])(int)); +EOF + +try_compile_error << EOF +int invalid(int values[static 0]); +EOF + +try_compile_error << EOF +int invalid(int values[static -1]); +EOF + +try_compile_error << EOF +int values[static 2]; +EOF + +try_compile_error << EOF +int invalid(int values[const 2]) { values = 0; return 0; } +EOF + +try_compile_error << EOF +int invalid(int values[const]) { values = 0; return 0; } +EOF + +try_compile_error << EOF +inline int invalid_inline_object; +int main(void) { return 0; } +EOF + +try_compile_error << EOF +inline struct invalid_inline_record { int value; } object; +EOF + +try_compile_error << EOF +inline union invalid_inline_union { int value; } object; +EOF + +try_compile_error << EOF +inline enum invalid_inline_enum { invalid_inline_value } object; +EOF + +try_compile_error << EOF +inline typedef int invalid_inline_typedef; +EOF + +try_ 13 << EOF +volatile int global_counter = 11, global_limit = 1; +int increment_volatile(volatile int *counter) { *counter += 1; return *counter; } +int main(void) { + volatile int local_counter = 12, local_limit = 1; + for (volatile int count = 0, limit = global_limit; count < limit; count++) + global_counter += count; + return increment_volatile(&local_counter) + local_limit - 1; +} +EOF + +try_ 1 << EOF +int named_for_func(void) { return __func__[0] == 'n'; } +int main(void) { return named_for_func(); } +EOF + +# __func__ behaves as a static character array before ordinary expression decay: +# taking its address, restoring the array, and dereferencing again yields its +# first byte. +try_ 1 << EOF +int addressed_func_name(void) { return *(*(&__func__)) == 'a'; } +int main(void) { return addressed_func_name(); } +EOF + +# Postfix subscripting applies to parenthesized pointer and string expressions. +try_ 7 << EOF +int main(void) { + int values[2] = { 4, 7 }; + int *pointer = values; + return ("cat")[1] + (pointer)[1] - 'a'; +} +EOF + +try_ 1 << EOF +int function_name_width(void) { return sizeof __func__ == 20; } +int main(void) { + int value = 0; + int *pointer = &value; + return function_name_width() && sizeof value == 4 && sizeof *pointer == 4; +} +EOF + +try_ 13 << EOF +int main(void) { + return sizeof "cat" + sizeof "a" "bc" + sizeof("tool"); +} +EOF + +try_ 12 << EOF +int main(void) { return sizeof((int[]){1, 2, 3}); } +EOF + +try_ 6 << EOF +int main(void) { + int value = 6; + int *restrict direct = (int *restrict)&value; + int *volatile indirect = (int *volatile)direct; + return *indirect; +} +EOF + +try_ 13 << EOF +int increment(int value) { return value + 1; } +int main(void) { + int (*restrict callback)(int) = increment; + return callback(12); +} +EOF + +try_compile_error << EOF +int invalid(register int value) { return *(&value); } +int main(void) { return invalid(1); } +EOF + +# Narrow signed values must stay negative through promotion and through a store +# and reload. An LP64 backend holds them in a 64-bit register, so a load that +# zero-extends or a promotion that forgets to extend turns a small negative +# number into a large positive one. The array-element form belongs here too, but +# it fails on the Arm backend, whose char elements load zero-extended, so it +# stays in tests/arm64-abi.sh until that is fixed. +try_ 42 << EOF +int main() { + char c = -5; + short s = -1000; + int ci = c; + int si = s; + if (ci != -5) + return 1; + if (si != -1000) + return 2; + if (c >= 0) + return 3; + if (s >= 0) + return 4; + return 42; +} +EOF + +# A pointer is wider than an int on an LP64 target, so testing one for truth has +# to consider the whole value, not just its low word. No fixture can force the +# case that separates the two, since pinning a pointer whose low word is zero +# needs a 64-bit literal and shecc has no integer constant that wide. What is +# testable is that every path which tests an address agrees: the backend emits a +# different width for a branch, for a logical negation and for a comparison, so +# each is reached here with a null and a non-null pointer. +try_ 42 << EOF +struct holder { + int *ptr; +}; + +int *pick(int *p, int take) +{ + if (take) + return p; + return 0; +} + +int main() { + int v = 42; + int *p = &v; + int *n = 0; + struct holder h; + int seen = 0; + + if (!p) + return 1; + if (n) + return 2; + if (p == 0) + return 3; + if (n != 0) + return 4; + + while (n) + return 5; + + seen = p ? 1 : 0; + if (!seen) + return 6; + seen = n ? 1 : 0; + if (seen) + return 7; + + if (p && !n) + seen = 2; + if (seen != 2) + return 8; + if (n || !p) + return 9; + + /* A pointer that reaches the test through a return value or a struct + * field has been through a store and a reload on the way. + */ + if (!pick(p, 1)) + return 10; + if (pick(p, 0)) + return 11; + + h.ptr = n; + if (h.ptr) + return 12; + h.ptr = p; + if (!h.ptr) + return 13; + + int *q = h.ptr; + return *q; +} +EOF + +# Block-scope tagged records use the same layout path as file-scope records, +# including immediate declarators, bit-fields, unions, and recursive pointers. +try_ 21 << EOF +int main(void) { + struct flags { unsigned int low : 3; unsigned int high : 3; } + bits = {2, 4}; + union payload { int number; char bytes[4]; } data = {7}; + struct node { struct node *next; int value; } + first = {0, 3}, second = {&first, 5}; + return bits.low + bits.high + data.number + first.value + + second.value + (second.next == &first ? 0 : 1); +} +EOF +try_ 40 << EOF +int struct_value(void) { + static struct pair { int left; int right; } saved = {3, 4}; + saved.left++; + return saved.left + saved.right; +} +int union_value(void) { + static union payload { int number; char bytes[4]; } saved = {5}; + saved.number++; + return saved.number; +} +int array_value(void) { + static struct point { int x; int y; } points[2] = {{1, 2}, {3, 4}}; + return points[0].x + points[0].y + points[1].x + points[1].y; +} +int main(void) { + return struct_value() + struct_value() + union_value() + union_value() + + array_value(); +} +EOF + +# Brace elision: scalars that meet an array member without braces fill its +# elements in order before the next member, in automatic and static records and +# in unions. The 32-byte member is wider than any store a backend emits. +try_ 8 << EOF +typedef struct { char name[32]; int a; } named_t; +typedef struct { char tag[4]; int a; } tagged_t; +typedef struct { int m[2][2]; int b; } matrix_t; +typedef struct { int *p[2]; int c; } slots_t; +typedef union { char buf[4]; int x; } bytes_t; +int first = 5, second = 6; +tagged_t global_tagged = {1, 2, 3, 4, 5}; +static slots_t global_slots = {&first, &second, 7}; +int main(void) { + named_t zero = {0}; + tagged_t tagged = {1, 2}; + matrix_t matrix = {1, 2, 3, 4, 5}; + bytes_t bytes = {1, 2}; + int r = zero.name[0] == 0 && zero.a == 0; + r += tagged.tag[1] == 2 && tagged.tag[2] == 0 && tagged.a == 0; + r += matrix.m[1][1] == 4 && matrix.b == 5; + r += bytes.buf[0] == 1 && bytes.buf[1] == 2; + r += global_tagged.tag[3] == 4 && global_tagged.a == 5; + r += *global_slots.p[1] == 6 && global_slots.c == 7; + zero.a = 2; + return r + zero.a; +} +EOF +# An array of pointers to records is still an array of scalars for elision. +try_ 7 << EOF +struct row { int values[2][3]; }; +struct rows { struct row *slots[2]; int tail; }; +static struct row first_row, second_row; +static struct rows global_rows = {&first_row, &second_row, 4}; +int main(void) { + struct rows local_rows = {&second_row}; + return (global_rows.slots[0] == &first_row) + + 2 * (global_rows.slots[1] == &second_row) + + (local_rows.slots[0] == &second_row) + global_rows.tail - 1; +} +EOF + +# A record element or member without braces takes one initializer per member +# from the enclosing list, so { 1, 2, 3, 4 } fills two points. The elided record +# stops at its last member, at a designator of the enclosing list, or at a +# braced element, and a block scope record value still initializes the whole +# element. Checked at file scope, block scope and for block statics. +try_ 0 << EOF +typedef struct p { int x, y; } P; +struct q { struct p a; int z; }; +struct r { char s[4]; int n; }; +struct u { struct p a[2]; int z; }; +struct b { unsigned lo : 3; unsigned : 2; unsigned hi : 3; int t; }; +union n { int i; char c[4]; }; +struct w { char c; long long v; }; +#define DECLS(S) \ + S P v1[3] = { 1, 2, [2] = 5, 6 }; \ + S struct q v2 = { 1, .z = 3 }; \ + S struct r v3[2] = { "abc", 1, "de", 2, }; \ + S struct u v4 = { 1, 2, {3, 4}, 5 }; \ + S struct b v5[2] = { 1, 7, 2, 3, 6, 4 }; \ + S union n v6[2] = { 5, 6 }; \ + S struct w v7[] = { 'a', 11, 'b', 22 }; \ + S struct p v8[2][2] = { 1, 2, 3, 4, 5, 6, 7, 8 }; \ + S struct q v9[] = { 1, 2, 3, {4, 5}, 6 }; \ + S struct u v10 = { { 1, 2, 3, 4 }, 5 }; \ + S struct p v11[] = { 1, [2] = 3, 4, [0] = 5 }; +#define CHECKS \ + int r = 0; \ + if (v1[0].x != 1 || v1[0].y != 2 || v1[1].x || v1[1].y || v1[2].x != 5 || \ + v1[2].y != 6) \ + r |= 1; \ + if (v2.a.x != 1 || v2.a.y || v2.z != 3) \ + r |= 2; \ + if (v3[0].s[2] != 'c' || v3[0].n != 1 || v3[1].s[1] != 'e' || \ + v3[1].n != 2) \ + r |= 4; \ + if (v4.a[0].y != 2 || v4.a[1].x != 3 || v4.a[1].y != 4 || v4.z != 5) \ + r |= 8; \ + if (v5[0].lo != 1 || v5[0].hi != 7 || v5[0].t != 2 || v5[1].lo != 3 || \ + v5[1].hi != 6 || v5[1].t != 4) \ + r |= 16; \ + if (v6[0].i != 5 || v6[1].i != 6) \ + r |= 32; \ + if (sizeof(v7) / sizeof(v7[0]) != 2 || v7[0].c != 'a' || v7[0].v != 11 || \ + v7[1].c != 'b' || v7[1].v != 22) \ + r |= 64; \ + if (v8[0][0].x != 1 || v8[0][1].y != 4 || v8[1][0].x != 5 || \ + v8[1][1].y != 8) \ + r |= 128; \ + if (sizeof(v9) / sizeof(v9[0]) != 3 || v9[0].z != 3 || v9[1].a.x != 4 || \ + v9[1].a.y != 5 || v9[1].z || v9[2].a.x != 6 || v9[2].z) \ + r |= 256; \ + if (v10.a[0].y != 2 || v10.a[1].x != 3 || v10.a[1].y != 4 || v10.z != 5) \ + r |= 512; \ + if (sizeof(v11) / sizeof(v11[0]) != 3 || v11[0].x != 5 || v11[0].y || \ + v11[1].x || v11[1].y || v11[2].x != 3 || v11[2].y != 4) \ + r |= 1024; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int values(void) { + struct p s1 = {1, 2}, s2 = {3, 4}; + struct p pair[2] = { s1, s2 }; + struct q outer[2] = { s2, 9, s1, 8 }; + struct p *lit = (struct p[]){ 7, 8, 9 }; + return pair[0].y != 2 || pair[1].x != 3 || outer[0].a.y != 4 || + outer[0].z != 9 || outer[1].a.x != 1 || outer[1].z != 8 || + lit[1].x != 9 || lit[1].y; +} +int main(void) { + int r = check_global() != 0; + dirty(); + return r | (check_local() != 0) << 1 | (check_static() != 0) << 2 | + values() << 3; +} +EOF +# A tag is an identifier, so it may be as long as any other identifier. +try_ 4 << EOF +struct a_record_tag_name_longer_than_thirty_two_bytes { int value; }; +int main(void) { + struct a_record_tag_name_longer_than_thirty_two_bytes item = {4}; + return item.value; +} +EOF + +# Category: Compound Literals +begin_category "Compound Literals" "Testing C99 compound literal features" + +try_ 7 << EOF +int main(void) { return (int){3} = 7; } +EOF +try_ 7 << EOF +int main(void) { return (int){3} += 4; } +EOF +try_ 9 << EOF +int main(void) { + int first = 3, second = 9; + int *pointer = (int *){&first} = &second; + return *pointer; +} +EOF +try_ 2 << EOF +int main(void) { + int values[3] = {1, 2, 3}; + int *pointer = ++(int *){values}; + return *pointer; +} +EOF +try_ 2 << EOF +int main(void) { + int values[3] = {1, 2, 3}; + int *pointer = --(int *){&values[2]}; + return *pointer; +} +EOF +try_ 3 << EOF +int main(void) { + int values[4] = {1, 2, 3, 4}; + int *pointer = ((int *){values} += 2); + return *pointer; +} +EOF +try_ 2 << EOF +int main(void) { + int values[4] = {1, 2, 3, 4}; + int *pointer = ((int *){&values[3]} -= 2); + return *pointer; +} +EOF +try_ 2 << EOF +int main(void) { + int values[4] = {1, 2, 3, 4}; + int *pointer = (int *){values}; + pointer += 2; + pointer -= 1; + return *pointer; +} +EOF +try_compile_error << EOF +int main(void) { + int value = 3; + return (int * const){&value} = &value; +} +EOF +try_compile_error << EOF +int main(void) { + int value = 3; + return (int * const){&value}++; +} +EOF +try_ 4 << EOF +int main(void) { return (unsigned char){250} += 10; } +EOF +try_ 1 << EOF +int main(void) { return (_Bool){0} = 2; } +EOF +try_ 1 << EOF +int main(void) { return (_Bool){1} += 2; } +EOF +try_compile_error << EOF +int main(void) { return (const int){3} += 4; } +EOF +try_ 7 << EOF +int main(void) { return (int[]){1, 2}[1] = 7; } +EOF +try_ 6 << EOF +int main(void) { + return (int[2][3]){{1, 2, 3}, {4, 5, 6}}[1][2]; +} +EOF +try_ 3 << EOF +int main(void) { + return (int[][2]){{1, 2}, {3, 4}}[1][0]; +} +EOF +try_ 16 << EOF +int main(void) { + return (int[2][2][2]){{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}[1][1][1] + + ((int[2][2][2]){{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}})[1][0][0] + + (int[][2][2]){{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}[0][1][0]; +} +EOF +try_ 17 << EOF +int main(void) { + return (int[2][2][2][2]){{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}}[1][0][1][0] + + ((int[2][2][2][2]){{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, + {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}})[0][1][0][1]; +} +EOF +try_ 6 << EOF +typedef int matrix[2][3]; +int main(void) { + return (matrix){{1, 2, 3}, {4, 5, 6}}[1][2]; +} +EOF +try_ 24 << EOF +int main(void) { + return sizeof((int[2][3]){{1, 2, 3}, {4, 5, 6}}); +} +EOF + +# Fewer subscripts than an array compound literal's rank select a row, which +# sizeof measures whole and a following use decays to a pointer that steps by +# the remaining bounds. +try_ 0 << 'EOF' +int main(void) +{ + int *row = (int[2][3]){{1, 2, 3}, {4, 5, 6}}[1]; + + if (sizeof((int[2][3]){{1}}[1]) != 3 * sizeof(int) || + sizeof((int[2][2][3]){{{1}}}[1]) != 6 * sizeof(int)) + return 1; + if (sizeof((int[2][3]){{1}}[1][2]) != sizeof(int) || + sizeof((int[2][3]){{1}}) != 6 * sizeof(int)) + return 2; + if (row[2] != 6 || *(int[2][3]){{1, 2, 3}, {4, 5, 6}}[1] != 4 || + (int[2][3]){{1, 2, 3}, {4, 5, 6}}[1][2] != 6) + return 3; + return 0; +} +EOF +try_ 7 << EOF +typedef int matrix[2][2]; +int main(void) { + matrix value = {{1, 2}, {3, 4}}; + return value[1][1] + value[0][0] * 3; +} +EOF +try_ 7 << EOF +typedef int row[2]; +typedef row matrix[2]; +int main(void) { + matrix value = {{1, 2}, {3, 4}}; + return value[1][1] + value[0][0] * 3; +} +EOF +try_ 11 << EOF +typedef int row[2]; +typedef row matrix[2]; +typedef matrix cube[2]; +int main(void) { + cube value = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; + return value[1][1][1] + value[0][0][0] * 3; +} +EOF + +# An object declared as an array of an array typedef, `row m[2]`, is an array of +# rows; its size and subscript strides include the typedef's own bound. Checked +# at file scope, block scope and for block-scope statics, through parameters and +# record members, and for a later declarator of the same declaration. +try_ 0 << 'EOF' +typedef int row[2]; +typedef row plane[2]; +struct rows { char tag; row r[3]; }; +row g_m[2] = {{1, 2}, {3, 4}}; +row g_t[] = {{1, 2}, {3, 4}, {5, 6}}; +row g_e[2] = {1, 2, 3, 4}; +plane g_c[2] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; +struct rows g_r = {9, {{1, 2}, {3, 4}, {5, 6}}}; +row *g_pr = &g_m[1]; +row g_a, g_b; +int sum_rows(row r[], int n) +{ + int s = 0; + for (int i = 0; i < n; i++) + s += r[i][0] * 10 + r[i][1]; + return s; +} +int last(row r[3]) { return r[2][1]; } +int cube_at(plane p[], int i, int j, int k) { return p[i][j][k]; } +#define SIZES(m, t, c, b) (sizeof m * 1000000 + sizeof t * 10000 + \ + sizeof c * 100 + sizeof b) +int check(row *m, row *t, row *e, plane *c, struct rows *r, row *pr, int sizes) +{ + if (sizes != 16243208) + return 1; + if ((char *) &m[1][1] - (char *) m != 12) + return 2; + if (m[1][0] != 3 || t[2][1] != 6 || e[1][1] != 4 || e[0][1] != 2) + return 3; + if ((char *) &c[1][1][1] - (char *) c != 28 || c[1][0][1] != 6) + return 4; + if (sizeof(struct rows) != 28 || r->r[2][1] != 6 || + (char *) &r->r[1][0] - (char *) r != 12) + return 5; + if ((char *) pr - (char *) m != 8 || (*pr)[1] != 4 || pr[0][0] != 3) + return 6; + if (sum_rows(t, 3) != 102 || last(t) != 6 || cube_at(c, 1, 1, 1) != 8) + return 7; + return 0; +} +int block(void) +{ + row m[2] = {{1, 2}, {3, 4}}; + row t[] = {{1, 2}, {3, 4}, {5, 6}}; + row e[2] = {1, 2, 3, 4}; + plane c[2] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; + struct rows r = {9, {{1, 2}, {3, 4}, {5, 6}}}; + row *pr = &m[1]; + row a, b; + b[1] = 7; + a[1] = 8; + c[0][1][1] = 41; + if (b[1] != 7 || c[0][1][1] != 41 || c[1][0][0] != 5) + return 8; + c[0][1][1] = 4; + return check(m, t, e, c, &r, pr, SIZES(m, t, c, b)); +} +int block_static(void) +{ + static row m[2] = {{1, 2}, {3, 4}}; + static row t[] = {{1, 2}, {3, 4}, {5, 6}}; + static row e[2] = {1, 2, 3, 4}; + static plane c[2] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; + static struct rows r = {9, {{1, 2}, {3, 4}, {5, 6}}}; + static row *pr = &m[1]; + static row a, b; + return check(m, t, e, c, &r, pr, SIZES(m, t, c, b)); +} +int main(void) +{ + int rc = check(g_m, g_t, g_e, g_c, &g_r, g_pr, SIZES(g_m, g_t, g_c, g_b)); + if (rc) + return rc; + rc = block(); + if (rc) + return rc + 10; + rc = block_static(); + return rc ? rc + 20 : 0; +} +EOF +try_ 11 << EOF +int main(void) { + return (int[2][2]){{1, 2}, {3, 4}}[1][0] += 8; +} +EOF +try_ 3 << EOF +int main(void) { + return (int[2][2]){{1, 2}, {3, 4}}[1][0]++; +} +EOF +try_ 5 << EOF +int main(void) { + return (int[2]){4, 5}[1]--; +} +EOF +try_ 4 << EOF +int main(void) { + return ++(int[2][2]){{1, 2}, {3, 4}}[1][0]; +} +EOF +try_compile_error << EOF +int main(void) { return ++(const int[1]){1}[0]; } +EOF +try_compile_error << EOF +int main(void) { return (int[2][2])0; } +EOF +try_ 7 << EOF +int main(void) { return (int[]){1, 2}[1] += 5; } +EOF +try_ 7 << EOF +struct point { int x; int y; }; +int main(void) { return (struct point){1, 2}.x = 7; } +EOF +try_ 7 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { return (struct flags){1}.value += 6; } +EOF +try_ 1 << EOF +struct point { int x; }; +int main(void) { return (struct point){1}.x++; } +EOF +try_ 2 << EOF +struct point { int x; }; +int main(void) { return ++(struct point){1}.x; } +EOF +try_ 7 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { return (struct flags){7}.value++; } +EOF +try_ 1 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { return ++(struct flags){0}.value; } +EOF +try_compile_error << EOF +struct point { int x; }; +int main(void) { return (const struct point){1}.x++; } +EOF +try_compile_error << EOF +struct point { int x; }; +int main(void) { return ++(const struct point){1}.x; } +EOF +try_ 7 << EOF +struct holder { int items[2]; }; +int main(void) { return (struct holder){{1, 2}}.items[1] = 7; } +EOF +try_ 3 << EOF +struct holder { int items[2]; }; +int main(void) { return ++(struct holder){{1, 2}}.items[1]; } +EOF +try_compile_error << EOF +struct holder { int items[2]; }; +int main(void) { return ++(const struct holder){{1, 2}}.items[1]; } +EOF +try_ 7 << EOF +struct holder { int items[2][2]; }; +int main(void) { + return (struct holder){{{1, 2}, {3, 4}}}.items[1][0] = 7; +} +EOF +try_ 8 << EOF +struct holder { int items[2][2]; }; +int main(void) { + return (struct holder){{{1, 2}, {3, 4}}}.items[1][0] += 5; +} +EOF +try_ 9 << EOF +struct holder { int items[2][2][2]; }; +int main(void) { + return (struct holder){{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}} + .items[1][0][1] = 9; +} +EOF +try_ 7 << EOF +struct point { int x; }; +struct holder { struct point point; }; +int main(void) { return (struct holder){{1}}.point.x = 7; } +EOF +try_compile_error << EOF +int main(void) { return (const int){1} = 2; } +EOF +try_compile_error << EOF +struct point { int x; }; +int main(void) { return (const struct point){1}.x = 2; } +EOF +try_compile_error << EOF +int main(void) { return (const int[]){1}[0] = 2; } +EOF +try_ 1 << EOF +int main(void) { return (int){1}++; } +EOF +try_compile_error << EOF +int main(void) { return (const int){1}++; } +EOF +try_ 2 << EOF +int main(void) { return ++(int){1}; } +EOF +try_compile_error << EOF +int main(void) { return ++(const int){1}; } +EOF +try_ 7 << EOF +struct point { int x; }; +int main(void) { + struct point point = {0}; + struct point *pointer = &point; + return (*pointer).x = 7; +} +EOF +try_ 7 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { + struct flags flags = {1}; + struct flags *pointer = &flags; + return (*pointer).value += 6; +} +EOF + +# ++ and -- apply to any modifiable lvalue, not only to an identifier: a +# dereference, a subscript of one, or a member reached through either. Each +# steps by the object's own type, so a pointer advances by its element size, a +# long long carries into its upper half and a _Bool stays 0 or 1. +try_ 0 << EOF +struct rec { int x; char c; long long w; int *p; _Bool b; int m; }; +struct holder { struct rec *slots[2]; }; +int main(void) { + int x = 5, arr[4] = {10, 20, 30, 40}; + int *p = &x, *ap = arr, **pp = ≈ + char ch = 'a', *cp = &ch; + long long wide = 0xFFFFFFFFLL, *lp = &wide; + _Bool flag = 1, *bp = &flag; + struct rec r = {1, 'b', 0x1FFFFFFFFLL, arr, 0, 7}; + struct rec *rp = &r, *slots[2] = {&r, &r}, **rpp = slots; + struct holder h; + int v; + + ++*p; + --*p; + (*p)++; + (*p)--; + if (x != 5) return 1; + if (++*p != 6 || (*p)++ != 6 || x != 7 || --*p != 6 || (*p)-- != 6 || + x != 5) + return 2; + ++*cp; + if (ch != 'b' || (*cp)++ != 'b' || ch != 'c') return 3; + ++*lp; + if (wide != 0x100000000LL || (*lp)-- != 0x100000000LL || + wide != 0xFFFFFFFFLL || --*lp != 0xFFFFFFFELL) + return 4; + ++*pp; + (*pp)++; + if (*ap != 30 || ap != arr + 2) return 5; + --*pp; + (*pp)[1]++; + if (arr[2] != 31) return 6; + ap = arr; + v = ++*ap++; + if (v != 11 || arr[0] != 11 || ap != arr + 1) return 7; + ++*bp; + if (flag != 1) return 8; + (*bp)--; + if (flag != 0) return 9; + --*bp; + if (flag != 1) return 10; + ++rp->x; + ++(*rp).m; + (*rp).w++; + (*rp).p++; + if (r.x != 2 || r.m != 8 || r.w != 0x200000000LL || *r.p != 20) return 11; + ++(*rp).b; + (*rp).b++; + if (r.b != 1) return 12; + (*rpp)->x++; + ++(*rpp)->c; + if (r.x != 3 || r.c != 'c') return 13; + h.slots[1] = &r; + (h.slots[1])->m--; + ++(h.slots[1])->m; + (h.slots[1])->m++; + return r.m != 9 ? 14 : 0; +} +EOF +try_compile_error << EOF +int main(void) { int x = 1, *p = &x; ++(*p + 1); return x; } +EOF +try_compile_error << EOF +int main(void) { int x = 1, *p = &x; (0, *p)++; return x; } +EOF +try_compile_error << EOF +int main(void) { const int x = 1; const int *p = &x; (*p)++; return x; } +EOF + +# Any postfix expression can follow a parenthesized primary (C99 6.5.2): a +# subscript, a member selection or a call applies to the grouped value, and a +# record loaded through a pointer is a whole object rather than one word of it. +# Reads, stores and compound assignments of every member width through (*q).m +# must agree with q->m, and a record stores into a member or an element whole. +try_ 0 << EOF +struct inner { char c; long long w; short s; }; +struct rec { + int pad; char c; short s; int i; long long m; int *p; + struct inner in; int arr[3]; +}; +struct point { int x; int m; }; +struct holder { struct point *slots[2]; int *ip; }; +struct point *identity(struct point *p) { return p; } +int main(void) { + int x = 7; + struct rec v, rows[2], *q = &v, *rp = rows, **rpp = &rp; + struct inner t; + struct point pts[2], *pp = pts, **ppp = &pp; + struct holder h; + + v.pad = 1; v.c = 2; v.s = 3; v.i = 4; v.m = 0x100000005LL; v.p = &x; + v.in.c = 6; v.in.w = 0x200000007LL; v.in.s = -3; + v.arr[0] = 8; v.arr[1] = 9; v.arr[2] = 10; + rows[1] = v; + if (rows[1].m != 0x100000005LL || rows[1].in.w != 0x200000007LL) return 1; + if ((*q).c != 2 || (*q).s != 3 || (*q).i != 4) return 2; + if ((*q).m != 0x100000005LL || (*q).m != q->m) return 3; + if (*(*q).p != 7 || (*q).in.c != 6 || (*q).in.s != -3) return 4; + if ((*q).in.w != 0x200000007LL || (*q).arr[2] != 10) return 5; + if ((*rpp)[1].m != 0x100000005LL || (rp)[1].in.w != 0x200000007LL) return 6; + if ((*rpp)[1].arr[1] != 9 || (rp + 1)->i != 4) return 7; + + (*q).c = 12; (*q).s = 13; (*q).i = 14; (*q).m = 0x300000015LL; + (*q).in.w = 0x400000017LL; (*q).arr[1] = 19; (*q).in.s = 21; + if (v.c != 12 || v.s != 13 || v.i != 14 || v.m != 0x300000015LL) return 8; + if (v.in.w != 0x400000017LL || v.arr[1] != 19 || v.in.s != 21) return 9; + (*rpp)[1].m = 0x500000001LL; + (*rpp)[1].in.w = 0x600000001LL; + if (rows[1].m != 0x500000001LL || rows[1].in.w != 0x600000001LL) return 10; + + (*q).c += 1; (*q).s -= 1; (*q).i *= 2; (*q).m += 0x100000000LL; + (*q).in.w -= 1; (*q).arr[1] <<= 1; (*q).p += 1; + if (v.c != 13 || v.s != 12 || v.i != 28 || v.m != 0x400000015LL) return 11; + if (v.in.w != 0x400000016LL || v.arr[1] != 38 || v.p != &x + 1) return 12; + (*rpp)[1].m |= 2; + if (rows[1].m != 0x500000003LL) return 13; + + t = (*q).in; + if (t.w != 0x400000016LL) return 14; + t.w = 1; + (*q).in = t; + if (v.in.w != 1) return 15; + q->in.w = 2; + t = q->in; + rows[0].in = t; + *rp = v; + if (t.w != 2 || rows[0].in.w != 2 || rp->m != v.m) return 16; + + pts[1].x = 5; + pts[1].m = 6; + h.slots[1] = &pts[1]; + h.ip = &x; + if ((h.slots[1])->x != 5 || (pts[1]).m != 6) return 17; + if ((identity(&pts[1]))->x != 5 || (pp + 1)->x != 5) return 18; + if ((*ppp)->x != pts[0].x || (h.ip)[0] != 7 || (v.arr)[2] != 10) return 19; + (h.slots[1])->x = 15; + (pp + 1)->m += 4; + (*ppp)[1].x++; + return pts[1].x != 16 || pts[1].m != 10 ? 20 : 0; +} +EOF + +# A record is neither arithmetic nor scalar: it cannot be the operand of an +# arithmetic, bitwise, relational, logical or unary operator, a cast, a +# controlling expression or a scalar assignment. Each is a diagnostic rather +# than a record reaching integer lowering, which aborted on 32-bit targets. +for expr in "x = 4 & s" "x = s + 1" "x = s == s" "x = s < x" "x = -s" "x = ~s" \ + "x = !s" "x = +s" "x = s && x" "x = x || s" "x = s ? 1 : 0" "x += s" \ + "x = (int) s" "x = s" "s = x" "p->a = s" "*p = x" "s++" "--s" "++*p" \ + "if (s) x = 1" "while (s) x = 1" "for (; s;) x = 1" "do x = 1; while (s)" \ + "x = *p + 1" "x -= *p" "x = s << 1"; do + try_compile_error << EOF +struct S { int a; int b; int c; char d; }; +int main(void) { + struct S s = {1, 2, 3, 4}, *p = &s; + int x = 4; + $expr; + return x; +} +EOF +done +try_ 7 << EOF +struct S { int a; int b; int c; char d; }; +int main(void) { + struct S s = {1, 2, 3, 4}, t, *p = &s; + int x = 1; + (void) s; + t = x ? s : *p; + x = (t = *p).b; + return x + (x ? s.a : 0) + t.d; +} +EOF + +# C99 permits long to use int's representation where both meet the required +# minimum range. Exercise spelling, typedefs, pointer scaling, and ABI slots. +try_ 13 << EOF +typedef long count_t; +typedef long signed signed_count_t; +long add(long left, long int right, signed long extra) { + return left + right + extra; +} +int main(void) { + count_t values[3] = {3, 4, 5}; + signed_count_t extra = (long signed)values[2]; + return add(values[0], values[1], extra) + + (sizeof(long const) == sizeof(int)); +} +EOF + +# Compound literal support - C90/C99 compliant implementation Basic struct +# compound literals (verified working) +try_compile_error << EOF +int main(void) { return (int){1, 2}; } +EOF + +try_compile_error << EOF +int main(void) { + int value = 1; + return (int *){&value, &value} != 0; +} +EOF + +try_ 1 << EOF +int main(void) { return (int){1,}; } +EOF + +try_ 42 << EOF +typedef struct { int x; int y; } point_t; +int main() { + point_t p = {42, 100}; + return p.x; +} +EOF + +# Typedef record declarations use the shared aggregate initializer path for both +# their first and continuation declarators. +try_ 6 << EOF +typedef struct { int x; int y; int z; } point_t; +int main() { + point_t first = {1}, second = {2, 3}; + return first.x + first.y + first.z + second.x + second.y + second.z; +} +EOF + +# A union initializer selects exactly one member, including through a typedef. +try_compile_error << EOF +typedef union { int a; char b; } value_t; +int main() { + value_t value = {1, 2}; + return value.a; +} +EOF + +try_ 42 << EOF +typedef struct { short x; short y; } point_t; +int main() { + point_t p = {42, 100}; + return p.x; +} +EOF + +try_ 100 << EOF +typedef struct { int x; int y; } point_t; +int main() { + point_t p = {42, 100}; + return p.y; +} +EOF + +try_ 5 << EOF +typedef struct { int x; } s_t; +int main() { + s_t s = {5}; + return s.x; +} +EOF + +# Multi-field struct compound literals +try_ 30 << EOF +struct point { int x; int y; }; +int main(void) { + struct point p = (struct point){10, 20}; + return p.x + p.y; +} +EOF + +# C99 aggregate initialization zero-fills all omitted record members. +try_ 0 << EOF +struct point { int x; int y; int z; }; +int main(void) { + struct point p = (struct point){7}; + return p.y != 0 || p.z != 0; +} +EOF + +# Explicitly bounded array compound literals support index designators and +# zero-fill the slots those designators skip. +try_ 7 << EOF +int main(void) { + int *values = (int[4]){[3] = 5, [1] = 2}; + return values[0] + values[1] + values[2] + values[3]; +} +EOF + +# A negative designator is outside every array, including one whose bound the +# compound literal leaves to be inferred. +try_compile_error_message "Array designator index is out of bounds" << EOF +int main(void) { + int *values = (int[]){[-1] = 1, 2}; + return values[0]; +} +EOF + +# Array compound literals use the same aggregate-element path as ordinary array +# initializers, including nested braces, omitted members, and element +# designators. +try_ 8 << EOF +struct pair { int first; int second; }; +int main(void) { + struct pair *values = (struct pair[]){ {1, 2}, {3, 4} }; + return values[0].first + values[1].second + values[1].first; +} +EOF + +try_ 10 << EOF +struct pair { int first; int second; }; +int main(void) { + struct pair *values = (struct pair[3]){ + [2] = {.second = 9}, [0] = {.first = 1} + }; + return values[0].first + values[0].second + values[1].first + + values[1].second + values[2].first + values[2].second; +} +EOF + +# An omitted bound is inferred through the largest designator. Reordered +# designators must preserve earlier stores while all untouched records remain +# zero-initialized. +try_ 10 << EOF +struct pair { int first; int second; }; +int main(void) { + struct pair *values = (struct pair[]){ + [3] = {.second = 3}, [1] = {.first = 1}, [4] = {.second = 6} + }; + return values[0].first + values[0].second + values[1].first + + values[2].second + values[3].second + values[4].second; +} +EOF + +# Nested record braces initialize the nested object before continuing at the +# following outer member. +try_ 12 << EOF +struct pair { int x; int y; }; +struct outer { struct pair pair; int tail; }; +int main(void) { + struct outer value = {{2, 3}, 7}; + return value.pair.x + value.pair.y + value.tail; +} +EOF + +# Nested array braces initialize the member array before the next outer field. +try_ 12 << EOF +struct outer { int values[2]; int tail; }; +int main(void) { + struct outer value = {{2, 3}, 7}; + return value.values[0] + value.values[1] + value.tail; +} +EOF + +try_ 9 << EOF +struct outer { int values[2]; int tail; }; +int main(void) { + struct outer value = {{2}, 7}; + return value.values[0] + value.values[1] + value.tail; +} +EOF + +try_ 12 << EOF +struct outer { int values[2]; int tail; }; +int main(void) { + struct outer value = (struct outer){{2, 3}, 7}; + return value.values[0] + value.values[1] + value.tail; +} +EOF + +try_ 12 << EOF +struct outer { int values[2]; int tail; }; +struct outer value = {{2, 3}, 7}; +int main(void) { + return value.values[0] + value.values[1] + value.tail; +} +EOF + +try_ 15 << EOF +struct pair { int x; int y; }; +struct outer { struct pair values[2]; int tail; }; +int main(void) { + struct outer value = {{{1, 2}, {3, 4}}, 5}; + return value.values[0].x + value.values[0].y + value.values[1].x + + value.values[1].y + value.tail; +} +EOF + +# Two-dimensional member arrays preserve row braces and then continue with the +# next outer member. +try_ 15 << EOF +struct outer { int values[2][2]; int tail; }; +int main(void) { + struct outer value = {{{1, 2}, {3, 4}}, 5}; + return value.values[0][0] + value.values[0][1] + value.values[1][0] + + value.values[1][1] + value.tail; +} +EOF + +try_ 15 << EOF +struct outer { int values[2][2]; int tail; }; +struct outer value = {{{1, 2}, {3, 4}}, 5}; +int main(void) { + return value.values[0][0] + value.values[0][1] + value.values[1][0] + + value.values[1][1] + value.tail; +} +EOF + +try_ 15 << EOF +struct outer { int values[2][2]; int tail; }; +int main(void) { + struct outer value = (struct outer){{{1, 2}, {3, 4}}, 5}; + return value.values[0][0] + value.values[0][1] + value.values[1][0] + + value.values[1][1] + value.tail; +} +EOF + +try_ 12 << EOF +struct pair { int x; int y; }; +struct outer { struct pair pair; int tail; }; +int main(void) { + struct outer value = (struct outer){{2, 3}, 7}; + return value.pair.x + value.pair.y + value.tail; +} +EOF + +try_ 12 << EOF +struct pair { int x; int y; }; +struct outer { struct pair pair; int tail; }; +struct outer value = {{2, 3}, 7}; +int main(void) { + return value.pair.x + value.pair.y + value.tail; +} +EOF + +# C99 member designators may reorder fields and leave other members zeroed. +try_ 7 << EOF +struct values { int first; int second; int third; }; +int main(void) { + struct values value = {.third = 5, .second = 2}; + return value.first + value.second + value.third; +} +EOF + +try_ 7 << EOF +struct values { int first; int second; int third; }; +int main(void) { + struct values value = (struct values){.third = 5, .second = 2}; + return value.first + value.second + value.third; +} +EOF + +# Compound literals use the record initializer path for unions as well. +try_ 42 << EOF +union number { int integer; char character; }; +int main(void) { + union number value = (union number){42}; + return value.integer; +} +EOF + +# Record assignment copies every byte, not just the scalar slot used by the +# register allocator. Five ints exercise a copy larger than one pointer. +try_ 150 << EOF +struct values { int a; int b; int c; int d; int e; }; +int main(void) { + struct values first = {10, 20, 30, 40, 50}; + struct values second; + second = first; + return second.a + second.b + second.c + second.d + second.e; +} +EOF + +# A non-word-sized record exercises the byte tail of aggregate copying. +try_ 66 << EOF +struct mixed { int value; char tag; }; +int main(void) { + struct mixed first = {65, 1}; + struct mixed second; + second = first; + return second.value + second.tag; +} +EOF + +# A record reached through a pointer, a subscript or a member is a whole object +# too, as a source and as a destination: every one of these used to move one +# register's worth of it, and a 32-bit backend could not encode the load of a +# record of odd size at all. +try_ 11 << EOF +typedef struct { _Bool a, b, c, d, e, f; } flags_t; +int count(const flags_t *spec) { + flags_t copy; + copy = *spec; + return copy.a + copy.b * 2 + copy.f * 4; +} +int main(void) { + flags_t flags = {1, 0, 0, 0, 0, 1}; + flags_t again = flags; + return count(&flags) + count(&again) + (sizeof(flags_t) == 6); +} +EOF + +try_ 44 << EOF +struct odd { char a; short b; char c; int d; char e; }; +struct wrap { char tag; struct odd in; }; +struct odd global; +struct odd id(struct odd value) { return value; } +struct odd pick(struct wrap *w, int i) { return i ? w->in : w[0].in; } +int main(void) { + struct wrap w = {1, {2, 3, 4, 5, 6}}; + struct wrap *pw = &w; + struct odd arr[2]; + struct odd *p = arr; + arr[0] = pw->in; + p[1] = id(*p); + global = p[1]; + global.e = 9; + w.in = global; + struct odd t = 1 ? pw->in : *p; + static struct odd s; + s = pick(pw, 1); + struct odd u = pick(&w, 0); + return global.a + global.b + global.c + global.d + w.in.e + arr[1].d + + s.e + t.b + u.c; +} +EOF + +try_ 22 << EOF +struct values { int a; int b; int c; int d; int e; }; +struct values source = {1, 2, 3, 4, 5}; +struct values read_through(const struct values *p) { return *p; } +int main(void) { + struct values arr[2] = {{0}}; + struct values *p = arr; + struct values q = {6, 7, 8, 9, 10}; + struct values r; + int first; + *p = source; + *(p + 1) = q; + r = q = *p; + first = (*p).e + p[1].a; + r = read_through(&arr[1]); + return first + r.e - q.a - p->c + source.e; +} +EOF + +# Selecting a member of, or taking the address within, a record reached through +# a pointer reads the object in place. Copying the whole record out first cost +# instructions in proportion to its size, seconds for these 64 KiB records. +try_ 44 << EOF +struct big { char a[65536]; int m; struct { int x; int y; } in; }; +struct big store; +int get_m(struct big *q) { return (*q).m; } +int get_a(struct big *q) { return (*q).a[3]; } +int *addr_m(struct big *q) { return &(*q).m; } +int main(void) { + struct big *q = &store; + store.a[3] = 5; + store.m = 10; + (*q).m++; + ++(*q).in.y; + (*q).in.x = 2; + *addr_m(q) += 1; + (void) *q; + *q; + return get_m(q) + get_a(q) + (*q).in.x + (*q).in.y + ((*q).in).x * 10 + + (*(q + 0)).a[3] - 1; +} +EOF + +# A record read through a pointer and then used whole is copied from the object +# it designates, once, into whatever it initializes or is assigned to. +try_output 0 "1 2 3 4 5 6 9 8 7" << EOF +struct in { int x; int y; }; +struct rec { char tag; struct in in; int m; }; +struct in make_in(int x) { struct in v = {x, x + 1}; return v; } +int sum(struct in v) { return v.x + v.y; } +int main(void) { + struct rec a = {0, {1, 2}, 3}, b = {0, {7, 8}, 9}; + struct rec *p = &a, *q = &b; + struct rec pair[2] = {*p, *q}; + struct in in = (*p).in; + struct in picked = 0 ? (*p).in : q->in; + struct rec c; + c = 1 ? *p : *q; + printf("%d %d %d ", pair[0].in.x, in.y, c.m); + *p = *q; + q->m = 4; + *q = (*q, *q); + printf("%d %d %d ", q->m, sum(p->in) - 10, (make_in(5), make_in(6)).x); + printf("%d %d %d", (*p).m, picked.y, (1 ? *p : *q).in.x); + return 0; +} +EOF + +# A dereferenced call result, array name or double pointer is a whole record +# too. `*get(1)` applied the star to the function name, and `*table` on an array +# of records had no pointer to read through, so neither could be copied. +try_ 1 << EOF +struct S { int a; char b; long long c; }; +struct S table[2] = { { 1, 2, 3 }, { 4, 5, 6 } }; +struct S *get(int i) { return &table[i]; } +struct S *ptab = table; +struct S **pp = &ptab; +int take(struct S s) { return s.a * 100 + s.b * 10 + (int) s.c; } +struct S ret1(void) { return *get(1); } +struct S ret2(void) { return *table; } +struct S ret3(void) { return **pp; } +int main(void) { + struct S s; + int r = 0; + s = *get(1); + r += (s.a == 4 && s.b == 5 && s.c == 6); + struct S t = *table; + r += (t.a == 1 && t.c == 3) * 2; + struct S s6 = **pp; + r += (s6.a == 1 && s6.b == 2) * 4; + r += (take(*get(0)) == 123) * 8; + r += (take(*table) == 123 && take(**pp) == 123) * 16; + r += (ret1().a == 4 && ret2().b == 2 && ret3().c == 3) * 32; + s = *table; + r += (s.a == 1) * 64; + s = **pp; + r += (s.b == 2) * 128; + struct S u = *get(1); + r += (u.c == 6) * 256; + t = *(table + 1); + r += (t.a == 4) * 512; + return r == 1023; +} +EOF + +# A unary star on an array name reads or stores its first element, whatever the +# element: read_lvalue() took `*a = v` for an assignment to the array itself, +# `**ptrs` read an int-sized word of the first pointer, and `**rows` loaded the +# first row as if it were a pointer. +try_output 0 "9 9 1 x 12 6 6 8 7 3 7" << EOF +typedef int *ip; +struct S { int a; char b; long long c; }; +int ga[3]; +void fill(int a[4], int n) { *a = n; *a += 1; } +void fill2(int a[][2]) { **a = 3; } +int main(void) { + int a[3] = { 1, 2, 3 }; + long long la[2]; + char buf[4]; + int rows[2][2]; + int x = 0, y = 5; + ip tps[2]; + int *ptrs[2]; + int loc[4]; + int m[2][2]; + struct S t[2], s = { 1, 2, 3 }; + *a += 2; + (*a) *= 2; + *(a) -= 1; + x = *a = 9; + *la = 1LL << 40; + *buf = 'x'; + **rows = 4; + **rows *= 3; + *tps = &y; + *ptrs = &y; + **ptrs += 1; + *ga = 8; + fill(loc, 6); + fill2(m); + *t = s; + (*t).a = 5; + printf("%d %d %d %c %d ", *a, x, (int) (*la >> 40), *buf, **rows); + printf("%d %d %d %d %d %d", **tps, **ptrs, *ga, *loc, **m, (*t).a + t->b); + return 0; +} +EOF +try_compile_error_message "assignment to expression with array type" << EOF +int main(void) { int rows[2][2], r[2]; *rows = r; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int main(void) { const int a[2] = {1, 2}; *a = 3; return 0; } +EOF + +# Record arguments are passed by value: the callee receives every byte, but a +# write to its parameter cannot modify the caller's object. +try_ 250 << EOF +struct values { int a; int b; int c; int d; int e; }; +int consume(struct values value) { + value.a = 100; + return value.a + value.b + value.c + value.d + value.e; +} +int main(void) { + struct values source = {10, 20, 30, 40, 50}; + return consume(source) + source.a; +} +EOF + +# The aggregate ABI slot must work after all register argument slots are full. +try_ 25 << EOF +struct pair { int first; int second; }; +int consume(int a, int b, int c, int d, int e, int f, struct pair value) { + return a + b + c + d + e + f + value.first + value.second; +} +int main(void) { + struct pair value = {7, 8}; + return consume(1, 2, 3, 4, 0, 0, value); +} +EOF + +try_compile_error << EOF +struct point { int x; int y; }; +int main(void) { + struct point p = (struct point){1, 2, 3}; + return p.x; +} +EOF + +try_compile_error << EOF +union number { int integer; char character; }; +int main(void) { + union number value = {1, 2}; + return value.integer; +} +EOF + +try_ 5 << EOF +union number { int integer; char character; }; +int main(void) { + union number value = {.character = 5}; + return value.character; +} +EOF + +try_compile_error << EOF +union number { int integer; char character; }; +int main(void) { + union number first = {1}, second = {2, 3}; + return first.integer + second.integer; +} +EOF + +try_ 30 << EOF +typedef struct { int a; int b; int c; } data_t; +int main() { + data_t d = {10, 20, 30}; + return d.c; +} +EOF + +# Array initialization +try_ 20 << EOF +int main() { + int arr[3] = {10, 20, 30}; + return arr[1]; +} +EOF + +# A declared-bound compound literal is an array object that decays to its first +# element when assigned to a pointer. +try_ 12 << EOF +int main(void) { + int *values = (int[3]){3, 4, 5}; + return values[0] + values[1] + values[2]; +} +EOF + +# A declared bound remains part of the type: omitted members are zero-filled. +try_ 0 << EOF +int main(void) { + int *values = (int[4]){3, 4}; + return values[2] != 0 || values[3] != 0; +} +EOF +try_ 0 << EOF +int a = 2, b = 3, c = 5, d = 7; +int *(*p)[2] = (int *[2][2]){{&a, &b}, {&c, &d}}; +int main(void) { + return *p[1][0] != 5 || *p[0][1] != 3; +} +EOF +try_ 0 << EOF +int values[2][3] = {{2, 3, 5}, {7, 11, 13}}; +int *(*rows)[2] = (int *[1][2]){{&values[0][1], &values[1][2]}}; +int main(void) { + int first = *rows[0][0]; + int second = *rows[0][1]; + return first != 3 || second != 13; +} +EOF +try_ 0 << EOF +int values[2][3] = {{2, 3, 5}, {7, 11, 13}}; +int *pointers[1] = {&values[1][1]}; +int main(void) { return *pointers[0] != 11; } +EOF +try_ 0 << EOF +int values[2][3] = {{2, 3, 5}, {7, 11, 13}}; +int (*next_row)[3] = &values[0] + 1; +int main(void) { return next_row[0][2] != 13; } +EOF +try_ 0 << EOF +int values[2][3] = {{2, 3, 5}, {7, 11, 13}}; +struct holder { int (*row)[3]; }; +struct holder value = {&values[0] + 1}; +int main(void) { return value.row[0][1] != 11; } +EOF +try_ 0 << EOF +int main(void) { + int a = 2, b = 3, c = 5, d = 7; + int *data[2][2] = {{&a, &b}, {&c, &d}}; + int *(*p)[2] = data; + return *p[1][0] != 5 || *p[0][1] != 3; +} +EOF + +# Parentheses around a pointer declarator only group it, in objects, members, +# parameters and typedefs; `*p` on a pointer to a row of pointers is that row. +try_ 78 << EOF +struct M { int v; }; +struct M m0 = {7}; +struct M *mp = &m0; +struct M *(*gmpp) = ∓ +int gx = 5; +int (*gp) = &gx, (**gppp) = &gp; +int *const (*gcp) = &gp; +typedef int (*int_ptr), *(**int_ptr_ptr); +struct holder { int (*m); int (**mm); struct M *(*self); }; +int sum(int (*q), int (**qq), int *(*rows)[2]) { return *q + **qq + *(*rows)[1]; } +int main(void) { + int x = 3, y = 4; + int (*p) = &x, (*q) = &y; + int (**pp) = &p; + int (*arr[2]) = {&x, &y}; + int *rows[2] = {&y, &x}; + int *(*rp)[2] = &rows; + int_ptr ip = &y; + int_ptr_ptr ipp = &pp; + struct M *(*mpp) = ∓ + struct holder h; + h.m = &x; + h.mm = &p; + h.self = ∓ + return *p + *q + **pp + *arr[1] + *(*rp)[0] + *(*rp)[1] + (*mpp)->v + + (*gmpp)->v + *gp + **gppp + **gcp + *h.m + **h.mm + + (*h.self)->v + sum(ip, pp, rp) + ***ipp + + (sizeof(arr) == 2 * sizeof(int *)) + (sizeof(*rp) == sizeof(rows)); +} +EOF + +# A parameter declared as a function is adjusted to a pointer to it (C99 +# 6.7.5.3p8), and in a parameter a typedef name or void in parentheses is an +# abstract function declarator (6.7.5.3p11). +try_ 0 << EOF +typedef int T; +int twice(int x) { return 2 * x; } +int seven(void) { return 7; } +int x9(T v) { return v + 9; } +char *name(void) { return "ab"; } +int apply(int fn(int), int v) { return fn(v); } +int apply2(int (fn)(int), int (v)) { return fn(v); } +int call0(int thunk(void)) { return thunk(); } +int k(int (T)); +int k(int f(T)) { return f(1); } +int k2(int (void), int *(int)); +int k2(int f(void), int *g(int)) { return f(); } +int strcall(char *s(void)) { return s()[1]; } +int main(void) +{ + return apply(twice, 3) + apply2(twice, 4) + call0(seven) + k(x9) + + k2(seven, 0) + strcall(name) != 6 + 8 + 7 + 10 + 7 + 'b'; +} +EOF +try_compile_error << EOF +typedef int T; +int k(int (T)); +int k(int f(void)) { return f(); } +int main(void) { return 0; } +EOF +# A parenthesized name without a star declares the name itself (C99 6.7.5p6). +try_ 0 << EOF +typedef int T; +int (g) = 3; +int (ga)[2] = {1, 2}; +int *(gp) = &g; +int (a1), (b1) = 2; +struct S { int (m); char (name)[4]; }; +int (f)(void) { return 1; } +int h(int (*), int (y)); +int h(int *p, int y) { return *p + y; } +int main(void) { + static int (s2) = 4; + int (s) = 5; + int (a)[3] = {1, 2, 3}; + T (t) = 6; + struct S r; + int sum = 0; + r.m = 5; + for (int (i) = 0; i < 3; i++) + sum += a[i]; + return g + ga[1] + *gp + b1 + r.m + f() + h(&g, 1) + s2 + s + sum + t + + sizeof(r.name) != 45; +} +EOF +try_compile_error_message "assignment of read-only variable" << EOF +int main(void) { int x = 3; int (*const q) = &x; q = 0; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int main(void) { int x = 3, *p = &x; int *const (*cp) = &p; *cp = 0; return 0; } +EOF +try_ 1 << EOF +int main(void) { + int (**rows)[2] = (int (*[])[2]){0, 0}; + return rows[0] == 0 && rows[1] == 0; +} +EOF +try_ 13 << EOF +int main(void) { + int first[2] = {3, 4}; + int second[2] = {9, 10}; + int (**rows)[2] = (int (*[])[2]){&first, &second}; + return rows[0][0][1] + rows[1][0][0]; +} +EOF +try_ 7 << EOF +int main(void) { + int values[2] = {3, 7}; + int *items[1] = {values}; + int **p = items; + return p[0][1]; +} +EOF +try_compile_error << EOF +int main(void) { + int *values = (int[2]){3, 4, 5}; + return values[0]; +} +EOF + +try_compile_error << EOF +int change(signed const int value) { + value = 2; + return value; +} +int main(void) { return change(1); } +EOF + +try_compile_error << EOF +typedef const int const_int; +int change(const_int value) { + value = 2; + return value; +} +int main(void) { return change(1); } +EOF + +# Extended compound literal tests (C99-style brace initialization) + +# Additional struct compound literals with different field counts +try_ 12 << EOF +typedef struct { int a; int b; int c; int d; } quad_t; +int main() { + quad_t q = {3, 4, 5, 0}; + return q.a + q.b + q.c; /* 3 + 4 + 5 = 12 */ +} +EOF + +# Array of int initialization +try_ 35 << EOF +int main() { + int values[4] = {5, 10, 15, 5}; + return values[0] + values[1] + values[2] + values[3]; /* 5 + 10 + 15 + 5 = 35 */ +} +EOF + +# Array initialization with struct compound literals - Advanced C99 features +# NOTE: These tests document the current implementation status + +# Test: Single element array of struct +try_ 10 << EOF +struct point { int x; int y; }; +int main() { + /* Single element struct arrays now work correctly */ + struct point pts[1] = { {10, 20} }; + return pts[0].x; /* Returns 10 correctly */ +} +EOF + +# Multi-element record arrays must retain every element and member; this is a +# full aggregate-initialization check rather than an observation of element 0. +try_ 10 << EOF +struct point { int x; int y; }; +int main() { + struct point pts[2] = { {1, 2}, {3, 4} }; + return pts[0].x + pts[0].y + pts[1].x + pts[1].y; +} +EOF + +try_ 7 << EOF +struct point { int x; int y; }; +int main(void) { + struct point pts[2] = { {1, 2}, {3, 4} }; + return pts[1].x + pts[1].y; +} +EOF + +# Test: Mixed array and struct compound literals +try_ 40 << EOF +struct point { int x; int y; }; +int main() { + /* Verify that regular int arrays still work correctly */ + int arr[3] = {10, 15, 10}; + + /* Verify that individual struct initialization still works */ + struct point p = {5, 0}; + + return arr[0] + arr[1] + arr[2] + p.x; /* 10 + 15 + 10 + 5 = 40 */ +} +EOF + +# Global arrays of structs with compound literals +try_ 9 << EOF +struct global_compound_pair { int first; int second; }; +struct global_compound_pair global_compound_value = + (struct global_compound_pair){.second = 6, .first = 3}; +int main(void) { + return global_compound_value.first + global_compound_value.second; +} +EOF + +try_compile_error << EOF +struct global_compound_left { int value; }; +struct global_compound_right { int value; }; +struct global_compound_left global_compound_mismatch = + (struct global_compound_right){1}; +int main(void) { return 0; } +EOF + +try_ 12 << EOF +int global_compound_scalar = (int){12}; +int main(void) { return global_compound_scalar; } +EOF + +try_compile_error << EOF +char global_compound_wrong_type = (int){1}; +int main(void) { return 0; } +EOF + +try_ 9 << EOF +int *global_compound_array = (int[]){2, 3, 4}; +int main(void) { + return global_compound_array[0] + global_compound_array[1] + + global_compound_array[2]; +} +EOF + +try_ 5 << EOF +int *global_compound_bounded = (int[4]){5}; +int main(void) { + return global_compound_bounded[0] + global_compound_bounded[1] + + global_compound_bounded[2] + global_compound_bounded[3]; +} +EOF + +# The element type of a file-scope array compound literal is a full type name: +# qualifiers, several keywords, enum tags and qualified typedefs. +try_flags 231 --std=c99 << EOF +enum E { A = 1, B = 2 }; +typedef unsigned char byte; +struct S { int x; }; +const int *global_const_compound = (const int[]){1, 2}; +unsigned char *global_uchar_compound = (unsigned char[]){1, 200}; +long long *global_llong_compound = (long long[2]){1, 0x100000000LL}; +volatile short *global_short_compound = (volatile short[3]){1, 2, 3}; +const long *global_long_compound = (long const[]){7, 8}; +enum E *global_enum_compound = (enum E[]){A, B}; +const byte *global_byte_compound = (const byte[]){9, 10}; +const struct S *global_record_compound = (const struct S[]){{4}, {5}}; +int main(void) { + return global_const_compound[1] + global_uchar_compound[1] + + (int) (global_llong_compound[1] >> 32) + global_short_compound[2] + + global_long_compound[1] + global_enum_compound[1] + + global_byte_compound[1] + global_record_compound[1].x; +} +EOF +try_compile_error << EOF +long long *global_llong_mismatch = (int[2]){1, 2}; +int main(void) { return 0; } +EOF +try_compile_error << EOF +unsigned char *global_sign_mismatch = (signed char[]){1, 2}; +int main(void) { return 0; } +EOF +try_ 5 << EOF +int global_pointer_array_rows[2][2] = {{1, 2}, {3, 4}}; +int (*global_pointer_array)[2] = global_pointer_array_rows; +int main(void) { + return global_pointer_array[1][0] + global_pointer_array[0][1]; +} +EOF +try_ 13 << EOF +int global_pointer_array_cubes[2][2][2] = + {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; +int (*global_pointer_cube)[2][2] = global_pointer_array_cubes; +int main(void) { + return global_pointer_cube[1][0][0] + global_pointer_cube[0][1][1] * 2; +} +EOF +try_ 6 << EOF +int global_pointer_array_rectangles[2][2][3] = + {{{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}}}; +int (*global_pointer_rectangle)[2][3] = global_pointer_array_rectangles; +int main(void) { + return global_pointer_rectangle[0][1][2]; +} +EOF +try_ 120 << EOF +typedef int global_typedef_row_t[2]; +typedef global_typedef_row_t global_typedef_plane_t[3]; +typedef global_typedef_plane_t global_typedef_cube_t[2]; +typedef global_typedef_cube_t global_typedef_hyper_t[2]; +typedef global_typedef_hyper_t global_typedef_hyper_alias_t; +int main(void) { + global_typedef_hyper_alias_t values = {0}; + values[1][1][2][1] = 24; + return sizeof(global_typedef_hyper_alias_t) + values[1][1][2][1]; +} +EOF +try_compile_error << EOF +typedef int global_typedef_four_t[1][1][1][1]; +typedef global_typedef_four_t global_typedef_five_t[1]; +int main(void) { return 0; } +EOF +try_ 1 << EOF +int **global_compound_pointer_array = (int *[]){0, 0}; +int main(void) { + return global_compound_pointer_array[0] == 0 && + global_compound_pointer_array[1] == 0; +} +EOF +try_ 1 << EOF +int **global_compound_bounded_pointer_array = (int *[2]){0, 0}; +int main(void) { + return global_compound_bounded_pointer_array[0] == 0 && + global_compound_bounded_pointer_array[1] == 0; +} +EOF +try_ 0 << EOF +typedef int global_compound_row[2]; +global_compound_row *global_compound_typedef_rows = + (global_compound_row[]){ {3, 8}, {4, 9} }; +int main(void) { + return global_compound_typedef_rows[1][0] != 4 || + global_compound_typedef_rows[0][1] != 8; +} +EOF +try_ 0 << EOF +typedef int global_compound_bounded_row[2]; +global_compound_bounded_row *global_compound_bounded_typedef_rows = + (global_compound_bounded_row[2]){ {5, 1}, {6, 7} }; +int main(void) { + return global_compound_bounded_typedef_rows[1][1] != 7 || + global_compound_bounded_typedef_rows[0][0] != 5; +} +EOF +try_ 1 << EOF +int (**global_compound_pointer_rows)[2] = (int (*[])[2]){0, 0}; +int main(void) { + return global_compound_pointer_rows[0] == 0 && + global_compound_pointer_rows[1] == 0; +} +EOF +try_ 0 << EOF +int global_compound_source_rows[2][2] = {{1, 2}, {3, 4}}; +int (**global_compound_initialized_pointer_rows)[2] = + (int (*[])[2]){global_compound_source_rows, + global_compound_source_rows + 1, + global_compound_source_rows - 0}; +int main(void) { + return global_compound_initialized_pointer_rows[1][0][1] != 4 || + global_compound_initialized_pointer_rows[0][0][0] != 1 || + global_compound_initialized_pointer_rows[2][1][0] != 3; +} +EOF +try_ 0 << EOF +int global_pointer_slot_rows[2][2] = {{1, 2}, {3, 4}}; +int (*global_pointer_slots[3])[2] = { + global_pointer_slot_rows, global_pointer_slot_rows + 1, + global_pointer_slot_rows +}; +int (**global_pointer_slot_rows_view)[2] = global_pointer_slots; +int main(void) { + return global_pointer_slot_rows_view[1][0][1] != 4 || + global_pointer_slot_rows_view[2][1][0] != 3; +} +EOF +try_ 0 << EOF +int global_compound_bounded_source_rows[2][2] = {{5, 6}, {7, 8}}; +int (**global_compound_bounded_initialized_pointer_rows)[2] = + (int (*[2])[2]){global_compound_bounded_source_rows + 1, + global_compound_bounded_source_rows}; +int main(void) { + return global_compound_bounded_initialized_pointer_rows[0][0][0] != 7 || + global_compound_bounded_initialized_pointer_rows[1][0][1] != 6; +} +EOF +try_ 0 << EOF +int global_compound_address_value = 12; +int **global_compound_address_values = + (int *[]){&global_compound_address_value}; +int main(void) { return **global_compound_address_values != 12; } +EOF +try_ 0 << EOF +int global_compound_address_elements[2] = {10, 13}; +int **global_compound_address_element_values = + (int *[]){&global_compound_address_elements[1]}; +int main(void) { + int *element = global_compound_address_element_values[0]; + return *element != 13; +} +EOF +try_ 0 << EOF +int global_compound_pointer_decay_values[2] = {3, 5}; +int *global_compound_pointer_decay_rows[1] = + {global_compound_pointer_decay_values}; +int ***global_compound_pointer_decay = + (int **[]){global_compound_pointer_decay_rows}; +int ***global_compound_pointer_address = + (int **[]){&global_compound_pointer_decay_rows[0]}; +int main(void) { + int **row = global_compound_pointer_decay[0]; + int **addressed_row = global_compound_pointer_address[0]; + int *element = row[0]; + int *addressed_element = addressed_row[0]; + return element[1] != 5 || addressed_element[1] != 5; +} +EOF +try_ 0 << EOF +int global_compound_address_matrix[2][3] = {{2, 3, 5}, {7, 11, 13}}; +int (**global_compound_address_matrix_rows)[3] = + (int (*[])[3]){&global_compound_address_matrix[1]}; +int main(void) { + int (*row)[3] = global_compound_address_matrix_rows[0]; + return row != &global_compound_address_matrix[1]; +} +EOF +try_ 0 << EOF +int global_compound_address_offset_values[3] = {17, 19, 23}; +int **global_compound_address_offset_pointers = + (int *[]){&global_compound_address_offset_values[0] + 2, + &global_compound_address_offset_values[2] - 1}; +int main(void) { + int *forward = global_compound_address_offset_pointers[0]; + int *backward = global_compound_address_offset_pointers[1]; + return *forward != 23 || *backward != 19; +} +EOF +try_ 0 << EOF +int global_address_row_matrix[2][3] = {{2, 3, 5}, {7, 11, 13}}; +int (*global_address_row)[3] = &global_address_row_matrix[1]; +int *global_address_matrix_element = &global_address_row_matrix[1][2]; +int (*global_address_decay_row)[3] = global_address_row_matrix + 1; +struct global_address_matrix_holder { int matrix[2][3]; }; +struct global_address_matrix_holder global_address_matrix_object = + {{{29, 31, 37}, {41, 43, 47}}}; +int *global_address_matrix_member = + &global_address_matrix_object.matrix[1][2]; +int main(void) { + return global_address_row != &global_address_row_matrix[1] || + global_address_decay_row != &global_address_row_matrix[1] || + *global_address_matrix_element != 13 || + *global_address_matrix_member != 47; +} +EOF +try_ 0 << EOF +int global_address_cube[2][2][2][2] = { + {{{2, 3}, {5, 7}}, {{11, 13}, {17, 19}}}, + {{{23, 29}, {31, 37}}, {{41, 43}, {47, 53}}} +}; +int *global_address_cube_leaf = &global_address_cube[1][0][1][1]; +int main(void) { return *global_address_cube_leaf != 37; } +EOF +try_ 0 << EOF +int global_address_plane_cube[2][2][2][2] = { + {{{2, 3}, {5, 7}}, {{11, 13}, {17, 19}}}, + {{{23, 29}, {31, 37}}, {{41, 43}, {47, 53}}} +}; +int (*global_address_plane)[2][2] = + &global_address_plane_cube[0][1] + 1; +int main(void) { + return global_address_plane != &global_address_plane_cube[1][0] || + global_address_plane[0][1][1] != 37; +} +EOF +try_ 1 << EOF +int (**global_compound_bounded_pointer_rows)[2] = (int (*[2])[2]){0, 0}; +int main(void) { + return global_compound_bounded_pointer_rows[0] == 0 && + global_compound_bounded_pointer_rows[1] == 0; +} +EOF +try_ 1 << EOF +int (**global_compound_constant_bound_pointer_rows)[2] = + (int (*[1 + 1])[2]){0, 0}; +int main(void) { + return global_compound_constant_bound_pointer_rows[0] == 0 && + global_compound_constant_bound_pointer_rows[1] == 0; +} +EOF +try_ 1 << EOF +int (***global_compound_nested_pointer_rows)[2] = + (int (**[])[2]){0, 0}; +int main(void) { + return global_compound_nested_pointer_rows[0] == 0 && + global_compound_nested_pointer_rows[1] == 0; +} +EOF +try_ 1 << EOF +struct global_compound_row_record { int value; }; +struct global_compound_row_record (**global_compound_record_rows)[2] = + (struct global_compound_row_record (*[])[2]){0, 0}; +int main(void) { + return global_compound_record_rows[0] == 0 && + global_compound_record_rows[1] == 0; +} +EOF + +try_ 10 << EOF +struct global_compound_record { int first; int second; }; +struct global_compound_record *global_compound_records = + (struct global_compound_record[]){ {1, 2}, {3, 4} }; +int main(void) { + return global_compound_records[0].first + + global_compound_records[1].first + + global_compound_records[1].second + + global_compound_records[0].second; +} +EOF + +try_compile_error << EOF +struct global_compound_array_left { int value; }; +struct global_compound_array_right { int value; }; +struct global_compound_array_left *global_compound_array_mismatch = + (struct global_compound_array_right[]){ {1} }; +int main(void) { return 0; } +EOF + +try_ 60 << EOF +struct nested_compound_point { int x; int y; }; +struct nested_compound_value { + struct nested_compound_point point; + int z; +}; +int main(void) { + struct nested_compound_value value = (struct nested_compound_value){ + .point = (struct nested_compound_point){10, 20}, .z = 30 + }; + return value.point.x + value.point.y + value.z; +} +EOF + +try_ 7 << EOF +struct point { int x; int y; }; +struct point gpts1[] = { {3, 4} }; +int main() { + return gpts1[0].x + gpts1[0].y; /* 3 + 4 = 7 */ +} +EOF + +try_ 7 << EOF +struct point { int x; int y; }; +struct point gpts2[2] = { {1, 2}, {3, 4}, }; +int main() { + return gpts2[1].x + gpts2[1].y; /* 3 + 4 = 7 */ +} +EOF + +try_ 9 << EOF +typedef struct { int x; int y; } point_t; +point_t gpts3[] = { {4, 5} }; +int main() { + return gpts3[0].x + gpts3[0].y; /* 4 + 5 = 9 */ +} +EOF + +# Enhanced compound literal tests - C99 features with non-standard extensions +# These tests validate both standard C99 compound literals and the non-standard +# behavior required by the test suite (array compound literals in scalar +# contexts) + +# Test: Array compound literal assigned to scalar int (non-standard) +try_ 100 << EOF +int main() { + /* Non-standard: Assigns first element of array to scalar int */ + int x = (int[]){100, 200, 300}; + return x; +} +EOF + +# Every declarator of a declaration takes that first element, not only the first +# declarator; a later one stored the literal's address. +try_ 105 << EOF +int main() { + int a = 1, b = (int[]){100, 200}, c = (int[]){4}; + return a + b + c; +} +EOF + +# Test: Array compound literal assigned to scalar short (non-standard) +try_ 100 << EOF +int main() { + /* Non-standard: Assigns first element of array to scalar short */ + short x = (short[]){100, 200, 300}; + return x; +} +EOF + +# Test: Array compound literal in arithmetic expression +try_ 150 << EOF +int main() { + int a = 50; + /* Non-standard: Uses first element (100) in addition */ + int b = a + (int[]){100, 200}; + return b; +} +EOF + +# Test: Array compound literal in arithmetic expression +try_ 150 << EOF +int main() { + short a = 50; + /* Non-standard: Uses first element (100) in addition */ + short b = a + (short[]){100, 200}; + return b; +} +EOF + +# Test: Mixed scalar and array compound literals +try_ 35 << EOF +int main() { + /* Scalar compound literals work normally */ + /* Array compound literal contributes its first element (5) */ + return (int){10} + (int){20} + (int[]){5, 15, 25}; +} +EOF + +# Test: Return statement with array compound literal +try_ 42 << EOF +int main() { + /* Non-standard: Returns first element of array */ + return (int[]){42, 84, 126}; +} +EOF + +# Test: Multiple array compound literals in expression +try_ 30 << EOF +int main() { + /* Both arrays contribute their first elements: 10 + 20 = 30 */ + int result = (int[]){10, 30, 50} + (int[]){20, 40, 60}; + return result; +} +EOF + +# Test: Array compound literal with single element +try_ 99 << EOF +int main() { + int val = (int[]){99}; + return val; +} +EOF + +# Test: Array compound literal decay to pointer in initializer +try_ 0 << EOF +int main(void) { + int *arr = (int[]){1, 2, 3, 4, 5}; + return arr[0] != 1 || arr[4] != 5; +} +EOF + +# Test: Passing array compound literal as pointer argument +try_ 0 << EOF +int sum(int *p, int n) { + int s = 0; + for (int i = 0; i < n; i++) + s += p[i]; + return s; +} +int main(void) { + int s = sum((int[]){1, 2, 3, 0, 0}, 3); + return s != 6; +} +EOF + +# Test: Complex expression with compound literals +try_ 77 << EOF +int main() { + int a = 7; + /* (7 * 10) + (100 / 10) - 3 = 70 + 10 - 3 = 77 */ + int b = (a * (int){10}) + ((int[]){100, 200} / 10) - (int[]){3}; + return b; +} +EOF + +# Test: Compound literal in conditional expression +try_ 25 << EOF +int main() { + int flag = 1; + /* Ternary with compound literals */ + int result = flag ? (int[]){25, 50} : (int){15}; + return result; +} +EOF + +# Test: Nested compound literals in function calls +try_ 15 << EOF +int add(int a, int b) { + return a + b; +} + +int main() { + /* Function arguments with compound literals */ + return add((int){5}, (int[]){10, 20, 30}); +} +EOF + +# Test: Array compound literal with variable initialization +try_ 60 << EOF +int main() { + int x = (int[]){10, 20, 30}; /* x = 10 */ + int y = (int[]){20, 40}; /* y = 20 */ + int z = (int[]){30}; /* z = 30 */ + return x + y + z; +} +EOF + +# Test: Compound assignment with array compound literal +try_ 125 << EOF +int main() { + int sum = 25; + sum += (int[]){100, 200}; /* sum += 100 */ + return sum; +} +EOF + +# Test: Array compound literal in loop +try_ 55 << EOF +int main() { + int sum = 0; + for (int i = 0; i < 5; i++) { + /* Each iteration adds 10 (first element) to sum */ + sum += (int[]){10, 20, 30}; + } + return sum + (int[]){5}; /* 50 + 5 = 55 */ +} +EOF + +# Test: Scalar compound literals (standard C99) +try_ 42 << EOF +int main() { + /* Standard scalar compound literals */ + int a = (int){42}; + return a; +} +EOF + +# Test: Char compound literals +try_ 65 << EOF +int main() { + char c = (char){'A'}; /* 'A' = 65 */ + return c; +} +EOF + +# File-scope scalar compound literals have static storage duration. They use the +# target object's initializer directly, but retain the C99 one-element +# constraint (with an optional trailing comma). +try_ 42 << EOF +int value = (int){42}; +int main() { return value; } +EOF +try_ 7 << EOF +int value = (int){7,}; +int main() { return value; } +EOF +try_compile_error << EOF +int invalid = (int){1, 2}; +int main() { return 0; } +EOF + +# A file-scope compound literal has static storage, so its address is an address +# constant for a file-scope pointer (C99 6.5.2.5p6). +try_ 0 << EOF +struct P { int x, y; }; +typedef struct P PT; +int *gp = &(int){8}; +long long *gl = &(long long){0x100000002LL}; +char *gc = &(char){'z'}; +unsigned long *gu = &(unsigned long){5}; +struct P *ps = &(struct P){3, 4}; +PT *pt = &(PT){.y = 6}; +int *(*gpp) = &(int *){0}; +void *gv = &(int){9}; +int main(void) +{ + *gp += 1; + return *gp != 9 || (int) (*gl >> 32) != 1 || (int) *gl != 2 || *gc != 'z' || + *gu != 5 || ps->x != 3 || ps->y != 4 || pt->y != 6 || pt->x != 0 || + *gpp != 0 || *(int *) gv != 9; +} +EOF +try_compile_error << EOF +int *gp = &(char){8}; +int main(void) { return 0; } +EOF + +# Compound literals of long types, spelled with keywords, at both scopes. The +# sole initializer is any assignment expression, not just a single token. +try_ 43 << EOF +long g = (long){7}; +unsigned long h = (unsigned long){8}; +long int i = (long int){9}; +long long ll = (long long){-2}; +int main() +{ + static long s = (long){3}; + long a = (long){7}; + unsigned long b = (unsigned long){7}; + long int c = (long int){-7}; + long long d = (long long){5} + (unsigned long long){1}; + long *p = &(long){4}; + *p += 1; + return g + h + i + ll + s + a + b + c + d + *p; +} +EOF + +# Test: Empty array compound literal (edge case) +try_ 0 << EOF +int main() { + /* Empty compound literal defaults to 0 */ + int x = (int[]){}; + return x; +} +EOF + +# variable with octal literals +items 10 "int var; var = 012; return var;" +items 100 "int var; var = 10 * 012; return var;" +items 32 "int var; var = 0100 / 2; return var;" +items 65 "int var; var = 010 << 3; var += 1; return var;" + +# Category: Conditional Statements +begin_category "Conditional Statements" "Testing if/else control flow" + +# if +items 5 "if (1) return 5; else return 20;" +items 10 "if (0) return 5; else if (0) return 20; else return 10;" +items 10 "int a; a = 0; int b; b = 0; if (a) b = 10; else if (0) return a; else if (a) return b; else return 10;" +items 27 "int a; a = 15; int b; b = 2; if(a - 15) b = 10; else if (b) return a + b + 10; else if (a) return b; else return 10;" +items 4 "int value = 1; if (value++, value == 2) return value + 2; return 0;" +items 2 "if (0 ? 1 : 0) return 1; return 2;" + +items 8 "if (1) return 010; else return 11;" +items 10 "int a; a = 012 - 10; int b; b = 0100 - 64; if (a) b = 10; else if (0) return a; else if (a) return b; else return 10;" + +# The values on both sides of the select, its condition, and unrelated values +# are all used after the join. This keeps the register file full when the +# allocator has to choose the select result's register. +try_ 30 << EOF +int pick(int a, int b, int c, int d, int e, int f, int g) { + int selected; + int hold = g; + if (a) + selected = b; + else + selected = c; + return a + b + c + d + e + f + hold + selected; +} + +int main() { + return pick(1, 2, 3, 4, 5, 6, 7); +} +EOF + +# Category: Compound Statements +begin_category "Compound Statements" "Testing block scoping and compound statements" + +# compound +items 5 "{ return 5; }" +items 10 "{ int a; a = 5; { a = 5 + a; } return a; }" +items 20 "int a; a = 10; if (1) { a = 20; } else { a = 10; } return a;" +items 30 "int a; a = 10; if (a) { if (a - 10) { a = a + 1; } else { a = a + 20; } a = a - 10; } else { a = a + 5; } return a + 10;" + +# Block-scope struct, union and enum objects take the same storage classes and +# qualifiers as scalar ones, and an untagged definition may declare objects. +try_ 12 << EOF +struct S { int a; }; +struct S s = { 5 }; +union U { int a; char c; }; +union U u = { 7 }; +enum E { E1 = 1, E2 }; +enum E ge = E2; +int main(void) +{ + extern struct S s; + register struct S r; + register union U ru = u; + extern union U u, *up; + register enum E re = E1; + extern enum E ge; + r.a = s.a + ru.a - re - ge + 3; + return r.a; +} +union U *up = &u; +EOF +try_ 4 << EOF +struct S { int a; }; +struct S make(int v) { struct S s; s.a = v; return s; } +int main(void) +{ + const struct S cs = { 3 }, *cp = &cs; + static struct S ss = { 4 }, *sp = &ss; + struct S a = make(5), b = a, arr[2] = { { 1 }, { 2 } }; + volatile enum { V1 = 2 } ve = V1, *vp = &ve; + struct { struct S inner; int k; } nest = { { 8 }, 9 }, *np = &nest; + union { int i; char c; } un = { 0 }; + static struct { int z; } st = { 1 }; + sp->a++; + np->k += st.z; + return cp->a + ss.a + a.a + b.a + arr[1].a + *vp + nest.inner.a + nest.k + + un.i - 36 + sizeof(nest) / sizeof(int) - 2; +} +EOF + +# A member may define the record it has, tagged or untagged, at any scope; a +# nested tag belongs to the scope of the outer record. A union may hold a struct +# with a flexible array member. +try_ 80 << EOF +struct outer { + struct inner { int a; char c; } in; + union { short s; struct { char x, y, z; } t; } u; + struct { int x; } pts[3], *first; + int b; +} file_outer; +struct { struct { int a; } in; int b; } untagged_file; +union { struct { int lo, hi; } pair; int raw[2]; } file_union; +typedef struct { struct { int x, y; } p[2]; union { int i; char c[5]; } u; } nested_t; +int main(void) { + struct inner reused; + nested_t t; + struct { struct { int a; } in; int b; } n; + struct block_outer { struct block_inner { char q; } arr[4]; } bo; + struct block_inner bi; + typedef struct { union { int v; } u[2]; } block_t; + block_t bt; + file_outer.in.a = 1; + file_outer.u.t.z = 2; + file_outer.pts[2].x = 3; + file_outer.first = file_outer.pts; + reused.c = 4; + untagged_file.in.a = 5; + file_union.pair.hi = 6; + t.p[1].y = 7; + t.u.c[4] = 8; + n.in.a = 9; + bo.arr[3].q = 10; + bi.q = 11; + bt.u[1].v = 12; + return file_outer.in.a + file_outer.u.t.z + file_outer.first[2].x + + reused.c + untagged_file.in.a + file_union.raw[1] + t.p[1].y + + t.u.c[4] + n.in.a + bo.arr[3].q + bi.q + bt.u[1].v + + (sizeof(nested_t) == 4 * sizeof(int) + 8) + (sizeof(bo) == 4); +} +EOF +try_ 3 << EOF +struct flexible_row { int count; int values[]; }; +union flexible_holder { struct flexible_row row; int count; }; +int main(void) { + union { struct flexible_row row; char tag; } local; + local.row.count = 3; + return local.row.count + sizeof(union flexible_holder) - sizeof(int); +} +EOF +try_compile_error << EOF +struct { struct { int a; }; int b; } anonymous_member; +int main(void) { return 0; } +EOF +try_compile_error_message "ordinary identifier conflicts with typedef name" << EOF +struct S { int a; }; +int main(void) { typedef int T; struct S T; return 0; } +EOF +try_compile_error_message "ordinary identifier conflicts with typedef name" << EOF +int main(void) { typedef int T; struct S { int a; } x, T; return 0; } +EOF +try_compile_error_message "ordinary identifier conflicts with typedef name" << EOF +enum E { A }; +int main(void) { typedef int T; enum E T; return 0; } +EOF +try_compile_error_message "ordinary identifier conflicts with typedef name" << EOF +int main(void) { typedef int T; union { int a; } T; return 0; } +EOF + +# An enumeration constant is an ordinary identifier too: it conflicts with a +# typedef of its block and hides an outer one. +try_compile_error_message "typedef name conflicts with an ordinary identifier" << EOF +int main(void) { enum { A }; typedef int A; return 0; } +EOF +try_compile_error_message "identifier redeclared as a different kind of symbol" << EOF +int main(void) { typedef int T; enum { T = 3 }; return 0; } +EOF +try_ 4 << EOF +typedef int T; +int main(void) { enum { T = 3 }; { T: return T + 1; } } +EOF + +# Category: Loop Constructs +begin_category "Loop Constructs" "Testing while, do-while, and for loops" + +# loop +items 55 "int acc; int p; acc = 0; p = 10; while (p) { acc = acc + p; p = p - 1; } return acc;" +items 60 "int acc; acc = 15; do { acc = acc * -2; } while (acc < 0); return acc;" +items 45 "int i; int acc; acc = 0; for (i = 0; i < 10; ++i) { acc = acc + i; } return acc;" +items 45 "int i; int j; i=0; j=0; while (i<10) { j=j+i; i=i+1; } return j;" +items 1 "int x; x=0; do {x = x + 1; break;} while (1); return x;" +items 2 "int x; x=0; do {x++; continue; abort();} while (x < 2); return x;" +items 2 "int x; x=0; while(x < 2){x++; continue; abort();} return x;" +items 7 "int i; i=0; int j; for (j = 0; j < 10; j++) { if (j < 3) continue; i = i + 1; } return i;" +items 10 "while(0); return 10;" +items 10 "while(1) break; return 10;" + +# A for initializer declaration is an ordinary declaration: its initializers are +# assignment expressions and each declarator keeps the resolved base type. +try_ 23 << EOF +struct P { int x, y; }; +int main(void) +{ + int j, k, n = 0; + for (int i = j = 2; i < 4; i++) + n += i + j; + for (int a = 1, b = k = 3, c = a ? b : 0; a < 2; a++) + n += a + b + c + k; + for (unsigned u = 0, v = 0; u < 1; u++) + v = -1, n += v > 0; + for (struct P p = { 1, 2 }, q = p; p.x < 2; p.x++) + n += q.y; + for (int m = 0, *pm = &m; m < 1; m++) + n += *pm + 1; + return n; +} +EOF +items 10 "for(;;) break; return 10;" +items 0 "int x; for(x = 10; x > 0; x--); return x;" +items 30 "int i; int acc; i = 0; acc = 0; do { i = i + 1; if (i - 1 < 5) continue; acc = acc + i; if (i == 9) break; } while (i < 10); return acc;" +items 26 "int acc; acc = 0; int i; for (i = 0; i < 100; i++) { if (i < 5) continue; if (i == 9) break; acc = acc + i; } return acc;" +items 1 "int i = 0; for (;;) { i++; if (i < 4) { continue; } break; } return i == 4;" +items 14 "int n = 0; for (int i = 0;;) { i++; if (i < 14) { continue; } n = i; break; } return n;" +items 14 "int i = 0; for (; i < 20;) { i++; if (i < 14) { continue; } break; } return i;" +items 14 "int n = 0; for (int i = 0; i < 20;) { i++; if (i < 14) { continue; } n = i; break; } return n;" +items 6 "int sum = 0; for (register int i = 1; i < 4; i++) sum += i; return sum;" +items 14 "int i = 0; for (; i < 14;) { i++; } return i;" +items 0 "int i = 0; for (;; i++) { break; } return i;" +items 3 "int i = 0; while (i++, i < 3) {} return i;" +items 3 "int i = 0; for (; i++, i < 3;) {} return i;" +items 3 "int i = 0; do {} while (i++, i < 3); return i;" +items 9 "int i, j; for (i = 0, j = 0; i < 3; i++, j += 2) {} return i + j;" +items 9 "int i = 0, j = 0; for (; i < 3; (i++, j += 2)) {} return i + j;" +items 3 "int i = 0; for (; i < 3; (i++)) {} return i;" +try_ 3 << EOF +int bump(int *value) { *value = *value + 1; return *value; } +int main(void) { + int value = 0; + for (; value < 3; bump(&value)) {} + return value; +} +EOF +items 9 "int i = 0, j = 0; for ((i = 0, j = 0); i < 3; i++) j += 2; return i + j;" +try_ 3 << EOF +int reset(int *value) { *value = 0; return 0; } +int main(void) { + int value = 7; + for (reset(&value); value < 3; value++) {} + return value; +} +EOF +try_compile_error << EOF +int main(void) { int i = 0; for (const (i = 0); i < 1; i++) {} return i; } +EOF +try_compile_error << EOF +int main(void) { int i = 0; for (static (i = 0); i < 1; i++) {} return i; } +EOF +try_ 3 << EOF +enum loop_mode { LOOP_MODE_ZERO }; +int main(void) { + int count = 0; + for (enum loop_mode mode = LOOP_MODE_ZERO; count < 3; count++) + count += mode; + return count; +} +EOF +try_ 3 << EOF +int main(void) { + enum local_loop_mode { LOCAL_LOOP_MODE_ZERO }; + int count = 0; + for (enum local_loop_mode mode = LOCAL_LOOP_MODE_ZERO; count < 3; count++) + count += mode; + return count; +} +EOF +try_compile_error << EOF +int main(void) { for (enum unknown_loop_mode value = 0; value < 1; value++) {} } +EOF +try_ 3 << EOF +struct loop_record { int value; }; +union loop_union { int value; char byte; }; +int main(void) { + int count = 0; + for (struct loop_record item; count < 3; count++) item.value = count; + count = 0; + for (union loop_union item; count < 3; count++) item.value = count; + return count; +} +EOF +try_ 3 << EOF +int main(void) { + struct local_loop_record { int value; }; + union local_loop_union { int value; char byte; }; + int count = 0; + for (struct local_loop_record item; count < 3; count++) item.value = count; + count = 0; + for (union local_loop_union item; count < 3; count++) item.value = count; + return count; +} +EOF +try_compile_error << EOF +int main(void) { for (struct unknown_loop_record item; 0; ) {} } +EOF +try_compile_error << EOF +union loop_kind_union { int value; }; +int main(void) { for (struct loop_kind_union item; 0; ) {} } +EOF +items 2 "int i = 0; while (i < 2 ? 1 : 0) i++; return i;" +items 2 "int i = 0; do i++; while (i < 2 ? 1 : 0); return i;" +items 2 "int i = 0; for (; i < 2 ? 1 : 0; i++) {} return i;" + +# A loop whose back edge leads to a block laid out before the branch: neither +# arm of the conditional falls through, so both need an explicit jump. +try_ 3 << EOF +int count(void) { + int n = 0; + do { + n++; + if (n == 3) + break; + } while (1); + return n; +} +int main(void) { return count(); } +EOF + +# Category: Comments +begin_category "Comments" "Testing C-style and C++-style comment parsing" + +# C-style comments / C++-style comments Start +try_ 0 << EOF +/* This is a test C-style comments */ +int main() { return 0; } +EOF +try_ 0 << EOF +// This is a test C++-style comments +int main() { return 0; } +EOF +# Middle +try_ 0 << EOF +int main() { + /* This is a test C-style comments */ + return 0; +} +EOF +try_ 0 << EOF +int main() { + // This is a test C++-style comments + return 0; +} +EOF +# End +try_ 0 << EOF +int main() { return 0; } +/* This is a test C-style comments */ +EOF +try_ 0 << EOF +int main() { return 0; } +// This is a test C++-style comments +EOF + +# Category: Functions +begin_category "Functions" "Testing function definitions, calls, and recursion" + +# `signed` is the existing signed scalar domain; both its explicit and +# omitted-`int` forms are valid declaration specifiers. +try_ 5 << EOF +signed sum(signed int left, signed right) +{ + signed char delta = -1; + signed short extra = 1; + return left + right + delta + extra; +} +int main(void) +{ + return sum(2, 3); +} +EOF + +try_compile_error << EOF +int main(void) +{ + signed const int first = 1, second = 2; + second = 3; + return first + second; +} +EOF + +try_ 5 << EOF +int main(void) +{ + signed char value = -1; + return (signed int)value + (signed)6 + sizeof(signed short) - 2; +} +EOF + +try_ 7 << EOF +typedef signed int signed_count_t; +typedef signed char signed_delta_t; +int main(void) +{ + signed_count_t count = 8; + signed_delta_t delta = -1; + return count + delta; +} +EOF + +# functions +try_ 0 << EOF +int main(void) { + return 0; +} +EOF + +# Identifier-led function calls must use the ordinary full-expression path: +# their results may participate in arithmetic and comma sequencing instead of +# being forced to be a standalone statement by declaration dispatch. +try_ 12 << EOF +int bump(int *value) { *value += 1; return *value; } +int scale(int value) { return value * 3; } +int main(void) +{ + int value = 0; + bump(&value), bump(&value); + return scale(value) + bump(&value) + value; +} +EOF +try_ 0 << EOF +int function_designator_statement(void) { return 1; } +int main(void) +{ + function_designator_statement; + return 0; +} +EOF +try_ 0 << EOF +int function_designator_condition(void) { return 1; } +int main(void) +{ + if (function_designator_condition) + return 0; + return 1; +} +EOF +try_ 0 << EOF +int function_designator_logical(void) { return 1; } +int main(void) +{ + if (function_designator_logical && 1) + return 0; + return 1; +} +EOF +try_ 0 << EOF +int function_designator_or(void) { return 1; } +int main(void) +{ + if (function_designator_or || 0) + return 0; + return 1; +} +EOF +try_ 0 << EOF +int function_designator_not(void) { return 1; } +int main(void) +{ + if (!function_designator_not) + return 1; + return 0; +} +EOF +try_ 0 << EOF +int function_designator_equality(void) { return 1; } +int main(void) { return function_designator_equality == 0 ? 1 : 0; } +EOF +try_ 0 << EOF +int function_designator_inequality(void) { return 1; } +int main(void) { return function_designator_inequality != 0 ? 0 : 1; } +EOF +try_compile_error << EOF +int incompatible_equality_left(int value) { return value; } +int incompatible_equality_right(void) { return 0; } +int main(void) { + return incompatible_equality_left == incompatible_equality_right; +} +EOF +try_compile_error << EOF +int invalid_function_relational(int value) { return value; } +int main(void) { + return invalid_function_relational < invalid_function_relational; +} +EOF +try_ 8 << EOF +typedef int (*function_selector_t)(int); +int increment_selected(int value) { return value + 1; } +int decrement_selected(int value) { return value - 1; } +int select_and_call(int select) +{ + function_selector_t callback = + select ? increment_selected : decrement_selected; + return callback(4); +} +int main(void) { return select_and_call(1) + select_and_call(0); } +EOF +try_ 5 << EOF +typedef int (*nullable_selector_t)(int); +int nullable_increment(int value) { return value + 1; } +int main(void) +{ + nullable_selector_t callback = 1 ? nullable_increment : 0; + return callback ? callback(4) : 0; +} +EOF +try_ 5 << EOF +int direct_increment(int value) { return value + 1; } +int direct_decrement(int value) { return value - 1; } +int main(void) { return (1 ? direct_increment : direct_decrement)(4); } +EOF +try_ 5 << EOF +int direct_nullable_increment(int value) { return value + 1; } +int main(void) { return (1 ? direct_nullable_increment : 0)(4); } +EOF +try_ 5 << EOF +int cast_nullable_increment(int value) { return value + 1; } +int main(void) { return (1 ? cast_nullable_increment : (int)0)(4); } +EOF +try_ 5 << EOF +int sizeof_nullable_increment(int value) { return value + 1; } +int main(void) { + return (1 ? sizeof_nullable_increment : sizeof(int) - 4)(4); +} +EOF +try_ 5 << EOF +int narrow_nullable_increment(int value) { return value + 1; } +int main(void) { + return (1 ? narrow_nullable_increment : (unsigned char)256)(4); +} +EOF +try_ 5 << EOF +int wide_nullable_increment(int value) { return value + 1; } +int main(void) { + return (1 ? wide_nullable_increment : (int)4294967296LL)(4); +} +EOF +try_compile_error << EOF +int void_cast_increment(int value) { return value + 1; } +int main(void) { return (1 ? void_cast_increment : (void)0)(4); } +EOF +try_compile_error << EOF +int bool_cast_increment(int value) { return value + 1; } +int main(void) { + return (1 ? bool_cast_increment : (_Bool)4294967296LL)(4); +} +EOF +try_compile_error << EOF +typedef int (*invalid_selector_t)(int); +int invalid_increment(int value) { return value + 1; } +int main(void) { + invalid_selector_t callback = 1 ? invalid_increment : 1; + return callback(0); +} +EOF +try_compile_error << EOF +typedef int (*incompatible_selector_t)(int); +int incompatible_increment(int value) { return value + 1; } +int incompatible_wrong(void) { return 0; } +int main(void) { + incompatible_selector_t callback = + 1 ? incompatible_increment : incompatible_wrong; + return callback(0); +} +EOF + +# Assignment expressions yield their stored value in argument and explicitly +# parenthesized comma-expression contexts. +try_ 17 << EOF +int twice(int value) { return value * 2; } +int main(void) { + int value = 1; + return twice(value = 5) + (value = 6, value + 1); +} +EOF +try_ 3 << EOF +int main(void) { + int first = 0, second = 0; + return first = second = 3; +} +EOF +try_ 9 << EOF +int main(void) { + int value = 0; + return 1 ? value = 9 : value = 2; +} +EOF +try_ 1 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { + struct flags flags = {0}; + return flags.value = 9; +} +EOF +try_ 1 << EOF +struct value { unsigned char byte; }; +int main(void) { + struct value value = {0}; + return value.byte = 257; +} +EOF +try_ 7 << EOF +int main(void) { + int value = 0; + if (value = 1) + value = 3; + switch (value = 4) { + case 4: return value = 7; + default: return 0; + } +} +EOF +try_ 7 << EOF +int main(void) { + int value = 3; + int *pointer = &value; + return *pointer += 4; +} +EOF +try_ 1 << EOF +int main(void) { + unsigned char value = 0; + unsigned char *pointer = &value; + return *pointer = 257; +} +EOF +try_compile_error << EOF +int main(void) { + const int value = 0; + const int *pointer = &value; + return *pointer = 1; +} +EOF +try_ 13 << EOF +int main(void) { + int values[1] = {0}; + int *pointer = values; + return ((values[0])) = 6 + ((*pointer) = 7); +} +EOF +try_compile_error << EOF +struct point { int x; }; +int main(void) { + const struct point point = {0}; + const struct point *pointer = &point; + return (*pointer).x = 7; +} +EOF + +# A member reached through a pointer to a const record is not a modifiable +# lvalue (C99 6.5.16p2), whatever the pointer itself is: plain, parameter, +# global, typedef, const or subscripted. Assignment, compound assignment and +# increment are all rejected, including members of nested records and elements +# of array members. +try_compile_error_message "read-only location" << EOF +struct S { int a; }; +int main(void) { struct S s; const struct S *p = &s; p->a = 1; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int a; }; +int bump(const struct S *p) { return p->a += 2; } +int main(void) { return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int a; }; +const struct S *global; +int main(void) { global->a++; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int a; }; +int main(void) { struct S s; const struct S *p = &s; --p->a; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int a; }; +typedef const struct S *const_s_ptr; +int main(void) { struct S s; const_s_ptr p = &s; p->a = 1; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct I { int b; }; +struct S { struct I in; int arr[2]; }; +int main(void) { struct S s; const struct S *const p = &s; p->in.b = 1; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int arr[2]; }; +int main(void) { struct S s; const struct S *p = &s; p->arr[1] = 1; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int arr[2]; }; +int main(void) { const struct S cs = {{0}}; cs.arr[1] = 1; return 0; } +EOF + +try_compile_error_message "read-only location" << EOF +struct S { int *ptr; struct S *next; }; +int main(void) { struct S s; const struct S *p = &s; p->next = 0; return 0; } +EOF + +# Reads stay valid, and so do writes through a non-const pointer, through a +# const pointer to a non-const record, and to objects a pointer member of a +# const record reaches. A pointer member to const is itself assignable, and a +# pointer typedef naming a record tag finds the tag's members. +try_ 19 << EOF +struct I { int b; }; +struct S { int a; struct I in; int arr[2]; int *ptr; const int *view; struct S *next; }; +typedef struct S *s_ptr; +int read_back(const struct S *p) { return p->a + p->next->in.b; } +int main(void) { + struct S s = {0}; + struct S *p = &s; + struct S *const fixed = &s; + const struct S *cp = &s; + s_ptr alias = &s; + s.next = &s; + s.ptr = &s.arr[1]; + p->a = 1; + p->in.b += 2; + ++p->a; + fixed->arr[0] = 3; + fixed->a++; + cp->next->in.b++; + cp->next->arr[1] = 4; + *cp->ptr += 1; + p->view = &s.a; + alias->a += 1; + return read_back(cp) + *cp->view + alias->arr[0] + cp->arr[1]; +} +EOF + +# Named struct/union specifiers are valid parameter declaration specifiers. +try_ 10 << EOF +struct value { int first; }; +int first(struct value input) { + return input.first; +} +int main(void) { + struct value input = {10}; + return first(input); +} +EOF + +# An eight-byte union or anonymous typedef record is still an aggregate on a +# 32-bit target, not a two-register scalar. Passing it between two scalar +# arguments must not shift them into different registers. +try_ 0 << EOF +union two_words { int a[2]; }; +typedef struct { int x; int y; } anon_pair; +int take_union(int k, union two_words u, int m) { + return k * 100 + u.a[0] * 10 + u.a[1] + m * 1000; +} +int take_anon(int k, anon_pair p, int m) { + return k * 100 + p.x * 10 + p.y + m * 1000; +} +int main(void) { + union two_words u; + anon_pair p; + u.a[0] = 3; + u.a[1] = 4; + p.x = 5; + p.y = 6; + return take_union(1, u, 2) != 2134 || take_anon(1, p, 2) != 2156; +} +EOF + +try_ 55 << EOF +int sum(int m, int n) { + int acc; + acc = 0; + int i; + for (i = m; i <= n; i = i + 1) + acc = acc + i; + return acc; +} + +int main() { + return sum(1, 10); +} +EOF + +try_ 120 << EOF +int fact(int x) { + if (x == 0) { + return 1; + } else { + return x * fact(x - 1); + } +} + +int main() { + return fact(5); +} +EOF + +try_ 55 << EOF +int fib(int n, int a, int b) +{ + if (n == 0) + return a; + else if (n == 1) + return b; + return fib(n - 1, b, a + b); +} + +int main() { + return fib(012, 0, 1); /* octal(12) = dec(10) */ +} +EOF + +# Test large fibonacci values using the new try_large function +try_large 987 << EOF +int fib(int n, int a, int b) +{ + if (n == 0) + return a; + if (n == 1) + return b; + return fib(n - 1, b, a + b); +} + +int test_function() { + return fib(16, 0, 1); /* fib(16) = 987 */ +} +EOF + +# Test function with short parameters and return type +try_ 35 << EOF +short add_shorts(short a, short b) { + return a + b; +} + +int main() { + return add_shorts(15, 20); +} +EOF + +# Test other large values +try_large 1000 << EOF +int test_function() { + return 1000; +} +EOF + +try_large 65536 << EOF +int test_function() { + return 1 << 16; /* 2^16 = 65536 */ +} +EOF + +try_large 999999 << EOF +int test_function() { + return 999999; +} +EOF + +try_compile_error << EOF +int main() { + int a = 03, b = 01118, c = 091; + printf("%d %d %d\n", a, b, c); + return 0; +} +EOF + +try_compile_error << EOF +int main(void v) {} +EOF + +try_compile_error << EOF +int main(void, int i) {} +EOF + +# A parameter list has no trailing comma in any dialect, and a comma must +# separate each parameter, including the ellipsis, from the one before it. +try_compile_error_message "trailing comma in parameter list" << EOF +int g(int a,) { return a; } +int main(void) { return g(1); } +EOF + +try_compile_error_message "trailing comma in parameter list" << EOF +int g(int a, char *b,); +int main(void) { return 0; } +EOF + +try_compile_error_message "trailing comma in parameter list" << EOF +int main(void) { int (*fp)(int,); return 0; } +EOF + +try_compile_error_message "trailing comma in parameter list" << EOF +int apply(int (*cb)(int,), int b) { return b; } +int main(void) { return 0; } +EOF + +try_compile_error_message "trailing comma in parameter list" << EOF +int main(void) { return sizeof(int (*)(int,)); } +EOF + +try_compile_error << EOF +int g(void,) { return 0; } +int main(void) { return g(); } +EOF + +try_compile_error << EOF +int g(int a int b) { return a + b; } +int main(void) { return g(1, 2); } +EOF + +try_compile_error << EOF +int g(int a ...) { return a; } +int main(void) { return g(1, 2); } +EOF + +try_ 3 << EOF +int g(int a, int (*cb)(int, char), ...) { return a; } +int main(void) { int (*fp)(int, ...) = 0; return g(3, 0) + (fp != 0); } +EOF + +# Only the keyword itself starts a void parameter list. A type name that merely +# begins with those letters is an ordinary parameter type, and an unknown one +# must be diagnosed rather than read as an empty prototype. +try_ 7 << EOF +typedef int voidptr; +int add(voidptr a, int b) { return a + b; } +int main(void) { return add(3, 4); } +EOF + +try_compile_error << EOF +int unknown_parameter_type(voidx); +int main(void) { return 0; } +EOF + +# Unreachable declaration should not cause prog segmentation fault (prog should +# leave normally with exit code 0) +try_ 0 << EOF +int main() +{ + return 0; + int a = 5; +} +EOF + +try_ 1 << EOF +int is_odd(int x); + +int is_even(int x) { + if (x == 0) { + return 1; + } else { + return is_odd(x - 1); + } +} + +int is_odd(int x) { + if (x == 0) { + return 0; + } else { + return is_even(x - 1); + } +} + +int main() { + return is_even(20); +} +EOF + +try_ 253 << EOF +int ack(int m, int n) { + if (m == 0) { + return n + 1; + } else if (n == 0) { + return ack(m - 1, 1); + } else { + return ack(m - 1, ack(m, n - 1)); + } +} + +int main() { + return ack(3, 5); +} +EOF + +# Category: Pointer Operations +begin_category "Pointer Operations" "Testing pointer declarations, dereferencing, and arithmetic" + +# pointers +items 3 "int x; int *y; x = 3; y = &x; return y[0];" +items 5 "int b; int *a; b = 10; a = &b; a[0] = 5; return b;" +items 2 "int x[2]; int y; x[1] = 2; y = *(x + 1); return y;" +items 2 "int x; int *y; int z; z = 2; y = &z; x = *y; return x;" +items 2 "short x; short *y; short z; z = 2; y = &z; x = *y; return x;" + +# pointer dereference immediately after declaration +items 42 "int x; x = 10; int *p; p = &x; p[0] = 42; exit(x);" +items 10 "int val; val = 5; int *ptr; ptr = &val; ptr[0] = 10; exit(val);" +items 7 "int a; a = 3; int *b; b = &a; b[0] = 7; exit(a);" + +# Pointers are ordered as unsigned addresses (C99 6.5.8), also on a 32-bit +# target for an address above 0x80000000; they were compared as signed. +try_ 1 << EOF +int lt(int *a, int *b) { return a < b; } +int main(void) { + int *high = (int *) -4, *low = (int *) 16; + int *mid = (int *) (unsigned long) 0x90000000UL; + int r = ((int *) -1 < (int *) 0) + (high <= low) + lt(high, low); + if (high < low || !(mid > low)) + r++; + return r == 0 && high > low && mid >= low && low < mid; +} +EOF + +# Pointer arithmetic follows operator precedence. A pointer operand used to take +# a following "+ n" as part of itself, so p + a * b became (p + a) * b and read +# past the array, and so did p + sizeof(int) * 2. +try_output 0 "2 2 9 5 2 2 2 4 7 12 21 8 6 4 7 1 1 6" << EOF +int *id(int *p) { return p; } +int main(void) { + int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int m[3][4]; + int (*pa)[4] = m; + int *p = arr; + int a = 1, b = 2; + long off = 3; + for (int r = 0; r < 3; r++) + for (int c = 0; c < 4; c++) + m[r][c] = r * 10 + c; + printf("%d %d %d ", *(p + 1 * 2), *(p + a * b), *(p + 5 - 2 + 3 * 2)); + printf("%d %d %d ", *(5 + p), *(a * b + p), *(p + a + a)); + printf("%d %d %d ", *(id(p) + a * b), *(arr + 2 * 2), *(arr + a + b * 3)); + printf("%d %d ", (*(pa + 1))[2], *(*(pa + a * 2) + 1)); + printf("%d %d %d ", *(p + sizeof(int) * 2), *(p + off * 2), + *(p + a * b + a * b)); + int *z = arr + a * b + b % 2 + b / 2 * 5 - a * 4; + printf("%d %d %d ", *z + 4, p + a * 0 ? 1 : 2, (int) ((char *) p + 1 - (char *) p)); + p = p + 2 * 3; + printf("%d", *p); + return 0; +} +EOF + +# Unary operators and casts read their operand recursively without passing +# through read_expr(), so a long chain of them overflowed the stack instead of +# reaching the expression nesting limit. +try_ 7 << EOF +int main(void) +{ + int x = 7; + return $(printf '*&%.0s' $(seq 100))x + $(printf '(int)%.0s' $(seq 100))0; +} +EOF +try_compile_error_message "Expression nesting too deep" << EOF +int main(void) +{ + int x = 3; + return $(printf '*&%.0s' $(seq 20000))x; +} +EOF +try_compile_error_message "Expression nesting too deep" << EOF +int main(void) +{ + int x = 3; + return $(printf '(int)%.0s' $(seq 20000))x; +} +EOF + +# The address of a member has the member's pointer type, so arithmetic on it +# advances by whole members. +try_ 0 << EOF +struct member_address_inner { int first; char second; }; +struct member_address_outer { char tag; int value; struct member_address_inner inner; }; +int main(void) +{ + struct member_address_outer object; + struct member_address_outer *pointer = &object; + + if ((char *) (&object.value + 1) - (char *) &object.value != sizeof(int)) + return 1; + if ((char *) (&pointer->inner + 1) - (char *) &pointer->inner != + sizeof(struct member_address_inner)) + return 2; + return 0; +} +EOF + +# A parameter whose address is taken later in the function is still in the +# register it arrived in when read before that. Reloading it from its slot read +# a slot nothing had written yet. +try_ 4 << EOF +int first_param(int a, int b) +{ + int x = a + b; + int *p = &a; + return x + *p; +} +int main(void) +{ + return first_param(1, 2); +} +EOF + +try_ 51 << EOF +int second_param(int a, int b) +{ + int x = a - b; + int *p = &b; + *p = 1; + return x * 10 + b; +} +int main(void) +{ + return second_param(7, 2); +} +EOF + +try_ 3 << EOF +int sum_before_write(int a, int b) +{ + int x = a + b; + int *p = &a; + *p = 99; + return x; +} +int main(void) +{ + return sum_before_write(1, 2); +} +EOF + +# A write through a pointer can land in a global as readily as in a local. The +# global's value must not be taken from a register loaded before the write. +try_ 57 << EOF +int g; +int main(void) +{ + int *q = &g; + g = 4; + int x = g + 1; + *q = 6; + int y = g + 1; + return x * 10 + y; +} +EOF + +try_ 18 << EOF +int g; +int main(void) +{ + int a = 3; + int *p = &a; + int *q = &g; + g = 4; + int x = a * g; + *p = 5; + *q = 6; + int y = a * g; + return y - x; +} +EOF + +# Assigning an address-taken variable by name and reading it through a pointer +# are two ways of reaching the same storage, in either order, on any path. +try_ 2 << EOF +int main(void) +{ + int a = 1; + int *p = &a; + a = 2; + return *p; +} +EOF + +try_ 10 << EOF +int main(void) +{ + int a = 3; + int *p = &a; + a = 9; + *p = a + 1; + return a; +} +EOF + +try_ 5 << EOF +int pick(int a) +{ + int *p = &a; + if (a > 2) + a = 5; + else + a = 6; + return *p; +} +int main(void) +{ + return pick(3); +} +EOF + +try_ 41 << EOF +int main(void) +{ + int x = 7; + int *p = &x; + int y = x; + if (y > 3) + x = 1; + else + x = 2; + int z = *p; + *p = z + 40; + return x; +} +EOF + +try_ 10 << EOF +int main(void) +{ + int a = 0; + int *p = &a; + for (int k = 0; k < 5; k++) { + *p = *p + 1; + a = a + 1; + } + return a; +} +EOF + +try_ 10 << EOF +int main(void) +{ + int i; + int *p = &i; + int s = 0; + for (i = 0; i < 5; i++) + s += *p; + return s; +} +EOF + +try_ 47 << EOF +int main(void) +{ + int a = 4, b = 7; + int *ptr; + int **pp = &ptr; + ptr = &a; + int x = *ptr; + *pp = &b; + int y = *ptr; + return x * 10 + y; +} +EOF + +try_ 120 << EOF +int main(void) +{ + int a[64]; + int i; + int sum = 0; + int *pi = &i; + for (int k = 0; k < 64; k++) + a[k] = k; + i = 0; +loop: + sum = sum + a[i * 4]; + *pi = *pi + 1; + i = i + 1; + if (i >= 12) + goto done; + goto loop; +done: + return sum; +} +EOF + +# asterisk dereference for reading after declaration +items 42 "int x; x = 42; int *p; p = &x; int y; y = *p; exit(y);" +items 15 "int val; val = 15; int *ptr; ptr = &val; exit(*ptr);" +items 100 "int a; a = 100; int *b; b = &a; int c; c = *b; exit(c);" +try_ 2 << EOF +int main(void) { + int values[3] = {1, 2, 3}; + int *pointer = values; + return *++pointer; +} +EOF +try_ 2 << EOF +int main(void) { + int values[3] = {1, 2, 3}; + int *pointer = &values[2]; + return *--pointer; +} +EOF +try_ 7 << EOF +int main(void) { + int values[3] = {1, 2, 3}; + int *pointer = values; + *++pointer = 7; + return values[1]; +} +EOF + +# complex pointer dereference patterns after declaration +try_ 25 << EOF +int main() { + int x; + int *p; + x = 10; + p = &x; /* pointer declaration and assignment */ + p[0] = 25; /* array-style assignment immediately after */ + return x; +} +EOF + +try_ 50 << EOF +int main() { + int arr[3]; + int *ptr; + arr[0] = 10; arr[1] = 20; arr[2] = 30; + ptr = arr; + ptr[0] = 50; /* should modify arr[0] */ + return arr[0]; +} +EOF + +try_ 50 << EOF +int main() { + int a, b; + int *p1, *p2; + a = 5; b = 15; + p1 = &a; + p2 = &b; + p1[0] = 100; /* multiple pointer assignments in same block */ + p2[0] = 200; + return p1[0] / 2; /* 100 / 2 = 50 */ +} +EOF + +try_ 10 << EOF +void change_it(int *p) { + if (p[0] == 0) { + p[0] = 10; + } else { + p[0] = p[0] - 1; + } +} + +int main() { + int v; + v = 2; + change_it(&v); + change_it(&v); + change_it(&v); + return v; +} +EOF + +# typedef pointer tests - testing fixes for typedef pointer compilation issues +# These tests verify typedef pointer functionality after: +# 1. Removing incorrect pointer level inheritance in read_full_var_decl() +# 2. Adding typedef pointer recognition in array indexing operations +# 3. Implementing proper pointer arithmetic scaling for typedef pointers + +# Test 1: Basic typedef pointer declaration and dereference +try_ 42 << EOF +typedef int *int_ptr; +int main() { + int x = 42; + int_ptr p = &x; + return *p; /* Basic dereference - WORKING */ +} +EOF + +# Test 2: Multiple typedef pointer variables +try_ 55 << EOF +typedef int *int_ptr; +int main() { + int a = 55, b = 100; + int_ptr p1 = &a; + int_ptr p2 = &b; + return *p1; /* Should return 55 - WORKING */ +} +EOF + +# Test 3: Typedef pointer in function parameters +try_ 30 << EOF +typedef int *int_ptr; +int add_via_ptr(int_ptr a, int_ptr b) { + return *a + *b; +} +int main() { + int x = 10, y = 20; + return add_via_ptr(&x, &y); /* Function call with typedef pointers - WORKING */ +} +EOF + +# Test 4: Multiple typedef declarations +try_ 7 << EOF +typedef int *int_ptr; +typedef char *char_ptr; +int main() { + int x = 7; + char c = 'A'; + int_ptr ip = &x; + char_ptr cp = &c; + return *ip; /* Different typedef pointer types - WORKING */ +} +EOF + +# Test 5: Global typedef pointer +try_ 88 << EOF +typedef int *int_ptr; +int global_value = 88; +int_ptr global_ptr; +int main() { + global_ptr = &global_value; + return *global_ptr; /* Global typedef pointer - WORKING */ +} +EOF + +# Test 6: Typedef pointer initialization +try_ 100 << EOF +typedef int *int_ptr; +int main() { + int val = 100; + int_ptr p = &val; /* Initialize at declaration */ + int result = *p; + return result; /* Indirect usage - WORKING */ +} +EOF + +# Test 7: Nested typedef pointer usage in expressions +try_ 15 << EOF +typedef int *int_ptr; +int main() { + int x = 5, y = 10; + int_ptr px = &x; + int_ptr py = &y; + return *px + *py; /* Expression with multiple derefs - WORKING */ +} +EOF + +# Test 8: Typedef pointer assignment after declaration +try_ 25 << EOF +typedef int *int_ptr; +int main() { + int value = 25; + int_ptr ptr; + ptr = &value; /* Assignment after declaration */ + return *ptr; /* WORKING */ +} +EOF + +# Test 9: Typedef pointer array indexing +try_ 100 << EOF +typedef int *int_ptr; +int main() { + int values[3] = {42, 100, 200}; + int_ptr p = values; + return p[1]; /* Array indexing - NOW WORKING with fix */ +} +EOF + +# Test 10: Complex array indexing with typedef pointer +try_ 90 << EOF +typedef int *int_ptr; +int main() { + int arr[5] = {10, 20, 30, 40, 50}; + int_ptr p = arr; + return p[0] + p[2] + p[4]; /* Multiple array accesses */ +} +EOF + +# Test 11: Typedef pointer arithmetic - increment +try_ 20 << EOF +typedef int *int_ptr; +int main() { + int values[3] = {10, 20, 30}; + int_ptr p = values; + p++; /* Move to next element */ + return *p; /* Should return 20 */ +} +EOF + +# Test 12: Typedef pointer arithmetic - addition +try_ 40 << EOF +typedef int *int_ptr; +int main() { + int values[5] = {10, 20, 30, 40, 50}; + int_ptr p = values; + p = p + 3; /* Move forward by 3 elements */ + return *p; /* Should return 40 */ +} +EOF + +# Test 13: Typedef pointer arithmetic - subtraction +try_ 30 << EOF +typedef int *int_ptr; +int main() { + int values[5] = {10, 20, 30, 40, 50}; + int_ptr p = values + 4; /* Point to last element */ + p = p - 2; /* Move back by 2 elements */ + return *p; /* Should return 30 */ +} +EOF + +# Test 14: Typedef pointer arithmetic - prefix increment +try_ 20 << EOF +typedef int *int_ptr; +int main() { + int values[3] = {10, 20, 30}; + int_ptr p = values; + ++p; /* Prefix increment */ + return *p; /* Should return 20 */ +} +EOF + +# Test 14b: Typedef pointer arithmetic - prefix decrement as an expression +# statement must use the same pointed-to stride as prefix increment. +try_ 20 << EOF +typedef int *int_ptr; +int main() { + int values[3] = {10, 20, 30}; + int_ptr p = &values[2]; + --p; + return *p; +} +EOF + +# Test 15: Typedef pointer arithmetic - postfix increment +try_ 10 << EOF +typedef int *int_ptr; +int main() { + int values[3] = {10, 20, 30}; + int_ptr p = values; + int val = *p++; /* Get value, then increment */ + return val; /* Should return 10 */ +} +EOF + +# Test 16: Typedef pointer arithmetic - decrement +try_ 20 << EOF +typedef int *int_ptr; +int main() { + int values[3] = {10, 20, 30}; + int_ptr p = values + 2; /* Point to values[2] */ + p--; /* Move back one element */ + return *p; /* Should return 20 */ +} +EOF + +# Test 17: Typedef char pointer arithmetic +try_ 98 << EOF +typedef char *char_ptr; +int main() { + char chars[5] = {'a', 'b', 'c', 'd', 'e'}; + char_ptr p = chars; + p = p + 1; /* Move forward by 1 byte */ + return *p; /* Should return 'b' = 98 */ +} +EOF + +# Test 18: Mixed typedef pointer operations +try_ 35 << EOF +typedef int *int_ptr; +int main() { + int values[10] = {5, 10, 15, 20, 25, 30, 35, 40, 45, 50}; + int_ptr p = values; + p = p + 2; /* Move to values[2] = 15 */ + p++; /* Move to values[3] = 20 */ + p = p + 3; /* Move to values[6] = 35 */ + return *p; +} +EOF + +try_ 0 << EOF +/* An array operand of a pointer difference decays to a pointer to its first + * element, a row for a deeper array. The difference is a complete operand of + * a following + or -, so end - start + 1 counts both ends. + */ +int main(void) { + char buf[4], *bp = buf + 2, *start = buf; + int values[5], *ip = values + 3, *first = values; + int m[3][2], k[2][3][4], (*row)[2] = m + 1; + if (bp - buf != 2 || buf - bp != -2 || &buf[3] - buf != 3) return 1; + if (ip - values != 3 || values - ip + 5 != 2 || buf + 3 - buf != 3) return 2; + if (m[2] - m[0] != 4 || (m + 2) - m != 2 || row - m != 1) return 3; + if ((k + 1) - k != 1 || &m[2][1] - m[0] != 5) return 4; + if (bp - start + 1 != 3 || ip - first + 1 + 2 != 6) return 5; + return 10 - (ip - first) + 1 != 8; +} +EOF + +# Pointer difference calculations Test basic pointer subtraction returning +# element count +try_ 5 << EOF +int main() { + char arr[10]; + char *p = arr; + char *q = arr + 5; + int diff = q - p; /* Should return 5 (5 elements) */ + return diff; +} +EOF + +try_ 3 << EOF +int main() { + char str[20]; + char *start = str + 2; + char *end = str + 5; + return end - start; /* Should return 3 */ +} +EOF + +# Test pointer difference with char pointers (element size = 1) +try_ 7 << EOF +int main() { + char buffer[100]; + char *p1 = buffer; + char *p2 = buffer + 7; + return p2 - p1; /* Should return 7 */ +} +EOF + +# Test reverse pointer difference +try_ 5 << EOF +int main() { + char data[50]; + char *high = data + 10; + char *low = data + 5; + return high - low; /* Should return 5 */ +} +EOF + +# C99 6.5.6: pointer subtraction yields an element count, not a byte count. +try_ 5 << EOF +int main(void) { + int values[10]; + return &values[7] - &values[2]; +} +EOF + +try_ 5 << EOF +struct point { int x; int y; }; +int main(void) { + struct point values[10]; + return &values[8] - &values[3]; +} +EOF + +try_ 4 << EOF +typedef int *int_ptr; +int main(void) { + int values[10]; + int_ptr start = &values[2]; + int_ptr end = &values[6]; + return end - start; +} +EOF + +try_ 2 << EOF +int main(void) { + int *values[4]; + return &values[3] - &values[1]; +} +EOF + +try_ 9 << EOF +int main(void) { + int first = 4, second = 9; + int *values[2] = {&first, &second}; + values[0] = &second; + return *values[0]; +} +EOF + +# C99 6.5.6 requires the two pointer operands to point at compatible types. +try_compile_error << EOF +int main(void) { + int words[2]; + char bytes[2]; + return &words[1] - &bytes[1]; +} +EOF + +try_compile_error << EOF +struct first { int value; }; +struct second { int value; }; +int main(void) { + struct first left[2]; + struct second right[2]; + return &left[1] - &right[1]; +} +EOF + +# C99 6.5.6 permits subtraction only for pointers to complete object types. +try_compile_error << EOF +int main(void) { + void *left = 0; + void *right = 0; + return left - right; +} +EOF +try_compile_error << EOF +int first(void) { return 1; } +int second(void) { return 2; } +int main(void) { + int (*left)(void) = first; + int (*right)(void) = second; + return left - right; +} +EOF +try_compile_error << EOF +int callback(void) { return 1; } +int main(void) { + int (*pointer)(void) = callback; + return pointer + 1; +} +EOF + +try_ 1 << EOF +typedef int *int_pointer; +int main(void) { + int values[2]; + int_pointer typed = &values[1]; + int *spelled = &values[0]; + return typed - spelled; +} +EOF + +# Pointer arithmetic tests + +# Basic integer pointer difference +try_ 7 << EOF +int main() { + int arr[10]; + int *p = arr; + int *q = arr + 7; + return q - p; +} +EOF + +# Char pointer differences +try_ 10 << EOF +int main() { + char text[50]; + char *start = text; + char *end = text + 10; + return end - start; +} +EOF + +try_ 0 << EOF +int main() { + char buffer[100]; + char *p1 = buffer + 25; + char *p2 = buffer + 25; + return p2 - p1; /* Same position = 0 */ +} +EOF + +# More complex char pointer arithmetic +try_ 15 << EOF +int main() { + char str[100]; + char *p = str + 5; + char *q = str + 20; + return q - p; /* 20 - 5 = 15 */ +} +EOF + +# C99 does not define arithmetic on void pointers. +try_compile_error << EOF +int main() { + char array[20]; + void *vp1 = array; + return vp1 + 8; +} +EOF + +try_compile_error << EOF +int main() { + char array[20]; + void *vp1 = array; + return vp1 - 1; +} +EOF + +try_compile_error << EOF +int main() { + char array[20]; + void *vp1 = array; + vp1++; + return 0; +} +EOF + +try_compile_error << EOF +int main() { + char array[20]; + void *vp1 = array; + ++vp1; + return 0; +} +EOF + +try_compile_error << EOF +int main() { + char array[20]; + void *vp1 = array; + vp1 += 1; + return 0; +} +EOF + +try_compile_error << EOF +typedef void *void_pointer_t; +int main() { + void_pointer_t value = 0; + return value + 1; +} +EOF + +# A pointer-to-void-pointer advances over pointer objects, so it remains valid. +try_ 1 << EOF +int main() { + void *values[2]; + return (char *)(values + 1) - (char *)values == sizeof(void *); +} +EOF + +try_ 1 << EOF +typedef void *void_pointer_t; +int main() { + void_pointer_t values[2]; + return (char *)(values + 1) - (char *)values == sizeof(void *); +} +EOF + +try_ 1 << EOF +int main() { + void *values[2]; + void **p = values; + p += 1; + return (char *)p - (char *)values == sizeof(void *); +} +EOF + +try_ 1 << EOF +int main() { + void *values[2]; + void **p = values; + return (char *)(p + 1) - (char *)values == sizeof(void *); +} +EOF + +try_ 1 << EOF +typedef void **void_pointer_slot_t; +int main() { + void *values[2]; + void_pointer_slot_t p = values; + p += 1; + return (char *)p - (char *)values == sizeof(void *); +} +EOF + +try_ 1 << EOF +typedef void **void_pointer_slot_t; +int main() { + void *values[2]; + void_pointer_slot_t p = values; + return (char *)(p + 1) - (char *)values == sizeof(void *); +} +EOF + +# An array of pointers decays to a pointer-to-pointer. Its stride is a pointer +# object, not the size of the pointee base type. +try_ 1 << EOF +int main() { + int *values[2]; + return (char *)(values + 1) - (char *)values == sizeof(void *); +} +EOF + +try_ 1 << EOF +int main() { + int *values[3]; + return (values + 2) - (values + 1); +} +EOF + +# Integer pointer with array indexing +try_ 3 << EOF +int main() { + int nums[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int *first = &nums[2]; + int *second = &nums[5]; + return second - first; /* Direct subtraction: (5-2) = 3 */ +} +EOF + +# Larger integer pointer difference +try_ 10 << EOF +int main() { + int values[20]; + int *p = values; + int *q = values + 10; + return q - p; /* Direct pointer arithmetic */ +} +EOF + +# Negative pointer difference +try_ 251 << EOF +int main() { + int arr[10]; + int *p = arr + 8; + int *q = arr + 3; + return q - p; /* 3 - 8 = -5, wraps to 251 in exit code */ +} +EOF + +# Zero pointer difference +try_ 0 << EOF +int main() { + int data[10]; + int *p1 = data + 5; + int *p2 = data + 5; + return p2 - p1; /* Same position = 0 */ +} +EOF + +# Struct pointer arithmetic +try_ 4 << EOF +struct point { + int x; + int y; + int z; +}; + +int main() { + struct point pts[10]; + struct point *p1 = pts; + struct point *p2 = pts + 4; + return p2 - p1; /* Struct pointer difference */ +} +EOF + +# Mixed pointer arithmetic operations +try_ 16 << EOF +int main() { + int arr[20]; + int *start = arr; + int *mid = arr + 10; + int *end = arr + 18; + return (end - mid) + (mid - start) - 2; /* (18-10) + (10-0) - 2 = 8 + 10 - 2 = 16 */ +} +EOF + +# Pointer arithmetic with typedef +try_ 6 << EOF +typedef int* int_ptr; +int main() { + int data[15]; + int_ptr p1 = data + 2; + int_ptr p2 = data + 8; + return p2 - p1; /* Typedef pointer difference: 8 - 2 = 6 */ +} +EOF + +# Complex expression with pointer differences +try_ 13 << EOF +int main() { + int vals[30]; + int *a = vals; + int *b = vals + 5; + int *c = vals + 9; + int *d = vals + 15; + return (d - a) - (c - b) + 2; /* (15-0) - (9-5) + 2 = 15 - 4 + 2 = 13 */ +} +EOF + +# Test negative pointer difference (converted to exit code) +try_ 253 << EOF +int main() { + char data[20]; + char *high = data + 5; + char *low = data + 8; + int diff = high - low; /* -3 */ + /* Convert negative to positive for exit code */ + return diff < 0 ? 256 + diff : diff; /* Returns 253 (256-3) */ +} +EOF + +# Test short pointer +try_ 150 << EOF +int main() { + short value = 150; + short *ptr = &value; + return *ptr; +} +EOF + +# Test short pointer arithmetic +try_ 20 << EOF +int main() { + short arr[3] = {10, 20, 30}; + short *p = arr; + p++; + return *p; +} +EOF + +# Test short pointer difference +try_ 2 << EOF +int main() { + short data[5] = {1, 2, 3, 4, 5}; + short *start = data + 1; + short *end = data + 3; + return end - start; +} +EOF + +# Taking a parameter's address must not disturb a read of that parameter made +# before the address exists. A parameter passed in a register has no stack slot +# until something needs one, and a read that went to the slot rather than the +# register picked up whatever the frame held there. +try_ 3 << EOF +int f(int a, int b) +{ + int x = a - b; + int *p = &b; + return x; +} +int main() { return f(5, 2); } +EOF + +try_ 7 << EOF +int f(int a, int b) +{ + int x = a + b; + int *p = &a; + return x; +} +int main() { return f(5, 2); } +EOF + +try_ 3 << EOF +int f(char a, short b) +{ + int x = a - b; + short *p = &b; + return x; +} +int main() { return f(5, 2); } +EOF + +try_ 3 << EOF +int f(long a, long b) +{ + long x = a - b; + long *p = &b; + return (int) x; +} +int main() { return f(5, 2); } +EOF + +# A later parameter, and one the 32-bit Arm and x86-64 conventions pass on the +# stack, written through the pointer and read back by name. +try_ 45 << EOF +int f(int a, int b, int c, int d, int e, int g, int h, int i) +{ + int x = d - c; + int y = i - h; + int *p = &d; + int *q = &i; + *q = 1; + return x * 10 + y + i + a + b + e + g + *p - 7; +} +int main() { return f(0, 0, 3, 7, 0, 0, 2, 6); } +EOF + +# The address taken in a branch and in a loop, after the parameter was read. +try_ 13 << EOF +int f(int a, int b) +{ + int x = a - b; + if (a > 0) { + int *p = &b; + *p = 10; + } + return x + b; +} +int main() { return f(5, 2); } +EOF + +try_ 11 << EOF +int f(int a, int b) +{ + int x = a - b; + for (int i = 0; i < 3; i++) { + int *p = &b; + *p += x; + } + return b; +} +int main() { return f(5, 2); } +EOF + +# The address handed to a callee that writes through it, and a function whose +# hidden aggregate-return pointer moves the parameters up one register. +try_ 12 << EOF +void set(int *p) { *p = 9; } +int f(int a, int b) +{ + int x = a - b; + set(&b); + return x + b; +} +int main() { return f(5, 2); } +EOF + +try_ 32 << EOF +typedef struct { int a; int b; int c; int d; int e; } S; +S f(int a, int b) +{ + S s; + int x = a - b; + int *p = &b; + s.a = x; + s.b = *p; + s.c = 0; + s.d = 0; + s.e = 0; + return s; +} +int main() +{ + S s = f(5, 2); + return s.a * 10 + s.b; +} +EOF + +# Assigning to a variable by name after its address was taken must reach the +# object the pointer names. Each assignment makes a new SSA version, and every +# version has to live in the one slot the pointer holds and be written there +# before anything reads through the pointer. +try_ 5 << EOF +int main() +{ + int b = 1; + int *p = &b; + b = 5; + return *p; +} +EOF + +try_ 6 << EOF +int main() +{ + int b = 1; + int *p = &b; + b = 5; + *p = *p + 1; + return b; +} +EOF + +try_ 7 << EOF +int main() +{ + int b = 1; + int *p = &b; + for (int i = 0; i < 3; i++) + b = b + 2; + return *p; +} +EOF + +try_ 55 << EOF +int f(int c) +{ + int b = 1; + int *p = &b; + if (c) + b = 2; + else + *p = 3; + return b * 10 + *p; +} +int main() { return f(1) + f(0); } +EOF + +try_ 7 << EOF +int main() +{ + int b = 1; + int *p = &b; + int i = 0; +again: + b = b + i; + i++; + if (i < 4) + goto again; + return *p; +} +EOF + +# The same for parameters, passed in a register or on the stack. +try_ 6 << EOF +int f(int a, int b) +{ + int *p = &b; + b = a + 5; + return *p; +} +int main() { return f(1, 2); } +EOF + +try_ 67 << EOF +int f(int a, int b, int c, int d, int e, int g, int h, int i) +{ + int *p = &i; + int *r = &b; + i = 5 + a; + b = i + 1; + return *p * 10 + *r + c + d + e + g + h; +} +int main() { return f(1, 0, 0, 0, 0, 0, 0, 9); } +EOF + +try_ 10 << EOF +int f(int n) +{ + int *p = &n; + int s = 0; + while (n > 0) { + s += *p; + n--; + } + return s; +} +int main() { return f(4); } +EOF + +# Narrow, unsigned, long and pointer variables, and a value read back after a +# library call. +try_ 3 << EOF +int main() +{ + char c = 'a'; + char *p = &c; + c = 'b'; + int r = *p; + *p = 'c'; + return r + c - 'a' - 'a'; +} +EOF + +try_ 15 << EOF +int main() +{ + unsigned short u = 65535; + unsigned short *p = &u; + long v = 3; + long *q = &v; + u = u + 2; + v = v + 4; + return *p + *q * 2; +} +EOF + +try_ 21 << EOF +int main() +{ + int x = 1, y = 2; + int *q = &x; + int **pp = &q; + q = &y; + int r = **pp; + *pp = &x; + return r * 10 + *q; +} +EOF + +try_ 41 << EOF +#include +#include +int main() +{ + char buf[16]; + int n = 0; + int *pn = &n; + n = 3; + sprintf(buf, "%d", *pn); + n = strlen(buf) + 40; + return *pn; +} +EOF + +# Category: Function Pointers +begin_category "Function Pointers" "Testing function pointer declarations and calls" + +# function pointers +try_ 18 << EOF +typedef struct { + int (*ta)(); + int (*tb)(int); +} fptrs; +int t1() { return 7; } +int t2(int x) { return x + 1; } +int main() { + fptrs fb; + fptrs *fs = &fb; + fs->ta = t1; + fs->tb = t2; + return fs->ta() + fs->tb(10); +} +EOF + +# C99 function designators decay to a function-pointer value when supplied as an +# argument, whether named directly, addressed explicitly, or parenthesized. +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int invoke(int (*callback)(int)) { return callback(4) - 5; } +int main(void) { + return invoke(plus1) || invoke(&plus1) || invoke((plus1)); +} +EOF + +# A parenthesized function designator remains an address constant for a +# file-scope callback initializer, not merely a call-argument convenience. +try_ 0 << EOF +int plus1(int value); +int (*callback)(int) = (plus1); +int (*addressed_callback)(int) = &(plus1); +int plus1(int value) { return value + 1; } +int main(void) { return callback(4) != 5 || addressed_callback(5) != 6; } +EOF + +# Aggregate constant initializers retain grouped function designators too. +try_ 0 << EOF +struct holder { int (*callback)(int); }; +int plus1(int value) { return value + 1; } +struct holder global_holder = { (plus1) }; +int main(void) { return global_holder.callback(6) != 7; } +EOF + +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int (*callbacks[2])(int) = { (plus1), &(plus1) }; +int main(void) { return callbacks[0](7) != 8 || callbacks[1](8) != 9; } +EOF + +# Unary address/dereference pairs around a function designator cancel before +# static initialization, while preserving the deferred code-address relocation. +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int (*first)(int) = &*plus1; +int (*second)(int) = *&plus1; +struct callbacks { int (*third)(int); int (*fourth)(int); }; +struct callbacks grouped = {&*plus1, *&plus1}; +int (*array[2])(int) = {&*plus1, *&plus1}; +int main(void) { + return first(1) != 2 || second(2) != 3 || grouped.third(3) != 4 || + grouped.fourth(4) != 5 || array[0](5) != 6 || array[1](6) != 7; +} +EOF + +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int (*first)(int) = &*(plus1); +int (*second)(int) = (*&plus1); +int (*third)(int) = (&*plus1); +int (*fourth)(int) = &(*plus1); +int (*fifth)(int) = *(&plus1); +int main(void) { + return first(1) != 2 || second(2) != 3 || third(3) != 4 || + fourth(4) != 5 || fifth(5) != 6; +} +EOF + +# Function-pointer typedefs retain their prototype through an alias chain. A +# callback parameter is already an SSA pointer value, so calling it must not +# reload its stack slot as though the slot held another function pointer. +try_ 0 << EOF +typedef int (*callback_t)(int); +typedef callback_t callback_alias_t; +int plus1(int value) { return value + 1; } +int invoke(callback_alias_t callback) { return callback(4) - 5; } +int main(void) { + callback_t callback = plus1; + return invoke(callback) || sizeof(callback_t) != sizeof(void *); +} +EOF + +# A callback typedef preserves its prototype and relocation semantics in every +# ordinary declaration context: file scope, local storage, and parameters. +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +callback_t global_callback = plus1; +int invoke(callback_t callback) { return callback(4); } +int main(void) { + callback_t local_callback = global_callback; + return invoke(local_callback) != 5; +} +EOF + +# A callback typedef used as a cast remains a pointer-valued callable result. +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int main(void) { return ((callback_t)plus1)(4) != 5; } +EOF + +# Callback typedef values decay on return, and a direct call result remains +# callable as a postfix expression. +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +callback_t maker(void) { callback_t callback = plus1; return callback; } +int main(void) { return maker()(4) != 5; } +EOF + +# The comma operator yields its final function designator, which decays before +# the callback-pointer initializer stores it. +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = (0, plus1); + return callback(4) != 5; +} +EOF + +# Pointer depth before the parenthesized callback declarator belongs to the +# callback return type, not to the typedef alias itself. +try_ 0 << EOF +typedef int *(*alloc_t)(int); +int value; +int *pick(int ignored) { return &value; } +int main(void) { + alloc_t callback = pick; + int *result = callback(0); + return result != &value; +} +EOF + +# Aliases with different callback prototypes must not make incompatible function +# declarations appear equivalent merely because both are pointer-sized. +try_ 1 << EOF +typedef int (*one_arg_t)(int); +typedef int (*long_arg_t)(long); +one_arg_t factory(void); +long_arg_t factory(void); +EOF + +# A derived callback alias is not a direct callback object. Until the +# dereference/subscript path carries its element prototype, reject this rather +# than treating the address of the callback object as a function address. +try_ 1 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_t *slot = &callback; + return slot(4); +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + callback_t callbacks[2] = {plus1, plus2}; + callback_t *slot = callbacks; + return (*++slot)(4) != 6; +} +EOF +try_ 1 << EOF +typedef int (*callback_t)(int); +typedef callback_t *callback_slot_t; +int main(void) { + callback_slot_t slot; + return slot(4); +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_t *slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callbacks[1] = {plus1}; + return callbacks[0](4) != 5; +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +typedef callback_t *callback_slot_t; +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_slot_t slot = &callback; + return (*slot)(4) != 5; +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + callback_t callbacks[2][1] = {{plus1}, {plus2}}; + return callbacks[1][0](4) != 6; +} +EOF +try_ 1 << EOF +typedef int (*callback_t)(int); +typedef callback_t *callback_slot_t; +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_slot_t slots[1] = {&callback}; + return slots[0](4); +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +typedef callback_t *callback_slot_t; +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_slot_t slots[1] = {&callback}; + return (*slots[0])(4) != 5; +} +EOF +try_ 1 << EOF +typedef int (*callback_t)(int); +typedef struct { callback_t *slot; } holder_t; +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + holder_t holder = {&callback}; + return holder.slot(4); +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +typedef struct { callback_t callback; } holder_t; +int plus1(int value) { return value + 1; } +int main(void) { + holder_t holder = {plus1}; + return holder.callback(4) != 5; +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int plus2(int value) { return value + 2; } +int main(void) { + callback_t callbacks[2] = {plus1, plus2}; + callback_t *slot = callbacks; + return (*(slot + 1))(4) != 6; +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(int); +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_t *slot = &callback; + callback_t **slots = &slot; + return (**slots)(4) != 5; +} +EOF +try_ 1 << EOF +typedef int (*callback_t)(int); +typedef callback_t *callback_slot_t; +int plus1(int value) { return value + 1; } +int main(void) { + callback_t callback = plus1; + callback_slot_t slots[1] = {&callback}; + return (*(slots + 0))(4); +} +EOF + +# The shared argument reader also serves indirect calls and must preserve a raw +# designator when it follows an ordinary scalar argument. +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int apply(int offset, int (*callback)(int)) { + return offset + callback(4) - 7; +} +int main(void) { + int (*indirect)(int, int (*)(int)) = apply; + return apply(2, plus1) || indirect(2, plus1); +} +EOF + +# Local function pointer, direct struct member, and pointer-to-struct member. +# The first path must use the pointer value directly; the latter two must load +# the pointer from the member slot. +try_ 6 << EOF +typedef struct { + int (*fn)(int); +} holder_t; + +int suc(int x) { return x + 1; } + +int main() { + int (*local)(int); + holder_t h; + holder_t *p = &h; + + local = suc; + h.fn = suc; + p->fn = suc; + + return local(1) + h.fn(1) + p->fn(1); +} +EOF + +# A function designator in a record initializer is a code address. At block +# scope it was converted as an int and truncated, so the call crashed on LP64. +# Cover automatic, static, file scope, arrays of records, designators, and an +# explicit address-of. +try_ 44 << EOF +struct ops { int (*fn)(int); int k; }; +int twice(int x) { return 2 * x; } +int thrice(int x) { return 3 * x; } +struct ops go = {twice, 1}; +struct ops garr[2] = {{twice, 1}, {thrice, 2}}; +int main(void) { + struct ops o = {twice, 1}; + static struct ops so = {thrice, 1}; + struct ops oa[2] = {{twice, 1}, thrice, 2}; + struct ops od = {.k = 3, .fn = twice}; + struct ops oe = {&thrice, 3}; + return o.fn(1) + so.fn(1) + oa[1].fn(1) + od.fn(1) + oe.fn(1) + + go.fn(1) + garr[1].fn(1) + garr[0].fn(1) + o.k + oa[1].k + + od.k + 18; +} +EOF + +# Parenthesized declarators may place an array suffix on the callback pointer. +# Exercise automatic, static-local, and file-scope storage separately: each +# requires pointer-sized element allocation and indexed indirect-call lowering. +try_ 23 << EOF +int plus1(int x) { return x + 1; } +int plus2(int x) { return x + 2; } +int main(void) { + int (*callbacks[2])(int) = {plus1, &plus2}; + return callbacks[0](10) + callbacks[1](10); +} +EOF +try_ 23 << EOF +int plus1(int x) { return x + 1; } +int plus2(int x) { return x + 2; } +int (*callbacks[2][2])(int) = {{plus1, &plus2}, {plus2, plus1}}; +int main(void) { return callbacks[1][0](10) + callbacks[1][1](10); } +EOF +try_ 48 << EOF +int plus1(int x) { return x + 1; } +int plus2(int x) { return x + 2; } +int (*callbacks[2][2][2])(int) = { + {{plus1, &plus2}, {plus2, plus1}}, + {{plus2, plus1}, {plus1, plus2}} +}; +int invoke(int value) { + static int (*local[2][2][2])(int) = { + {{plus1, plus2}, {plus2, plus1}}, + {{plus2, plus1}, {plus1, plus2}} + }; + return local[1][0][0](value) + local[1][1][1](value); +} +int main(void) { + return callbacks[1][0][0](10) + callbacks[1][1][1](10) + invoke(10); +} +EOF +try_ 23 << EOF +int plus1(int x) { return x + 1; } +int plus2(int x) { return x + 2; } +int invoke(int value) { + static int (*callbacks[2])(int) = {plus1, &plus2}; + return callbacks[0](value) + callbacks[1](value); +} +int main(void) { return invoke(10); } +EOF +try_ 23 << EOF +int plus1(int x) { return x + 1; } +int plus2(int x) { return x + 2; } +int (*callbacks[2])(int) = {plus1, &plus2}; +int main(void) { return callbacks[0](10) + callbacks[1](10); } +EOF +try_ 23 << EOF +int plus1(int x) { return x + 1; } +int plus2(int x) { return x + 2; } +struct callbacks { int (*items[2])(int); }; +int main(void) { + struct callbacks value; + value.items[0] = plus1; + value.items[1] = plus2; + return value.items[0](10) + value.items[1](10); +} +EOF + +# Parenthesized function designators remain callable. In particular, a +# function-pointer dereference is a designator, not a read of the callback's +# integer return type. +try_ 42 << EOF +int add(int left, int right) { return left + right; } +int main(void) { + int (*callback)(int, int) = add; + return (add)(19, 23) + (callback)(8, 13) + (*callback)(5, 16) == 84 + ? 42 + : 1; +} +EOF + +# RV32 stages an indirect target before all four ABI argument registers are +# populated. This scalar shape keeps that staging path covered without admitting +# the still-gated 64-bit value ABI on 32-bit targets. +try_ 10 << EOF +int sum4(int first, int second, int third, int fourth) +{ + return first + second + third + fourth; +} +int main(void) +{ + int (*callback)(int, int, int, int) = sum4; + return callback(1, 2, 3, 4); +} +EOF + +# An eight-byte callback result comes back in the integer return register, or in +# the r0/r1 or a0/a1 pair on a 32-bit target. +try_ 42 << EOF +unsigned long long add_wide(unsigned long long left, + unsigned long long right) +{ + return left + right; +} +int main(void) +{ + unsigned long long (*callback)(unsigned long long, unsigned long long) = + add_wide; + return callback(0x100000000ULL, 0x100000000ULL) == 0x200000000ULL + ? 42 + : 1; +} +EOF + +# C99 permits neither addition nor subtraction on function pointers: only +# pointers to complete object types have elements to scale or subtract. +try_compile_error << EOF +int callback(int value) { return value; } +int main(void) { return callback + 1 != 0; } +EOF +try_compile_error << EOF +int callback(int value) { return value; } +int main(void) { return 1 + callback != 0; } +EOF +try_compile_error << EOF +int callback(int value) { return value; } +int main(void) { return callback - 1 != 0; } +EOF +try_compile_error << EOF +int callback(int value) { return value; } +int main(void) { return callback - callback; } +EOF + +# Assignment between function-pointer variables copies the stored function +# address; it must not treat the RHS variable name as a function symbol. +try_ 5 << EOF +int suc(int x) { return x + 1; } + +int main() { + int (*first)(int); + int (*second)(int); + + first = suc; + second = first; + return second(4); +} +EOF + +# A local function pointer shadows a global function. Copying it must load the +# local variable's stored target, rather than materializing the global +# function's address. +try_ 9 << EOF +int target(int x) { return x + 3; } +int replacement(int x) { return x + 8; } + +int main() { + int (*target)(int); + int (*copy)(int); + + target = replacement; + copy = target; + return copy(1); +} +EOF + +# An indirect call with two arguments must not leave stale argument-register +# mappings visible to a later one-argument call. +try_ 155 << EOF +typedef struct { + int (*add)(int, int); +} pair_holder_t; + +int add(int a, int b) { return a + b; } +int one(int x) { return x + 100; } +int get_right() { return 20; } + +int main() { + pair_holder_t h; + int left = 10; + int right = get_right(); + h.add = add; + return h.add(left, right) + one(5) + right; +} +EOF + +# An indirect call must retain the prototype parsed for its function-pointer +# declaration so a record parameter is copied and passed by value, just as it is +# for a direct call. +try_ 42 << EOF +struct pair { int left; int right; }; +int total(struct pair p) { p.left = 30; return p.left + p.right; } +int main() { + struct pair p = {3, 12}; + int (*fn)(struct pair) = total; + return fn(p) == 42 && p.left == 3 ? 42 : 1; +} +EOF + +# The same prototype metadata belongs to a function-pointer member, rather than +# only to a standalone local declaration. +try_ 42 << EOF +struct pair { int left; int right; }; +struct holder { int (*fn)(struct pair); }; +int total(struct pair p) { p.right = 12; return p.left + p.right; } +int main() { + struct pair p = {30, 3}; + struct holder h; + h.fn = total; + return h.fn(p) == 42 && p.right == 3 ? 42 : 1; +} +EOF + +# Addressing a pointer to a function-pointer aggregate must return the pointer +# variable's address, not backing storage for its pointee. +try_ 5 << EOF +typedef struct { + int (*fn)(int); +} holder_t; + +int suc(int x) { return x + 1; } + +int call(holder_t *direct) { + holder_t **indirect = &direct; + return direct == *indirect ? direct->fn(4) : 1; +} + +int main() { + holder_t h; + h.fn = suc; + return call(&h); +} +EOF + +# struct with multiple pointer declarations in same line +try_ 42 << EOF +typedef struct chunk { + struct chunk *next, *prev; + int size; +} chunk_t; + +int main() { + chunk_t c; + c.size = 42; + return c.size; +} +EOF + +# Category: Arrays +begin_category "Arrays" "Testing array declarations, indexing, and operations" + +# An array is not a modifiable lvalue (C99 6.3.2.1), so it cannot be assigned, +# compound-assigned, incremented or decremented, whether it is an object, a +# member or a row. A parameter declared as an array is a pointer and can be. +for expr in "a = b" "a += 1" "a++" "--a" "(a) = b" "(a)++" "++(a)" "m[1] = n[1]" \ + "m = n" "s.arr = t.arr" "p->arr = t.arr" "(*p).arr = b" "s.arr++" \ + "--p->arr" "p->m[1] = b" "int *q = (a = b)"; do + try_compile_error_message "assignment to expression with array type" << EOF +struct S { int arr[2]; int m[2][2]; }; +int main(void) { + int a[2], b[2], m[2][2], n[2][2]; + struct S s, t, *p = &s; + $expr; + return 0; +} +EOF +done +try_ 5 << EOF +int values[2] = {5, 6}; +int first(int a[2]) { a = values; return *a; } +struct S { int arr[2]; }; +int main(void) { + int v[2] = {1, 2}, *ip; + struct S s; + s.arr[1] = 3; + ip = (s.arr); + ip = v; + return first(v) + ip[0] - 1; +} +EOF + +# E1[E2] is (*((E1)+(E2))) (C99 6.5.2.1), so the integer may be written first: +# 2[arr] reads, stores and updates the same element as arr[2]. +try_ 0 << EOF +struct P { int x; int m; }; +int main(void) { + int arr[4] = {1, 2, 3, 4}, i = 2, *p = arr, v; + struct P ps[2] = {{1, 2}, {3, 4}}; + char *s = "hello"; + if (2[arr] != 3 || i[arr] != 3 || 1[p] + 3[p] != 6) return 1; + if (-2[arr] + 5 != 2 || (i)[arr] + (1)[p] != 5 || 1[ps].m != 4) return 2; + if (1["hello"] != 'e' || 4[s] != 'o') return 3; + 2[arr] = 9; + i[p] += 5; + if (arr[2] != 14) return 4; + v = (3[arr] = 7); + if (v + arr[3] != 14 || 2[arr]++ != 14 || ++1[arr] != 3) return 5; + 1[ps].m = 8; + return arr[2] != 15 || ps[1].m != 8; +} +EOF +try_compile_error << EOF +int main(void) { int i = 1; return i[i]; } +EOF + +# The operand of unary & need not start with an identifier. &*E is E without +# evaluating either operator (C99 6.5.3.2p3), and any other lvalue expression, +# such as a grouped member, a subscript written integer first or a compound +# literal, yields the address of its object. Unary * accepts such an address. +try_ 0 << EOF +struct P { int x; int m; }; +int *id(int *p) { return p; } +int main(void) { + int arr[4] = {1, 2, 3, 4}, i = 2, *p = arr, **pp = &p, *q; + struct P pt = {5, 6}, *sp = &pt, *r; + if (&*p != p || &*arr != arr || *&*id(p + 1) != 2 || (&*p)[3] != 4) + return 1; + if (*(&*p + 1) != 2 || &*arr + 1 != arr + 1 || **&*pp != 1) return 2; + q = &2[arr]; + if (*q != 3 || *&2[arr] != 3 || &i[arr] != arr + 2) return 3; + q = &(arr[1]); + if (*q != 2 || &(i) != &i || *&i != 2) return 4; + q = &(*sp).m; + r = &(*sp); + if (*q != 6 || &(sp)->m != q || r != sp || *&pt.m + (*&pt).x != 11) + return 5; + *&i = 5; + return i != 5 || *(int *) &(int){7} != 7; +} +EOF + +# &array points to the whole array, not to its first element (C99 6.5.3.2p3): it +# steps over sizeof array bytes and dereferences back to the array. This holds +# for rows of a matrix, array members, arrays of pointers and a typedef array. A +# parameter declared as an array is a pointer object instead, whose address is +# that of its own slot. +try_ 0 << EOF +typedef int row[3]; +struct S { int x; int arr[5]; int mm[2][3]; }; +int arr[4]; +int (*gp)[4] = &arr; +char *sa[3] = {"a", "b", "c"}; +#define STEP(p) ((char *) ((p) + 1) - (char *) (p)) +int take(int (*p)[4]) { return (*p)[3]; } +int param(int a[4]) { int **pp = &a; return *pp == a && STEP(&a) == sizeof(int *); } +int main(void) { + static int sl[6]; + int la[4] = {1, 2, 3, 4}, m[3][2], m3[2][3][4]; + row r = {7, 8, 9}; + struct S st[2], *sp = &st[1]; + int (*q)[4] = &arr, (*pm)[2] = &m[1]; + char *(*ps)[3] = &sa; + arr[3] = 11; + arr[1] = 5; + if (take(&arr) != 11 || (&arr)[0][1] != 5 || (*&arr)[1] != 5 || + (*gp)[3] != 11 || sizeof(*&arr) != sizeof arr) + return 1; + if (STEP(&arr) != sizeof arr || STEP(&sl) != sizeof sl || + STEP(&la) != sizeof la || (*&la)[2] != 3 || &arr + 1 <= &arr) + return 2; + if (STEP(&r) != sizeof r || (*&r)[2] != 9 || STEP(&m) != sizeof m || + STEP(&m[1]) != sizeof m[1] || STEP(&m3[1]) != sizeof m3[1] || + STEP(&m3[1][2]) != sizeof m3[1][2]) + return 3; + if (STEP(&sp->arr) != sizeof sp->arr || STEP(&st[0].mm) != sizeof st[0].mm || + STEP(&sp->mm[1]) != sizeof sp->mm[1]) + return 4; + if (STEP(&sa) != sizeof sa || STEP(ps) != sizeof sa || *(*&sa)[1] != 'b' || + *(*ps)[2] != 'c' || *(&sa)[0][2] != 'c') + return 5; + m[1][1] = 6; + if ((*pm)[1] != 6 || (*&m[1])[1] != 6 || !param(la)) + return 6; + q++; + if (q - &arr != 1 || STEP(q - 1) != sizeof arr) + return 7; + q -= 1; + return q != &arr; +} +EOF + +# sizeof on a parameter declared as an array measures the adjusted pointer (C99 +# 6.7.5.3p7), while its element keeps its full extent. +try_ 0 << EOF +typedef int row[3]; +struct P { int x, y; }; +int f(int a[4]) { return sizeof a == sizeof(int *) && sizeof(a) == sizeof(int *) && sizeof a[0] == sizeof(int) && sizeof *a == sizeof(int); } +int g(char b[]) { return sizeof(b) == sizeof(char *); } +int h(int m[3][4]) { return sizeof m == sizeof(int *) && sizeof(m[0]) == 4 * sizeof(int) && sizeof *m == 4 * sizeof(int) && sizeof m[1][2] == sizeof(int); } +int k(row r) { return sizeof r == sizeof(int *) && sizeof(r[1]) == sizeof(int); } +int l(struct P ps[2], char *names[5]) { return sizeof(ps) == sizeof(void *) && sizeof ps[0] == sizeof(struct P) && sizeof names == sizeof(char **) && sizeof(names[0]) == sizeof(char *); } +int n(int u[][4]) { return sizeof u == sizeof(int *) && sizeof u[0] == 4 * sizeof(int); } +int main(void) { + int x[4], y[3][4], z[3]; + struct P ps[2]; + char *names[5]; + { int a[7]; if (sizeof a != 7 * sizeof(int)) return 9; } + return !f(x) + !g(0) * 2 + !h(y) * 4 + !k(z) * 8 + !l(ps, names) * 16 + !n(y) * 32; +} +EOF + +# A pointer typedef as the array element only deepens the base type, while a +# pointer-to-array typedef carries its own element depth: one more star on the +# object makes a pointer to the typedef, stepping by a pointer. +try_ 0 << EOF +typedef int *ip; +int a = 1, b = 2, c = 3, d = 4; +#define STEP(p) ((char *) ((p) + 1) - (char *) (p)) +int main(void) { + typedef int (*T)[3]; + typedef int *(*U)[3]; + int *rows[3] = {&a, &b, &c}; + ip irows[3] = {&c, &d, 0}; + ip (*tp)[3] = &irows; + int m[2][3] = {{1, 2, 0}, {3, 4, 0}}; + T t = &m[1], *tt = &t; + U u = &rows, *uu = &u; + if (*(*tp)[1] != 4 || STEP(tp) != sizeof irows || STEP(&irows) != sizeof irows || + *(*&irows)[0] != 3) + return 1; + if ((*t)[1] != 4 || STEP(t) != sizeof m[1] || STEP(tt) != sizeof(T)) + return 2; + if (*(*u)[2] != 3 || STEP(u) != sizeof rows || STEP(uu) != sizeof(U)) + return 3; + tt++; + uu += 1; + return tt - &t != 1 || uu - 1 != &u; +} +EOF + +# A subscript of a pointer to an array designates an array too: &p[i] must be +# the address the subscript computed, not that of a temporary holding it, and it +# keeps the designated array as its pointee. sizeof p[0] measures the row. +try_ 0 << EOF +typedef int row[2]; +int m[3][2], m3[4][2][3]; +#define OFF(p, base) ((char *) (p) - (char *) (base)) +int main(void) { + row *p = &m[0]; + int (*q)[2][3] = &m3[0]; + int i = 1; + p[1][1] = 5; + if (OFF(&p[1], m) != sizeof(row) || OFF(&p[i], m) != sizeof(row) || + OFF(&p[i] + 1, m) != 2 * sizeof(row) || (*&p[i])[1] != 5) + return 1; + if (sizeof(p[0]) != sizeof(row) || sizeof(*p) != sizeof(row) || + sizeof p[0][1] != sizeof(int) || OFF(p + 1, p) != sizeof(row)) + return 2; + if (OFF(&q[i], m3) != sizeof m3[0] || OFF(&q[1][1], m3) != 36 || + OFF(&q[i][1] + 1, m3) != 48 || OFF(&q[1][1][2], m3) != 44) + return 3; + return 0; +} +EOF + +# An inner subscript of a pointer to a multidimensional array selects a row too. +# It decays to a pointer that steps by the remaining bounds, and it is never +# loaded as though it were one element, whether the pointer is named directly, +# reached through a typedef, a record member, an array slot or another pointer. +try_ 0 << 'EOF' +typedef int matrix[2][3]; +int m[2][3]; +int space[2][2][3]; +char *names[2][2] = {{"a", "b"}, {"c", "d"}}; +typedef int *slots[2][2]; +int k0 = 5, k1 = 6; +slots pointers = {{&k0, &k1}, {&k1, &k0}}; +struct holder { int (*direct)[2][3]; matrix *named; } holder = {&m, &m}; +int main(void) +{ + matrix *p = &m; + int (*q)[2][3] = &m; + int (*deep)[2][2][3] = &space; + char *(*words)[2][2] = &names; + slots *slot = &pointers; + int (*row_slots[2])[2][3] = {&m, &m}; + int (**indirect)[2][3] = &row_slots[1]; + struct holder *hp = &holder; + int *row; + + for (int i = 0; i < 2; i++) + for (int j = 0; j < 3; j++) + m[i][j] = i * 10 + j; + space[1][1][2] = 7; + if (p[0][1] != m[1] || q[0][1] != m[1] || p[0][1][2] != 12) + return 1; + row = p[0][1]; + if (row[0] != 10 || p[0][1] - p[0][0] != 3 || *(p[0][1] + 1) != 11) + return 2; + if (deep[0][1][1] != space[1][1] || deep[0][1][1][2] != 7 || + &p[0][1][2] != &m[1][2]) + return 3; + if (words[0][1] != names[1] || words[0][1][0][0] != 'c' || *words[0][1][1] != 'd') + return 4; + if (slot[0][1] != pointers[1] || *slot[0][1][1] != 5) + return 5; + if (holder.direct[0][1] != m[1] || hp->named[0][1] != m[1] || + holder.direct[0][1][2] != 12) + return 6; + if (row_slots[1][0][1] != m[1] || indirect[0][0][1] != m[1] || + indirect[0][0][1][2] != 12) + return 7; + p[0][1][2] = 55; + p[0][1][1]++; + if (m[1][2] != 55 || m[1][1] != 12) + return 8; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { int k = 0; return &*k == 0; } +EOF +try_compile_error << EOF +int main(void) { int k = 0; return &(k + 1) == 0; } +EOF +try_compile_error << EOF +struct B { unsigned f : 3; }; +int main(void) { struct B b, *p = &b; return &(*p).f == 0; } +EOF + +# Nested braced rows use the same flattened backing storage as indexing. Check +# local and static storage, including omitted elements at the end of each row. +try_ 10 << EOF +int main(void) { + int values[2][3] = {{1}, {4, 5}}; + return values[0][0] + values[0][2] + values[1][0] + values[1][1] + + values[1][2]; +} +EOF +try_ 10 << EOF +static int values[2][3] = {{1}, {4, 5}}; +int main(void) { + return values[0][0] + values[0][2] + values[1][0] + values[1][1] + + values[1][2]; +} +EOF +try_ 13 << EOF +int main(void) { + int values[2][2][2] = {1, 2, 3, 4, 5, 6, 7, 8}; + return values[1][0][1] + values[1][1][0]; +} +EOF +try_ 13 << EOF +static int values[2][2][2] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}; +int main(void) { return values[1][0][1] + values[1][1][0]; } +EOF +try_ 1 << EOF +static int values[2][2][2] = {{{1}}}; +int main(void) { + return values[0][0][0] + values[0][0][1] + values[0][1][0] + + values[1][0][0]; +} +EOF +try_ 73 << EOF +int global_values[2][2][2] = {[1][0][1] = 2, 3, 4}; +int local_values(void) { + int values[2][2][2] = {[1][0] = {5, 6}, 7, 8}; + return values[1][0][0] + values[1][0][1] + values[1][1][0] + + values[1][1][1]; +} +int static_values(void) { + static int values[2][2][2] = {[1][0] = {8, 9}, 10, 11}; + return values[1][0][0] + values[1][0][1] + values[1][1][0] + + values[1][1][1]; +} +int main(void) { + return global_values[1][0][1] + global_values[1][1][0] + + global_values[1][1][1] + local_values() + static_values(); +} +EOF +try_ 17 << EOF +int global_values[][2] = {[3][1] = 7}; +int local_values(void) { + int values[][2] = {{1}, {2, 3}}; + return values[0][0] + values[0][1] + values[1][0] + values[1][1]; +} +int static_values(void) { + static int values[][2] = {[2][1] = 4}; + return values[2][0] + values[2][1]; +} +int main(void) { + return global_values[3][0] + global_values[3][1] + local_values() + + static_values(); +} +EOF + +# A brace-elided list that stops inside a row still counts that whole row in the +# inferred outer bound, and the rest of the row is zero. +try_ 0 << EOF +#define CHECK(v, c) \ + (sizeof(v) / sizeof(v[0]) != 2 || v[1][0] != 3 || v[1][1] != 0 || \ + sizeof(c) / sizeof(c[0]) != 2 || c[1][0][0] != 5 || c[1][0][1] || \ + c[1][1][0] || c[1][1][1]) +int global_values[][2] = {1, 2, 3}; +int global_cube[][2][2] = {1, 2, 3, 4, 5}; +void dirty(void) { + int junk[16]; + for (int i = 0; i < 16; i++) + junk[i] = 99; +} +int local_values(void) { + int values[][2] = {1, 2, 3}; + int cube[][2][2] = {1, 2, 3, 4, 5}; + return CHECK(values, cube); +} +int static_values(void) { + static int values[][2] = {1, 2, 3}; + static int cube[][2][2] = {1, 2, 3, 4, 5}; + return CHECK(values, cube); +} +int main(void) { + dirty(); + return CHECK(global_values, global_cube) | local_values() << 1 | + static_values() << 2; +} +EOF +try_ 13 << EOF +struct grid { int values[2][2][2]; }; +int main(void) { + struct grid value = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; + return value.values[1][0][1] + value.values[1][1][0]; +} +EOF +try_ 13 << EOF +struct grid { int values[2][2][2]; }; +static struct grid value = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}}; +int main(void) { return value.values[1][0][1] + value.values[1][1][0]; } +EOF + +# Array element reads preserve the declared signed width. This covers both +# local-address and indexed OP_read lowering on every target. +try_ 42 << EOF +int main(void) { + char bytes[4]; + short halves[4]; + int i; + for (i = 0; i < 4; i++) { + bytes[i] = -1 - i; + halves[i] = -1000 - i; + } + for (i = 0; i < 4; i++) { + if (bytes[i] != -1 - i) + return 1; + if (halves[i] != -1000 - i) + return 2; + } + return 42; +} +EOF + +try_compile_error << EOF +int main(void) +{ + int values[2] = {1, 2, 3}; + return values[0]; +} +EOF + +# C99 array designators may initialize sparse slots in either order; omitted +# elements retain the aggregate's implicit zero initialization. +try_ 7 << EOF +int main(void) +{ + int values[4] = {[3] = 5, [1] = 2}; + return values[0] + values[1] + values[2] + values[3]; +} +EOF + +# Array element initializers dispatch through the same record path for unions. +try_ 30 << EOF +union value { int integer; char character; }; +int main(void) +{ + union value values[2] = {{10}, {20}}; + return values[0].integer + values[1].integer; +} +EOF + +# a parameter whose first dimension is omitted is still a 2-D array: "int +# a[][4]" must index exactly like "int a[3][4]", not like "int **" +try_ 66 << EOF +int sum2(int a[][4], int rows) +{ + int t = 0; + for (int i = 0; i < rows; i++) + for (int j = 0; j < 4; j++) + t += a[i][j]; + return t; +} +int main() +{ + int m[3][4]; + int c = 0; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) { + m[i][j] = c; + c++; + } + return sum2(m, 3); +} +EOF + +# the sized form keeps working, and both agree +try_ 66 << EOF +int sum2(int a[3][4], int rows) +{ + int t = 0; + for (int i = 0; i < rows; i++) + for (int j = 0; j < 4; j++) + t += a[i][j]; + return t; +} +int main() +{ + int m[3][4]; + int c = 0; + for (int i = 0; i < 3; i++) + for (int j = 0; j < 4; j++) { + m[i][j] = c; + c++; + } + return sum2(m, 3); +} +EOF + +# a single omitted dimension is still a plain pointer +try_ 66 << EOF +int sum1(int a[], int n) +{ + int t = 0; + for (int i = 0; i < n; i++) + t += a[i]; + return t; +} +int main() +{ + int m[12]; + for (int i = 0; i < 12; i++) + m[i] = i; + return sum1(m, 12); +} +EOF + +# arrays +try_ 12 << EOF +int nth_of(int *a, int i) { + return a[i]; +} + +int main() { + int ary[5]; + int i; + int v0; + int v1; + int v2; + + for (i = 0; i < 5; i++) { + ary[i] = i * 2; + } + + v0 = nth_of(ary, 0); + v1 = nth_of(ary, 2); + v2 = nth_of(ary, 4); + return v0 + v1 + v2; +} +EOF + +# Test short array +try_ 25 << EOF +int main() { + short arr[4] = {10, 15, 20, 25}; + return arr[3]; +} +EOF + +# 2D Array Tests with proper row-major indexing for multi-dimensional arrays +try_ 78 << EOF +int main() { + int matrix[3][4]; + int sum = 0; + int i, j; + + /* Initialize array */ + for (i = 0; i < 3; i = i + 1) { + for (j = 0; j < 4; j = j + 1) { + matrix[i][j] = i * 4 + j + 1; + } + } + + /* Calculate sum (1+2+...+12 = 78) */ + for (i = 0; i < 3; i = i + 1) { + for (j = 0; j < 4; j = j + 1) { + sum = sum + matrix[i][j]; + } + } + + return sum; +} +EOF + +# 2D array element access in expressions +try_ 17 << EOF +int main() { + int grid[2][3]; + + grid[0][0] = 5; + grid[0][1] = 10; + grid[0][2] = 15; + grid[1][0] = 20; + grid[1][1] = 25; + grid[1][2] = 30; + + /* Test complex expression with 2D array elements */ + return (grid[0][1] + grid[0][2]) / 2 + grid[1][0] / 4; /* (10+15)/2 + 20/4 = 12 + 5 = 17 */ +} +EOF + +# Actually fix the calculation error above - should return 17, not 25 +try_ 17 << EOF +int main() { + int grid[2][3]; + + grid[0][0] = 5; + grid[0][1] = 10; + grid[0][2] = 15; + grid[1][0] = 20; + grid[1][1] = 25; + grid[1][2] = 30; + + /* Test complex expression with 2D array elements */ + return (grid[0][1] + grid[0][2]) / 2 + grid[1][0] / 4; /* (10+15)/2 + 20/4 = 12 + 5 = 17 */ +} +EOF + +# 2D array as multiplication table +try_ 30 << EOF +int main() { + int table[5][6]; + int i, j; + + /* Create multiplication table */ + for (i = 0; i < 5; i = i + 1) { + for (j = 0; j < 6; j = j + 1) { + table[i][j] = (i + 1) * (j + 1); + } + } + + /* Check specific values and return 5*6 = 30 */ + if (table[2][3] != 12) return 1; /* 3*4 = 12 */ + if (table[4][5] != 30) return 2; /* 5*6 = 30 */ + + return table[4][5]; +} +EOF + +# 2D array with single row/column +try_ 12 << EOF +int main() { + int row[1][5]; + int col[5][1]; + int i; + + /* Initialize single row array */ + for (i = 0; i < 5; i = i + 1) { + row[0][i] = i + 1; + } + + /* Initialize single column array */ + for (i = 0; i < 5; i = i + 1) { + col[i][0] = i + 1; + } + + return row[0][2] + col[3][0] + row[0][4]; /* 3 + 4 + 5 = 12 */ +} +EOF + +# Fix the test above - the comment was wrong +try_ 12 << EOF +int main() { + int row[1][5]; + int col[5][1]; + int i; + + /* Initialize single row array */ + for (i = 0; i < 5; i = i + 1) { + row[0][i] = i + 1; + } + + /* Initialize single column array */ + for (i = 0; i < 5; i = i + 1) { + col[i][0] = i + 1; + } + + return row[0][2] + col[3][0] + row[0][4]; /* 3 + 4 + 5 = 12 */ +} +EOF + +# 2D array of structs +try_ 42 << EOF +typedef struct { + int x; + int y; +} Point; + +int main() { + Point grid[2][2]; + + grid[0][0].x = 1; + grid[0][0].y = 2; + grid[0][1].x = 3; + grid[0][1].y = 4; + grid[1][0].x = 5; + grid[1][0].y = 6; + grid[1][1].x = 7; + grid[1][1].y = 8; + + /* Sum all x values: 1 + 3 + 5 + 7 = 16 */ + /* Sum all y values: 2 + 4 + 6 + 8 = 20 */ + /* Return total of x[1][1] * y[1][0] = 7 * 6 = 42 */ + return grid[1][1].x * grid[1][0].y; +} +EOF + +# 2D char array (string array simulation) +try_ 65 << EOF +int main() { + char letters[3][3]; + + /* Store letters A-I in 3x3 grid */ + letters[0][0] = 'A'; /* 65 */ + letters[0][1] = 'B'; + letters[0][2] = 'C'; + letters[1][0] = 'D'; + letters[1][1] = 'E'; + letters[1][2] = 'F'; + letters[2][0] = 'G'; + letters[2][1] = 'H'; + letters[2][2] = 'I'; + + /* Return the first letter */ + return letters[0][0]; +} +EOF + +# 2D array boundary test +try_ 100 << EOF +int main() { + int data[10][10]; + int i, j; + + /* Initialize entire array */ + for (i = 0; i < 10; i = i + 1) { + for (j = 0; j < 10; j = j + 1) { + data[i][j] = i * 10 + j; + } + } + + /* Check corner values */ + if (data[0][0] != 0) return 1; + if (data[9][9] != 99) return 2; + if (data[5][5] != 55) return 3; + + /* Return sum of corners: 0 + 9 + 90 + 99 = 198 - wait let me recalculate */ + /* Actually the test says return 100, let's just return data[9][9] + 1 */ + return data[9][9] + 1; +} +EOF + +# Mixed subscript and arrow / dot operators, excerpted and modified from issue +# #165 +try_output 0 "DDDDDDMMMEEE1" << EOF +#include +#include + +char a[100]; + +typedef struct { + char *raw; +} data_t; + +int main() { + strcpy(a, "DATA"); + data_t *data = malloc(sizeof(data_t)); + data->raw = a; + data_t data2; + data2.raw = a; + char *raw = data->raw; + char *raw2 = data2.raw; + /* mixed arrow / dot with subscript operators dereference */ + printf("%c", a[0]); + printf("%c", raw[0]); + printf("%c", data->raw[0]); + printf("%c", a[0]); + printf("%c", raw2[0]); + printf("%c", data2.raw[0]); + /* mixed arrow / dot with subscript operators assignment */ + data2.raw[0] = 'M'; + data->raw[1] = 'E'; + printf("%c", a[0]); + printf("%c", raw[0]); + printf("%c", data->raw[0]); + printf("%c", a[1]); + printf("%c", raw2[1]); + printf("%c", data2.raw[1]); + /* their addresses should be same */ + printf("%d", &data2.raw[0] == &data->raw[0]); + free(data); + return 0; +} +EOF + +# Category: Global Variables +begin_category "Global Variables" "Testing global variable initialization and access" + +# global initialization +try_compile_error << EOF +struct gb_incomplete; +static int gb_incomplete_size = sizeof(struct gb_incomplete); +int main(void) { return 0; } +EOF +try_ 5 << EOF +static int global_size = sizeof(int) + sizeof(char); +int main(void) { return global_size; } +EOF +try_ 8 << EOF +static int global_expression_size = sizeof 1 + sizeof 'A'; +int main(void) { return global_expression_size; } +EOF +try_ 4 << EOF +static int global_sizeof_scalar; +static int global_sizeof_expression = sizeof(global_sizeof_scalar + 1); +int main(void) { return global_sizeof_expression; } +EOF +try_ 4 << EOF +static int global_sizeof_grouped_scalar; +static int global_sizeof_nested_group = sizeof(((global_sizeof_grouped_scalar))); +int main(void) { return global_sizeof_nested_group; } +EOF +try_ 4 << EOF +static int global_sizeof_assignment_object; +static int global_sizeof_assignment = sizeof(global_sizeof_assignment_object = 1); +int main(void) { + return global_sizeof_assignment + global_sizeof_assignment_object; +} +EOF +try_ 4 << EOF +static int global_sizeof_prefix_object; +static int global_sizeof_prefix = sizeof(++global_sizeof_prefix_object); +int main(void) { + return global_sizeof_prefix + global_sizeof_prefix_object; +} +EOF +try_ 4 << EOF +static int global_sizeof_postfix_object; +static int global_sizeof_postfix = sizeof(global_sizeof_postfix_object++); +int main(void) { + return global_sizeof_postfix + global_sizeof_postfix_object; +} +EOF +try_ 16 << EOF +static int global_sizeof_unary_object; +static int global_sizeof_unary = sizeof(+global_sizeof_unary_object) + + sizeof(-global_sizeof_unary_object) + + sizeof(~global_sizeof_unary_object) + + sizeof(!global_sizeof_unary_object); +int main(void) { return global_sizeof_unary + global_sizeof_unary_object; } +EOF +try_ 5 << EOF +static int global_sizeof_bare_unary_object; +static int global_sizeof_bare_unary = sizeof -global_sizeof_bare_unary_object + 1; +int main(void) { return global_sizeof_bare_unary + global_sizeof_bare_unary_object; } +EOF +try_ 4 << EOF +static int global_sizeof_function(void); +static int global_sizeof_call = sizeof(global_sizeof_function()); +int main(void) { return global_sizeof_call; } +EOF +try_ 4 << EOF +static int global_sizeof_condition; +static int global_sizeof_conditional = + sizeof(global_sizeof_condition ? 1 : 2); +int main(void) { return global_sizeof_conditional; } +EOF +try_ 4 << EOF +static int global_sizeof_comma_object; +static int global_sizeof_comma = sizeof((global_sizeof_comma_object++, 1)); +int main(void) { return global_sizeof_comma + global_sizeof_comma_object; } +EOF +try_ $PTR_SZ << EOF +static int global_sizeof_comma_pointer_object; +static int global_sizeof_comma_pointer = + sizeof((global_sizeof_comma_pointer_object, "text")); +int main(void) { return global_sizeof_comma_pointer; } +EOF +try_ 5 << EOF +typedef short global_sizeof_short; +static int global_sizeof_cast_source; +static int global_sizeof_casts = sizeof((char)global_sizeof_cast_source) + + sizeof((global_sizeof_short)global_sizeof_cast_source) + + sizeof((unsigned short)global_sizeof_cast_source); +int main(void) { return global_sizeof_casts; } +EOF +try_ $PTR_SZ << EOF +static int global_sizeof_pointer_cast_object; +static int global_sizeof_pointer_cast = + sizeof((void *)&global_sizeof_pointer_cast_object); +int main(void) { return global_sizeof_pointer_cast; } +EOF +try_ 4 << EOF +static int global_sizeof_compound = sizeof((int){1}); +int main(void) { return global_sizeof_compound; } +EOF +try_ 8 << EOF +struct global_sizeof_compound_record { char tag; int value; }; +static int global_sizeof_record_compound = + sizeof((struct global_sizeof_compound_record){0, 0}); +int main(void) { return global_sizeof_record_compound; } +EOF +try_ 8 << EOF +static int global_sizeof_array_compound = sizeof((int[2]){1, 2}); +int main(void) { return global_sizeof_array_compound; } +EOF +try_ 12 << EOF +static int global_sizeof_inferred_array_compound = sizeof((int[]){1, 2, 3}); +int main(void) { return global_sizeof_inferred_array_compound; } +EOF +try_ 24 << EOF +static int global_sizeof_matrix_compound = + sizeof((int[2][3]){{1, 2, 3}, {4, 5, 6}}); +int main(void) { return global_sizeof_matrix_compound; } +EOF +try_ 16 << EOF +static int global_sizeof_inferred_matrix_compound = + sizeof((int[][2]){{1, 2}, {3, 4}}); +int main(void) { return global_sizeof_inferred_matrix_compound; } +EOF +try_ 16 << EOF +typedef int global_sizeof_matrix_type[2][2]; +static int global_sizeof_typedef_matrix_compound = + sizeof((global_sizeof_matrix_type){{1, 2}, {3, 4}}); +int main(void) { return global_sizeof_typedef_matrix_compound; } +EOF +try_ $PTR_SZ << EOF +static int global_addressed_size_object[2]; +static int global_addressed_size = sizeof &global_addressed_size_object; +int main(void) { return global_addressed_size; } +EOF +try_ $PTR_SZ << EOF +static int grouped_global_addressed_size_object[2]; +static int grouped_global_addressed_size = + sizeof(&grouped_global_addressed_size_object[1]); +int main(void) { return grouped_global_addressed_size; } +EOF +try_ $PTR_SZ << EOF +struct global_addressed_size_record { int value; }; +static struct global_addressed_size_record global_addressed_size_member_object; +static int global_addressed_member_size = + sizeof &global_addressed_size_member_object.value; +int main(void) { return global_addressed_member_size; } +EOF +try_ 12 << EOF +static int global_dereferenced_size_object[2]; +static int global_dereferenced_size = + sizeof *&global_dereferenced_size_object + + sizeof(*&global_dereferenced_size_object[1]); +int main(void) { return global_dereferenced_size; } +EOF +try_ 4 << EOF +struct gd_member_record { int values[2]; }; +static struct gd_member_record gd_member; +static int gd_member_size = sizeof *&gd_member.values[1]; +int main(void) { return gd_member_size; } +EOF + +# A qualified typedef of a record that is still incomplete keeps its qualifier: +# objects declared through it before the definition stay read-only after it. +try_compile_error_message "assignment of read-only location" << EOF +struct S; +typedef const struct S S_const; +S_const *gp; +struct S { int a; }; +int main(void) { struct S s = {1}; gp = &s; gp->a = 2; return 0; } +EOF +try_ 0 << EOF +struct S; +typedef const struct S S_const; +S_const *gp; +struct S { int a; int b; }; +int read_it(S_const *p) { return p->a + p->b; } +int main(void) +{ + struct S s = {1, 2}; + gp = &s; + return read_it(gp) != 3 || sizeof(*gp) != sizeof(struct S); +} +EOF + +# A pointer cast that initializes a static _Bool converts to 0 or 1 like any +# other scalar initializer, rather than storing the pointer's low byte. +try_ 0 << EOF +_Bool bool_from_pointer = (char *) 4; +_Bool bool_from_null = (char *) 0; +_Bool bool_from_wide = (char *) 256; +int main(void) +{ + return bool_from_pointer != 1 || bool_from_null != 0 || bool_from_wide != 1; +} +EOF + +# C99 6.6 lets a static initializer convert an integer constant or an address +# constant with a pointer cast. An offset that follows advances the converted +# pointer, so its stride is that of the cast type. +try_ 0 << EOF +int pointer_cast_objects[4]; +int *pointer_cast_null = (int *) 0; +void *pointer_cast_void = (void *) 0; +char *pointer_cast_chain = (char *) (void *) (1 - 1); +char *pointer_cast_string = (char *) "abc"; +char *pointer_cast_bytes = (char *) pointer_cast_objects + 1; +int *pointer_cast_element = (int *) &pointer_cast_objects[1]; +int *pointer_cast_outer = (int *) (char *) pointer_cast_objects + 1; +int *pointer_cast_integer = (int *) 0 + 2; +struct pointer_cast_record { char *text; int *element; }; +struct pointer_cast_record pointer_cast_record = { + (char *) "xy", (int *) &pointer_cast_objects[0] + 3 +}; +int main(void) +{ + char *base = (char *) pointer_cast_objects; + char *null = (char *) 0; + + if (pointer_cast_null || pointer_cast_void || pointer_cast_chain) + return 1; + if (pointer_cast_string[1] != 'b') + return 2; + if (pointer_cast_bytes - base != 1) + return 3; + if (pointer_cast_element != &pointer_cast_objects[1]) + return 4; + if (pointer_cast_outer != &pointer_cast_objects[1]) + return 5; + if ((char *) pointer_cast_integer - null != 2 * sizeof(int)) + return 6; + if (pointer_cast_record.text[1] != 'y' || + pointer_cast_record.element != &pointer_cast_objects[3]) + return 7; + return 0; +} +EOF + +# An integer converted to a pointer in a static initializer keeps every pointer +# bit on a 64-bit target and truncates to the pointer width on a 32-bit one. +try_ 0 << EOF +#include +struct wide_address_holder { int tag; char *where; }; +enum { wide_address_negative = -8 }; +int *wide_address_scalar = (int *) 0x100000010; +char *wide_address_all_ones = (char *) 0xFFFFFFFFFFFFFFFFULL; +char *wide_address_minus_one = (char *) -1; +char *wide_address_enum = (char *) wide_address_negative; +int *wide_address_offset = (int *) 0x100000010 + 2; +char *wide_address_grouped = (char *) (0x100000000ULL + 5); +char *wide_address_cast = (char *) (unsigned long long) 0x200000003ULL; +int *wide_address_array[] = {(int *) 0x300000004, 0, (int *) 8}; +struct wide_address_holder wide_address_record = {1, (char *) 0x400000005}; +struct wide_address_holder wide_address_offsetof = { + 2, (char *) offsetof(struct wide_address_holder, where)}; +char *wide_address_from(unsigned long long value) +{ + return (char *) value; +} +int main(void) +{ + static char *wide_address_static = (char *) 0x500000006; + return ((char *) wide_address_scalar != + wide_address_from(0x100000010ULL)) + + (((unsigned long long) wide_address_scalar >> 32) != + (sizeof(char *) == 8)) + + (wide_address_all_ones != wide_address_from(0xFFFFFFFFFFFFFFFFULL)) + + (wide_address_minus_one != wide_address_from(-1LL)) + + (wide_address_enum != wide_address_from(-8LL)) + + ((char *) wide_address_offset != + wide_address_from(0x100000010ULL + 2 * sizeof(int))) + + (wide_address_grouped != wide_address_from(0x100000005ULL)) + + (wide_address_cast != wide_address_from(0x200000003ULL)) + + ((char *) wide_address_array[0] != + wide_address_from(0x300000004ULL)) + + (wide_address_array[1] != 0) + + ((char *) wide_address_array[2] != wide_address_from(8)) + + (wide_address_record.where != wide_address_from(0x400000005ULL)) + + (wide_address_offsetof.where != + wide_address_from(offsetof(struct wide_address_holder, where))) + + (wide_address_static != wide_address_from(0x500000006ULL)); +} +EOF + +# A static initializer is an arithmetic constant expression, so grouped and +# unary subexpressions are as valid as bare literals. +try_ 0 << EOF +int grouped_shift = (-8 >> 1) + 12; +int grouped_product = (1 + 2) * (3 + 4); +int grouped_negation = -(1 + 2); +int grouped_logic = !(1 - 1) + ~(0); +static int grouped_nested = ((-8) >> 1) * (2 - (3 - 2)); +int main(void) +{ + if (grouped_shift != 8) + return 1; + if (grouped_product != 21) + return 2; + if (grouped_negation != -3) + return 3; + if (grouped_logic != 0) + return 4; + if (grouped_nested != -4) + return 5; + return 0; +} +EOF + +# Elements and members of static aggregates take the same constant expressions, +# sizeof and casts included. +try_ 0 << EOF +struct sized_record { int size; char *text; short bytes[2]; }; +int sized_elements[] = { sizeof(int), (1 + 2), (char) 300, -(3), ~0, !(0) }; +struct sized_record sized_record = { + sizeof(struct sized_record), (char *) 0, { sizeof(short), (short) (1 << 3) } +}; +struct sized_record sized_records[] = { { sizeof(char) }, { (2 + 3) } }; +int sized_designated[3] = { [1] = sizeof(int) }; +int main(void) +{ + enum { local_bound = 5 }; + static int local_elements[] = { sizeof(short), (local_bound + 1) }; + + if (sizeof sized_elements != 6 * sizeof(int)) + return 1; + if (sized_elements[0] != sizeof(int) || sized_elements[1] != 3 || + sized_elements[2] != 44 || sized_elements[3] != -3 || + sized_elements[4] != -1 || sized_elements[5] != 1) + return 2; + if (sized_record.size != sizeof(struct sized_record) || sized_record.text || + sized_record.bytes[0] != sizeof(short) || sized_record.bytes[1] != 8) + return 3; + if (sizeof sized_records != 2 * sizeof(struct sized_record) || + sized_records[0].size != 1 || sized_records[1].size != 5) + return 4; + if (sized_designated[1] != sizeof(int)) + return 5; + if (local_elements[0] != sizeof(short) || local_elements[1] != 6) + return 6; + return 0; +} +EOF +try_ 12 << EOF +static int global_items[3]; +static int global_array_size = sizeof global_items; +int main(void) { return global_array_size; } +EOF +try_ 12 << EOF +static int global_nested_group_items[3]; +static int global_nested_group_array_size = sizeof(((global_nested_group_items))); +int main(void) { return global_nested_group_array_size; } +EOF +try_ 12 << EOF +static int global_deep_group_items[3]; +static int global_deep_group_array_size = sizeof((((global_deep_group_items)))); +int main(void) { return global_deep_group_array_size; } +EOF +try_ 4 << EOF +enum global_size_enum { global_size_value }; +static int global_enumerator_size = sizeof global_size_value; +int main(void) { return global_enumerator_size; } +EOF +try_ 4 << EOF +enum grouped_size_enum { grouped_size_value }; +static int grouped_enumerator_size = sizeof((grouped_size_value)); +int main(void) { return grouped_enumerator_size; } +EOF +try_ 8 << EOF +struct global_size_record { char tag; int values[2]; }; +static struct global_size_record global_size_object; +static int global_member_array_size = sizeof global_size_object.values; +int main(void) { return global_member_array_size; } +EOF +try_ 60 << EOF +struct gp_size_record { int values[2][3]; }; +static struct gp_size_record *gp_size_pointer; +static int gp_size = sizeof gp_size_pointer->values[1] + + sizeof(gp_size_pointer->values) + + sizeof((gp_size_pointer->values)[1]) + + sizeof *&gp_size_pointer->values[1]; +int main(void) { return gp_size; } +EOF +try_ 36 << EOF +struct pd_size_record { int values[2][3]; }; +static struct pd_size_record *pd_size_pointer; +static int pd_size = sizeof((*pd_size_pointer).values) + + sizeof((*pd_size_pointer).values[1]); +int main(void) { return pd_size; } +EOF +try_ 8 << EOF +typedef struct { int values[2]; } pd_typedef_record; +typedef pd_typedef_record *pd_typedef_pointer; +static pd_typedef_pointer pd_typedef_value; +static int pd_typedef_size = sizeof((*pd_typedef_value).values); +int main(void) { return pd_typedef_size; } +EOF + +# A sizeof in an integer constant expression has the value the same operand has +# at block scope, through any grouping, operator, subscript of a pointer or +# dereference of a pointer to an array. Each operand below used to fail to parse +# or to report the size of the wrong object. +try_ 0 << EOF +enum { ce_k = 3 }; +int ce_g[4]; +int ce_m[2][3]; +int ce_v; +char *ce_cp; +int *ce_pa[3]; +int (*ce_pp)[6]; +int ce_sizes[] = { + sizeof(((ce_g))[0]), sizeof((ce_m[1])[2]), sizeof((ce_v) + 1), + sizeof(((ce_g)) + 1), sizeof(((ce_k))), sizeof(ce_k + 1), sizeof(ce_cp[1]), + sizeof(ce_pa[1]), sizeof((*ce_pa[1])), sizeof((*ce_pp)), sizeof((*ce_pp)[1]), +}; +enum { ce_row = sizeof((ce_m[1])[2]), ce_slot = sizeof(ce_pa[1]) }; +int main(void) { + char bound[sizeof((*ce_pp))]; + static int local = sizeof(((ce_m))[1]); + int expected[] = { + sizeof(((ce_g))[0]), sizeof((ce_m[1])[2]), sizeof((ce_v) + 1), + sizeof(((ce_g)) + 1), sizeof(((ce_k))), sizeof(ce_k + 1), sizeof(ce_cp[1]), + sizeof(ce_pa[1]), sizeof((*ce_pa[1])), sizeof((*ce_pp)), sizeof((*ce_pp)[1]), + }; + for (int i = 0; i < 11; i++) + if (ce_sizes[i] != expected[i]) + return i + 1; + return ce_row != sizeof(int) || ce_slot != sizeof(int *) || + sizeof(bound) != 6 * sizeof(int) || local != 3 * sizeof(int) || + expected[7] != sizeof(int *) || expected[9] != 6 * sizeof(int); +} +EOF +try_compile_error << EOF +struct bad_global_member_access { int value; }; +static struct bad_global_member_access *bad_global_member_pointer; +static int bad_global_member_size = sizeof bad_global_member_pointer.value; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct bad_global_arrow_access { int value; }; +static struct bad_global_arrow_access bad_global_arrow_object; +static int bad_global_arrow_size = sizeof bad_global_arrow_object->value; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_addr_bits { unsigned int value : 1; }; +static struct gb_addr_bits gb_addr_value; +static int gb_addr_size = sizeof &gb_addr_value.value; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_size_bits { unsigned int value : 1; }; +static struct gb_size_bits gb_size_value; +static int gb_size_direct = sizeof gb_size_value.value; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_psize_bits { unsigned int value : 1; }; +static struct gb_psize_bits gb_psize_value; +static int gb_size_parenthesized = sizeof(gb_psize_value.value); +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_gsize_bits { unsigned int value : 1; }; +static struct gb_gsize_bits gb_gsize_value; +static int gb_size_grouped = sizeof((gb_gsize_value.value)); +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_deref_bits { unsigned int value : 1; }; +static struct gb_deref_bits *gb_deref_value; +static int gb_size_dereferenced = sizeof((*gb_deref_value).value); +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_flex_size { int count; int values[]; }; +static struct gb_flex_size gb_flex_value; +static int gb_flex_direct = sizeof gb_flex_value.values; +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_pflex_size { int count; int values[]; }; +static struct gb_pflex_size *gb_pflex_value; +static int gb_flex_pointer = sizeof(gb_pflex_value->values); +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_dflex_size { int count; int values[]; }; +static struct gb_dflex_size *gb_dflex_value; +static int gb_flex_dereferenced = sizeof((*gb_dflex_value).values); +int main(void) { return 0; } +EOF +try_compile_error << EOF +struct gb_gflex_size { int count; int values[]; }; +static struct gb_gflex_size gb_gflex_value; +static int gb_flex_grouped = sizeof((gb_gflex_value.values)); +int main(void) { return 0; } +EOF +try_ 8 << EOF +struct psize_record { int values[2]; }; +static struct psize_record psize_object; +static int psize_member_array = sizeof(psize_object.values); +int main(void) { return psize_member_array; } +EOF +try_ 8 << EOF +struct psize_inner { int values[2]; }; +struct psize_outer { struct psize_inner inner; }; +static struct psize_outer psize_nested_object; +static int psize_nested_member_array = sizeof(psize_nested_object.inner.values); +int main(void) { return psize_nested_member_array; } +EOF +try_ 12 << EOF +struct psize_rows { int values[2][3]; }; +static struct psize_rows psize_rows_object; +static int psize_member_row = sizeof((psize_rows_object.values)[1]); +int main(void) { return psize_member_row; } +EOF +try_ 4 << EOF +struct global_element_size_record { int values[2]; }; +static struct global_element_size_record global_element_size_object; +static int global_member_element_size = sizeof global_element_size_object.values[1]; +int main(void) { return global_member_element_size; } +EOF +try_ 12 << EOF +struct global_row_size_record { int values[2][3]; }; +static struct global_row_size_record global_row_size_object; +static int global_member_row_size = sizeof global_row_size_object.values[1]; +int main(void) { return global_member_row_size; } +EOF +try_ 8 << EOF +struct global_size_inner { int values[2]; }; +struct global_size_outer { char tag; struct global_size_inner inner; }; +static struct global_size_outer global_nested_size_object; +static int global_nested_member_array_size = + sizeof global_nested_size_object.inner.values; +int main(void) { return global_nested_member_array_size; } +EOF +try_ 16 << EOF +static int parenthesized_items[3]; +static int parenthesized_size = sizeof(1) + sizeof(parenthesized_items); +int main(void) { return parenthesized_size; } +EOF +try_ 4 << EOF +static int global_grouped_string_size = sizeof(("a" "bc")); +int main(void) { return global_grouped_string_size; } +EOF +try_ 4 << EOF +static int folded_expression_size = sizeof(1 + 2 * 3); +int main(void) { return folded_expression_size; } +EOF +try_ 4 << EOF +static int unary_expression_size = sizeof -1; +int main(void) { return unary_expression_size; } +EOF +try_ 4 << EOF +static int nested_expression_size = sizeof((1 + 2)); +int main(void) { return nested_expression_size; } +EOF +try_ 11 << EOF +static int string_expression_size = + sizeof "abc" + sizeof "a" "bc" + sizeof "\0x"; +int main(void) { return string_expression_size; } +EOF +try_ 20 << EOF +int a = 5 * 2; +int b = -4 * 3 + 7 + 9 / 3 * 5; +int main() +{ + return a + b; +} +EOF + +# File-scope extern declarations share the later object's or function's +# definition and preserve its ordinary external linkage. +try_ 17 << EOF +extern int external_value; +extern int external_function(void); +int external_value = 10; +int external_function(void) { return 7; } +int main(void) { return external_value + external_function(); } +EOF + +# A block-scope extern declaration has no automatic storage and hides an +# enclosing local name while referring to the translation unit's object or +# function declaration, including one defined later in the file. +try_ 18 << EOF +int main(void) { + int later_value = 99; + { + extern int later_value; + extern int later_extra, later_value; + extern int later_function(void); + later_value += later_extra + later_function(); + return later_value; + } +} +int later_function(void) { return 8; } +int later_value = 10; +int later_extra = 0; +EOF + +try_ 8 << EOF +int hidden_function(void) { return 8; } +int main(void) { + int hidden_function = 0; + { + extern int hidden_function(void); + return hidden_function(); + } +} +EOF + +# C99 6.7p3: an identifier without linkage is declared at most once in a scope. +# Only repeated extern object or function declarations, which have linkage, may +# share a block; an object and a function never may. +try_compile_error_message "redeclaration of identifier with no linkage" << EOF +int main(void) { int x; int x; return 0; } +EOF + +try_compile_error_message "redeclaration of identifier with no linkage" << EOF +int main(void) { int x = 1, x = 2; return x; } +EOF + +try_compile_error_message "redeclaration of identifier with no linkage" << EOF +int main(void) { static int x; extern int x; return x; } +int x; +EOF + +try_compile_error_message "redeclaration of identifier with no linkage" << EOF +int x; +int main(void) { extern int x; struct { int a; } x; return 0; } +EOF + +try_compile_error_message "different kind of symbol" << EOF +int main(void) { int f(void); int f; return 0; } +EOF + +try_compile_error_message "different kind of symbol" << EOF +int main(void) { int f; extern int f(void); return 0; } +EOF + +try_compile_error_message "different kind of symbol" << EOF +int main(void) { enum { A }; int A; return 0; } +EOF + +try_compile_error_message "different kind of symbol" << EOF +int main(void) { int A; enum { A }; return 0; } +EOF + +# An enumeration constant shares the ordinary identifier name space: a nearer +# object, parameter or typedef name hides it, and a nearer constant hides an +# outer object (C99 6.2.1p4). +try_ 5 << EOF +enum { V = 1 }; +int main(void) { int V = 5; return V; } +EOF +try_ 60 << EOF +enum { V = 1, P = 2 }; +int param_hides(int P) { return P; } +int main(void) { + int r = param_hides(9); /* 9 */ + { + enum { W = 2 }; + { int W = 3; r += W; } /* 12 */ + r += W; /* 14 */ + } + { + int V = 4; + { enum { V = 6 }; r += V; } /* 20 */ + r += V; /* 24 */ + { static int V = 7; r += V; } /* 31 */ + { extern int ext; r += ext; } /* 34 */ + } + for (int V = 10; V < 11; V++) + r += V; /* 44 */ + { + typedef int V; + V value = 15; + r += value + sizeof(V) - 4; /* 59 */ + } + switch (r) { + case V + 58: + return r + V; /* 60 */ + } + return 0; +} +int ext = 3; +EOF + +try_compile_error_message "redeclaration of parameter" << EOF +int f(int a) { int a = 2; return a; } +int main(void) { return f(1); } +EOF + +try_ 22 << EOF +int y = 2; +int twice(int); +int main(void) { + y = 3; + int y = 4; + { + extern int y; + extern int y; + if (y != 3) + return 1; + } + extern int twice(int); + int twice(int), twice(int); + for (int i = 0; i < 1; i++) { + int i = 9; + if (i != 9) + return 2; + } + { + int y = 5; + if (y != 5) + return 3; + } + switch (y) { + int y; + case 4: + y = 7; + return twice(y) + 8 + (y - 7); + } + return 0; +} +int twice(int a) { return a * 2; } +EOF + +# C99 permits a string literal to initialize a character-array member without an +# extra brace level, for automatic and file-scope record objects. +try_ 15 << EOF +struct named_text { char text[6]; int tag; } global_text = {"hi", 7}; +int main(void) { + struct named_text local_text = {"ok", 9}; + return global_text.text[0] + global_text.text[1] + global_text.text[2] + + global_text.tag + local_text.text[0] + local_text.text[1] + + local_text.text[2] + local_text.tag - 428; +} +EOF + +# Default mode retains the historical extension allowing extern declarations in +# a for initializer. They bind later file-scope definitions and hide an +# enclosing automatic object for the whole loop. +try_ 4 << EOF +int main(void) { + int for_value = 99; + for (extern int for_value; for_value == 0; for_value++) + return for_value + 4; + return 0; +} +int for_value = 0; +EOF + +try_ 12 << EOF +int main(void) { + for (extern int for_function(void); 1; ) + return for_function(); +} +int for_function(void) { return 12; } +EOF + +# Nested record-member designators select the resolved leaf, rather than only +# the outer record member, in local and static-storage initializers. +try_ 24 << EOF +struct nested_leaf { int first; int second; }; +struct nested_outer { int prefix; struct nested_leaf inner; int suffix; }; +struct nested_outer global_nested = {.inner.second = 7, .suffix = 5}; +int main(void) { + struct nested_outer local_nested = {.inner.first = 3, .inner.second = 4, + .suffix = 5}; + return global_nested.inner.first + global_nested.inner.second + + global_nested.suffix + local_nested.inner.first + + local_nested.inner.second + local_nested.suffix; +} +EOF + +# An array-member designator writes one selected element, including when the +# array is itself reached through a nested record member. +try_ 32 << EOF +struct array_inner { int items[4]; }; +struct array_outer { int prefix; struct array_inner inner; int suffix; }; +struct array_outer global_array = {.inner.items[2] = 7, .suffix = 5}; +int main(void) { + struct array_outer local_array = {.inner.items[1] = 9, + .inner.items[3] = 11}; + return global_array.inner.items[0] + global_array.inner.items[2] + + global_array.suffix + local_array.inner.items[1] + + local_array.inner.items[3]; +} +EOF + +# A two-dimensional member designator carries the stored row stride, while a +# one-subscript designator names a row that can receive a braced initializer. +try_ 22 << EOF +struct matrix_inner { int cells[2][3]; }; +struct matrix_outer { struct matrix_inner inner; int tag; }; +struct matrix_outer global_matrix = {.inner.cells[1][2] = 7, .tag = 3}; +int main(void) { + struct matrix_outer local_matrix = {.inner.cells[0] = {4, 5, 6}, + .inner.cells[1][1] = 2}; + return global_matrix.inner.cells[1][2] + global_matrix.tag + + local_matrix.inner.cells[0][0] + local_matrix.inner.cells[0][2] + + local_matrix.inner.cells[1][1]; +} +EOF + +# Designators inside a braced array member may reorder rows, and a complete +# subscript path names a scalar that later positional values follow. Zero fill +# must not clear a row an earlier designator stored, including in a union, whose +# automatic storage is not cleared up front. +try_ 0 << EOF +struct rows { int m[2][2]; int k; }; +union urows { int m[2][3]; char c; }; +struct flat { int a[4]; int b; }; +struct rows g1 = { .m = { [1] = {3, 4}, [0] = {1, 2} } }; +struct rows g2 = { .m = { [1][0] = 3, 4 }, 9 }; +union urows g3 = { .m = { [1] = {4, 5}, [0] = {1} } }; +struct flat g4 = { .a = { [2] = 5, [0] = 1, 2 }, 7 }; +int check(struct rows *r1, struct rows *r2, union urows *u, struct flat *f) { + int r = 0; + if (r1->m[0][0] != 1 || r1->m[0][1] != 2 || r1->m[1][0] != 3 || + r1->m[1][1] != 4) + r |= 1; + if (r2->m[0][0] || r2->m[0][1] || r2->m[1][0] != 3 || r2->m[1][1] != 4 || + r2->k != 9) + r |= 2; + if (u->m[0][0] != 1 || u->m[0][1] || u->m[0][2] || u->m[1][0] != 4 || + u->m[1][1] != 5 || u->m[1][2]) + r |= 4; + if (f->a[0] != 1 || f->a[1] != 2 || f->a[2] != 5 || f->a[3] || f->b != 7) + r |= 8; + return r; +} +int main(void) { + struct rows l1 = { .m = { [1] = {3, 4}, [0] = {1, 2} } }; + struct rows l2 = { .m = { [1][0] = 3, 4 }, 9 }; + union urows l3 = { .m = { [1] = {4, 5}, [0] = {1} } }; + struct flat l4 = { .a = { [2] = 5, [0] = 1, 2 }, 7 }; + static struct rows s1 = { .m = { [1] = {3, 4}, [0] = {1, 2} } }; + static struct rows s2 = { .m = { [1][0] = 3, 4 }, 9 }; + static union urows s3 = { .m = { [1] = {4, 5}, [0] = {1} } }; + static struct flat s4 = { .a = { [2] = 5, [0] = 1, 2 }, 7 }; + return check(&g1, &g2, &g3, &g4) | check(&l1, &l2, &l3, &l4) << 4 | + check(&s1, &s2, &s3, &s4); +} +EOF + +# The same holds one level down: a braced plane or row may name its rows or +# elements in any order, and zero fill leaves the ones already written alone. +try_ 0 << EOF +struct c { int m[2][2][2]; }; +#define DECLS(S) \ + S int v1[2][2][2] = { { [1] = {3, 4}, [0] = {1, 2} } }; \ + S struct c v2 = { .m = { { [1] = {3, 4}, [0] = {1, 2} } } }; \ + S int v3[2][3] = { { [2] = 7, [0] = 1 }, { 4, [2] = 6, [1] = 5 } }; \ + S int v4[2][2][2][2] = { { [1] = { [1] = {7, 8} }, [0] = { {1, 2} } } }; +#define CHECKS \ + int r = 0; \ + if (v1[0][0][0] != 1 || v1[0][0][1] != 2 || v1[0][1][0] != 3 || \ + v1[0][1][1] != 4 || v1[1][1][1]) \ + r |= 1; \ + if (v2.m[0][0][0] != 1 || v2.m[0][0][1] != 2 || v2.m[0][1][0] != 3 || \ + v2.m[0][1][1] != 4 || v2.m[1][1][1]) \ + r |= 2; \ + if (v3[0][0] != 1 || v3[0][1] || v3[0][2] != 7 || v3[1][0] != 4 || \ + v3[1][1] != 5 || v3[1][2] != 6) \ + r |= 4; \ + if (v4[0][0][0][0] != 1 || v4[0][0][0][1] != 2 || v4[0][0][1][0] || \ + v4[0][1][0][0] || v4[0][1][1][0] != 7 || v4[0][1][1][1] != 8 || \ + v4[1][1][1][1]) \ + r |= 8; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + int r = check_global(); + dirty(); + return r | check_local() << 4 | check_static(); +} +EOF +try_compile_error_message "Array designator index is out of bounds" << EOF +struct rows { int m[2][2]; }; +int main(void) { + struct rows r = { .m = { [2] = {1, 2} } }; + return r.m[0][0]; +} +EOF + +# Following positional initializers continue from a one-dimensional designated +# array element before advancing to the next record member. +try_ 30 << EOF +struct continued_array { int items[4]; int tail; } global_continue = + {.items[1] = 4, 5, 6}; +int main(void) { + struct continued_array local_continue = {.items[2] = 7, 8}; + return global_continue.items[0] + global_continue.items[1] + + global_continue.items[2] + global_continue.items[3] + + global_continue.tail + local_continue.items[2] + + local_continue.items[3] + local_continue.tail; +} +EOF + +# A later member designator ends that element sequence: the positional value +# after `.b = 6` initializes c, not the array slot after items[1]. +try_ 75 << EOF +struct resumed { int items[3]; int b; int c; }; +struct resumed global_resumed = {.items[1] = 5, .b = 6, 7}; +int score(struct resumed *v) { + return v->items[1] + v->b + v->c * 2 + v->items[2] * 3 + v->items[0] * 5; +} +int main(void) { + struct resumed local_resumed = {.items[1] = 5, .b = 6, 7}; + static struct resumed static_resumed = {.items[1] = 5, .b = 6, 7}; + return score(&global_resumed) + score(&local_resumed) + + score(&static_resumed); +} +EOF + +# Positional values after a two-dimensional member leaf advance in row-major +# order through the remaining elements. +try_ 30 << EOF +struct continued_matrix { int cells[2][3]; } global_matrix_continue = + {.cells[0][1] = 4, 5, 6, 7, 8}; +int main(void) { + return global_matrix_continue.cells[0][0] + + global_matrix_continue.cells[0][1] + + global_matrix_continue.cells[0][2] + + global_matrix_continue.cells[1][0] + + global_matrix_continue.cells[1][1] + + global_matrix_continue.cells[1][2]; +} +EOF + +# Continuation follows a nested array-member leaf as well: after the final +# element it advances to the outer record's next field. +try_ 90 << EOF +struct inner { int items[2][2]; }; +struct outer { struct inner inner; int tail; }; +struct outer global = {.inner.items[0][1] = 2, 3, 4, .tail = 5}; +int local(void) { + struct outer value = {.inner.items[0][1] = 6, 7, 8, .tail = 9}; + return value.inner.items[0][1] + value.inner.items[1][0] + + value.inner.items[1][1] + value.tail; +} +int fixed(void) { + static struct outer value = + {.inner.items[0][1] = 10, 11, 12, .tail = 13}; + return value.inner.items[0][1] + value.inner.items[1][0] + + value.inner.items[1][1] + value.tail; +} +int main(void) { + return global.inner.items[0][1] + global.inner.items[1][0] + + global.inner.items[1][1] + global.tail + local() + fixed(); +} +EOF +try_ 14 << EOF +struct inner { int cells[2][2][2]; }; +struct outer { struct inner inner; int tail; }; +struct outer value = {.inner.cells[1][0][1] = 2, 3, 4, .tail = 5}; +int main(void) { + return value.inner.cells[1][0][1] + value.inner.cells[1][1][0] + + value.inner.cells[1][1][1] + value.tail; +} +EOF + +# A designator that selects a row, plane or record row of a member array names +# an array subobject. An unbraced value fills its first element, and positional +# values continue through the rest of the member and then past it, while every +# element no initializer names stays zero. +try_ 0 << EOF +struct pt { int x, y; }; +struct m2 { int a[2][2]; int b; int c; }; +struct m3 { int a[2][2][2]; int b; }; +struct m4 { char a[2][2][2][2]; int b; }; +struct rec { struct pt p[2][2]; int b; }; +#define DECLS(S) \ + S struct m2 v1 = { .a[1] = 3, 4 }; \ + S struct m2 v2 = { .a[1] = 3, 4, 5, 6 }; \ + S struct m2 v3 = { .a[1] = {3}, 5 }; \ + S struct m3 v4 = { .a[1] = 1, 2, 3, 4, 5 }; \ + S struct m3 v5 = { .a[0][1] = 1, 2, 3 }; \ + S struct m4 v6 = { .a[1][1] = 1, 2, 3, 4, 5 }; \ + S struct rec v7 = { .p[1] = 1, 2, 3, 4, 5 }; \ + S int v8[2][2][2] = { [0][1] = 1, 2, 3 }; +#define CHECKS \ + int r = 0; \ + if (v1.a[0][0] || v1.a[0][1] || v1.a[1][0] != 3 || v1.a[1][1] != 4 || \ + v1.b || v1.c) \ + r |= 1; \ + if (v2.a[0][0] || v2.a[0][1] || v2.a[1][1] != 4 || v2.b != 5 || \ + v2.c != 6) \ + r |= 2; \ + if (v3.a[0][1] || v3.a[1][0] != 3 || v3.a[1][1] || v3.b != 5 || v3.c) \ + r |= 4; \ + if (v4.a[0][1][1] || v4.a[1][0][0] != 1 || v4.a[1][1][1] != 4 || \ + v4.b != 5) \ + r |= 8; \ + if (v5.a[0][0][1] || v5.a[0][1][0] != 1 || v5.a[1][0][0] != 3 || \ + v5.a[1][0][1] || v5.b) \ + r |= 16; \ + if (v6.a[1][0][1][1] || v6.a[1][1][0][0] != 1 || \ + v6.a[1][1][1][1] != 4 || v6.b != 5) \ + r |= 32; \ + if (v7.p[0][1].y || v7.p[1][0].x != 1 || v7.p[1][1].y != 4 || \ + v7.b != 5) \ + r |= 64; \ + if (v8[0][0][1] || v8[0][1][0] != 1 || v8[1][0][0] != 3 || v8[1][0][1]) \ + r |= 128; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + int r = check_global(); + dirty(); + r |= check_local(); + dirty(); + return r | check_static(); +} +EOF + +# Positional values after a nested designator continue inside the record it +# entered, then past that record in the enclosing one (C99 6.7.8p17 and p18). +# Two designators of one bit-field unit in static storage keep both slices. +try_ 0 << EOF +struct s { int a[2][2]; int b; }; +struct o { struct s in; int z; }; +struct p { int x, y; }; +struct h { int t; struct p arr[3]; int u; }; +struct d { int k; struct o o; int m; }; +struct b { int t; struct { unsigned lo : 3; unsigned hi : 5; int w; } in; int z; }; +#define DECLS(S) \ + S struct o v1 = { .in.a[1][1] = 4, 5, 6 }; \ + S struct h v2 = { .arr[1].x = 1, 2, 3, 4, 5 }; \ + S struct o v3 = { .in.a[1] = 3, 4, 5, 6 }; \ + S struct d v4 = { .o.in.b = 1, 2, 3 }; \ + S struct o v5 = { .in.b = 7, .in.a[0][1] = 8, 9 }; \ + S struct b v6 = { .in.lo = 5, .in.w = 6, 7, .in.hi = 9 }; \ + S struct b v7 = { 1, .in.w = 2, 3 }; +#define CHECKS \ + int r = 0; \ + if (v1.in.a[0][0] || v1.in.a[1][0] || v1.in.a[1][1] != 4 || \ + v1.in.b != 5 || v1.z != 6) \ + r |= 1; \ + if (v2.t || v2.arr[0].x || v2.arr[1].x != 1 || v2.arr[1].y != 2 || \ + v2.arr[2].x != 3 || v2.arr[2].y != 4 || v2.u != 5) \ + r |= 2; \ + if (v3.in.a[0][1] || v3.in.a[1][0] != 3 || v3.in.a[1][1] != 4 || \ + v3.in.b != 5 || v3.z != 6) \ + r |= 4; \ + if (v4.k || v4.o.in.b != 1 || v4.o.z != 2 || v4.m != 3) \ + r |= 8; \ + if (v5.in.b != 7 || v5.in.a[0][0] || v5.in.a[0][1] != 8 || \ + v5.in.a[1][0] != 9 || v5.z) \ + r |= 16; \ + if (v6.t || v6.in.lo != 5 || v6.in.hi != 9 || v6.in.w != 6 || \ + v6.z != 7) \ + r |= 32; \ + if (v7.t != 1 || v7.in.lo || v7.in.w != 2 || v7.z != 3) \ + r |= 64; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + int r = check_global(); + dirty(); + r |= check_local(); + dirty(); + return r | check_static(); +} +EOF + +# When positional values reach a record through brace elision after a designator +# already stored some of its members, those members are kept: the elided list +# initializes only the members it names. +try_ 0 << EOF +struct p { int x, y; }; +struct q { int pad; unsigned a : 4, b : 4; }; +struct r { char c; struct q q[3]; int z; }; +#define DECLS(S) \ + S struct r v1 = { .q[2].b = 3, .q[2].a = 5, .q[1].a = 1, 2, 7 }; \ + S struct p v2[2] = { [1].y = 2, [0].x = 1, 3, 4 }; +#define CHECKS \ + int r = 0; \ + if (v1.c || v1.q[0].a || v1.q[1].pad || v1.q[1].a != 1 || \ + v1.q[1].b != 2 || v1.q[2].pad != 7 || v1.q[2].a != 5 || \ + v1.q[2].b != 3 || v1.z) \ + r |= 1; \ + if (v2[0].x != 1 || v2[0].y != 3 || v2[1].x != 4 || v2[1].y != 2) \ + r |= 2; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + int r = check_global(); + dirty(); + r |= check_local() << 2; + dirty(); + return r | check_static() << 4; +} +EOF + +# The same holds for an element of an array with an inferred bound, whose +# skipped elements are zero whatever form the next initializer takes, and for +# elements and rows of a braced array list. +try_ 0 << EOF +struct p { int x, y; }; +struct h { struct p arr[3]; int z; }; +struct h2 { struct p m[2][2]; int z; }; +#define DECLS(S) \ + S struct p v1[] = { [1].y = 2, [0].x = 1, 3, 4 }; \ + S struct p v2[] = { [2] = {5, 6}, [0].y = 7 }; \ + S struct h v3 = { .arr = { [1] = { .y = 2 }, [0] = 1, 2, 3 }, 9 }; \ + S struct h2 v4 = { .m = { [0][1] = { .y = 2 }, [0][0] = 1, 2, 3 } }; \ + S struct h2 v5 = { .m = { [1] = { [1] = { .y = 2 }, [0] = 1, 2, 3 } } }; \ + S struct p v6[2][2] = { [1] = { [1] = { .y = 2 }, [0] = 1, 2, 3 } }; +#define CHECKS \ + int r = 0; \ + if (sizeof v1 != 2 * sizeof(struct p) || v1[0].x != 1 || v1[0].y != 3 || \ + v1[1].x != 4 || v1[1].y != 2) \ + r |= 1; \ + if (sizeof v2 != 3 * sizeof(struct p) || v2[0].x || v2[0].y != 7 || \ + v2[1].x || v2[1].y || v2[2].x != 5 || v2[2].y != 6) \ + r |= 2; \ + if (v3.arr[0].x != 1 || v3.arr[0].y != 2 || v3.arr[1].x != 3 || \ + v3.arr[1].y != 2 || v3.arr[2].x || v3.z != 9) \ + r |= 4; \ + if (v4.m[0][0].x != 1 || v4.m[0][0].y != 2 || v4.m[0][1].x != 3 || \ + v4.m[0][1].y != 2 || v4.m[1][0].x || v4.z) \ + r |= 8; \ + if (v5.m[1][0].x != 1 || v5.m[1][0].y != 2 || v5.m[1][1].x != 3 || \ + v5.m[1][1].y != 2 || v5.m[0][0].x || v5.m[0][1].y) \ + r |= 16; \ + if (v6[1][0].x != 1 || v6[1][0].y != 2 || v6[1][1].x != 3 || \ + v6[1][1].y != 2 || v6[0][0].x || v6[0][1].y) \ + r |= 32; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + int r = check_global(); + dirty(); + r |= check_local(); + dirty(); + return r | check_static(); +} +EOF +try_ 0 << EOF +struct p { int x, y; }; +int f(void) { + struct p v[] = { [3] = {5, 6} }; + int m[][2] = { [2] = {1, 2} }; + return v[0].x | v[0].y | v[1].x | v[2].y | m[0][0] | m[1][1]; +} +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + dirty(); + return f() != 0; +} +EOF + +# Each designator in a union initializer names a member again: two designators +# of one member initialize both of its parts, a later member replaces an earlier +# one, and a positional value after a scalar member is still excess. +try_ 0 << EOF +union u { struct { int x, y; } p; int i; }; +struct w { int t; union u u; }; +#define DECLS(S) \ + S union u v1 = { .p.x = 1, .p.y = 2 }; \ + S union u v2 = { .i = 7, .p.y = 3 }; \ + S union u v3 = { .p.y = 5, .i = 9 }; \ + S struct w v4 = { .u.p.y = 4, .u.p.x = 6, 8 }; +#define CHECKS \ + int r = 0; \ + if (v1.p.x != 1 || v1.p.y != 2) \ + r |= 1; \ + if (v2.p.y != 3 || v3.i != 9) \ + r |= 2; \ + if (v4.t || v4.u.p.x != 6 || v4.u.p.y != 8) \ + r |= 4; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +int main(void) { + return check_global() | check_local() << 3 | check_static() << 6; +} +EOF +try_compile_error_message "Too many elements in record initializer" << EOF +union u { struct { int x, y; } p; int i; }; +union u v = { .i = 1, 2 }; +int main(void) { return v.i; } +EOF + +# An automatic union is cleared before its initializer, so the elements of a +# member array that neither a designator nor a positional value reaches are +# zero, as C99 requires for the initialized member. +try_ 0 << EOF +union u { int a[2][2]; char c; }; +struct w { int t; union u u; }; +int f(void) { + union u v = { .a[1] = 7, 8 }; + return v.a[0][0] || v.a[0][1] || v.a[1][0] != 7 || v.a[1][1] != 8; +} +int g(void) { + union u v = { 5 }; + return v.a[0][0] != 5 || v.a[0][1] || v.a[1][1]; +} +int h(void) { + struct w v = { 1, { .a[0][1] = 2 } }; + return v.t != 1 || v.u.a[0][0] || v.u.a[0][1] != 2 || v.u.a[1][1]; +} +void dirty(void) { + int junk[16]; + for (int i = 0; i < 16; i++) + junk[i] = 99; +} +int main(void) { + int r; + dirty(); + r = f(); + dirty(); + r |= g() << 1; + dirty(); + return r | h() << 2; +} +EOF + +# An array designator may continue with member designators into one record +# element. A later designator of the same element keeps the members an earlier +# one stored, and positional values continue with the element's next member. +try_ 0 << EOF +int inc(int x) { return x + 1; } +struct ops { int (*fn)(int); int k; }; +struct pt { int x, y; }; +struct box { struct pt inner; int z; }; +struct holder { struct pt arr[3]; }; +#define DECLS(S) \ + S struct ops v1[2] = { [1].fn = inc, [1].k = 3 }; \ + S struct box v2[2] = { [1].inner.x = 2, [1].z = 5 }; \ + S struct holder v3 = { .arr[2].y = 3 }; \ + S struct pt v4[] = { [2].x = 3, 4, [1].y = 2, [1].x = 1 }; +#define CHECKS \ + int r = 0; \ + if (v1[0].fn || v1[0].k || v1[1].fn(1) != 2 || v1[1].k != 3) \ + r |= 1; \ + if (v2[0].inner.x || v2[0].z || v2[1].inner.x != 2 || v2[1].inner.y || \ + v2[1].z != 5) \ + r |= 2; \ + if (v3.arr[0].y || v3.arr[2].x || v3.arr[2].y != 3) \ + r |= 4; \ + if (sizeof(v4) != 3 * sizeof(struct pt) || v4[0].x || v4[0].y || \ + v4[1].x != 1 || v4[1].y != 2 || v4[2].x != 3 || v4[2].y != 4) \ + r |= 8; \ + return r; +DECLS() +int check_global(void) { CHECKS } +int check_local(void) { DECLS() CHECKS } +int check_static(void) { DECLS(static) CHECKS } +void dirty(void) { + int junk[64]; + for (int i = 0; i < 64; i++) + junk[i] = 99; +} +int main(void) { + int r = check_global(); + dirty(); + r |= check_local() << 4; + dirty(); + return r | check_static() << 8; +} +EOF + +# Four-dimensional arrays retain every inner stride for declaration, nested +# aggregate initialization, and ordinary row-major indexing. +try_ 30 << EOF +static int file_table[2][2][2][2] = + {{{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}}, + {{{8, 9}, {10, 11}}, {{12, 13}, {14, 15}}}}; +int main(void) { + int local_table[2][2][2][2] = + {{{{15, 14}, {13, 12}}, {{11, 10}, {9, 8}}}}; + return file_table[1][0][1][1] + local_table[0][1][1][0] + + local_table[1][0][0][0] + (sizeof(local_table) == 64 ? 10 : 0); +} +EOF +try_ 33 << EOF +static int file_table[2][2][2][2] = {[1][0][1][1] = 7, 8, 9}; +int main(void) { + int local_table[2][2][2][2] = {[0][1][0][1] = 4, 5}; + return file_table[1][0][1][1] + file_table[1][1][0][0] + + file_table[1][1][0][1] + local_table[0][1][0][1] + + local_table[0][1][1][0]; +} +EOF +try_ 22 << EOF +static int file_table[2][2][2][2] = {[1][1][0] = {4, 5}}; +int main(void) { + int local_table[2][2][2][2] = {[0][1][1] = {6, 7}}; + return file_table[1][1][0][0] + file_table[1][1][0][1] + + local_table[0][1][1][0] + local_table[0][1][1][1]; +} +EOF +try_ 44 << EOF +struct grid { int cells[2][2][2][2]; int tail; }; +static struct grid file_grid = {.cells[1][0][1][1] = 7, 8, 9, .tail = 5}; +int main(void) { + struct grid local_grid = {.cells[0][1][0][1] = 4, 5, .tail = 6}; + return file_grid.cells[1][0][1][1] + file_grid.cells[1][1][0][0] + + file_grid.cells[1][1][0][1] + file_grid.tail + + local_grid.cells[0][1][0][1] + local_grid.cells[0][1][1][0] + + local_grid.tail; +} +EOF +try_compile_error << EOF +int too_many_dimensions[1][1][1][1][1]; +EOF + +# Forward record tags, including aliases of them, may be used through pointers +# before their complete definition; both global static storage and block scope +# retain that rule. +try_ 17 << EOF +struct node; +union payload; +typedef struct deferred deferred_t; +static struct node *head; +static union payload *slot; +static deferred_t *deferred; +struct node { struct node *next; int value; }; +union payload { int number; char bytes[4]; }; +int local_forward(void) { + struct local; + struct local *ptr = 0; + return !ptr; +} +int main(void) { + struct node value; + union payload data; + value.next = 0; + value.value = 7; + data.number = 9; + head = &value; + slot = &data; + return head->value + slot->number + local_forward(); +} +EOF +try_compile_error << EOF +struct incomplete; +struct incomplete value; +EOF +try_compile_error << EOF +typedef struct incomplete incomplete_t; +incomplete_t value; +EOF +try_compile_error << EOF +int main(void) { + typedef union incomplete incomplete_t; + incomplete_t value; + return 0; +} +EOF +try_compile_error << EOF +int main(void) { + typedef struct incomplete incomplete_t; + for (incomplete_t value; ; ) + return 0; +} +EOF +try_compile_error << EOF +typedef struct incomplete incomplete_t; +void invalid(incomplete_t value) { } +EOF +try_compile_error << EOF +typedef struct incomplete incomplete_t; +incomplete_t invalid(void) { } +EOF +try_ 0 << EOF +typedef struct incomplete incomplete_t; +void valid(incomplete_t values[2]) { } +int main(void) { return 0; } +EOF +try_ 9 << EOF +struct external; +typedef struct external external_t; +extern struct external direct; +extern external_t alias; +struct external { int value; }; +struct external direct = {4}; +external_t alias = {5}; +int main(void) { return direct.value + alias.value; } +EOF + +# Three-dimensional member paths retain both inner strides. A leaf designator +# also resumes positional initialization in row-major order for every storage +# duration supported by aggregate initialization. +try_ 54 << EOF +struct grid { int cells[2][2][2]; }; +struct grid global_grid = {.cells[1][0][1] = 2, 3, 4}; +int local_grid(void) { + struct grid value = {.cells[1][0][1] = 5, 6, 7}; + return value.cells[1][0][1] + value.cells[1][1][0] + + value.cells[1][1][1]; +} +int static_grid(void) { + static struct grid value = {.cells[1][0][1] = 8, 9, 10}; + return value.cells[1][0][1] + value.cells[1][1][0] + + value.cells[1][1][1]; +} +int main(void) { + return global_grid.cells[1][0][1] + global_grid.cells[1][1][0] + + global_grid.cells[1][1][1] + local_grid() + static_grid(); +} +EOF + +# The subscripts of a block-scope static's member designators are constant +# expressions of the declaration's block, even though its storage is lowered +# through the file-scope initializer. +try_ 26 << EOF +struct S { int a[3]; int m[2][2]; }; +int main(void) { + enum { K = 1 }; + static struct S s = {.a[K] = 5, .m[K][K] = 6}; + static struct S t = {.a = {[K] = 7}, .m = {[K] = {[K] = 8}}}; + return s.a[1] + s.m[1][1] + t.a[1] + t.m[1][1]; +} +EOF + +# Translation phase 6 also concatenates literals made adjacent by macro +# expansion, before the expression parser sees them. +try_ 3 << EOF +#define STRING_LEFT "ab" +#define STRING_RIGHT "cd" +int main(void) { + char *text = STRING_LEFT STRING_RIGHT; + return (text[0] == 'a') + (text[2] == 'c') + (text[4] == 0); +} +EOF + +# A declaration's base type applies to every global declarator, while each +# declarator keeps its own pointer and array modifiers and initializer. +try_ 39 << EOF +typedef int myint; +struct pair { int x; int y; } first, second, *selected; +union choice { int number; char letter; } chosen, *chosen_ptr; +int plain = 3, *pointer, array[2]; +char *left = "A", *right = "B"; +myint alpha = 4, beta = 5; + +int main(void) +{ + pointer = &plain; + first.x = 6; + first.y = 7; + second.x = 8; + second.y = 9; + selected = &second; + chosen.number = 10; + chosen_ptr = &chosen; + array[0] = first.x; + array[1] = selected->y; + return *pointer + array[0] + array[1] + chosen_ptr->number + alpha + beta + + (left != 0) + (right != 0); +} +EOF + +# Unsigned scalar declarations preserve their storage widths. unsigned char and +# unsigned short promote to int in arithmetic, while unsigned int remains +# unsigned through the expression pipeline. +try_ 26 << EOF +unsigned int global_value = 1000; +int main(void) +{ + unsigned char byte = 20; + unsigned short half = 30; + unsigned int word = global_value; + return byte + half + word; +} +EOF + +# Static declarations have static storage duration. The local counter must be +# initialized once in the synthetic global frame, while its name stays scoped to +# next_value(). +try_ 19 << EOF +const static int file_value = 4; +static int next_value(void) +{ + static int counter = 7; + const static int bias = 0; + return counter++ + bias; +} +int main(void) +{ + return file_value + next_value() + next_value(); +} +EOF + +# Continuation declarators retain their individual pointer, array, and function +# pointer forms while sharing the base type and processing each initializer. +try_ 13 << EOF +int add_one(int value) { return value + 1; } +int value = 4, *value_ref = &value, values[2] = {3, 5}, + (*apply)(int) = add_one; +int main(void) { return *value_ref + values[1] + apply(values[0]); } +EOF + +try_ 5 << EOF +int static_address_constants(void) +{ + static int values[3] = {1, 2, 3}; + static int *decayed = values; + static int *addressed = &values[0]; + return decayed[1] + addressed[2]; +} +int main(void) { return static_address_constants(); } +EOF + +# A block-scope aggregate static must keep both its initializer and later member +# writes across calls; this covers the global-frame lowering beyond the scalar +# counter above. +try_ 12 << EOF +struct tally { int count; int values[2]; }; +int next_tally(void) +{ + static struct tally state = {1, {2, 3}}; + state.count++; + state.values[0]++; + return state.count + state.values[0]; +} +int main(void) { return next_tally() + next_tally(); } +EOF + +try_ 1 << EOF +int writable_static_string(void) +{ + static char buffer[8] = "hi"; + buffer[0] = 'H'; + return buffer[0] == 'H' && buffer[1] == 'i' && buffer[2] == 0; +} +int main(void) { return writable_static_string(); } +EOF + +# A shorter string initializer zeroes the rest of an automatic array, each time +# the declaration is reached, even after the slot was overwritten. +try_ 0 << EOF +int main(void) +{ + int dirty = 0; + for (int pass = 0; pass < 2; pass++) { + char text[12] = "ab"; + for (int i = 2; i < 12; i++) { + if (text[i] != 0) + dirty++; + text[i] = 'Q'; + } + } + return dirty; +} +EOF + +try_ 1 << EOF +static char inferred_file_scope_string[] = "map"; + +int inferred_static_string(void) +{ + static char buffer[] = "cat"; + buffer[0] = 'C'; + return buffer[0] == 'C' && buffer[1] == 'a' && buffer[2] == 't' && + sizeof(buffer) == 4; +} +int inferred_for_string(void) +{ + int n = 0; + for (char word[] = "go"; word[n]; n++) + ; + return n == 2; +} +int inferred_for_array(void) +{ + int total = 0; + for (int values[] = {2, 3}; values[0] && sizeof(values) == 8; + values[0] = 0) + total = values[0] + values[1]; + return total == 5; +} +int inferred_file_scope_string_test(void) +{ + inferred_file_scope_string[0] = 'M'; + return inferred_file_scope_string[0] == 'M' && + inferred_file_scope_string[1] == 'a' && + sizeof(inferred_file_scope_string) == 4; +} +int main(void) +{ + return inferred_static_string() && inferred_for_string() && + inferred_for_array() && inferred_file_scope_string_test(); +} +EOF + +try_ 10 << EOF +static int triangular(int value) +{ + return value ? value + triangular(value - 1) : 0; +} +int main(void) { return triangular(4); } +EOF + +try_ 3 << EOF +int next_zeroed(void) +{ + static int count; + return count++; +} +int main(void) +{ + return next_zeroed() + next_zeroed() + next_zeroed(); +} +EOF + +try_ 1 << EOF +int zeroed_entry(void) +{ + static int entries[2]; + entries[1]++; + return entries[0] == 0; +} +int main(void) { return zeroed_entry(); } +EOF + +# Global-storage declarators are temporarily placed on the expression stack for +# constant initialization. Aggregate and zero initializers must discard their +# own entries too, or a declaration-heavy block exhausts that stack. +try_ 33 << EOF +int many_static_aggregates(void) +{ + static int slot00[1] = {0}; + static int slot01[1] = {1}; + static int slot02[1] = {2}; + static int slot03[1] = {3}; + static int slot04[1] = {4}; + static int slot05[1] = {5}; + static int slot06[1] = {6}; + static int slot07[1] = {7}; + static int slot08[1] = {8}; + static int slot09[1] = {9}; + static int slot10[1] = {10}; + static int slot11[1] = {11}; + static int slot12[1] = {12}; + static int slot13[1] = {13}; + static int slot14[1] = {14}; + static int slot15[1] = {15}; + static int slot16[1] = {16}; + static int slot17[1] = {17}; + static int slot18[1] = {18}; + static int slot19[1] = {19}; + static int slot20[1] = {20}; + static int slot21[1] = {21}; + static int slot22[1] = {22}; + static int slot23[1] = {23}; + static int slot24[1] = {24}; + static int slot25[1] = {25}; + static int slot26[1] = {26}; + static int slot27[1] = {27}; + static int slot28[1] = {28}; + static int slot29[1] = {29}; + static int slot30[1] = {30}; + static int slot31[1] = {31}; + static int slot32[1] = {32}; + return slot00[0] + slot32[0] + slot01[0]; +} +int main(void) { return many_static_aggregates(); } +EOF + +try_ 1 << EOF +struct zeroed_pair { int first; int second; }; +int zeroed_record(void) +{ + static struct zeroed_pair pair; + pair.second = 1; + return pair.first == 0; +} +int main(void) { return zeroed_record(); } +EOF + +# A block-scope static initializer is lowered as global data and therefore must +# be a C99 constant expression, not a run-time call. +try_compile_error << EOF +int runtime_value(void) { return 7; } +int main(void) +{ + static int value = runtime_value(); + return value; +} +EOF + +try_ 13 << EOF +struct static_compound_pair { int left; int right; }; +int static_compound_literals(void) +{ + static int scalar = (int){3}; + static int *values = (int[]){4, 5}; + static struct static_compound_pair pair = + (struct static_compound_pair){2, 3}; + return scalar + values[1] + pair.left + pair.right; +} +int main(void) { return static_compound_literals(); } +EOF + +# A block-scope static compound literal has global storage duration and must +# retain nested designated initializers through the global aggregate path. +try_ 11 << EOF +struct static_compound_inner { int left; int right; }; +struct static_compound_outer { struct static_compound_inner inner; int tail; }; +int static_nested_compound_literal(void) +{ + static struct static_compound_outer value = + (struct static_compound_outer){.inner.right = 7, .tail = 4}; + return value.inner.left + value.inner.right + value.tail; +} +int main(void) { return static_nested_compound_literal(); } +EOF + +try_ 9 << EOF +int static_designated_array_compound_literal(void) +{ + static int *values = (int[4]){[3] = 7, [1] = 2}; + return values[0] + values[1] + values[2] + values[3]; +} +int main(void) { return static_designated_array_compound_literal(); } +EOF + +try_compile_error << EOF +int counter; +int main(void) +{ + static counter = 5; + return counter; +} +EOF + +try_compile_error << EOF +int main(void) +{ + static ++missing; + return 0; +} +EOF + +# A block-scope static array has global storage duration but retains block +# scope. Its constant initializer must be emitted with global data. +try_ 15 << EOF +int values(void) +{ + static int entries[3] = {4, 5, 6}; + return entries[0] + entries[1] + entries[2]; +} +int main(void) +{ + return values(); +} +EOF + +try_ 10 << EOF +int values(void) +{ + static int left[2] = {1, 2}, right[2] = {3, 4}; + return left[0] + left[1] + right[0] + right[1]; +} +int main(void) +{ + return values(); +} +EOF + +try_ 17 << EOF +struct static_pair { int first; int second; }; +static struct static_pair global_pair = {8, 9}; +int main(void) +{ + return global_pair.first + global_pair.second; +} +EOF + +# File-scope member designators may reorder fields; omitted fields are zero. +try_ 7 << EOF +struct designated_values { int first; int second; int third; }; +struct designated_values values = {.third = 5, .second = 2}; +int main(void) +{ + return values.first + values.second + values.third; +} +EOF + +# Keep byte-wide designated stores from clobbering an adjacent member. +try_ 7 << EOF +struct byte_designated_values { char first; char second; int third; }; +struct byte_designated_values byte_values = {.third = 5, .second = 2}; +int main(void) +{ + return byte_values.first + byte_values.second + byte_values.third; +} +EOF + +# Bounded array designators use the same global initializer lowering. +try_ 9 << EOF +int designated_entries[4] = {[3] = 7, [1] = 2}; +int main(void) +{ + return designated_entries[0] + designated_entries[1] + + designated_entries[2] + designated_entries[3]; +} +EOF + +# An omitted bound is one past the highest designated element. +try_ 9 << EOF +int inferred_entries[] = {[3] = 7, [1] = 2}; +int main(void) +{ + return inferred_entries[0] + inferred_entries[1] + + inferred_entries[2] + inferred_entries[3]; +} +EOF + +# The same lowering serves block-scope static records. +try_ 12 << EOF +struct static_designated_values { int first; int second; int third; }; +int values(void) +{ + static struct static_designated_values value = {.third = 8, .second = 4}; + return value.first + value.second + value.third; +} +int main(void) +{ + return values(); +} +EOF + +try_ 8 << EOF +int values(void) +{ + static int entries[3] = {[2] = 5, [0] = 3}; + return entries[0] + entries[1] + entries[2]; +} +int main(void) +{ + return values(); +} +EOF + +try_ 8 << EOF +int values(void) +{ + int entries[] = {[2] = 5, [0] = 3}; + return entries[0] + entries[1] + entries[2]; +} +int main(void) +{ + return values(); +} +EOF + +try_ 8 << EOF +int values(void) +{ + static int entries[] = {[2] = 5, [0] = 3}; + return entries[0] + entries[1] + entries[2]; +} +int main(void) +{ + return values(); +} +EOF + +try_ 9 << EOF +struct local_pair { int first; int second; }; +int values(void) +{ + static struct local_pair pair = {4, 5}; + return pair.first + pair.second; +} +int main(void) +{ + return values(); +} +EOF + +try_ 10 << EOF +struct local_pair { int first; int second; }; +int values(void) +{ + static struct local_pair left = {1, 2}, right = {3, 4}; + return left.first + left.second + right.first + right.second; +} +int main(void) +{ + return values(); +} +EOF + +try_ 15 << EOF +struct static_grid { int values[2][2]; int tail; }; +int values(void) +{ + static struct static_grid grid = {{{1, 2}, {3, 4}}, 5}; + return grid.values[0][0] + grid.values[0][1] + grid.values[1][0] + + grid.values[1][1] + grid.tail; +} +int main(void) +{ + return values(); +} +EOF + +# Block scope gives same-spelled statics distinct objects in distinct functions. +try_ 62 << EOF +int first(void) +{ + static int value = 10; + return value++; +} +int second(void) +{ + static int value = 20; + return value++; +} +int main(void) +{ + return first() + second() + first() + second(); +} +EOF + +# Nested block scope must give a same-spelled static a second persistent object, +# without losing the enclosing static object between calls. +try_ 24 << EOF +int nested_static_values(void) +{ + static int value = 1; + int outer = value++; + { + static int value = 10; + return outer + value++; + } +} +int main(void) +{ + return nested_static_values() + nested_static_values(); +} +EOF + +# Default mode retains the historical extension allowing a static for-init +# object. It persists across calls but is visible only to the loop clauses/body. +try_ 6 << EOF +int run_once(void) +{ + int total = 0; + for (static int count = 1; count < 4; count++) + total += count; + return total; +} +int main(void) +{ + return run_once() + run_once(); +} +EOF + +# The same global-storage lowering applies to each declarator in a for-init +# declaration. +try_ 4 << EOF +int run_once(void) +{ + int total = 0; + for (static int count = 1, step = 2; count < 5; count += step) + total += count; + return total; +} +int main(void) +{ + return run_once() + run_once(); +} +EOF + +# Built-in specifiers in a for-init declaration take the same declaration path +# as their block-scope counterparts; this also checks unsigned wraparound in the +# loop condition/increment sequence. +try_ 3 << EOF +int main(void) +{ + int count = 0; + for (unsigned int value = 0xfffffffeU; value != 1U; value++) + count++; + return count; +} +EOF + +# A redeclaration without a storage class inherits an earlier static function's +# internal linkage. Reversing that order is a constraint violation. +try_ 42 << EOF +static int helper(void); +int helper(void) { return 42; } +int main(void) { return helper(); } +EOF +try_compile_error_message "undefined static function 'missing_static_function'" << EOF +static int missing_static_function(void); +int main(void) { return missing_static_function(); } +EOF +try_compile_error << EOF +int runtime_value(void) { return 7; } +int main(void) { + static int invalid_static_value = runtime_value; + return invalid_static_value; +} +EOF +try_ 42 << EOF +static int increment(int value); +int increment(int value) { return value + 1; } +int main(void) { + int (*internal_call)(int) = increment; + return internal_call(41); +} +EOF + +# A static function designator retains internal linkage when materialized into a +# local function pointer and invoked indirectly. +try_ 42 << EOF +static int increment(int value) { return value + 1; } +int main(void) { + int (*internal_call)(int) = increment; + return internal_call(41); +} +EOF + +# A file-scope function designator is an address constant too. This exercises +# global setup before main and both internal and external linkage targets. +try_ 49 << EOF +static int increment(int value) { return value + 1; } +int double_value(int value) { return value * 2; } +static int (*internal_call)(int) = increment; +int (*external_call)(int) = double_value; +int (*address_call)(int) = &increment; +static int internal_value = 3; +int external_value = 4; +static int *internal_value_ptr = &internal_value; +int *external_value_ptr = &external_value; +int main(void) { + return internal_call(20) + external_call(10) + address_call(0) + + *internal_value_ptr + *external_value_ptr; +} +EOF + +try_ 13 << EOF +static int internal_values[] = {1, 2, 3}; +int external_values[] = {4, 5}; +static int *internal_first = internal_values; +int *internal_last = internal_values + 2; +int *external_first = &external_values; +int main(void) { + return internal_first[2] + *internal_last + external_first[0] + + external_first[1] - 2; +} +EOF + +try_ 7 << EOF +struct global_pair { char tag; int value; }; +static struct global_pair internal_pair = {1, 7}; +int *internal_value = &internal_pair.value; +int main(void) { return *internal_value; } +EOF + +try_ 9 << EOF +struct nested_inner { char pad; int value; }; +struct nested_outer { int prefix; struct nested_inner inner; }; +static struct nested_outer global_nested = {2, {1, 9}}; +int *nested_value = &global_nested.inner.value; +int main(void) { return *nested_value; } +EOF + +try_ 8 << EOF +struct global_array_record { int prefix; int values[3]; }; +static struct global_array_record global_array = {1, {2, 8, 3}}; +int *array_member_value = &global_array.values[1]; +int main(void) { return *array_member_value; } +EOF + +try_ 11 << EOF +struct global_array_element { int value; }; +static struct global_array_element global_elements[] = {{3}, {11}}; +int *array_element_value = &global_elements[1].value; +int main(void) { return *array_element_value; } +EOF + +# Explicitly addressed global elements and member arrays may carry a byte-scaled +# integer constant-expression offset, not just a decayed array name or a bare +# literal. +try_ 25 << EOF +int global_values[] = {3, 5, 8}; +struct global_offset_record { int values[3]; }; +static struct global_offset_record global_offset = {{1, 2, 8}}; +enum global_offsets { + global_offset_count = 1 + 1, + global_offset_shift = global_offset_count << 1 +} global_marker = global_offset_shift; +static enum global_offsets global_half = global_offset_count; +int *global_last = &global_values[0] + global_offset_count; +int *global_first = &global_values[2] - (1 + 1); +int *member_last = &global_offset.values[0] + (global_offset_shift / 2); +int main(void) { + return *global_last + *global_first + *member_last + global_marker + + global_half; +} +EOF + +# Block-scope static address constants share the global initializer lowering. +# Preserve rank-four row strides both from an array root and after selecting an +# array member. +try_ 0 << EOF +int static_rank4_leaf(void) { + static int values[2][2][2][2] = { + {{{2, 3}, {5, 7}}, {{11, 13}, {17, 19}}}, + {{{23, 29}, {31, 37}}, {{41, 43}, {47, 53}}} + }; + static int *leaf = &values[1][0][1][1]; + return *leaf != 37; +} +int main(void) { return static_rank4_leaf(); } +EOF +try_ 0 << EOF +struct static_rank4_box { int values[2][2][2][2]; }; +int static_member_rank4_leaf(void) { + static struct static_rank4_box box = { + {{{{2, 3}, {5, 7}}, {{11, 13}, {17, 19}}}, + {{{23, 29}, {31, 37}}, {{41, 43}, {47, 53}}}} + }; + static int *leaf = &box.values[1][0][1][1]; + return *leaf != 37; +} +int main(void) { return static_member_rank4_leaf(); } +EOF +try_ 0 << EOF +struct static_rank4_plane_box { int values[2][2][2][2]; }; +int static_member_rank4_plane(void) { + static struct static_rank4_plane_box box = { + {{{{2, 3}, {5, 7}}, {{11, 13}, {17, 19}}}, + {{{23, 29}, {31, 37}}, {{41, 43}, {47, 53}}}} + }; + static int (*plane)[2][2] = &box.values[0][1] + 1; + return plane != &box.values[1][0] || plane[0][1][1] != 37; +} +int main(void) { return static_member_rank4_plane(); } +EOF + +# Aggregate pointer initializers use the same address-constant walker. A +# trailing offset after partial rank-four subscripting advances by a plane, +# rather than by the original array's row extent. +try_ 0 << EOF +int aggregate_address_rows[2][2][2][2] = { + {{{2, 3}, {5, 7}}, {{11, 13}, {17, 19}}}, + {{{23, 29}, {31, 37}}, {{41, 43}, {47, 53}}} +}; +int *aggregate_address_leaf[] = { + &aggregate_address_rows[1][0][1][1] +}; +int (*aggregate_address_plane[])[2][2] = { + &aggregate_address_rows[0][1] + 1 +}; +int main(void) { + if (*aggregate_address_leaf[0] != 37) + return 1; + return aggregate_address_plane[0][0][1][1] != 37; +} +EOF + +# Multiple global pointer-to-fixed-array slots must retain the selected slot's +# row shape, rather than falling back to scalar element strides after loading. +try_ 0 << EOF +int repeated_slot_rows[2][2] = { + {2, 5}, {3, 7} +}; +int (*repeated_slot_planes[])[2] = { + repeated_slot_rows, repeated_slot_rows + 1, repeated_slot_rows +}; +int main(void) { + return repeated_slot_planes[2][1][0] != 3; +} +EOF + +# This evaluates a direct unsigned long long global expression in an address +# offset. +try_ 0 << EOF +int global_wide_offset_values[] = {17, 23}; +int *global_wide_offset = + &global_wide_offset_values[0] + (0x100000000ULL >> 32); +int *global_wide_decay_offset = + global_wide_offset_values + (0x100000000ULL >> 32); +int *global_wide_negative_offset = + &global_wide_offset_values[1] - (0x100000000ULL >> 32); +struct global_wide_offset_holder { int *value; }; +struct global_wide_offset_holder global_wide_offset_aggregate = { + &global_wide_offset_values[0] + (0x100000000ULL >> 32)}; +int main(void) { + int *aggregate_value = global_wide_offset_aggregate.value; + return *global_wide_offset != 23 || *global_wide_decay_offset != 23 || + *global_wide_negative_offset != 17 || + *aggregate_value != 23; +} +EOF +try_ 0 << EOF +struct pointer_member_holder { int *value; }; +typedef char *pointer_member_char_ptr; +struct pointer_member_indirect_holder { + pointer_member_char_ptr character; + int **value; +}; +int main(void) { + int direct = 29; + int *direct_pointer = &direct; + char character = 'x'; + struct pointer_member_holder holder = {&direct}; + struct pointer_member_holder *pointer = &holder; + struct pointer_member_indirect_holder indirect = {&character, + &direct_pointer}; + struct pointer_member_indirect_holder *indirect_pointer = &indirect; + return *holder.value != 29 || *pointer->value != 29 || + *indirect.character != 'x' || **indirect.value != 29 || + **indirect_pointer->value != 29; +} +EOF +try_ 0 << EOF +struct pointer_member_inner { int field; }; +struct pointer_member_outer { struct pointer_member_inner *member; }; +typedef int (*pointer_member_callback)(int); +struct pointer_member_callback_holder { pointer_member_callback callback; }; +int add_one(int value) { return value + 1; } +int main(void) { + struct pointer_member_inner inner = {55}; + struct pointer_member_outer outer = {&inner}; + struct pointer_member_outer *outer_pointer = &outer; + struct pointer_member_callback_holder callback_holder = {add_one}; + return (*outer.member).field != 55 || + (*outer_pointer->member).field != 55 || + (*callback_holder.callback)(41) != 42; +} +EOF +try_compile_error << EOF +int global_wide_offset_range_values[1]; +int *global_wide_offset_out_of_range = + &global_wide_offset_range_values[0] + 0x100000000ULL; +EOF + +# Repeated compatible file-scope declarations name the same static object. A +# later initialized definition supplies that object's initial value. +try_ 42 << EOF +static int file_value; +int file_value; +int file_value = 42; +int main(void) { return file_value; } +EOF + +try_compile_error << EOF +static int initialized_once = 1; +static int initialized_once = 2; +int main(void) { return initialized_once; } +EOF + +try_compile_error << EOF +int helper(void); +static int helper(void) { return 42; } +int main(void) { return helper(); } +EOF + +try_compile_error << EOF +static int defined_twice(void) { return 1; } +static int defined_twice(void) { return 2; } +int main(void) { return defined_twice(); } +EOF + +try_compile_error << EOF +static static int duplicated_file_storage; +int main(void) { return duplicated_file_storage; } +EOF + +try_compile_error << EOF +int main(void) { + static const static int duplicated_block_storage = 1; + return duplicated_block_storage; +} +EOF + +# LDRH, LDRSH and STRH take an eight-bit offset on Arm. A halfword slot or +# global field beyond 255 bytes must be addressed through a materialized offset, +# and the size estimate must match what is emitted. +try_ 0 << EOF +void bump_half(short *p) { *p = *p + 1; } +int main(void) { + char big[600]; + short s = 1000; + big[0] = 1; + bump_half(&s); + s = s + 2; + bump_half(&s); + return s != 1004 || big[0] != 1; +} +EOF + +try_ 0 << EOF +int half_target = 9; +struct half_record { int pad[100]; short h1; short h2; int *link; }; +struct half_record half_global = { .h1 = 1, .h2 = 2, .link = &half_target }; +int main(void) { + return half_global.h1 != 1 || half_global.h2 != 2 || + *half_global.link != 9; +} +EOF + +# Address constants inside aggregate static initializers accept the same +# designators as scalar ones: members, constant-expression subscripts, offsets. +try_ 31 << EOF +struct inner { int pad; int values[3]; }; +struct outer { int tag; struct inner nested[2]; }; +enum { SECOND = 1 }; +static struct outer record = {7, {{1, {2, 3, 4}}, {5, {6, 7, 8}}}}; +struct slots { int *first; int *second; int *third; }; +static struct slots table = {&record.tag, &record.nested[SECOND].values[1 + 1], + &record.nested[0].values[0] + 2}; +int main(void) { + static int *local[2] = {&record.nested[SECOND].pad, + &record.nested[1].values[0] + 1}; + return *table.first + *table.second + *table.third + *local[0] + *local[1]; +} +EOF + +# An initializer large enough that its setup spills temporaries must keep them +# in the global data area, not past the top of the stack. +try_ 4 << EOF +int table[1500] = {0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1,2,3,4,5,6,0,1}; +int main(void) { return table[1499] + table[3]; } +EOF + +# Every declarator in a block-scope list shares the resolved base type, not only +# the first: b and d below are unsigned and long like a and c. +try_ 7 << EOF +int main(void) { + unsigned int a = 0, b = 0x80000000U; + long c = 0, d = 0; + unsigned e = 1, f = 0xffffffffU; + return (b >> 31) + 2 * (sizeof(d) == sizeof(long)) + 4 * (f > e); +} +EOF + +# A cast between signed and unsigned of the same width changes how the value +# extends: (int) of an unsigned int with every bit set shifts right as -1. +try_ 3 << EOF +int shift_cast(unsigned int value) { return ((int) value >> 1) == -1; } +int widen_cast(unsigned int value) { int s = (int) value; return (s >> 4) == -1; } +int main(void) { return shift_cast(0xffffffffU) + 2 * widen_cast(0xfffffff0U); } +EOF + +# An old-style definition types its identifier list in a declaration list (C99 +# 6.9.1p6), called with and without a prototype in scope. A prototype must agree +# with the promoted parameter types (6.7.5.3p15), and C99 has no implicit int +# for an undeclared parameter. +try_ 0 << EOF +int add(x, y) int x; int y; { return x + y; } +int proto(int a, char *s); +int proto(n, str) int n; char *str; { return n + str[1]; } +int later(); +int narrow(c, s) char c; short s; { return c + s; } +int arr(a, n) int a[]; int n; { return a[n - 1]; } +int fn(f, v) int f(int); int v; { return f(v); } +int twice(int v) { return 2 * v; } +long long wide(ll, i) long long ll; register int i; { return ll + i; } +int multi(a, b, c) int a, *b; char c; { return a + *b + c; } +int main(void) +{ + int v[3] = {4, 5, 6}; + int seven = 7; + int (*fp)() = add; + return add(1, 2) != 3 || proto(1, "ab") != 'b' + 1 || later(5) != 6 || + narrow('a', 1) != 'a' + 1 || arr(v, 3) != 6 || fn(twice, 4) != 8 || + wide(0x100000000LL, 1) != 0x100000001LL || multi(1, &seven, 2) != 10 || + fp(3, 4) != 7; +} +int later(x) int x; { return x + 1; } +EOF +try_ 0 << EOF +int f(int); +int f(c) char c; { return c; } +int main(void) { return f(65) - 65; } +EOF +try_compile_error_message "parameter type defaults to int" << EOF +int f(x) { return x; } +int main(void) { return f(1) - 1; } +EOF +try_compile_error_message "parameter type defaults to int" << EOF +int f(x, y) int x; { return x + y; } +int main(void) { return 0; } +EOF +try_compile_error_message "declaration of a name not in the identifier list" << EOF +int f(x) int x; int z; { return x; } +int main(void) { return 0; } +EOF +try_compile_error_message "conflicting types for function declaration" << EOF +int f(char); +int f(c) char c; { return c; } +int main(void) { return 0; } +EOF +try_compile_error_message "identifier list requires a function definition" << EOF +int f(x); +int main(void) { return 0; } +EOF +try_compile_error_message "conflicting types for function declaration" << EOF +int f(x) int x; { return x; } +int f(char); +int main(void) { return 0; } +EOF +try_compile_error_message "conflicting types for function declaration" << EOF +int f(x, y) int x, y; { return x; } +int f(int); +int main(void) { return 0; } +EOF + +# Brace initializers take casts to function pointer types, and each element of +# an array of callbacks converts like any other callback object. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int a(void) { return 7; } +struct ops { int (*f)(int); int (*g)(void); }; +struct ops o = { (int (*)(int)) inc, (int (*)(void)) a }; +int (*t[2])(void) = { (int (*)(void)) a, 0 }; +int main(void) +{ + struct ops lo = { (int (*)(int)) inc, (int (*)(void)) 0 }; + int (*lt[2])(void) = { (int (*)(void)) a, 0 }; + return o.f(1) != 2 || o.g() != 7 || t[0]() != 7 || t[1] != 0 || lo.f(2) != 3 || lo.g != 0 || lt[0]() != 7; +} +EOF +try_ 0 << EOF +typedef int (*thunk_t)(void); +int inc(int x) { return x + 1; } +int a(void) { return 7; } +struct ops { int (*f)(int); int (*g)(void); thunk_t h; }; +struct ops o = { .g = (int (*)(void)) a, .f = (int (*)(int)) &inc, .h = (thunk_t) a }; +struct ops list[2] = { { (int (*)(int)) inc, 0, 0 }, { 0, (int (*)(void)) 0, (thunk_t) 0 } }; +static int (*t[3])(void) = { (int (*)(void)) a, 0, (thunk_t) a }; +int main(void) +{ + static struct ops so = { (int (*)(int)) inc }; + return o.f(1) != 2 || o.g() != 7 || o.h() != 7 || list[0].f(5) != 6 || + list[1].g != 0 || t[2]() != 7 || so.f(0) != 1; +} +EOF + +# In an expression too, a cast to a pointer to a callback typedef is an object +# pointer, so it compares with a pointer of that type, with an array of the +# callbacks and with null; it was taken for a callable function pointer and the +# comparison rejected. A cast naming the function pointer itself still is one. +try_ 0 << EOF +typedef int (*callback_t)(void); +typedef int function_t(void); +int one(void) { return 1; } +int main(void) +{ + callback_t table[2] = { one, one }; + callback_t *p = table, *n = 0; + int (**q)(void) = table; + callback_t c = (callback_t) one; + function_t *g = (function_t *) one; + if (p == (callback_t *) 16 || p != (callback_t *) table) return 1; + if (q != (int (**)(void)) table || p != q || n != (callback_t *) 0) return 2; + if ((*(callback_t *) table)() != 1 || c() != 1 || g() != 1) return 3; + return c != one || g != (callback_t) one; +} +EOF +try_compile_error_message "Function pointer comparison requires compatible pointers or null" << EOF +typedef int (*callback_t)(void); +int one(void) { return 1; } +int main(void) { callback_t *p = 0; return p == one; } +EOF + +# A cast to a pointer to function pointers, spelled out or through a callback +# typedef, is an object pointer cast in a static initializer: it converts an +# integer or an address for a scalar, a member or an element, and keeps the +# prototype it points to for the slot check. It was rejected as a non-constant. +try_ 0 << EOF +typedef int (*callback_t)(void); +struct holder { int (**slots)(void); int n; }; +int five(void) { return 5; } +int (*fs[2])(void) = { five, five }; +int (**s)(void) = (int (**)(void)) 4; +int (***r)(void) = (int (***)(void)) 20; +int (**q)(void) = (int (**)(void)) fs; +callback_t *p = (callback_t *) 16; +callback_t *pa = (int (**)(void)) 24; +struct holder g = { (int (**)(void)) 12, 3 }; +int (**t[2])(void) = { (int (**)(void)) 4, 0 }; +int main(void) +{ + static int (**ls)(void) = (int (**)(void)) 8; + static struct holder lg = { (int (**)(void)) 28, 1 }; + if (s != (int (**)(void)) 4 || r != (int (***)(void)) 20) return 1; + if ((*q[1])() != 5 || (void *) p != (void *) 16 || (void *) pa != (void *) 24) + return 2; + if (g.slots != (int (**)(void)) 12 || g.n != 3) return 3; + if (t[0] != (int (**)(void)) 4 || t[1] != 0) return 4; + if (ls != (int (**)(void)) 8 || lg.slots != (int (**)(void)) 28) return 5; + return 0; +} +EOF +try_compile_error_message "incompatible callback slot types in initializer" << EOF +int (**s)(void) = (int (**)(int)) 4; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible callback slot types in initializer" << EOF +struct holder { int (**slots)(void); }; +struct holder g = { (int (**)(int)) 12 }; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible callback slot types in array initializer" << EOF +int (**t[1])(void) = { (int (**)(int)) 4 }; +int main(void) { return 0; } +EOF + +# So is a cast to a function typedef with two stars, which points to function +# pointers as a callback typedef with one does. A single star still names the +# function pointer itself. It was rejected as a non-constant. +try_ 0 << EOF +typedef int fnty(void); +struct holder { int (**slots)(void); }; +int one(void) { return 1; } +int (**s)(void) = (fnty **) 4; +fnty **p = (fnty **) 16; +struct holder g = { (fnty **) 12 }; +int (**t[2])(void) = { (fnty **) 8, 0 }; +int (*f)(void) = (fnty *) one; +int (*z)(void) = (fnty *) 0; +int main(void) +{ + static int (**ls)(void) = (fnty **) 8; + if (s != (int (**)(void)) 4 || p != (int (**)(void)) 16) return 1; + if (g.slots != (int (**)(void)) 12 || t[0] != (fnty **) 8 || t[1]) return 2; + if (ls != (int (**)(void)) 8 || f() != 1 || z) return 3; + return 0; +} +EOF +try_compile_error_message "incompatible callback slot types in initializer" << EOF +typedef int fnty(int); +int (**s)(void) = (fnty **) 4; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible callback slot types in initializer" << EOF +typedef int fnty(int); +int main(void) { static int (**s)(void) = (fnty **) 4; return 0; } +EOF + +# An integer explicitly cast to a function pointer type initializes a member or +# element of a static aggregate; it was rejected as an implicit conversion. +try_ 0 << EOF +typedef int (*callback_t)(void); +struct ops { callback_t cb; int (*d)(int); }; +int (*f)(void) = (int (*)(void)) 1; +callback_t c = (callback_t) 4; +int (*z)(void) = (int (*)(void)) 0; +struct ops o = { (callback_t) 1, (int (*)(int)) 8 }; +struct ops arr[2] = { { (callback_t) 3 }, [1].cb = (callback_t) 4 }; +int (*t[2])(void) = { (int (*)(void)) 2, (callback_t) 0 }; +int main(void) +{ + static int (*sf)(void) = (int (*)(void)) 3; + static int (*st[1])(void) = { (int (*)(void)) 5 }; + static struct ops so = { (callback_t) 6 }; + if (f != (callback_t) 1 || c != (callback_t) 4 || z != 0) return 1; + if (o.cb != (callback_t) 1 || o.d != (int (*)(int)) 8) return 2; + if (arr[0].cb != (callback_t) 3 || arr[1].cb != (callback_t) 4) return 3; + if (t[0] != (callback_t) 2 || t[1] != 0) return 4; + if (sf != (callback_t) 3 || st[0] != (callback_t) 5 || so.cb != (callback_t) 6) + return 5; + return 0; +} +EOF +try_compile_error_message "incompatible function pointer types" << EOF +typedef int (*callback_t)(void); +struct ops { callback_t cb; }; +struct ops o = { (int (*)(int)) 1 }; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int inc(int x) { return x + 1; } +struct ops { int (*f)(int); }; +struct ops o = { (int (*)(void)) inc }; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int inc(int x) { return x + 1; } +int (*t[1])(void) = { (int (*)(int)) inc }; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int inc(int x) { return x + 1; } +int (*t[1])(void) = { inc }; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int inc(int x) { return x + 1; } +int main(void) { int (*t[1])(void) = { inc }; return 0; } +EOF + +# A callback may return a record, through the hidden result pointer, whether it +# is declared, named by a typedef or returned by a function. +try_ 0 << EOF +struct S { int a, b, c, d; }; +typedef struct S (*maker_t)(int); +struct S make(int x) { struct S s = {x, x + 1, x + 2, x + 3}; return s; } +struct S (*cb)(int) = make; +maker_t tcb = make; +maker_t get_t(void) { return make; } +int main(void) +{ + struct S (*local)(int) = make; + struct S r1 = cb(1); + struct S r3 = local(20); + struct S r4 = tcb(30); + struct S r5 = get_t()(40); + return r1.d != 4 || r3.c != 22 || r4.b != 31 || r5.d != 43; +} +EOF +try_ 0 << EOF +struct S { int a, b, c, d; }; +union U { int i; char c[8]; }; +struct S make(int x) { struct S s = {x, x + 1, x + 2, x + 3}; return s; } +union U make_u(int x) { union U u; u.i = x; return u; } +struct S (*get(void))(int) { return make; } +union U (*get_u(void))(int) { return make_u; } +struct S (*(*pg)(void))(int) = get; +struct S apply(struct S (*f)(int), int v) { return f(v); } +int main(void) +{ + struct S (*cb)(int) = get(); + struct S r1 = get()(10); + struct S r2 = pg()(20); + struct S r3 = apply(get(), 30); + union U u = get_u()(7); + return r1.a != 10 || r1.d != 13 || r2.c != 22 || r3.b != 31 || + cb(1).d != 4 || u.i != 7; +} +EOF + +# A compound literal may have a function pointer type, spelled out or through a +# typedef; at file scope its address is an address constant. +try_ 0 << EOF +typedef int (*unary_t)(int); +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +char *name(void) { return "ab"; } +int main(void) +{ + int (*f)(int) = (int (*)(int)){inc}; + int (**p)(int) = &(int (*)(int)){inc}; + int (*z)(int) = (int (*)(int)){0}; + char *(*n)(void) = (char *(*)(void)){name}; + unary_t t = (unary_t){dec}; + int (**q)(int) = (int (**)(int)){p}; + *p = dec; + return f(1) != 2 || (*p)(2) != 1 || z != 0 || n()[1] != 'b' || t(5) != 4 || + (*q)(9) != 8 || (int (*)(int)){inc}(4) != 5; +} +EOF +try_ 0 << EOF +typedef int (*unary_t)(int); +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +int (**gp)(int) = &(int (*)(int)){inc}; +unary_t *gt = &(unary_t){dec}; +int (**gn)(int) = &(int (*)(int)){0}; +int main(void) +{ + int r = (*gp)(3) != 4 || (*gt)(3) != 2 || *gn != 0; + *gp = dec; + return r || (*gp)(3) != 2; +} +EOF + +# A pointer to a const callback typedef object keeps the callback read-only, +# while the pointer itself stays modifiable. +try_ 0 << EOF +typedef int (*const cfn_t)(int); typedef int (*fn_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } cfn_t g = dec; int main(void) { cfn_t f = inc; cfn_t *p = &f; cfn_t **pp = &p; fn_t h = inc; fn_t *q = &h; *q = dec; p = &g; *pp = &g; return (*p)(5) != 4 || (**pp)(3) != 2 || h(1) != 0; } +EOF +try_ 0 << EOF +typedef int (*const cfn_t)(int); typedef int (*fn_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } cfn_t g = dec; int main(void) { cfn_t f = inc; cfn_t *p = &f; cfn_t **pp = &p; *pp = &g; return (**pp)(3) - 2; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +typedef int (*const cfn_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { cfn_t f = inc; cfn_t *p = &f; *p = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +typedef int (*const cfn_t)(int); typedef int (*fn_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { cfn_t f = inc; cfn_t *p = &f; p[0] = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +typedef int (*const cfn_t)(int); typedef int (*fn_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { cfn_t f = inc; cfn_t *p = &f; cfn_t **pp = &p; **pp = dec; return 0; } +EOF +try_compile_error_message "incompatible callback slot types" << EOF +typedef int (*const cfn_t)(int); typedef int (*fn_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { cfn_t f = inc; fn_t *p = &f; return 0; } +EOF + +# Any pointer level of a callback slot may be qualified, the callback pointer +# itself included, and a store through a const level is rejected; restrict may +# not qualify the callback pointer (C99 6.7.3p2). +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +int (*f)(int) = inc; +int (*const cf)(int) = dec; +int main(void) +{ + int (*g)(int) = dec; + int (*const *p)(int) = &f; + int (*const **pp)(int) = &p; + int (**const *q)(int); + int (**m)(int) = &g; + int (* volatile *vp)(int) = &g; + int r = (*p)(1) != 2 || (**pp)(2) != 3; + p = &cf; + r |= (*p)(5) != 4 || (**pp)(5) != 4; + q = &m; + **q = inc; + r |= g(1) != 2; + *vp = dec; + r |= g(1) != 0 || (*vp)(3) != 2; + return r; +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*const f)(int) = inc; int (*const *p)(int) = &f; return (*p)(1) - 2; } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*f)(int) = inc; int (**const q)(int) = &f; int (**const *p)(int) = &q; **p = dec; return f(1); } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*volatile f)(int) = inc; int (*volatile *p)(int) = &f; *p = dec; return f(3) - 2; } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (**restrict *slot_t)(int); int (*f)(int) = inc; int (**s)(int) = &f; int main(void) { slot_t t = &s; return (**t)(1) - 2; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*const f)(int) = inc; int (*const *p)(int) = &f; *p = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*const f)(int) = inc; int (*const *p)(int) = &f; int (*const **pp)(int) = &p; **pp = dec; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*f)(int) = inc; int (**const q)(int) = &f; int (**const *p)(int) = &q; *p = 0; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +int (*const cf)(int) = inc; +int main(void) { int (*const *p)(int) = &cf; p[0] = inc; return 0; } +EOF +try_compile_error_message "incompatible callback slot types" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int main(void) { int (*const f)(int) = inc; int (*const *p)(int) = &f; int (*const **pp)(int) = &p; int (**bad)(int) = p; return 0; } +EOF +try_compile_error_message "incompatible callback slot types" << EOF +int inc(int x) { return x + 1; } +int (*const cf)(int) = inc; +int main(void) { int (**p)(int) = &cf; return 0; } +EOF +try_compile_error_message "incompatible callback slot types" << EOF +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int main(void) { int (*const **pp)(int) = &s; return 0; } +EOF +try_compile_error_message "restrict requires a pointer to an object type" << EOF +int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } typedef int (*restrict *slot_t)(int); int main(void) { return 0; } +EOF + +# A callback slot may be any number of pointers deep: declared, as a typedef at +# either scope, cast, as a compound literal, returned, or through void *. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +char *name(void) { return "ab"; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int (***t)(int) = &s; +char *(*nf)(void) = name; +char *(**ns)(void) = &nf; +char *(***nt)(void) = &ns; +int call3(int (***p)(int), int v) { return (**p)(v); } +int (***get3(void))(int) { return t; } +int main(void) +{ + int (*g)(int) = dec; + int (**gs)(int) = &g; + int (***lt)(int) = &s; + int (****u)(int) = < + int r = 0; + r |= (**t)(1) != 2; + r |= (***u)(3) != 4; + r |= (*nf)()[1] != 'b' || (*ns)()[0] != 'a' || (**nt)()[1] != 'b'; + r |= call3(t, 5) != 6; + r |= (**get3())(6) != 7; + *lt = gs; + r |= (**lt)(5) != 4; + **lt = inc; + r |= g(1) != 2; + ***u = dec; + r |= g(1) != 0; + r |= **t != dec; + return r; +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int (***t)(int) = &s; +void *erased = &t; +int size3 = sizeof(int (***)(int)); +int main(void) +{ + int (****u)(int) = (int (****)(int)) erased; + int (***v)(int) = (int (***)(int)){&s}; + int (****w)(int) = &(int (***)(int)){t}; + char *(***x)(void) = (char *(***)(void)) 0; + return (***u)(1) != 2 || (**v)(2) != 3 || (***w)(3) != 4 || x != 0 || + size3 != sizeof(void *) || + sizeof((int (****)(int)) erased) != sizeof(void *); +} +EOF +try_ 0 << EOF +typedef int (**slot_t)(int); +typedef int (***slot3_t)(int); +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +slot_t s = &f; +slot3_t t = &s; +int main(void) +{ + typedef int (***local3_t)(int); + local3_t lt = &s; + slot3_t *pt = &t; + return (**t)(1) != 2 || (**lt)(2) != 3 || (***pt)(3) != 4 || + sizeof(slot3_t) != sizeof(void *); +} +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } int (*f)(int) = inc; int (**s)(int) = &f; int main(void) { typedef int (***l3)(int); l3 q = &s; int (**r)(int) = *q; return (*r)(3) - 4; } +EOF +try_ 0 << EOF +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int (** const *t)(int) = &s; +int main(void) +{ + int (** const cs)(int) = &f; + int (*** const ct)(int) = &s; + return (**t)(1) != 2 || (*cs)(2) != 3 || (**ct)(3) != 4; +} +EOF +try_compile_error_message "incompatible callback slot types" << EOF +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int main(void) { int (***t)(int) = s; return 0; } +EOF +try_compile_error_message "incompatible callback slot types" << EOF +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int (***t)(int) = &s; +int main(void) { int (**q)(int) = t; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << EOF +int inc(int x) { return x + 1; } +int (*f)(int) = inc; +int (**s)(int) = &f; +int (** const *t)(int) = &s; +int main(void) { *t = s; return 0; } +EOF + +# A call returning a callback slot typedef, or a pointer to one, keeps the slot +# on its value, directly or through a function pointer of that return type. +try_ 0 << EOF +typedef int (**slot_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int (*cb)(int) = inc; slot_t get(void) { return &cb; } int main(void) { slot_t s = get(); return (*get())(2) - 3 + (*s)(1) - 2; } +EOF +try_ 0 << EOF +typedef int (**slot_t)(int); int inc(int x) { return x + 1; } int dec(int x) { return x - 1; } int (*cb)(int) = inc; slot_t *get(void) { static slot_t s = &cb; return &s; } int main(void) { slot_t *s = get(); return (**get())(2) - 3 + (**s)(1) - 2; } +EOF +try_ 0 << EOF +typedef int (**slot_t)(int); +typedef int (*callback_t)(int); +int inc(int x) { return x + 1; } +int (*cb)(int) = inc; +slot_t get(void) { return &cb; } +slot_t *get_deeper(void) { static slot_t s = &cb; return &s; } +callback_t *get_typedef(void) { return &cb; } +int main(void) +{ + typedef int (**local_slot_t)(int); + local_slot_t (*pg)(void) = get; + slot_t s = get(); + slot_t *ss = get_deeper(); + callback_t *ts = get_typedef(); + local_slot_t ls = pg(); + return (*get())(2) != 3 || (*s)(1) != 2 || (**get_deeper())(3) != 4 || + (**ss)(4) != 5 || (*pg())(5) != 6 || (*ls)(6) != 7 || + (*get_typedef())(7) != 8 || (*ts)(8) != 9; +} +EOF + +# A function may return a pointer to a callback slot, spelled out or through a +# typedef, and a call's result then dereferences to the callback. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int (*slot)(int) = inc; +int (**get(void))(int) { return &slot; } +int (**(*pg)(void))(int) = get; +int main(void) +{ + int (**s)(int) = get(); + return (*s)(1) != 2 || (*get())(2) != 3 || (*pg())(3) != 4; +} +EOF +try_ 0 << EOF +typedef int (*UP)(int); +int inc(int x) { return x + 1; } +UP slot = inc; +UP *get(void) { return &slot; } +UP *(*pg)(void) = get; +int main(void) +{ + UP *s = get(); + return (*s)(1) != 2 || (*get())(2) != 3 || (*pg())(3) != 4; +} +EOF + +# The returned function pointer may be qualified, and a typedef may name the +# type of a function returning a function pointer, at either scope. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +typedef int (*get_t(void))(int); +typedef int (*pick_t(int which))(int); +get_t get; +pick_t pick; +int (*const cget(void))(int); +get_t *pointer_to_get = get; +int main(void) +{ + typedef int (*local_t(void))(int); + local_t *local = get; + return get()(1) != 2 || pick(0)(5) != 4 || cget()(2) != 3 || + pointer_to_get()(3) != 4 || local()(4) != 5; +} +int (*get(void))(int) { return inc; } +int (*pick(int which))(int) { return which ? inc : dec; } +int (*const cget(void))(int) { return inc; } +EOF +try_compile_error_message "function definition cannot take its type from a typedef" << EOF +int inc(int x) { return x + 1; } +typedef int (*get_t(void))(int); +get_t get { return inc; } +int main(void) { return 0; } +EOF + +# A function may return a spelled function pointer, `int (*get(void))(int)`, and +# a pointer may point to such a function. +try_ 0 << EOF +int inc(int x) { return x + 1; } +int dec(int x) { return x - 1; } +char *hello(void) { return "hi"; } +int (*get(void))(int); +int (*get(void))(int) { return inc; } +int (*pick(int which))(int) { return which ? inc : dec; } +char *(*greeter(void))(void) { return hello; } +int (*(*gpg)(void))(int) = get; +int (*(*table[2])(int))(int) = {pick, pick}; +int use(int (*(*g)(int))(int)) { return g(0)(10); } +int main(void) +{ + int (*local(void))(int); + int (*(*pg)(void))(int) = get; + int (*(*pp)(int))(int) = &pick; + int (*cb)(int) = get(); + return get()(3) != 4 || pick(1)(5) != 6 || pick(0)(5) != 4 || + greeter()()[1] != 'i' || pg()(1) != 2 || gpg()(2) != 3 || + pp(0)(9) != 8 || cb(0) != 1 || use(pick) != 9 || + local()(7) != 8 || table[1](1)(1) != 2; +} +int (*local(void))(int) { return inc; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +char *hello(void) { return "hi"; } +int (*get2(void))(int) { return hello; } +int main(void) { return 0; } +EOF +try_compile_error_message "conflicting types for function declaration" << EOF +int inc(int x) { return x + 1; } +int (*get(void))(void); +int (*get(void))(int) { return inc; } +int main(void) { return 0; } +EOF + +# A cast may spell a function pointer type out, in expressions, sizeof and +# static initializers alike. +try_ 0 << EOF +typedef int (*thunk_t)(void); +int one(int x) { return x + 1; } +void set(int v) {} +char *name(void) { return "ab"; } +int (*global_cast)(void) = (int (*)(void)) one; +int (*global_null)(int) = (int (*)(int)) 0; +thunk_t global_typedef = (thunk_t) one; +int size_one = sizeof(int (*)(void)); +int size_two = sizeof(char *(**)(int, ...)); +int main(void) { + static int (*from_address)(int) = (int (*)(int)) &one; + int (*f)(void) = (int (*)(void)) one; + int (*back)(int) = (int (*)(int)) f; + void (*g)(int, ...) = (void (*)(int, ...)) set; + int (*fp)(int) = one; + int (**pp)(int) = &fp; + int (**qq)(int) = (int (**)(int)) pp; + char *(*nf)(void) = (char *(*)(void)) 0; + char *(*nm)(void) = (char *(*)(void)) name; + g(1, 2); + return back(2) != 3 || (*qq)(3) != 4 || ((int (*)(int)) f)(4) != 5 || + nf != 0 || nm()[1] != 'b' || global_null != 0 || + ((int (*)(int)) global_cast)(1) != 2 || + ((int (*)(int)) global_typedef)(2) != 3 || from_address(5) != 6 || + size_one + size_two != 2 * sizeof(void *) || + sizeof((int (*)(void)) one) != sizeof(void *); +} +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int one(int x) { return x + 1; } +int (*g)(int) = (int (*)(void)) one; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int one(int x) { return x + 1; } +int main(void) { int (*fp)(int) = (int (*)(void)) one; return 0; } +EOF + +# A function cast to a callback typedef is that callback, even when the +# prototypes differ, not a pointer to a callback slot. +try_ 0 << EOF +typedef int (*call_t)(int, long long); +typedef int (*thunk_t)(void); +int words(int a, int skipped, int low, int high) { return low + high; } +int one(int x) { return x; } +int main(void) { + call_t call = (call_t) words; + int (*thunk)(void) = (thunk_t) one; + thunk_t again = (thunk_t) one; + return call != (call_t) words || thunk != again; +} +EOF + +# Function pointers convert implicitly only between compatible function types +# (C99 6.5.16.1p1); an unprototyped type is compatible with a prototype without +# an ellipsis or promoted parameters (6.7.5.3p15). A void pointer converts to +# none but the null pointer constant. +try_ 0 << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +typedef int unary_t(int); +typedef int (*unary_ptr)(int); +typedef void (*handler_t)(int); +int unprototyped(); +int unprototyped(int x) { return x; } +int old_style() { return 3; } +int apply(int (*fn)(int), int v) { return fn(v); } +int apply_typedef(unary_t *fn, int v) { return fn(v); } +void handler(int s) {} +handler_t install(handler_t f) { return f; } +int (*global_fp)(int) = one; +unary_ptr global_addr = &one; +unary_t *global_null = 0; +int (*global_void_null)(int) = (void *) 0; +struct ops { int (*f)(int); handler_t g; } table = {one, handler}; +unary_ptr get(void) { return one; } +int main(void) { + int (*fp)(int) = one; + int (*np)(int) = (void *) 0; + int (*up)() = one; + int (*up_void)() = zero; + int (*from_unprototyped)(int) = unprototyped; + unary_ptr u = fp; + unary_t *deref = *one; + struct ops local = {one, handler}; + handler_t old = install(handler); + old = install(0); + local.f = u; + fp = 0; + fp = up; + fp = global_fp; + fp = get(); + np = deref; + up = old_style; + return apply(one, 1) + apply_typedef(fp, 1) + apply(u, 1) + + apply(0 ? one : fp, 1) + local.f(1) + get()(1) + global_addr(1) + + from_unprototyped(1) + up(0) + table.f(1) + (np == 0) + + (global_null != 0) + (global_void_null != 0) + (old != 0) != + 12; +} +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int apply(int (*fn)(int), int v) { return fn(v); } +int main(void) { return apply(zero, 2); } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +typedef int unary_t(int); +int apply(unary_t *fn, int v) { return fn(v); } +int main(void) { int (*z)(void) = zero; return apply(z, 2); } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +typedef int (*unary_ptr)(int); +unary_ptr get(void) { return zero; } +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int main(void) { int (*fp)(void) = name; return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int (*global_fp)(int) = zero; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +struct ops { int (*f)(void); } table = {one}; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int main(void) { int (*fp)(int); fp = count; return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +struct S { int (*cb)(int); }; +int main(void) { struct S s; s.cb = zero; return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int main(void) { void *p = 0; int (*fp)(int) = p; return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int main(void) { void *p = one; return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int zero(void) { return 0; } +int one(int x) { return x; } +char *name(void) { return "a"; } +int count(int n, ...) { return n; } +int take_char(char c) { return c; } +int main(void) { int (*fp)() = take_char; return 0; } +EOF + +# An integer becomes a pointer without a cast only as a null pointer constant +# (C99 6.5.16.1p1), in initializers, assignments, arguments and returns alike. A +# conditional with a pointer operand has that pointer type (6.5.15p6). +try_ 0 << EOF +#define NULL_ALT 0 +enum { ZERO }; +int *g1 = 0, *g2 = (void *) 0, *g3 = 1 - 1, *g4 = NULL_ALT; +char *g5 = ZERO; +int *ret0(void) { return 0; } +void take(int *p, char *q) {} +int main(void) { + int x = 3; + int *p = 0, *q = (int *) 7, *r = &x, *s = (void *) 0, *t = ZERO; + char *u = '\0'; + int arr[2] = {5, 6}; + int *ap = x ? arr : 0; + p = 0; + p = 2 - 2; + q = x ? &x : (void *) 0; + if (*(x ? ap : arr) != 5 || *((x ? ap : arr) + 1) != 6) + return 99; + take(0, 0); + take((int *) 0, (char *) 0); + int *(cl) = (int *){0}; + return (ret0() != 0) + (p != 0) + (*q != 3) + (*r != 3) + (s != 0) + + (t != 0) + (u != 0) + (g1 != 0) + (g2 != 0) + (g3 != 0) + + (g4 != 0) + (g5 != 0) + (cl != 0); +} +EOF +try_ 0 << EOF +enum { ZERO, ONE }; +int take(int *p) { return p == 0; } +int *ret(void) { return ZERO; } +int main(void) { + int *t = ZERO; + char *u; + u = ZERO; + return !take(ZERO) || t || u || ret() || ONE + ONE != 2; +} +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +enum { ZERO, ONE }; +int main(void) { int *t = ONE; return t != 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { int *p = 7; return 0; } +EOF + +# A character read out of a string literal is an integer, and only its address +# points into the literal. +try_compile_error_message "integer converted to pointer without a cast" << EOF +void sink(int *p) {} +int main(void) { sink(*("x")); return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { int *p = "xy"[1]; return p != 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { int *p; int x = 3; p = x; return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +void f(int *p) {} +int main(void) { f(1); return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +void g(int q[3]) {} +int main(void) { g(1); return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int *f(int x) { return x; } +int main(void) { return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int *p = 3 + 4; +int main(void) { return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +long long *p = 0x100000000LL; +int main(void) { return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +typedef int *P; +int main(void) { P v = (P){7}; return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { static int *p = 5; return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int (*f)(void) = 1; +int main(void) { return 0; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +typedef int (*callback_t)(void); +int main(void) { static callback_t c = 4; return c != 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int (*f)(void) = (int (*)(int)) 2; +int main(void) { return 0; } +EOF + +# A function pointer, even one an explicit cast produced, does not convert to an +# object pointer in a static initializer, for a scalar, a member or an element. +try_compile_error_message "incompatible function pointer types" << EOF +int *p = (int (*)(void)) 1; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int main(void) { static void *v = (int (*)(void)) 0; return v != 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int *t[1] = { (int (*)(void)) 1 }; +int main(void) { return 0; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int f(void) { return 0; } +int main(void) { int (*g)(void) = f; int *t[1] = { g }; return t[0] != 0; } +EOF +try_ 0 << EOF +int x; +int f(void) { return 1; } +int *t[2] = { &x, (int *) 4 }; +void *u[2] = { 0, &x }; +int *p = (int *) 8; +int (*fs[2])(void) = { f, (int (*)(void)) 0 }; +int main(void) +{ + return t[0] != &x || t[1] != (int *) 4 || u[1] != &x || p != (int *) 8 || + fs[0]() != 1 || fs[1] != 0; +} +EOF +try_ 0 << EOF +typedef int (*callback_t)(void); +int (*f)(void) = (int (*)(void)) 1; +callback_t c = (callback_t) 4, n = 0; +int main(void) { return f != (callback_t) 1 || c != (callback_t) 4 || n; } +EOF +try_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { int x = 1; int *p = x ? 1 : 2; return 0; } +EOF + +# Every declarator of a block declaration has its initializer converted and +# diagnosed, not just the first one. +try_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { int *a = 0, *b = 7; return a != b; } +EOF +try_compile_error_message "incompatible function pointer types" << EOF +int g(void) { return 0; } +int main(void) { int (*f)(void) = g, (*h)(int) = g; return f != 0 && h != 0; } +EOF +try_compile_error_message "discarding const qualifier" << EOF +int main(void) { const int c = 1; int *a = 0, *b = &c; return a != b; } +EOF +try_ 0 << EOF +int g(void) { return 3; } +int main(void) { + int x = 1, (*h)(void) = g, (*k)(void) = &g, *p = 0, *q = &x; + char *e = 0, *s = "x"; + return h() + k() + x != 7 || p || *q != 1 || e || s[0] != 'x'; +} +EOF + +# Category: Const Qualifiers +begin_category "Const Qualifiers" "Testing const qualifier support for variables and parameters" + +# Explicit casts may remove const in C, but must be diagnosed. Adding const +# remains legal and should not spuriously warn. +try_compile_warning "Warning: discarding const qualifier in cast" << EOF +int main(void) { + const int value = 42; + int *mutable = (int *)&value; + return *mutable; +} +EOF + +try_compile_warning "Warning: string literal is read-only" "--warn-string-literals" << EOF +int main(void) { + char *text = "hello"; + return text[0]; +} +EOF + +try_compile_warning "Warning: string literal is read-only" "--warn-string-literals" << EOF +char *message = "global"; +int main(void) { + return message[0]; +} +EOF + +try_compile_warning "Warning: string literal is read-only" "--warn-string-literals" << EOF +int first(char *text) { return text[0]; } +int main(void) { + return first("argument"); +} +EOF + +# Assigning a string literal to a char pointer stores it; with the warning +# enabled it warns as well. The assignment was dropped, and as an expression it +# was rejected as yielding no value. +try_ 0 << EOF +char *g; +int main(void) { + char *s = "a"; + char *t; + s = "xy"; + t = g = "pq"; + for (s = "zz"; 0;) + ; + return s[1] != 'z' || g[1] != 'q' || t[0] != 'p'; +} +EOF +try_compile_warning "Warning: string literal is read-only" "--warn-string-literals" << EOF +int main(void) { + char *text; + text = "hello"; + return text[0] != 'h'; +} +EOF + +try_ 42 << EOF +int main(void) { + int value = 42; + const int *read_only = (const int *)&value; + return *read_only; +} +EOF + +# A pointer object's qualifier belongs one level inward after address-of. +# Preserve it so a valid pointer-to-const-pointer declaration is accepted, while +# the reverse conversion cannot discard that intermediate qualifier. +try_ 2 << EOF +int main(void) { + int first = 1, second = 2; + int * const fixed = &first; + int * const *indirect = &fixed; + indirect = &fixed; + return **indirect + (*fixed == first); +} +EOF + +try_compile_error << EOF +int main(void) { + int value = 1; + int * const fixed = &value; + int **mutable_indirect = &fixed; + return **mutable_indirect; +} +EOF + +try_compile_error << EOF +int main(void) { + int first = 1, second = 2; + int * const fixed = &first; + int * const *indirect = &fixed; + *indirect = &second; + return *fixed; +} +EOF + +# A cast to the same multi-level qualified type preserves the inner pointer +# qualifier instead of treating it as a discarded base-object qualifier. +try_ 1 << EOF +int main(void) { + int value = 1; + int * const fixed = &value; + int * const *indirect = &fixed; + int * const *copy = (int * const *)indirect; + return **copy; +} +EOF + +# Qualified union objects use the normal aggregate initializer path. +try_ 42 << EOF +union number { int integer; char character; }; +int main(void) { + const union number value = {42}; + return value.integer; +} +EOF + +# C99 constraint violations: qualifiers make the designated object read-only. +try_compile_error << EOF +int main(void) { + const int x = 1; + x = 2; + return x; +} +EOF + +try_compile_error << EOF +struct pair { int x; int y; }; +int main(void) { + const struct pair value = {1, 2}; + value.x = 3; + return value.x; +} +EOF + +try_compile_error << EOF +int main(void) { + const int values[2] = {1, 2}; + values[1] = 3; + return values[1]; +} +EOF + +# Qualifiers apply to every declarator in one declaration, not just the first. +try_compile_error << EOF +int main(void) { + const int first = 1, second = 2; + second = 3; + return first + second; +} +EOF + +# A scalar typedef preserves const qualification on each use. +try_compile_error << EOF +typedef const int const_int; +int main(void) { + const_int value = 1; + value = 2; + return value; +} +EOF + +# A qualifier after a typedef declarator's star belongs to the pointer object. +# It must parse, retain its type-level depth, and reject conversion through a +# pointer-to-pointer that would permit replacing the fixed pointer. +try_ 1 << EOF +typedef int *const fixed_ptr; +int main(void) { + int value = 1; + fixed_ptr fixed = &value; + return *fixed; +} +EOF +try_compile_error << EOF +typedef int *const fixed_ptr; +typedef fixed_ptr *const fixed_handle; +int main(void) { + int value = 1; + fixed_ptr fixed = &value; + fixed_handle handle = &fixed; + int ***mutable = &handle; + return ***mutable; +} +EOF +try_compile_error << EOF +typedef int *const fixed_ptr; +int main(void) { + int first = 1, second = 2; + fixed_ptr fixed = &first; + fixed = &second; + return *fixed; +} +EOF +try_ 1 << EOF +typedef int *const fixed_ptr; +typedef fixed_ptr *const fixed_handle; +int main(void) { + int value = 1; + fixed_ptr fixed = &value; + fixed_handle handle = &fixed; + return **handle; +} +EOF +try_compile_error << EOF +typedef int *const fixed_ptr; +int main(void) { + int value = 1; + fixed_ptr fixed = &value; + int **mutable = &fixed; + return **mutable; +} +EOF +try_compile_error << EOF +int main(void) { + const int x = 1; + x++; + return x; +} +EOF + +# C99 permits a qualifier after the base type. It still qualifies the object, +# whereas a qualifier after `*` qualifies the pointer itself. +try_compile_error << EOF +int main(void) { + int const value = 1; + value = 2; + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + int value = 1; + int const *pointer = &value; + *pointer = 2; + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + int first = 1, second = 2; + int * const pointer = &first; + pointer = &second; + return *pointer; +} +EOF + +try_compile_error << EOF +struct trailing_const_pair { int value; }; +int main(void) { + struct trailing_const_pair const pair = {1}; + pair.value = 2; + return pair.value; +} +EOF + +try_compile_error << EOF +void change(int const *pointer) { + *pointer = 2; +} +int main(void) { + int value = 1; + change(&value); + return value; +} +EOF + +try_compile_error << EOF +int const trailing_global = 1; +int main(void) { + trailing_global = 2; + return trailing_global; +} +EOF + +try_compile_error << EOF +typedef int *int_pointer; +int main(void) { + int first = 1, second = 2; + int_pointer const pointer = &first; + pointer = &second; + return *pointer; +} +EOF + +try_compile_error << EOF +typedef int *int_pointer; +int main(void) { + int first = 1, second = 2; + const int_pointer pointer = &first; + pointer = &second; + return *pointer; +} +EOF + +# A qualifier hidden in a typedef must survive another typedef and a +# dereference: *slot designates the const pointer object, not its int pointee. +try_compile_error << EOF +typedef int *const fixed_ptr; +typedef fixed_ptr *fixed_ptr_slot; +int main(void) { + int value = 1; + fixed_ptr fixed = &value; + fixed_ptr_slot slot = &fixed; + *slot = &value; + return **slot; +} +EOF + +try_ 2 << EOF +typedef int *const fixed_ptr; +typedef fixed_ptr *fixed_ptr_slot; +int main(void) { + int value = 1; + fixed_ptr fixed = &value; + fixed_ptr_slot slot = &fixed; + **slot = 2; + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + int value = 1; + const int *p = &value; + *p = 2; + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + const int value = 1; + int *p = &value; + return *p; +} +EOF + +# Adding const through two pointer levels is unsafe: a const pointer could be +# written back through the original int **. +try_compile_error << EOF +int main(void) { + int value = 7; + int *p = &value; + const int **cpp = &p; + return **cpp; +} +EOF + +try_compile_error << EOF +int main(void) { + int value = 7; + int *p = &value; + int **pp = &p; + const int **cpp; + cpp = pp; + return **cpp; +} +EOF + +try_compile_error << EOF +int main(void) { + int *p = 0; + const int value = 1; + p = &value; + return *p; +} +EOF + +# Parameter conversion must reject the same qualifier loss as an assignment. +try_compile_error << EOF +void overwrite(int *p) { *p = 2; } +int main(void) { + const int value = 1; + overwrite(&value); + return value; +} +EOF + +# A retained function-pointer prototype must enforce it as well. +try_compile_error << EOF +void overwrite(int *p) { *p = 2; } +int main(void) { + const int value = 1; + void (*fn)(int *) = overwrite; + fn(&value); + return value; +} +EOF + +# C99 prototype calls require exactly the declared fixed argument count. +try_compile_error << EOF +int identity(int value) { return value; } +int main(void) { return identity(); } +EOF +try_compile_error << EOF +int identity(int value) { return value; } +int main(void) { return identity(1, 2); } +EOF +try_compile_error << EOF +int identity(int value) { return value; } +int main(void) { int (*fn)(int) = identity; return fn(); } +EOF +try_compile_error << EOF +int sum(int first, ...) { return first; } +int main(void) { return sum(); } +EOF +try_ 4 << EOF +/* An empty parameter list is not a prototype in C99. */ +int legacy() { return 4; } +int main(void) { return legacy(1, 2); } +EOF +try_ 4 << EOF +int refined(); +int refined(int value) { return value; } +int main(void) { return refined(4); } +EOF +try_ 4 << EOF +int qualified_parameter(const int value); +int qualified_parameter(int value) { return value; } +int main(void) { return qualified_parameter(4); } +EOF +try_compile_error << EOF +int incompatible_parameter(const int *value); +int incompatible_parameter(int *value) { return *value; } +EOF +try_compile_error << EOF +int incompatible_volatile_parameter(volatile int *value); +int incompatible_volatile_parameter(int *value) { return *value; } +EOF +try_compile_error << EOF +int retained(int value); +int retained(); +int retained(int value) { return value; } +int main(void) { return retained(); } +EOF +try_compile_error << EOF +int incompatible_definition(int value); +int incompatible_definition() { return 0; } +EOF + +try_compile_error << EOF +int main(void) { + int value = 1; + const int *p = &value; + *p += 2; + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + int first = 1, second = 2; + int * const p = &first; + p = &second; + return *p; +} +EOF + +try_compile_error << EOF +void set_value(const int *p) { + *p = 2; +} +int main(void) { + int value = 1; + set_value(&value); + return value; +} +EOF + +try_compile_error << EOF +int main(void) { + int value = 1; + const int *p = &value; + const int **pp = &p; + **pp = 2; + return value; +} +EOF + +# Test 1: Basic const local variable +try_ 42 << EOF +int main() { + const int x = 42; + return x; +} +EOF + +# Test 2: Const global variable +try_ 100 << EOF +const int global_const = 100; +int main() { + return global_const; +} +EOF + +# Test 3: Multiple const variables +try_ 30 << EOF +int main() { + const int a = 10; + const int b = 20; + return a + b; +} +EOF + +# Test 4: Const parameter in function +try_ 15 << EOF +int add_five(const int x) { + return x + 5; +} +int main() { + return add_five(10); +} +EOF + +# Test 5: Const pointer value (simplified) +try_ 25 << EOF +int main() { + const int value = 25; + const int *ptr = &value; + return *ptr; +} +EOF + +# Test 6: Pointer to const data +try_ 35 << EOF +int main() { + const int value = 35; + const int *ptr = &value; + return *ptr; +} +EOF + +# Test 7: Const in arithmetic expressions +try_ 60 << EOF +int main() { + const int x = 20; + const int y = 30; + const int z = 10; + return x + y + z; +} +EOF + +# Test 8: Const with initialization from expression +try_ 50 << EOF +int main() { + int a = 10; + const int b = a * 5; + return b; +} +EOF + +# Test 9: Function returning through const variable +try_ 77 << EOF +int compute() { + const int result = 77; + return result; +} +int main() { + return compute(); +} +EOF + +# Test 10: Const array element access +try_ 30 << EOF +int main() { + const int arr[3] = {10, 20, 30}; + return arr[2]; +} +EOF + +# Test 11: Mixed const and non-const +try_ 45 << EOF +int main() { + const int x = 15; + int y = 20; + const int z = 10; + return x + y + z; +} +EOF + +# Test 12: Const with conditional +try_ 40 << EOF +int main() { + const int x = 40; + const int y = 50; + return (x < y) ? x : y; +} +EOF + +# Test 13: Const value from struct (simplified) +try_ 99 << EOF +struct Point { + int x; + int y; +}; +int main() { + struct Point p = {99, 100}; + const int val = p.x; + return val; +} +EOF + +# Test 14: Const char array (string) +try_ 72 << EOF +int main() { + const char str[] = "Hello"; + return str[0]; /* 'H' = 72 */ +} +EOF + +# Test 15: Multiple const on same line +try_ 55 << EOF +int main() { + const int a = 10, b = 20, c = 25; + return a + b + c; +} +EOF + +# Test 16: Const with typedef +try_ 88 << EOF +typedef int myint; +int main() { + const myint value = 88; + return value; +} +EOF + +# Test 17: Const void pointer +try_ 12 << EOF +int main() { + int val = 12; + const void *ptr = &val; + const int *iptr = ptr; + return *iptr; +} +EOF + +# Test 18: Nested const usage +try_ 18 << EOF +int get_value(const int x) { + const int multiplier = 2; + return x * multiplier; +} +int main() { + const int input = 9; + return get_value(input); +} +EOF + +# Test 19: Const with pointer arithmetic +try_ 30 << EOF +int main() { + const int arr[] = {10, 20, 30, 40}; + const int *ptr = arr; + ptr = ptr + 2; + return *ptr; +} +EOF + +# Test 20: Const with literal value +try_ 3 << EOF +int main() { + const int x = 3; + return x; +} +EOF + +# Category: Ternary Operator +begin_category "Ternary Operator" "Testing conditional ?: operator" + +# conditional operator +expr 10 "1 ? 10 : 5" +expr 25 "0 ? 10 : 25" + +# Record operands are objects: the selected one is copied whole, not joined as a +# truncated scalar, whether it comes from a variable or a call result. +try_ 131 << EOF +typedef struct { int rank; int bounds[4]; } shape_t; +typedef struct { char c; short s; } narrow_t; +shape_t wide(void) { shape_t s = {3, {10, 20, 30, 40}}; return s; } +shape_t low(void) { shape_t s = {1, {5, 6, 7, 8}}; return s; } +int pick(int c) { + shape_t local = {9, {1, 2, 3, 4}}; + shape_t chosen; + chosen = c ? wide() : low(); + shape_t mixed = c ? local : low(); + narrow_t a = {1, 300}, b = {2, 400}; + narrow_t nested = c > 1 ? a : c ? b : a; + return chosen.rank + chosen.bounds[3] + mixed.bounds[0] + mixed.rank + + nested.s / 100; +} +int main(void) { return pick(1) + pick(0) + pick(2); } +EOF +try_compile_error_message "Conditional record operands must have the same type" << EOF +typedef struct { int a; } one_t; +typedef struct { int a; int b; } two_t; +int main(void) { + one_t x = {1}; + two_t y = {1, 2}; + int c = 1; + one_t z = c ? x : y; + return z.a; +} +EOF + +# Category: Compound Assignment +begin_category "Compound Assignment" "Testing +=, -=, *=, /=, %=, <<=, >>=, ^= operators" + +# compound assignemnt +items 5 "int a; a = 2; a += 3; return a;" +items 5 "int a; a = 10; a -= 5; return a;" +items 4 "int a; a = 2; a *= 2; return a;" +items 33 "int a; a = 100; a /= 3; return a;" +items 1 "int a; a = 100; a %= 3; return a;" +items 4 "int a; a = 2; a <<= 1; return a;" +items 2 "int a; a = 4; a >>= 1; return a;" +items 1 "int a; a = 1; a ^= 0; return a;" +items 20 "int *p; int a[3]; a[0] = 10; a[1] = 20; a[2] = 30; p = a; p+=1; return p[0];" +items 8 "short s; s = 5; s += 3; return s;" +items 15 "short s; s = 20; s -= 5; return s;" +items 24 "short s; s = 6; s *= 4; return s;" +try_ 7 << EOF +int main(void) { + int value = 1; + value += 1 ? 6 : 9; + return value; +} +EOF +try_ 6 << EOF +int main(void) { + int value = 7; + value ^= 0 ? 1 : 1; + return value; +} +EOF +try_ 2 << EOF +int main(void) { + int negative = -1; + unsigned int divisor = 2U; + negative /= divisor; + int quotient = negative; + negative %= divisor; + return (quotient == 2147483647U) + (negative == 1U); +} +EOF + +# Compound assignment promotes an unsigned char or unsigned short right operand +# before the usual arithmetic conversions, so it is zero-extended into a long +# long left operand exactly as in sum = sum + c. +try_output 0 "0.c8;0.c8;ffffffff.fff0bcf8;ffffffff.f4143e00;ffffffff.fffffff0;ffffffff.fffffffb;0.a840;ffffffff.fff0bdc8;ffffffff.119b95c0;ffffffff.118595c0;ffffffff.fffe17b8;100.b8;ff.ffff1658;1.47ae134f;1.a9c53b4f;" << EOF +void show(long long value) +{ + printf("%x.%x;", (unsigned) (value >> 32), (unsigned) value); +} +int main(void) +{ + unsigned char c = 200; + unsigned short s = 60000; + unsigned int u = 4000000000U; + long long sum = 0; + unsigned long long total = 0xfffffffff0ULL; + sum += c; + show(sum); + sum = 0; + sum = sum + c; + show(sum); + sum = -1000000; + sum -= c; + show(sum); + sum = -1000000; + sum *= c; + show(sum); + sum = -1000000; + sum /= s; + show(sum); + sum = -5; + sum %= c; + show(sum); + sum = -1000000; + sum &= s; + show(sum); + sum = -1000000; + sum |= c; + show(sum); + sum = -1000000; + sum ^= u; + show(sum); + sum = -1000000; + sum -= u; + show(sum); + sum = -1000000; + sum >>= c - 197; + show(sum); + total += c; + show(total); + total -= s; + show(total); + total /= c; + show(total); + total ^= u; + show(total); + return 0; +} +EOF + +# The result converts back to the left operand's type, including a change of +# signedness: int += unsigned int stores a negative int, and a signed int +# division result widens by sign extension. +try_output 0 "ffffffff.fff0be88;0.7b8a800;ffffffff.ffffec78;ffffffff.fffffffb;ffffffff.fff057a0;ffffffff.ee7a6a40;0.11a41a40;0.b8;0.0;0.fffffffb;" << EOF +void show(long long value) +{ + printf("%x.%x;", (unsigned) (value >> 32), (unsigned) value); +} +int main(void) +{ + unsigned char c = 200; + unsigned short s = 60000; + unsigned int u = 4000000000U; + int i = -1000000; + unsigned int w = 0xfffffff0U; + i += c; + show(i); + i = -1000000; + i *= s; + show(i); + i = -1000000; + i /= c; + show(i); + i = -5; + i %= c; + show(i); + i = -1000000; + i ^= s; + show(i); + i = 1000000; + i += u; + show(i); + i = 1000000; + show(i -= u); + w += c; + show(w); + w = 7; + w /= c; + show(w); + w = 7; + show(w += -12); + return 0; +} +EOF + +# The same conversions through members, subscripts, pointers and globals, where +# the assignment expression's value is the converted result. +try_output 0 "ffffffff.fff0be88;ffffffff.f4143e00;ffffffff.fffffffb;ffffffff.fff0bcf8;ffffffff.ffffec78;ffffffff.ee7a6a40;0.11a41a40;0.ffffff28;0.ffffff28;" << EOF +void show(long long value) +{ + printf("%x.%x;", (unsigned) (value >> 32), (unsigned) value); +} +struct record { + long long wide; + int narrow; + unsigned int word; +}; +long long global_sum; +int main(void) +{ + unsigned char c = 200; + unsigned int u = 4000000000U; + struct record r, *p = &r; + long long sums[2]; + int narrow[2]; + long long *q = &sums[1]; + r.wide = -1000000; + r.wide += c; + show(r.wide); + p->wide = -1000000; + show(p->wide *= c); + sums[1] = -5; + sums[1] %= c; + show(sums[1]); + *q = -1000000; + *q -= c; + show(sums[1]); + global_sum = -1000000; + global_sum /= c; + show(global_sum); + r.narrow = 1000000; + show(r.narrow += u); + narrow[1] = 1000000; + narrow[1] -= u; + show(narrow[1]); + r.word = 0xfffffff0U; + show(r.word -= c); + r.word = 0xfffffff0U; + show(r.word -= c); + return 0; +} +EOF + +# A move eliminated by the peephole pass must not keep the narrowing flags of +# the unsigned constant load it replaced, or the member address is truncated. +try_output 0 "0.ffffff28;0.ffffff28;" << EOF +void show(long long value) +{ + printf("%x.%x;", (unsigned) (value >> 32), (unsigned) value); +} +struct record { + unsigned int word; +}; +int main(void) +{ + struct record r; + unsigned char c = 200; + r.word = 0xfffffff0U; + show(r.word -= c); + r.word = 0xfffffff0U; + show(r.word -= c); + return 0; +} +EOF +try_ 1 << EOF +int main(void) { + unsigned long long value = 0ULL; + value += -1; + return value == 0xffffffffffffffffULL; +} +EOF +try_ 2 << EOF +int main(void) { + unsigned long long quotient = 0x100000000ULL; + unsigned long long remainder = 0x100000001ULL; + int divisor = 2; + quotient /= divisor; + remainder %= 3; + return (quotient == 0x80000000ULL) + (remainder == 2ULL); +} +EOF + +# Category: Sizeof Operator +begin_category "Sizeof Operator" "Testing sizeof operator on various types" + +# sizeof +try_compile_error << EOF +int main(void) +{ + return sizeof(void); +} +EOF +try_compile_error << EOF +int value(void) +{ + return 1; +} +int main(void) +{ + return sizeof(value); +} +EOF +expr 1 "sizeof(_Bool)" +expr 1 "sizeof(char)" +expr 2 "sizeof(short)" +expr 4 "sizeof(int)" + +# Type specifiers may appear in any order, qualifiers among them, in every type +# name and declaration; each reader shares one specifier parser. +try_ 73 << EOF +char spelled[sizeof(char const) + sizeof(short volatile int const)]; +int main(void) { + typedef int volatile unsigned const U; + struct { const int signed a; char c; } r = {-1, 0}; + return sizeof(spelled) + sizeof(unsigned volatile char const) * 10 + + (sizeof(U) == 4) * 20 + (r.a < 0) * 40; +} +EOF +try_compile_error_message "assignment of read-only variable" << EOF +int main(void) { const int signed x = 1; x = 2; return x; } +EOF +try_compile_error_message "duplicate type specifier" << EOF +char bad[sizeof(char short)]; +int main(void) { return 0; } +EOF +try_compile_error_message "int cannot be combined with char" << EOF +int main(void) { return sizeof(int char); } +EOF +try_compile_error_message "long cannot be combined with char" << EOF +int main(void) { long char c = 0; return c; } +EOF +try_compile_error_message "long cannot be combined with short" << EOF +typedef short long bad; +int main(void) { return 0; } +EOF +try_compile_error << EOF +typedef int T; +char bad[sizeof(unsigned T)]; +int main(void) { return 0; } +EOF +try_compile_error_message "record type cannot be combined with integer specifiers" << EOF +struct S { int a; }; +unsigned struct S s; +int main(void) { return 0; } +EOF +try_compile_error_message "enum type cannot be combined with integer specifiers" << EOF +enum E { A }; +int main(void) { long enum E e = A; return e; } +EOF +# sizeof pointers +expr $PTR_SZ "sizeof(void*)" +expr $PTR_SZ "sizeof(_Bool*)" +expr $PTR_SZ "sizeof(char*)" +expr $PTR_SZ "sizeof(short*)" +expr $PTR_SZ "sizeof(int*)" +# sizeof multi-level pointer +expr $PTR_SZ "sizeof(void**)" +expr $PTR_SZ "sizeof(_Bool**)" +expr $PTR_SZ "sizeof(char**)" +expr $PTR_SZ "sizeof(short**)" +expr $PTR_SZ "sizeof(int**)" +# sizeof struct +try_ $PTR_SZ << EOF +typedef struct { + int a; + int b; +} struct_t; +int main() { return sizeof(struct_t*); } +EOF + +try_ 8 << EOF +typedef struct { + int x; + short y; +} struct_t; + +int main() { return sizeof(struct_t); } +EOF + +# Record members use natural alignment, and a struct's trailing size is rounded +# to its strongest member so arrays of the record keep every element aligned. +try_ 4 << EOF +struct layout { + char tag; + int value; + short tail; +}; +int main(void) { + struct layout value; + struct layout values[2]; + return (sizeof(value) == 12) + + ((char *)&value.value - (char *)&value == 4) + + ((char *)&value.tail - (char *)&value == 8) + + ((char *)&values[1] - (char *)&values[0] == 12); +} +EOF + +# A typedef-backed nested record carries its own alignment into its enclosing +# record rather than being treated as its scalar storage size alone. +try_ 4 << EOF +typedef struct { char c; int value; } inner_t; +struct outer { char tag; inner_t inner; short tail; }; +int main(void) { + struct outer value; + return (sizeof(value) == 16) + + ((char *)&value.inner - (char *)&value == 4) + + ((char *)&value.tail - (char *)&value == 12) + + (sizeof(value.inner) == 8); +} +EOF + +# sizeof enum +try_ $PTR_SZ << EOF +typedef enum { + A, + B +} enum_t; +int main() { return sizeof(enum_t*); } +EOF + +# sizeof with expressions +items 4 "int x = 42; return sizeof(x);" +items 12 "int values[3]; return sizeof(values);" +items 12 "int values[3]; return sizeof((values));" +items 12 "int values[3]; return sizeof(((values)));" +items 12 "int values[3]; return sizeof(*&values);" +items 12 "int values[3]; return sizeof *&values;" +items 24 "int values[2][3]; return sizeof(*&values);" +items 12 "int values[2][3]; return sizeof(values[0]);" +items 12 "int values[2][3]; return sizeof values[0];" +items 12 "int values[2][3], index = 0; return sizeof(values[index++]) + index;" +items 4 "int values[2][3]; return sizeof values[0][1];" +items 16 "int values[2][2][2][2]; return sizeof(values[0][0]);" +items 8 "int values[2][2][2][2]; return sizeof(values[0][0][0]);" +items 4 "int values[2][2][2][2]; return sizeof(values[0][0][0][0]);" +items 12 "struct holder { int values[3]; }; struct holder value; return sizeof value.values;" +items 12 "struct holder { int values[3]; }; struct holder value; return sizeof((value.values));" +items 12 "struct holder { int values[2][3]; }; struct holder value; return sizeof(value.values[0]);" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner rows[2]; }; struct holder value; return sizeof(value.rows[1].values[1]);" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner rows[2]; }; struct holder value; int index = 0; return sizeof(value.rows[index++].values[index++]) + index;" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner *rows[2]; }; struct holder value; return sizeof(value.rows[1]->values[1]);" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner *rows[2]; }; struct holder value; int index = 0; return sizeof(value.rows[index++]->values[index++]) + index;" + +# A sizeof operand made of '&', '*', casts, member selections and subscripts has +# the type those operators build, in any order. Each check returns its own code +# so a failure names the form. +try_ 0 << EOF +struct walk_record { int value; char tag; int values[5]; }; +int main(void) +{ + struct walk_record object, *pointer = &object, records[3]; + int values[4], *cursor = values, matrix[2][3], *slots[7], (*row)[6] = 0; + char byte; + + if (sizeof &object.value != sizeof(int *)) return 1; + if (sizeof(&pointer->tag) != sizeof(char *)) return 2; + if (sizeof &records[1].value != sizeof(int *)) return 3; + if (sizeof *&values[1] != sizeof(int)) return 4; + if (sizeof(&*cursor) != sizeof(int *)) return 5; + if (sizeof *&byte != 1) return 6; + if (sizeof(*&object) != sizeof(struct walk_record)) return 7; + if (sizeof (&object)->tag != 1) return 8; + if (sizeof records->values != 5 * sizeof(int)) return 9; + if (sizeof records[2].tag != 1) return 10; + if (sizeof *slots != sizeof(int *)) return 11; + if (sizeof *matrix != 3 * sizeof(int)) return 12; + if (sizeof row[0] != 6 * sizeof(int)) return 13; + if (sizeof (*row)[1] != sizeof(int)) return 14; + if (sizeof &(*row)[1] != sizeof(int *)) return 15; + if (sizeof(*(char *) cursor) != 1) return 16; + if (sizeof((struct walk_record *) 0)->values != 5 * sizeof(int)) return 17; + if (sizeof(&*matrix) != sizeof(int *)) return 18; + if (sizeof((char) object.value) != 1) return 19; + if (sizeof(*&pointer->values) != 5 * sizeof(int)) return 20; + return 0; +} +EOF +try_ 0 << EOF +int sizeof_walk_callee(int value) { return value; } +int main(void) +{ + int (*callback)(int) = sizeof_walk_callee; + int (*callbacks[3])(int); + + if (sizeof callback != sizeof(int (*)(int))) return 1; + if (sizeof(&sizeof_walk_callee) != sizeof(int (*)(int))) return 2; + if (sizeof &*callback != sizeof(int (*)(int))) return 3; + if (sizeof callbacks[1] != sizeof(int (*)(int))) return 4; + return 0; +} +EOF + +try_compile_error_message "Member reference through '->' requires a pointer" << EOF +struct walk_arrow { int value; }; +int main(void) { struct walk_arrow object; return sizeof object->value; } +EOF +try_compile_error_message "Member reference base is not a struct or union" << EOF +struct walk_dot { int value; }; +int main(void) { struct walk_dot *pointer = 0; return sizeof pointer.value; } +EOF +try_compile_error_message "Cannot dereference non-pointer in sizeof" << EOF +int main(void) { char byte = 0; return sizeof *byte; } +EOF +try_compile_error_message "Cannot apply square operator to non-pointer" << EOF +int main(void) { int scalar = 0; return sizeof scalar[0]; } +EOF +try_compile_error_message "sizeof(function) is invalid" << EOF +int walk_function(void) { return 0; } +int main(void) { int (*callback)(void) = walk_function; return sizeof *callback; } +EOF +try_compile_error_message "lvalue required as unary '&' operand" << EOF +int main(void) { char byte = 0; return sizeof &(char) byte; } +EOF + +# The operand of an unparenthesized sizeof ends at its identifier; a following +# "+ 1" adds to the size rather than stepping the pointer being measured. +try_ 0 << EOF +int main(void) +{ + int values[4]; + int *cursor = values; + char byte = 0; + + if (sizeof cursor + 1 != sizeof(int *) + 1) return 1; + if (sizeof values + 1 != 4 * sizeof(int) + 1) return 2; + if (sizeof byte + 1 != 2) return 3; + if (sizeof (cursor) + 1 != sizeof(int *) + 1) return 4; + return 0; +} +EOF +try_ 12 << EOF +typedef struct { int values[2][3]; } sizeof_pointer_alias_inner; +typedef sizeof_pointer_alias_inner *sizeof_pointer_alias; +struct sizeof_pointer_alias_holder { sizeof_pointer_alias rows[2]; }; +int main(void) { + struct sizeof_pointer_alias_holder value; + return sizeof(value.rows[1]->values[1]); +} +EOF +try_ 12 << EOF +struct sizeof_postfix_inner { int values[2][3]; }; +struct sizeof_postfix_holder { struct sizeof_postfix_inner rows[2]; }; +static struct sizeof_postfix_holder sizeof_postfix_value; +static int sizeof_postfix_row = sizeof(sizeof_postfix_value.rows[1].values[1]); +int main(void) { return sizeof_postfix_row; } +EOF +try_ 12 << EOF +struct sizeof_postfix_pointer_inner { int values[2][3]; }; +struct sizeof_postfix_pointer_holder { struct sizeof_postfix_pointer_inner *rows[2]; }; +static struct sizeof_postfix_pointer_inner sizeof_postfix_pointer_inner_value; +static struct sizeof_postfix_pointer_holder sizeof_postfix_pointer_value = + { &sizeof_postfix_pointer_inner_value }; +static int sizeof_postfix_pointer_row = + sizeof(sizeof_postfix_pointer_value.rows[0]->values[1]); +int main(void) { return sizeof_postfix_pointer_row; } +EOF +try_ 12 << EOF +struct sizeof_double_pointer_inner { int values[2][3]; }; +struct sizeof_double_pointer_holder { struct sizeof_double_pointer_inner **rows[2]; }; +int main(void) { + struct sizeof_double_pointer_holder value; + return sizeof((**value.rows[1]).values[1]); +} +EOF +try_ 12 << EOF +struct sizeof_double_pointer_inner { int values[2][3]; }; +struct sizeof_double_pointer_holder { struct sizeof_double_pointer_inner **rows[2]; }; +int main(void) { + struct sizeof_double_pointer_holder value; + int index = 0; + return sizeof((**value.rows[index++]).values[index++]) + index; +} +EOF +try_ 12 << EOF +struct sizeof_dp_global_inner { int values[2][3]; }; +struct sizeof_dp_global_holder { struct sizeof_dp_global_inner **rows[2]; }; +static struct sizeof_dp_global_holder sizeof_dp_global_value; +static int sizeof_dp_global_row = + sizeof((**sizeof_dp_global_value.rows[1]).values[1]); +int main(void) { return sizeof_dp_global_row; } +EOF +try_ 12 << EOF +struct sizeof_deref_inner { int values[2][3]; }; +struct sizeof_deref_holder { struct sizeof_deref_inner *rows[2]; }; +int main(void) { + struct sizeof_deref_holder value; + return sizeof((*value.rows[1]).values[1]); +} +EOF +try_ 12 << EOF +struct sizeof_deref_inner { int values[2][3]; }; +struct sizeof_deref_holder { struct sizeof_deref_inner *rows[2]; }; +int main(void) { + struct sizeof_deref_holder value; + int index = 0; + return sizeof((*value.rows[index++]).values[index++]) + index; +} +EOF +try_ 12 << EOF +struct sizeof_deref_global_inner { int values[2][3]; }; +struct sizeof_deref_global_holder { struct sizeof_deref_global_inner *rows[2]; }; +static struct sizeof_deref_global_holder sizeof_deref_global_value; +static int sizeof_deref_global_row = + sizeof((*sizeof_deref_global_value.rows[1]).values[1]); +int main(void) { return sizeof_deref_global_row; } +EOF +try_ 12 << EOF +struct sizeof_grouped_row_inner { int values[2][3]; }; +struct sizeof_grouped_row_holder { struct sizeof_grouped_row_inner *rows[2]; }; +int main(void) { + struct sizeof_grouped_row_holder value; + return sizeof((value.rows[1])->values[1]); +} +EOF +try_ 12 << EOF +struct sizeof_grouped_row_inner { int values[2][3]; }; +struct sizeof_grouped_row_holder { struct sizeof_grouped_row_inner *rows[2]; }; +int main(void) { + struct sizeof_grouped_row_holder value; + int index = 0; + return sizeof((value.rows[index++])->values[index++]) + index; +} +EOF +try_ 12 << EOF +struct sizeof_gr_global_inner { int values[2][3]; }; +struct sizeof_gr_global_holder { struct sizeof_gr_global_inner *rows[2]; }; +static struct sizeof_gr_global_holder sizeof_gr_global_value; +static int sizeof_gr_global_row = + sizeof((sizeof_gr_global_value.rows[1])->values[1]); +int main(void) { return sizeof_gr_global_row; } +EOF +try_ 12 << EOF +struct sizeof_inner_group_inner { int values[2][3]; }; +struct sizeof_inner_group_holder { struct sizeof_inner_group_inner *rows[2]; }; +int main(void) { + struct sizeof_inner_group_holder value; + return sizeof((*(value.rows[1])).values[1]); +} +EOF +try_ 12 << EOF +struct sizeof_inner_group_inner { int values[2][3]; }; +struct sizeof_inner_group_holder { struct sizeof_inner_group_inner *rows[2]; }; +int main(void) { + struct sizeof_inner_group_holder value; + int index = 0; + return sizeof((*(value.rows[index++])).values[index++]) + index; +} +EOF +try_ 12 << EOF +struct sizeof_ig_global_inner { int values[2][3]; }; +struct sizeof_ig_global_holder { struct sizeof_ig_global_inner *rows[2]; }; +static struct sizeof_ig_global_holder sizeof_ig_global_value; +static int sizeof_ig_global_row = + sizeof((*(sizeof_ig_global_value.rows[1])).values[1]); +int main(void) { return sizeof_ig_global_row; } +EOF +try_ 12 << EOF +struct sizeof_walker_inner { int values[2][3]; }; +struct sizeof_walker_holder { struct sizeof_walker_inner rows[2]; }; +int main(void) { + struct sizeof_walker_holder value; + return sizeof((value.rows)[1].values[1]); +} +EOF +try_ 12 << EOF +struct sizeof_walker_inner { int values[2][3]; }; +struct sizeof_walker_holder { struct sizeof_walker_inner *rows[2]; }; +int main(void) { + struct sizeof_walker_holder value; + int index = 0; + return sizeof((value.rows)[index++]->values[index++]) + index; +} +EOF +try_ 12 << EOF +struct sizeof_walker_global_inner { int values[2][3]; }; +struct sizeof_walker_global_holder { struct sizeof_walker_global_inner rows[2]; }; +static struct sizeof_walker_global_holder sizeof_walker_global_value; +static int sizeof_walker_global_row = + sizeof((sizeof_walker_global_value.rows)[1].values[1]); +int main(void) { return sizeof_walker_global_row; } +EOF +try_ 13 << EOF +struct sizeof_wgb_inner { int values[2][3]; }; +struct sizeof_wgb_holder { struct sizeof_wgb_inner rows[2]; }; +static struct sizeof_wgb_holder sizeof_wgb_value; +static int sizeof_wgb_row = + sizeof sizeof_wgb_value.rows[1].values[1] + 1; +int main(void) { return sizeof_wgb_row; } +EOF +try_ 13 << EOF +struct sizeof_walker_boundary_inner { int values[2][3]; }; +struct sizeof_walker_boundary_holder { struct sizeof_walker_boundary_inner rows[2]; }; +int main(void) { + struct sizeof_walker_boundary_holder value; + return sizeof value.rows[1].values[1] + 1; +} +EOF +try_ 12 << EOF +typedef struct { int values[2][3]; } sizeof_walker_alias_inner; +typedef sizeof_walker_alias_inner *sizeof_walker_alias; +struct sizeof_walker_alias_holder { sizeof_walker_alias rows[2]; }; +int main(void) { + struct sizeof_walker_alias_holder value; + return sizeof((*value.rows[1]).values[1]); +} +EOF +items 12 "struct holder { int values[2][3]; }; struct holder value; return sizeof value.values[0];" +items 12 "struct holder { int values[2][3]; }; struct holder value; int index = 0; return sizeof((value.values[index++])) + index;" +items 12 "struct holder { int values[2][3]; }; struct holder value; return sizeof((((value.values)))[1]);" +items 12 "struct holder { int values[2][3]; }; struct holder value; int index = 0; return sizeof((((value.values)))[index++]) + index;" +items 12 "struct holder { int values[2][3]; }; struct holder value; return sizeof((((value.values[1]))));" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner inner; }; struct holder value; return sizeof((((value.inner.values)))[1]);" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner inner; }; struct holder value; int index = 0; return sizeof((((value.inner.values)))[index++]) + index;" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner inner; }; struct holder value; return sizeof((((value.inner.values[1]))));" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner *inner; }; struct holder value; return sizeof((((value.inner->values)))[1]);" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner *inner; }; struct holder value; int index = 0; return sizeof((((value.inner->values)))[index++]) + index;" +items 12 "struct inner { int values[2][3]; }; struct holder { struct inner *inner; }; struct holder value; return sizeof((((value.inner->values[1]))));" +try_ 12 << EOF +struct grouped_global_holder { int values[2][3]; }; +static struct grouped_global_holder grouped_global_value; +static int grouped_global_row_size = + sizeof((((grouped_global_value.values)))[1]); +int main(void) { return grouped_global_row_size; } +EOF +try_ 12 << EOF +struct grouped_nested_inner { int values[2][3]; }; +struct grouped_nested_holder { struct grouped_nested_inner inner; }; +static struct grouped_nested_holder grouped_nested_value; +static int grouped_nested_row_size = + sizeof((((grouped_nested_value.inner.values)))[1]); +int main(void) { return grouped_nested_row_size; } +EOF +try_ 12 << EOF +struct grouped_arrow_inner { int values[2][3]; }; +struct grouped_arrow_holder { struct grouped_arrow_inner *inner; }; +static struct grouped_arrow_inner grouped_arrow_inner_value; +static struct grouped_arrow_holder grouped_arrow_value = { &grouped_arrow_inner_value }; +static int grouped_arrow_row_size = + sizeof((((grouped_arrow_value.inner->values)))[1]); +int main(void) { return grouped_arrow_row_size; } +EOF +items 4 "struct holder { int values[2][3]; }; struct holder value; return sizeof(value.values[0][0]);" +items 12 "struct holder { int values[2][3]; }; struct holder *value = 0; return sizeof(value->values[0]);" +items 12 "struct holder { int values[3]; }; struct holder *value = 0; return sizeof(value->values);" +items 12 "struct holder { int values[3]; }; struct holder *value = 0; return sizeof((*value).values);" +items 12 "struct holder { int values[3]; }; struct holder *value = 0; return sizeof (*value).values;" +try_ 15 << EOF +typedef struct { int values[3]; } holder_t; +typedef holder_t holder_alias; +typedef holder_alias *holder_ptr; +int main(void) { + holder_alias value = {{1, 2, 3}}; + holder_ptr pointer = &value; + return pointer->values[2] + sizeof(pointer->values); +} +EOF +try_ 20 << EOF +struct nested { int values[2]; }; +struct holder { int values[3]; struct nested nested; }; +int main(void) { + struct holder value; + return sizeof(value.values) + sizeof(value.nested.values); +} +EOF +try_ 8 << EOF +int main(void) +{ + static char buffer[8] = "hi"; + return sizeof(buffer); +} +EOF +items 4 "int arr[5]; return sizeof(arr[0]);" +items 32 "int values[2][2][2][2]; int index = 0; return sizeof(values[index++]) + index;" +items 16 "int values[2][2][2][2]; return sizeof(values[0][0]);" +items 8 "int values[2][2][2][2]; return sizeof(values[0][0][0]);" +items 4 "int rows[2][1]; return sizeof(rows[0]);" +items 4 "int rows[2][1]; return sizeof(rows[0][0]);" +try_ 4 << EOF +struct sizeof_singleton_inner { int values[1]; }; +struct sizeof_singleton_holder { struct sizeof_singleton_inner rows[2][1]; }; +int main(void) { + struct sizeof_singleton_holder value; + int index = 0; + return sizeof(value.rows[index++][0].values) + index; +} +EOF +try_ 4 << EOF +struct sizeof_singleton_static_inner { int values[1]; }; +struct sizeof_singleton_static_holder { + struct sizeof_singleton_static_inner rows[2][1]; +}; +static struct sizeof_singleton_static_holder value; +static int size = sizeof value.rows[0][0].values; +int main(void) { return size; } +EOF +items 4 "int value = 0; return sizeof(value = 9);" +items 7 "int value = 3; int size = sizeof(value = 9); return value + size;" +items 4 "int value = 0; return sizeof((value = 9, value)) + value;" +try_ 32 << EOF +int sizeof_slot_source[2][2][2][2]; +int (*sizeof_slot[])[2][2] = { &sizeof_slot_source[0][1] + 1 }; +static int sizeof_slot_static = sizeof(*sizeof_slot[0]); +int main(void) { + int index = 0; + return sizeof(*sizeof_slot[index++]) + sizeof_slot_static + index; +} +EOF +try_ 16 << EOF +int sizeof_repeated_slot_source[2][2]; +int (*sizeof_repeated_slots[])[2] = { + sizeof_repeated_slot_source, sizeof_repeated_slot_source +}; +static int sizeof_repeated_slot_static = sizeof(*sizeof_repeated_slots[1]); +int main(void) { + int index = 1; + return sizeof(*sizeof_repeated_slots[index++]) + + sizeof_repeated_slot_static + index - 1; +} +EOF +try_ 9 << EOF +int main(void) { + int sizeof_automatic_slot_source[2][2]; + int (*sizeof_automatic_slots[])[2] = { + sizeof_automatic_slot_source, sizeof_automatic_slot_source + }; + int index = 1; + return sizeof(*sizeof_automatic_slots[index++]) + index; +} +EOF +items 4 "int x = 10; int *ptr = &x; return sizeof(*ptr);" +items 1 "char c = 'A'; return sizeof(c);" +items 2 "short s = 100; return sizeof(s);" +items 4 "int a = 1, b = 2; return sizeof(a + b);" +items 3 "return (1, 3);" +items 4 "int value = 1; return (value++, value + 2);" +items 3 "return (1, (2, 3));" +items 7 "int value = 7; return +value;" +items 255 "unsigned char value = 255; return +value;" +try_compile_error << EOF +int main(void) { int value = 0; int *pointer = &value; return +pointer; } +EOF +try_compile_error << EOF +int main(void) { int value = 0; int *pointer = &value; return -pointer; } +EOF +try_compile_error << EOF +int main(void) { int value = 0; int *pointer = &value; return ~pointer; } +EOF +try_compile_error << EOF +int function_designator(void) { return 0; } +int main(void) { return -function_designator; } +EOF +items $PTR_SZ "return sizeof(volatile int) + sizeof(int *restrict) - 4;" +items 0 "int value = 0; int size = sizeof(++value); return value;" +items 0 "int value = 0; int size = sizeof(value++); return value;" + +# sizeof with complex expressions +try_ 4 << EOF +int main() { + int arr[10]; + int i = 5; + return sizeof(arr[i]); +} +EOF + +try_ 4 << EOF +int main() { + int x = 100; + int *p = &x; + int **pp = &p; + return sizeof(**pp); +} +EOF + +try_ 4 << EOF +int main() { + int values[3]; + values[1] = 2; + values[2] = 3; + return sizeof(values[1] + values[2]); +} +EOF + +# sizeof with function calls +try_ 4 << EOF +int get_value() { return 42; } +int main() { + return sizeof(get_value()); +} +EOF + +# sizeof with ternary expressions +try_ 4 << EOF +int main() { + int a = 5, b = 10; + return sizeof(a > b ? a : b); +} +EOF + +# A row or element of an array of pointers is pointer-sized per element. +try_ 7 << EOF +int main(void) { + int *rows[2][3]; + return (sizeof rows[0] == 3 * sizeof(int *)) + + 2 * (sizeof(rows[1]) == 3 * sizeof(int *)) + + 4 * (sizeof rows[1][2] == sizeof(int *)); +} +EOF + +# The extent of a string literal operand counts the bytes after an embedded null +# character, in each adjacent literal. +try_ 3 << EOF +int main(void) { + int grouped = sizeof("a\0bc"); + int adjacent = sizeof "a\0" "b\0c"; + return (grouped == 5) + 2 * (adjacent == 6); +} +EOF + +# Every sizeof result is an integer constant expression, so a zero-valued +# expression built from sizeof of an object or string literal is a null pointer +# constant next to a function pointer. +try_ 15 << EOF +int three(void) { return 3; } +int main(void) { + int values[4]; + int (*callback)(void) = three; + return (callback != !sizeof values) + + 2 * (callback != sizeof(values) - sizeof(values)) + + 4 * ((1 ? callback : !sizeof "ab")() == 3) + + 8 * ((0 ? !sizeof(values[0]) : callback)() == 3); +} +EOF + +# A global initializer takes sizeof of a parenthesized narrow or wide string +# literal, with or without extra grouping, as the size of the literal array. +try_ 5 << EOF +int global_sizeof_string = sizeof("abc"); +int global_sizeof_grouped_string = (sizeof(("a" "bc"))); +int global_sizeof_string_sum = 1 + sizeof("a\0b"); +int global_sizeof_wstring = sizeof(L"ab") / sizeof(L""); +int global_sizeof_grouped_wstring = sizeof(((L"a" L"b"))) / sizeof(L""); +int main(void) { + return (global_sizeof_string == 4) + (global_sizeof_grouped_string == 4) + + (global_sizeof_string_sum == 5) + (global_sizeof_wstring == 3) + + (global_sizeof_grouped_wstring == 3); +} +EOF + +# Grouping around a literal does not decay it inside sizeof in a function body +# either; an operator after the group still makes an ordinary expression. +try_ 6 << EOF +int main(void) { + return (sizeof(("abc")) == 4) + (sizeof((("a" "b"))) == 3) + + (sizeof((L"ab")) == 3 * sizeof(L"")) + + (sizeof(((L"a" L"b"))) == 3 * sizeof(L"")) + + (sizeof(("abc") + 1) == sizeof(char *)) + + (sizeof ("a" "bc") == 4); +} +EOF + +# Every integer constant expression evaluates sizeof alike: a string literal +# keeps its array size, a dereference takes the pointee type, and a typed +# literal such as 1LL takes the size its suffix selects. +try_ 0 << EOF +short *constant_short_pointer; +int global_dereference_size = sizeof *constant_short_pointer; +int global_dereference_sum = sizeof *constant_short_pointer + 1; +int global_long_long_size = sizeof(1LL) + sizeof 2LL; +int string_bound[sizeof "abc"]; +int grouped_string_bound[sizeof(("a" "bc"))]; +int dereference_bound[sizeof *constant_short_pointer]; +enum { + string_enum = sizeof "abcd", + grouped_string_enum = sizeof((("ab"))), + dereference_enum = sizeof(*constant_short_pointer), + long_long_enum = sizeof 1LL + sizeof('a') +}; +int main(void) +{ + char *local_pointer = 0; + int local_string_bound[sizeof "ab" "c"]; + int local_long_long_bound[sizeof 1LL]; + int local_dereference_bound[sizeof *local_pointer]; + switch (sizeof(long long)) { + case sizeof "abcdef" + sizeof *local_pointer: + break; + default: + return 1; + } + switch (sizeof(long long)) { + case sizeof 1LL: + break; + default: + return 2; + } + return global_dereference_size != sizeof(short) || + global_dereference_sum != sizeof(short) + 1 || + global_long_long_size != 2 * sizeof(long long) || + sizeof(string_bound) != 4 * sizeof(int) || + sizeof(grouped_string_bound) != 4 * sizeof(int) || + sizeof(dereference_bound) != sizeof(short) * sizeof(int) || + string_enum != 5 || grouped_string_enum != 3 || + dereference_enum != sizeof(short) || + long_long_enum != sizeof(long long) + sizeof(int) || + sizeof(local_string_bound) != 4 * sizeof(int) || + sizeof(local_long_long_bound) != sizeof(long long) * sizeof(int) || + sizeof(local_dereference_bound) != sizeof(int); +} +EOF + +# A literal too wide for an int, or unsigned beyond INT_MAX, keeps its value and +# type in every integer constant expression: an array bound, a designator, a +# bit-field width, a pointer-to-array bound or a compound literal bound. A +# result an int cannot hold is diagnosed rather than wrapped. +try_ 9 << EOF +#include +struct wide_bound_record { int a; int b; }; +int wide_bound_quotient[3000000000U / 1000000000U]; +int wide_bound_difference[0x100000003ULL - 0x100000000ULL]; +int wide_bound_signed[2147483648 / 1073741824]; +int wide_bound_designated[5] = {[4294967299 - 4294967296] = 7}; +char wide_bound_offset[16U - offsetof(struct wide_bound_record, b)]; +struct wide_bound_bits { unsigned field : 4294967299 - 4294967296; } wide_bound_bits; +int main(void) { + int local[3000000000U / 1500000000U]; + int (*rows)[0x100000004ULL - 0x100000000ULL] = 0; + int *literal = (int[2U]){5, 6}; + wide_bound_bits.field = 7; + return (sizeof(wide_bound_quotient) == 3 * sizeof(int)) + + (sizeof(wide_bound_difference) == 3 * sizeof(int)) + + (sizeof(wide_bound_signed) == 2 * sizeof(int)) + + (wide_bound_designated[3] == 7) + + (sizeof(wide_bound_offset) == 12) + + (wide_bound_bits.field == 7) + + (sizeof(local) == 2 * sizeof(int)) + + (sizeof(*rows) == 4 * sizeof(int)) + (literal[1] == 6); +} +EOF +try_compile_error << EOF +int wide_bound_wrapped[4294967297]; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { int *p = (int[4294967298]){1, 2}; return p[1]; } +EOF +try_compile_error << EOF +struct wide_width { unsigned field : 4294967297; }; +int main(void) { return 0; } +EOF + +# Category: Switch Statements +begin_category "Switch Statements" "Testing switch-case control flow" + +# switch-case +items 10 "int a; a = 0; switch (3) { case 0: return 2; case 3: a = 10; break; case 1: return 0; } return a;" +items 10 "int a; a = 0; switch (3) { case 0: return 2; default: a = 10; break; } return a;" +items 7 "int value = 1; switch (value++, value + 1) { case 3: return value + 5; default: return 0; }" +items 2 "switch (0 ? 1 : 2) { case 2: return 2; default: return 0; }" +try_compile_error << EOF +int main(void) { int value = 0; switch (&value) { default: return 0; } } +EOF +try_compile_error << EOF +struct switch_record { int value; }; +int main(void) { + struct switch_record value = { 0 }; + switch (value) { default: return 0; } +} +EOF +try_compile_error << EOF +void switch_void(void) {} +int main(void) { switch (switch_void()) { default: return 0; } } +EOF +try_ 2 << EOF +int main(void) { + unsigned char byte = 1; + switch (byte) { case 1: return 2; default: return 0; } +} +EOF + +# A case constant converts to the promoted controlling type: a long long switch +# keeps every word of a wide or unsigned label, an int switch folds it. +try_ 0 << EOF +int wide(long long x) { + switch (x) { + case -2: return 1; + case 1 ? 0x200000000LL : 3: return 2; + case (0x100000000LL << 1) + 1: return 3; + case 0xffffffffU: return 4; + case -0x7fffffffffffffffLL - 1: return 6; + default: return 5; + } +} +int narrow(unsigned x) { + switch (x) { + case 0x100000002LL: return 1; + default: return 2; + } +} +int main(void) { + if (wide(-2) != 1 || wide(0x200000000LL) != 2 || wide(0x200000001LL) != 3) + return 1; + if (wide(0xffffffffLL) != 4 || wide(-1) != 5 || wide(1) != 5) + return 2; + if (wide(-0x7fffffffffffffffLL - 1) != 6 || wide(0x100000002LL) != 5) + return 3; + return narrow(2) != 1 || narrow(0x100000002LL) != 1; +} +EOF + +# Category: Enumerations +begin_category "Enumerations" "Testing enum declarations and usage" + +# enum +try_ 6 << EOF +typedef enum { enum1 = 5, enum2 } enum_t; +int main() { enum_t v = enum2; return v; } +EOF + +# A typedef may name an existing enum tag, define a tagged enum, qualify it or +# derive a pointer or array from it, at file scope and in a block. +try_ 20 << EOF +enum E { A, B = 5 }; +typedef enum E EE; +typedef enum E *EP; +typedef enum E const CE; +typedef enum { C = 3 } CA[2]; +typedef enum F { D = 1 } volatile VF; +EE e = B; +CE ce = A; +CA arr = { C, D }; +int main(void) { + EP p = &e; + VF v = D; + enum F w = v; + typedef enum E LE; + LE le = B; + return *p + ce + arr[0] + sizeof(CA) / sizeof(int) + w + le + (int) sizeof(EE); +} +EOF + +try_ 13 << EOF +int main(void) { + typedef enum E { A, B = 7 } EE; + EE e = B; + typedef enum { C = 2 } const CE; + CE c = C; + typedef volatile enum E VE; + VE v = A; + typedef enum E *EP; + EP q = &e; + int got = *q; + enum E again = got; + return again + c + v + sizeof(EE); +} +EOF + +try_compile_error << EOF +enum E { A }; +typedef enum E const CE; +CE value = A; +int main(void) { value = A; return 0; } +EOF + +try_compile_error << EOF +int main(void) { enum E { A }; typedef const enum E CE; CE c = A; c = A; return c; } +EOF + +try_compile_error_message "Unknown enum type" << EOF +int main(void) { typedef enum missing M; return 0; } +EOF + +# C99 has no incomplete enum types (6.7.2.3p2 needs the list first), so an enum +# tag without a visible definition is diagnosed wherever it is named, and a bare +# reference to a complete one declares nothing (6.7p2). +try_compile_error_message "C99 forbids forward references to enums" << EOF +enum forward_only; +int main(void) { return 0; } +EOF +try_compile_error_message "C99 forbids forward references to enums" << EOF +int main(void) { enum forward_only; return 0; } +EOF +try_compile_error_message "C99 forbids forward references to enums" << EOF +int takes_forward(enum forward_only *p); +int main(void) { return 0; } +EOF +try_compile_error_message "C99 forbids forward references to enums" << EOF +struct holds_forward { enum forward_only *p; }; +int main(void) { return 0; } +EOF +try_compile_error_message "C99 forbids forward references to enums" << EOF +int forward_bound[sizeof(enum forward_only)]; +int main(void) { return 0; } +EOF +try_compile_error_message "C99 forbids forward references to enums" << EOF +int main(void) { return sizeof(enum forward_only); } +EOF +try_compile_error_message "C99 forbids forward references to enums" << EOF +int main(void) { return (enum forward_only) 0; } +EOF +try_compile_error_message "enum declaration without an enumerator list declares nothing" << EOF +enum complete_first { complete_a, complete_b }; +enum complete_first; +int main(void) { return 0; } +EOF +try_compile_error_message "enum declaration without an enumerator list declares nothing" << EOF +enum complete_first { complete_a, complete_b }; +int main(void) { enum complete_first; return 0; } +EOF +try_ 3 << EOF +enum complete_later { later_a, later_b }; +enum complete_later file_value = later_b; +int main(void) { + enum complete_later local = later_b; + enum complete_later *p = &local; + return file_value + *p + (sizeof(enum complete_later) == sizeof(int)); +} +EOF +try_ 2 << EOF +enum trailing_values { trailing_first = 2, }; +int main(void) { return trailing_first; } +EOF +try_compile_error << EOF +enum invalid_values { last_int = 2147483647, out_of_range }; +EOF +try_compile_error << EOF +enum explicit_out_of_range { value = 2147483648 }; +EOF +try_compile_error << EOF +enum expression_out_of_range { value = 2147483647 + 1 }; +EOF +try_compile_error << EOF +enum unsigned_out_of_range { value = 0xffffffffU }; +EOF +try_ 1 << EOF +enum signed_minimum { value = -2147483648 }; +int main(void) { return value < 0; } +EOF +try_ 1 << EOF +enum suffixed_signed_minimum { value = -2147483648L }; +int main(void) { return value < 0; } +EOF +try_ 0 << EOF +enum conditional_range { value = 0 ? 2147483648 : 1 }; +int main(void) { return value - 1; } +EOF +try_ 0 << EOF +enum unsigned_int_range { zero = 0U, maximum = 2147483647U, + value = -1U + 1U }; +int main(void) { return zero + maximum - 2147483647 + value; } +EOF +try_ 0 << EOF +enum wide_intermediates { one = 2147483648L - 2147483647L, + zero = 2147483648L - 2147483648L, + minimum = -2147483649L + 1L }; +int main(void) { return one + zero + (minimum < 0) - 2; } +EOF +try_ 1 << EOF +enum explicit_reset { maximum = 2147483647, reset = 0, successor }; +int main(void) { return successor; } +EOF +try_ 2 << EOF +enum typed_named_values { first = 1, second = first + 1U }; +int main(void) { return second; } +EOF +try_ 0 << EOF +enum unsigned_wrap_values { first = 4294967295U + 1U, + second = 2147483647U + 2147483649U, + third = 0x80000000 * 2, + fourth = 0xffffffffL + 1L, + fifth = 0x80000000L * 2L }; +int main(void) { return first + second + third + fourth + fifth; } +EOF +try_ 0 << EOF +enum typed_sizeof_values { first = sizeof(int) + 1U, + second = 0 ? sizeof(int) : 1U }; +int main(void) { return first + second - 6; } +EOF + +# A character constant is an int operand of the typed evaluator, so it can be +# shifted and extended at int width like a number. +try_ 5 << EOF +enum typed_char_values { first = 'a' << 1U, second = ('x' << 2) + 0U, + third = 'b' >> 1U, fourth = -'c' >> 1U }; +int main(void) { + int hit = 0; + switch (194) { + case 'a' << 1U: + hit = 1; + } + return (first == 194) + (second == 480) + (third == 49) + + (fourth == -50) + hit; +} +EOF +try_ 1 << EOF +enum conditional_wide_rank { first = 1 ? -1LL : 1U, + second = first + 1U }; +int main(void) { return (first < 0) + second; } +EOF +try_ 1 << EOF +enum preserved_wide_rank { value = (-2147483648LL / 1LL) + 2147483647U }; +int main(void) { return value < 0; } +EOF +try_ 3 << EOF +int main(void) { + enum typed_local_values { first = 2, second = first + 1U }; + return second; +} +EOF +try_compile_error << EOF +enum negative_out_of_range { value = -2147483649 }; +EOF +try_compile_error << EOF +enum implicit_out_of_range { maximum = 2147483647, successor }; +EOF +try_compile_error << EOF +enum unary_out_of_range { value = -(-2147483648) }; +EOF +try_compile_error << EOF +enum named_unary_out_of_range { minimum = -2147483648, value = -minimum }; +EOF +try_compile_error << EOF +enum shifted_out_of_range { value = 1 << 31 }; +EOF +try_compile_error << EOF +enum typed_shift_count_out_of_range { value = 1U << 32 }; +EOF +try_compile_error << EOF +enum typed_signed_shift_out_of_range { value = (1LL << 63) >> 63 }; +EOF + +# A shift count outside the int width has no value in any integer constant +# expression, so the compiler must reject it rather than fold it on the host. +try_compile_error << EOF +int shifted_global = 1 << 32; +int main(void) { return shifted_global; } +EOF +try_compile_error << EOF +int main(void) { int values[-1 >> 40]; return sizeof(values); } +EOF +try_compile_error << EOF +int main(void) { switch (1) { case 1 << -1: return 1; } return 0; } +EOF +try_ 12 << EOF +enum { shift_base = 1 << 30 }; +int shift_bound[(shift_base >> 29) + (3 >> 1)]; +int shift_global = -8 >> 1; +int main(void) +{ + switch (4) { + case 16 >> 2: + return sizeof(shift_bound) / sizeof(int) + shift_global + 13; + } + return 0; +} +EOF + +# Block-scope enum definitions supply integer constants to expressions and +# accept C99's trailing comma after their final enumerator. +try_ 4 << EOF +int main(void) +{ + enum local_values { local_base = 1 + 1, local_count = local_base << 1, }; + return local_count; +} +EOF + +# A block-scope enum definition may introduce scalar declarators after its +# enumerator list, including a comma-separated declarator list. +try_ 12 << EOF +int main(void) +{ + enum local_values { local_base = 1 + 1, local_count = local_base << 1 } + local_marker = local_count, local_next = local_marker + 1; + return local_marker + local_next + local_base + 1; +} +EOF + +try_ 14 << EOF +int main(void) +{ + enum local_values { local_base = 2, local_count = local_base << 1 }; + enum local_values local_marker = local_count; + enum local_values local_next = local_marker + local_base + 4; + return local_marker + local_next; +} +EOF + +# A nested enum definition shadows both a file-scope tag and an enumerator; its +# tag and constants disappear when the nested block closes. +try_ 13 << EOF +enum enum_scope { scoped_value = 3 }; +int main(void) +{ + int before = scoped_value; + { + enum enum_scope { scoped_value = 5, scoped_count = scoped_value + 1 }; + enum enum_scope values[scoped_count]; + int nested = scoped_value + scoped_count; + int converted = (enum enum_scope) scoped_count; + if (nested != 11 || converted != 6 || sizeof(enum enum_scope) != 4) + return 1; + } + return before + scoped_value + 7; +} +EOF + +# Leading qualifiers must not bypass enum declarators: const enum objects are +# read-only and static enum objects retain their initialized value between calls +# just like other block-scope static scalar objects. +try_ 10 << EOF +enum persistent_enum { persistent_fixed = 3 }; +int next_persistent_enum(void) +{ + enum local_persistent_enum { persistent_start = 3 }; + static enum local_persistent_enum value = persistent_start; + return value++; +} +int main(void) +{ + const enum persistent_enum fixed = persistent_fixed; + return next_persistent_enum() + next_persistent_enum() + fixed; +} +EOF + +try_ 12 << EOF +enum static_enum_entries { static_enum_first = 3, static_enum_second = 4 }; +int static_enum_array_sum(void) +{ + static enum static_enum_entries entries[3] = { + static_enum_first, static_enum_second, static_enum_first + 2 + }; + return entries[0] + entries[1] + entries[2]; +} +int main(void) { return static_enum_array_sum(); } +EOF + +try_ 10 << EOF +enum global_array_values { global_array_first = 1, global_array_second = 2 }; +static int global_values[2] = {global_array_first, global_array_second}; +int static_enum_array_sum(void) +{ + enum static_array_values { static_array_first = 2, static_array_second = 5 }; + static int values[3] = { + [static_array_first - 2] = static_array_first, + [static_array_second - 3] = static_array_second + }; + return values[0] + values[1] + values[2]; +} +int main(void) { return static_enum_array_sum() + global_values[0] + global_values[1]; } +EOF + +# Enum tags are valid parameter and return type specifiers, including a +# qualified parameter declaration in an ordinary file-scope function API. +try_ 12 << EOF +enum api_state { api_ready = 5 }; +enum api_state echo_api_state(const enum api_state value) +{ + return value; +} +int main(void) +{ + return echo_api_state(api_ready) + sizeof(enum api_state) + 3; +} +EOF + +# C99 leaves an enum's compatible integer type implementation-defined. shecc +# selects int, so the choice must remain consistent for objects, aggregate +# layout, conversions, and the ordinary multi-argument call ABI. +try_ 0 << EOF +enum representation_state { representation_low = -3, representation_high = 7 }; +struct enum_representation_record { + char first; + enum representation_state state; + char last; +}; +union enum_representation_union { + enum representation_state state; + int integer; +}; +enum representation_state pass_representation(int a, int b, int c, int d, + int e, int f, + enum representation_state state) +{ + return (enum representation_state) (state + a - b + c - d + e - f); +} +enum representation_state return_negative_representation(void) +{ + return representation_low; +} +int main(void) +{ + enum representation_state values[2] = { + representation_low, representation_high + }; + struct enum_representation_record record = { 0, representation_high, 0 }; + union enum_representation_union value = { representation_low }; + return sizeof(enum representation_state) != sizeof(int) || + sizeof values != 2 * sizeof(int) || + /* Current shecc ABI: int fields align to four bytes. */ + sizeof record != 12 || sizeof value != sizeof(int) || + (int) values[0] != -3 || value.integer != -3 || + (int) return_negative_representation() != -3 || + pass_representation(1, 2, 3, 4, 5, 6, record.state) != 4; +} +EOF + +# Category: Memory Management +begin_category "Memory Management" "Testing malloc, free, and dynamic memory allocation" + +if [ "$LINK_MODE" = "static" ]; then + # malloc and free + try_ 1 << EOF +int main() +{ + /* change test bench if different scheme apply */ + int *a = malloc(sizeof(int) * 5); + free(a); + if (a == NULL) + abort(); + int *b = malloc(sizeof(int) * 3); + + /* "malloc" will reuse memory free'd by "free(a)" */ + return a == b; +} +EOF +else + echo "Skip test cases because of using dynamic linking mode" +fi # "LINK_MODE" = "static" + +try_ 1 << EOF +int main() +{ + char *ptr = "hello"; + return (0 == strcmp(ptr, "hello")) == (!strcmp(ptr, "hello")); +} +EOF + +# Category: Preprocessor Directives +begin_category "Preprocessor Directives" "Testing #define, #ifdef, #ifndef, #if, #elif, #else, #endif" + +# The compiler resolves quoted headers relative to the translation unit. The +# harness writes that unit into TEST_TMPDIR, so stage the nested fixture headers +# beside it before compiling the checked-in main file. +cp "$TESTS_DIR/include-base.h" "$TEST_TMPDIR/include-base.h" +cp "$TESTS_DIR/include-values.h" "$TEST_TMPDIR/include-values.h" +cp "$TESTS_DIR/include-macro.h" "$TEST_TMPDIR/include-macro.h" +cp -R "$TESTS_DIR/include-nested" "$TEST_TMPDIR/include-nested" +try_file 24 "$TESTS_DIR/include-main.c" + +# line changes diagnostics and __FILE__, not the physical directory used to +# resolve a following quoted include. +try_ 7 << EOF +#line 10 "generated/virtual.c" +#include "include-base.h" +int main(void) { return QUOTED_INCLUDE_BASE; } +EOF + +# A function-like macro name without an argument list is not expanded, so it +# cannot supply a header name, either directly or at the end of an alias chain. +try_compile_error_message "#include macro must expand to a header name" << EOF +#define BASE_HEADER() "include-base.h" +#include BASE_HEADER +int main(void) { return QUOTED_INCLUDE_BASE; } +EOF +try_compile_error_message "#include macro must expand to a header name" << EOF +#define BASE_HEADER() "include-base.h" +#define BASE_ALIAS BASE_HEADER +#include BASE_ALIAS +int main(void) { return QUOTED_INCLUDE_BASE; } +EOF + +# White space before a trailing comment ends no replacement list, so it does not +# keep a macro from naming a header, directly or through an alias. +try_ 7 << EOF +#define BASE_HEADER "include-base.h" /* quoted */ +#define BASE_ALIAS BASE_HEADER /* alias */ +#include BASE_HEADER +#include BASE_ALIAS +int main(void) { return QUOTED_INCLUDE_BASE; } +EOF +try_flags 24 "-I$TESTS_DIR" << EOF +#define ANGLE_HEADER /* angle */ +#include ANGLE_HEADER +int main(void) { return ANGLE_INCLUDE_BASE + ANGLE_INCLUDE_CHILD; } +EOF + +# A header's #pragma once identity is its normalized relative path, not the +# spelling used by an includer. The outer header reaches the same header again +# through ./ and ../ components after its canonical spelling; a duplicate +# inclusion would define its object twice. +try_ 19 << EOF +#include "include-nested/outer.h" +int main(void) { return NESTED_ONCE_VALUE + nested_once_object; } +EOF + +# _Pragma("once") guards its header as #pragma once does, and one produced by a +# macro from another header guards the header that invoked the macro. +printf '#define PRAGMA_OP(x) _Pragma(#x)\n' > "$TEST_TMPDIR/pragma-op-macro.h" +printf '_Pragma("once")\nint pragma_op_direct = 3;\n' \ + > "$TEST_TMPDIR/pragma-op-direct.h" +printf '#include "pragma-op-macro.h"\nPRAGMA_OP(once)\nint pragma_op_via = 4;\n' \ + > "$TEST_TMPDIR/pragma-op-via.h" +try_ 7 << EOF +#include "pragma-op-direct.h" +#include "pragma-op-via.h" +#include "pragma-op-direct.h" +#include "pragma-op-via.h" +int main(void) { return pragma_op_direct + pragma_op_via; } +EOF +try_flags 24 "-I$TESTS_DIR" << EOF +#include +#include +int main(void) { return ANGLE_INCLUDE_BASE + ANGLE_INCLUDE_CHILD; } +EOF +try_flags 24 "-I$TESTS_DIR" << EOF +#define ANGLE_HEADER +#include ANGLE_HEADER +int main(void) { return ANGLE_INCLUDE_BASE + ANGLE_INCLUDE_CHILD; } +EOF +try_flags 1 "--no-libc" << EOF +#include +int main(void) { + bool value = true; + return value == true && __bool_true_false_are_defined == 1; +} +EOF +try_flags 1 "--no-libc" << EOF +#include +int main(void) { + int value = 6; + value and_eq 3; + value or_eq 4; + value xor_eq 1; + return value == 7 and not (value not_eq 7) and + ((5 bitand 3) == 1) and ((1 bitor 2) == 3) and + ((1 xor 3) == 2) and ((compl 0) < 0); +} +EOF +try_flags 12 "--no-libc" << EOF +#include +#include +#if CHAR_BIT != 8 || INT_MAX != 2147483647 +#error builtin limits.h macros are incorrect +#endif +int main(void) { + return (SCHAR_MIN == -128) + (SCHAR_MAX == 127) + + (UCHAR_MAX == 255U) + (CHAR_MIN == -128) + + (CHAR_MAX == 127) + (SHRT_MIN == -32768) + + (SHRT_MAX == 32767) + (USHRT_MAX == 65535U) + + (INT_MIN < 0) + (UINT_MAX > INT_MAX) + + (LONG_MIN < 0) + (ULONG_MAX > LONG_MAX); +} +EOF +try_flags 2 "--no-libc" << EOF +#include +int main(void) { return (LLONG_MIN < 0) + (ULLONG_MAX > LLONG_MAX); } +EOF +try_flags "$((8 + 2 * PTR_SZ))" "--no-libc" << EOF +#include +#include +int main(void) { + int value = 3; + int *pointer = NULL; + size_t count = sizeof(pointer); + ptrdiff_t delta = -2; + wchar_t code = 65; + return (pointer == NULL) + ((pointer ? 0 : value) == 3) + + sizeof(count) + sizeof(delta) + sizeof(code) + (delta < 0) + + (code == 65); +} +EOF +try_flags 4 "--no-libc" << EOF +#include +struct inner { char tag; int value; }; +struct outer { char lead; struct inner nested; short tail; }; +typedef struct outer outer_t; +static int tail_offset = offsetof(outer_t, tail); +int main(void) { + return (offsetof(struct outer, nested) == 4) + + (offsetof(struct inner, value) == 4) + + (offsetof(struct outer, nested.value) == 8) + + (tail_offset == 12); +} +EOF +try_flags 4 "--no-libc" << EOF +#include +struct offsetof_array_holder { int prefix; int values[3]; }; +static int offsetof_array_value = offsetof(struct offsetof_array_holder, values[1]); +static int offsetof_array_one_past = offsetof(struct offsetof_array_holder, values[3]); +int main(void) { + return (offsetof(struct offsetof_array_holder, values[1]) == + sizeof(int) + sizeof(int)) + + (offsetof_array_value == sizeof(int) + sizeof(int)) + + (offsetof(struct offsetof_array_holder, values[3]) == + offsetof(struct offsetof_array_holder, values) + 3 * sizeof(int)) + + (offsetof_array_one_past == offsetof(struct offsetof_array_holder, values) + + 3 * sizeof(int)); +} +EOF +try_compile_error << EOF +#include +struct offsetof_scalar_holder { int value; }; +int invalid_offsetof = offsetof(struct offsetof_scalar_holder, value[0]); +EOF +try_flags 4 "--no-libc" << EOF +#include +struct offsetof_matrix_holder { int prefix; int values[2][3]; }; +static int offsetof_matrix_value = offsetof(struct offsetof_matrix_holder, values[1][2]); +static int offsetof_matrix_one_past = offsetof(struct offsetof_matrix_holder, values[1][3]); +int main(void) { + return (offsetof(struct offsetof_matrix_holder, values[1][2]) == + sizeof(int) + 5 * sizeof(int)) + + (offsetof_matrix_value == sizeof(int) + 5 * sizeof(int)) + + (offsetof(struct offsetof_matrix_holder, values[1][3]) == + sizeof(int) + 6 * sizeof(int)) + + (offsetof_matrix_one_past == sizeof(int) + 6 * sizeof(int)); +} +EOF +try_compile_error << EOF +#include +struct offsetof_matrix_holder { int values[2][3]; }; +int invalid_offsetof = offsetof(struct offsetof_matrix_holder, values[2][0]); +EOF +try_flags 4 "--no-libc" << EOF +#include +struct offsetof_cube_holder { int prefix; int values[2][3][4]; }; +static int offsetof_cube_value = offsetof(struct offsetof_cube_holder, values[1][2][3]); +static int offsetof_cube_one_past = offsetof(struct offsetof_cube_holder, values[1][2][4]); +int main(void) { + return (offsetof(struct offsetof_cube_holder, values[1][2][3]) == + sizeof(int) + 23 * sizeof(int)) + + (offsetof_cube_value == sizeof(int) + 23 * sizeof(int)) + + (offsetof(struct offsetof_cube_holder, values[1][2][4]) == + sizeof(int) + 24 * sizeof(int)) + + (offsetof_cube_one_past == sizeof(int) + 24 * sizeof(int)); +} +EOF +try_flags 4 "--no-libc" << EOF +#include +struct offsetof_hypercube_holder { int prefix; int values[2][2][2][2]; }; +static int offsetof_hypercube_value = offsetof(struct offsetof_hypercube_holder, values[1][1][1][1]); +static int offsetof_hypercube_one_past = offsetof(struct offsetof_hypercube_holder, values[1][1][1][2]); +int main(void) { + return (offsetof(struct offsetof_hypercube_holder, values[1][1][1][1]) == + sizeof(int) + 15 * sizeof(int)) + + (offsetof_hypercube_value == sizeof(int) + 15 * sizeof(int)) + + (offsetof(struct offsetof_hypercube_holder, values[1][1][1][2]) == + sizeof(int) + 16 * sizeof(int)) + + (offsetof_hypercube_one_past == sizeof(int) + 16 * sizeof(int)); +} +EOF +try_compile_error << EOF +#include +struct offsetof_hypercube_holder { int values[2][2][2][2]; }; +int invalid_offsetof = offsetof(struct offsetof_hypercube_holder, values[2][0][0][0]); +EOF +try_compile_error << EOF +#include +struct offsetof_cube_holder { int values[2][3][4]; }; +int invalid_offsetof = offsetof(struct offsetof_cube_holder, values[1][3][0]); +EOF +try_flags "$((38 + PTR_SZ))" "--no-libc" << EOF +#include +int main(void) { + return sizeof(int8_t) + sizeof(uint16_t) + sizeof(int32_t) + + sizeof(uint64_t) + sizeof(int_least8_t) + + sizeof(uint_least16_t) + sizeof(int_fast32_t) + + sizeof(uint_fast64_t) + sizeof(intmax_t) + sizeof(uintptr_t); +} +EOF +try_flags 9 "--no-libc" << EOF +#include +int main(void) { + return (INT8_MIN == -128) + (INT8_MAX == 127) + (UINT8_MAX == 255U) + + (INT16_MIN == -32768) + (INT16_MAX == 32767) + (UINT16_MAX == 65535U) + + (INT32_MIN < 0) + (INT32_MAX > 0) + (UINT32_MAX > INT32_MAX); +} +EOF +# INTPTR_MAX exceeds INT32_MAX only where pointers are eight bytes. +try_flags "$((PTR_SZ >= 8 ? 9 : 8))" "--no-libc" << EOF +#include +int main(void) { + return (INT64_MIN < 0) + (INT64_MAX > 0) + (UINT64_MAX > INT64_MAX) + + (INTMAX_MIN < 0) + (INTMAX_MAX > 0) + (UINTMAX_MAX > INTMAX_MAX) + + (INTPTR_MIN < 0) + (INTPTR_MAX > INT32_MAX) + (UINTPTR_MAX > INTPTR_MAX); +} +EOF +try_flags 10 "--no-libc" << EOF +#include +#include +#include +int main(void) { + sig_atomic_t signal_value = SIG_ATOMIC_MAX; + wint_t wide_value = WINT_MAX; + return (sizeof(size_t) == sizeof(ptrdiff_t)) + + (sizeof(sig_atomic_t) == 4) + (sizeof(wint_t) == 4) + + (PTRDIFF_MIN < 0) + (PTRDIFF_MAX > 0) + + (SIZE_MAX > PTRDIFF_MAX) + (SIG_ATOMIC_MIN < 0) + + (WCHAR_MIN < 0) + (WINT_MIN == 0U) + + (signal_value > 0 && wide_value == WINT_MAX); +} +EOF +try_flags 12 "--no-libc" << EOF +#include +int main(void) { + return (INT_LEAST8_MIN < 0) + (UINT_LEAST8_MAX > 0) + + (INT_LEAST16_MIN < 0) + (UINT_LEAST16_MAX > 0) + + (INT_LEAST32_MIN < 0) + (UINT_LEAST32_MAX > INT_LEAST32_MAX) + + (INT_FAST8_MIN < 0) + (UINT_FAST8_MAX > INT_FAST8_MAX) + + (INT_FAST16_MIN < 0) + (UINT_FAST16_MAX > INT_FAST16_MAX) + + (INT_FAST32_MIN < 0) + (UINT_FAST32_MAX > INT_FAST32_MAX); +} +EOF +try_flags 4 "--no-libc" << EOF +#include +int main(void) { + return (INT_LEAST64_MIN < 0) + (UINT_LEAST64_MAX > INT_LEAST64_MAX) + + (INT_FAST64_MIN < 0) + (UINT_FAST64_MAX > INT_FAST64_MAX); +} +EOF +try_flags 12 "--no-libc" << EOF +#include +int main(void) { + return (INT8_C(12) == 12) + (UINT8_C(12) == 12) + + (INT16_C(12) == 12) + (UINT16_C(12) == 12) + + (INT32_C(12) == 12) + (UINT32_C(12) == 12U) + + (sizeof(INT8_C(12)) == 4) + (sizeof(UINT8_C(12)) == 4) + + (sizeof(INT16_C(12)) == 4) + (sizeof(UINT16_C(12)) == 4) + + (sizeof(INT32_C(12)) == 4) + (sizeof(UINT32_C(12)) == 4); +} +EOF +try_flags 8 "--no-libc" << EOF +#include +int main(void) { + return (INT64_C(12) == 12LL) + (UINT64_C(12) == 12ULL) + + (INTMAX_C(12) == 12LL) + (UINTMAX_C(12) == 12ULL) + + (sizeof(INT64_C(12)) == 8) + (sizeof(UINT64_C(12)) == 8) + + (sizeof(INTMAX_C(12)) == 8) + (sizeof(UINTMAX_C(12)) == 8); +} +EOF +try_compile_error_message "Angle header not found in -I search paths" << EOF +#include +EOF + +# An -I directory and header name that together exceed the path buffer must not +# be truncated: the truncated lookup missed this stdbool.h and silently used the +# built-in header instead. Diagnosing the length and reading the file both fail. +LONG_INCLUDE_DIR="$TEST_TMPDIR/$(printf 'd%.0s' {1..120})/$(printf 'e%.0s' {1..120})" +mkdir -p "$LONG_INCLUDE_DIR" +echo '#error the long include directory was searched' > "$LONG_INCLUDE_DIR/stdbool.h" +try_compile_error_flag "-I$LONG_INCLUDE_DIR" << EOF +#include +int main(void) { return 0; } +EOF +try_compile_error << EOF +#define FIRST_HEADER SECOND_HEADER +#define SECOND_HEADER FIRST_HEADER +#include FIRST_HEADER +int main(void) { return 0; } +EOF + +# An angle header name is read through translation phases 1 and 2, so a +# backslash-newline or a trigraph splice inside it joins the name. +try_ 2 << 'EOF' +#include +#include +int main(void) { bool yes = true; return yes + (int) true; } +EOF +try_compile_error_message "unsupported platform configuration" << EOF +#error unsupported platform configuration +int main(void) { return 0; } +EOF +try_ 0 << EOF +#pragma vendor_extension ignored payload +int main(void) { return 0; } +EOF +try_ 0 << EOF +_Pragma("vendor_extension ignored payload") +#define DO_PRAGMA(value) _Pragma(#value) +DO_PRAGMA(another_extension ignored) +int main(void) { return 0; } +EOF +try_compile_error << EOF +_Pragma(123) +EOF +try_ 8 << EOF +??=define TRI_LEFT 4 ??/ ++ 3 +??=if TRI_LEFT == 7 +??=define TRI_ENABLED 1 +??=else +??=define TRI_ENABLED 0 +??=endif +??=define JOIN_TRI(left, right) left ??=??= right +int JOIN_TRI(tri, graph) = TRI_LEFT; +int main(void) ??< int values??(2??) = ??< 3, trigraph ??>; char *word = "??/n"; return TRI_ENABLED * values??(1??) + (word??(0??) == '\n'); ??> +EOF +try_ 1 << EOF +int main(void) { + // ??/ + return 0; + return 1; +} +EOF +try_ 8 << EOF +int main(void) { + char *raw = "a\ +b"; + char *trigraph = "a??/ +b"; + return (1 \ ++ 2) + (1 ??/ ++ 2) + (raw[1] == 'b') + (trigraph[1] == 'b'); +} +EOF +try_ 7 << EOF +#def\ +ine RAW_NAME ma\ +in +??=def??/ +ine TRI_VALUE 7 +int RAW_NAME(void) { return TRI_VALUE; } +EOF +try_ 8 << EOF +%:define DIGRAPH_VALUE 7 +%:if DIGRAPH_VALUE == 7 +%:define DIGRAPH_ENABLED 1 +%:else +%:define DIGRAPH_ENABLED 0 +%:endif +%:define JOIN(left, right) left %:%: right +%:define STRINGIFY(value) %: value +int JOIN(di, graph) = DIGRAPH_VALUE; +int main(void) <% int values<:2:> = <% 3, digraph %>; char *word = STRINGIFY(ok); return DIGRAPH_ENABLED * values<:1:> + (word<:0:> == 'o'); %> +EOF + +# #ifdef...#else...#endif +try_ 0 << EOF +#define A 0 +#define B 200 +int main() +{ + int x; +#ifdef A + x = A; +#else + x = B; +#endif + return x; +} +EOF + +# #ifndef...#else...#endif +try_ 0 << EOF +#ifndef A +#define A 0 +#else +#define A 1 +#endif + +#ifndef A +#define B 1 +#else +#define B 0 +#endif +int main() +{ + return A + B; +} +EOF + +# include guard test, simulates inclusion of a file named defs.h and global.c +try_ 0 << EOF +/* #include "defs.h" */ +#ifndef DEFS_H +#define DEFS_H + +#define A 1 + +#endif +/* end if "defs.h" inclusion */ + +/* #include "global.c" */ +#ifndef GLOBAL_C +#define GLOBAL_C + +#define B 1 + +/* [global.c] #include "defs.h" */ +#ifndef DEFS_H +#define DEFS_H + +#define A 2 + +#endif +/* end if "defs.h" inclusion */ +#endif +/* end if "global.c" inclusion */ + +int main() +{ + return A - B; +} +EOF + +# #if defined(...) ... #elif defined(...) ... #else ... #endif +try_ 0 << EOF +#define A 0 +#define B 0xDEAD +int main() +{ + int x; +#if defined(A) + x = A; +#elif defined(B) + x = B; +#else + x = 0xCAFE; +#endif + return x; +} +EOF + +# A preprocessor expression is not limited to two operands. In particular, +# chained logical operators must keep consuming the directive through newline. +try_ 7 << EOF +#define A +#define C +#if defined(A) || defined(B) || defined(C) +#define RESULT 7 +#else +#define RESULT 0 +#endif +int main(void) { return RESULT; } +EOF + +# C99 permits both defined(NAME) and defined NAME in a #if expression. +try_ 9 << EOF +#define ENABLED +#if defined ENABLED && !defined DISABLED +#define RESULT 9 +#else +#define RESULT 0 +#endif +int main(void) { return RESULT; } +EOF + +# Conditional inclusion uses the full integer constant-expression grammar. +try_ 11 << EOF +#if ((1 << 2) == 4) && (5 % 3 == 2) && !defined UNKNOWN && (0 ? 0 : 1) +#define RESULT 11 +#else +#define RESULT 0 +#endif +int main(void) { return RESULT; } +EOF + +# Inactive operands must be parsed but not evaluated: both divisions are invalid +# if reached, yet are protected by C99 short-circuit operators. +try_ 16 << EOF +#if 1 || (1 / 0) +#define OR_RESULT 1 +#endif +#if 0 && (1 / 0) +#define AND_RESULT 0 +#else +#define AND_RESULT 12 +#endif +#if 1 ? 3 : (1 / 0) +#define TERNARY_RESULT 3 +#endif +int main(void) { return OR_RESULT + AND_RESULT + TERNARY_RESULT; } +EOF + +# An active invalid arithmetic operation in a #if expression must be diagnosed +# by the preprocessor rather than reaching host division/modulo behavior. +try_compile_error << EOF +#if 1 / 0 +#endif +EOF +try_compile_error << EOF +#if 1 % 0 +#endif +EOF + +# A conditional nested inside a skipped group is skipped whole: its #elif, #else +# and #endif belong to it rather than to the group that encloses it. +try_ 0 << EOF +#if 0 +#if 1 +#endif +#endif +int main(void) { return 0; } +EOF +try_ 5 << EOF +#if 0 +#ifdef __STDC__ +#elif 1 +#else +#endif +#elif 1 +#define RESULT 5 +#else +#define RESULT 0 +#endif +int main(void) { return RESULT; } +EOF +try_ 7 << EOF +#ifndef __STDC__ +#if 1 +#ifdef __STDC__ +#else +#ifndef UNKNOWN +#endif +#endif +#elif 0 +#endif +#define RESULT 0 +#else +#define RESULT 7 +#endif +int main(void) { return RESULT; } +EOF + +# Skipped groups several levels deep inside active ones, and an active group +# after a skipped #if and #elif chain at the same depth. +try_ 21 << EOF +#if 1 +#define A 1 +#if 0 +#if 1 +#if 1 +#endif +#else +#endif +#define A 99 +#elif 1 +#ifdef UNKNOWN +#if 1 +#endif +#elif 1 +#define B 4 +#endif +#else +#define B 99 +#endif +#ifndef __STDC__ +#if 0 +#endif +#else +#define C 16 +#endif +#endif +int main(void) { return A + B + C; } +EOF +try_compile_error_message "Unterminated conditional directive" << EOF +#if 0 +#if 1 +#endif +int main(void) { return 0; } +EOF +try_compile_error_message "Stray #endif" << EOF +#if 0 +#if 1 +#endif +#endif +#endif +int main(void) { return 0; } +EOF + +# White space, comments included, may separate '#' from the directive name, and +# a line holding only '#' is the null directive (C99 6.10p2, 6.10.7). +try_ 9 << 'EOF' +# +# define SIX 6 +# define THREE 3 + # /* comment */ ifdef SIX +%: define NINE (SIX + THREE) +# endif +# +# // a null directive with a comment +#/* a comment + across lines */ +int main(void) { return NINE; } +# +EOF +try_ 3 << 'EOF' +# if 0 +# if 1 +# +# else +# endif +# define RESULT 0 +# elif 1 +# define RESULT 3 +# endif +int main(void) { return RESULT; } +EOF + +# A skipped group may hold lines that are no directive shecc knows, while the +# same line in an active group is still rejected. +try_ 2 << 'EOF' +#if 0 +#warning not a C99 directive +# unknown +#! +#endif +int main(void) { return 2; } +EOF +try_compile_error_message "Unsupported directive" << 'EOF' +# unknown +int main(void) { return 0; } +EOF +try_compile_error_message "Unsupported directive" << 'EOF' +#if 1 +#else +#else_if +#endif +# unknown_directive +int main(void) { return 0; } +EOF + +# The '#' and '##' operators in replacement lists are not directives, spaced or +# not. +try_output 0 "[ab] 12 [c d]" << 'EOF' +#define STR(x) # x +#define CAT(a, b) a ## b +#define PAIR(x, y) %: x, %:y +int main(void) +{ + char *pair[2] = {PAIR(c, d)}; + printf("[%s] %d [%s %s]\n", STR(ab), CAT(1, 2), pair[0], pair[1]); + return 0; +} +EOF + +# Character constants are integer constants in a C99 #if expression. +try_ 14 << EOF +#if 'A' == 65 && '\\n' == 10 +#define CHARACTER_RESULT 14 +#else +#define CHARACTER_RESULT 0 +#endif +int main(void) { return CHARACTER_RESULT; } +EOF + +try_compile_error_message "Hexadecimal escape sequence out of range" << EOF +#if '\\x123' == 0x23 +#endif +int main(void) { return 0; } +EOF + +try_ 1 << EOF +#if '\\x0041' == 65 +#define HEX_ESCAPE_RESULT 1 +#else +#define HEX_ESCAPE_RESULT 0 +#endif +int main(void) { return HEX_ESCAPE_RESULT; } +EOF + +try_ 2 << EOF +#if 'AB' == 0x4142 +#define MULTI_CHARACTER_RESULT 1 +#else +#define MULTI_CHARACTER_RESULT 0 +#endif +int main(void) { return ('AB' == 0x4142) + MULTI_CHARACTER_RESULT; } +EOF + +try_ 3 << EOF +#define PREPROCESSOR_WIDE_CHARACTER L'C' +#if L'A' == 65 && L'\x42' == 66 && PREPROCESSOR_WIDE_CHARACTER == 67 +#define WIDE_CHARACTER_RESULT 3 +#else +#define WIDE_CHARACTER_RESULT 0 +#endif +int main(void) { return WIDE_CHARACTER_RESULT; } +EOF + +try_ 1 << EOF +#include +#if INT64_MIN < 0 && UINT64_MAX > 0 && UINT64_MAX > INT64_MAX && \ + (UINT64_MAX >> 63) == 1 && ~0ULL == UINT64_MAX && \ + UINT64_MAX + 1ULL == 0 && UINT64_MAX / 2ULL == INT64_MAX && \ + UINT64_MAX % 2ULL == 1 && -1LL > 1ULL && -2LL < -1LL && \ + (-2LL >> 1) == -1LL && (-1LL >> 33) == -1LL && \ + -7LL / 3LL == -2LL && -7LL % 3LL == -1LL && \ + 0xffffffffffffffff == UINT64_MAX && \ + 01777777777777777777777 == UINT64_MAX +#define WIDE_INTEGER_CONDITION 1 +#else +#define WIDE_INTEGER_CONDITION 0 +#endif +int main(void) { return WIDE_INTEGER_CONDITION; } +EOF + +try_ 1 << EOF +int main(void) { return 'A\\0' == 0x4100; } +EOF + +# Parentheses do not turn an object-like macro into a function-like macro: they +# remain available to call the replacement identifier. +try_ 15 << EOF +#define OBJECT_MACRO identity +int identity(int value) { return value; } +int main(void) { return OBJECT_MACRO(15); } +EOF + +# Redefinition also replaces the old function-like signature. +try_ 4 << EOF +#define REUSED_MACRO(value) value +#undef REUSED_MACRO +#define REUSED_MACRO identity +int identity(int value) { return value; } +int main(void) { return REUSED_MACRO(4); } +EOF + +# Function-like macros expand before a C99 #if expression is evaluated. +try_ 17 << EOF +#define TWO() 2 +#define ADD(left, right) ((left) + (right)) +#if ADD(TWO(), 3) == 5 +#define FUNCTION_MACRO_RESULT 17 +#else +#define FUNCTION_MACRO_RESULT 0 +#endif +int main(void) { return FUNCTION_MACRO_RESULT; } +EOF + +# #define ... #undef +try_output 0 "1" << EOF +#define A 1 +void log() +{ + printf("%d", A); +} +#undef A +#define A 0 +int main() +{ + log(); + return A; +} +EOF + +# An empty replacement list expands to nothing, in both macro shapes. Producing +# no tokens used to hand the caller a pointer into the dead frame that expanded +# them, which spliced the token list into a cycle the parser never left. +try_output 42 "" << EOF +#define EMPTY +#define NOTHING(x) +EMPTY int main(void) +{ + NOTHING(1) + EMPTY return 42; +} +EOF + +try_output 0 "ab" << EOF +#define BLANK +#define JOIN(a, b) printf(a); BLANK printf(b); +int main(void) +{ + JOIN("a", "b") + return 0; +} +EOF + +# format +try_output 0 "2147483647" << EOF +int main() { + printf("%d", 2147483647); + return 0; +} +EOF + +try_output 0 "-2147483648" << EOF +int main() { + printf("%d", -2147483648); + return 0; +} +EOF + +try_output 0 "-2147483647" << EOF +int main() { + printf("%d", -2147483647); + return 0; +} +EOF + +try_output 0 "-214748364" << EOF +int main() { + printf("%d", -214748364); + return 0; +} +EOF + +try_output 0 " -214748364" << EOF +int main() { + printf("%11d", -214748364); + return 0; +} +EOF + +try_output 0 " -214748364" << EOF +int main() { + printf("%16d", -214748364); + return 0; +} +EOF + +try_output 0 "$(printf '%97s123')" << EOF +int main() { + printf("%100d", 123); + return 0; +} +EOF + +try_output 0 "%1" << EOF +int main() { + printf("%%%d", 1); + return 0; +} +EOF + +try_output 0 "144" << EOF +int main() { + printf("%o", 100); + return 0; +} +EOF + +try_output 0 "0144" << EOF +int main() { + printf("%#o", 100); + return 0; +} +EOF + +try_output 0 "7f" << EOF +int main() { + printf("%x", 127); + return 0; +} +EOF + +try_output 0 "0x7f" << EOF +int main() { + printf("%#x", 127); + return 0; +} +EOF + +fmt_ans="0x0000000000000000000000ff00cde1 + 0xff00cde1 +000000000000000000000000ff00cde1 + ff00cde1 +0xff00cde1 +ff00cde1 +0x00ff00cde1 + 0xff00cde1 +0000ff00cde1 + ff00cde1 +00000000000000000000037700146741 + 037700146741 +00000000000000000000037700146741 + 37700146741 +037700146741 +37700146741 +037700146741 +037700146741 +037700146741 + 37700146741 +-0000000000000000000000016724511 + -16724511 +-16724511 +-00016724511 + -16724511 +0x0000000000000000000000fffff204 + 0xfffff204 +000000000000000000000000fffff204 + fffff204 +0xfffff204 +fffff204 +0x00fffff204 + 0xfffff204 +0000fffff204 + fffff204 +00000000000000000000037777771004 + 037777771004 +00000000000000000000037777771004 + 37777771004 +037777771004 +37777771004 +037777771004 +037777771004 +037777771004 + 37777771004 +-0000000000000000000000000003580 + -3580 +-3580 +-00000003580 + -3580 +0x00000000000000000000000001000c + 0x1000c +0000000000000000000000000001000c + 1000c +0x1000c +1000c +0x000001000c + 0x1000c +00000001000c + 1000c +00000000000000000000000000200014 + 0200014 +00000000000000000000000000200014 + 200014 +0200014 +200014 +000000200014 + 0200014 +000000200014 + 200014 +00000000000000000000000000065548 + 65548 +65548 +000000065548 + 65548 +00000000000000000000000000000000 + 0 +00000000000000000000000000000000 + 0 +0 +0 +000000000000 + 0 +000000000000 + 0 +00000000000000000000000000000000 + 0 +00000000000000000000000000000000 + 0 +0 +0 +000000000000 + 0 +000000000000 + 0 +00000000000000000000000000000000 + 0 +0 +000000000000 + 0" + +try_output 0 "$fmt_ans" << EOF +void printf_conversion(int num) { + printf("%#032x\n%#32x\n%032x\n%32x\n%#x\n%x\n", num, num, num, num, num, num); + printf("%#012x\n%#12x\n%012x\n%12x\n", num, num, num, num); + printf("%#032o\n%#32o\n%032o\n%32o\n%#o\n%o\n", num, num, num, num, num, num); + printf("%#012o\n%#12o\n%012o\n%12o\n", num, num, num, num); + printf("%032d\n%32d\n%d\n", num, num, num); + printf("%012d\n%12d\n", num, num); +} + +int main() { + int a = 0xFF00CDE1, b = 0xFFFFF204, c = 65548, d = 0; + printf_conversion(a); + printf_conversion(b); + printf_conversion(c); + printf_conversion(d); + return 0; +} +EOF + +# The printf family follows C99 7.19.6.1 for everything but floating point: the +# -, +, space, # and 0 flags, width and precision given inline or by *, the hh, +# h, l, ll, j, z and t length modifiers, and %i, %X, %c, %s, %p, %n and %%. The +# expected text is glibc's. Only #, 0, width and l forms were accepted before. +printf_c99_ans=$( + cat << 'EOF' +[42 ][ff ][ab ][z ][ y] +[+5][-5][ 5][-5][+7] +[0xff][0XFF][010][0][0] +[-0042][-42 ][000ab][+0042][ 0042] +[007][][ -007][0a ][] +[010][0][ 005][ABCDEF][abcdef] +[ 1][2 ][3 ][004][he][5] +[ab][ abc][x ][][] +[0x1234][ 0x10][0x20 ] +[(nil)][ (nil)] +[%][%] +[44][44][4464][4464][1170] +[-3][3000000000][feed][-9][10] +[-5000000000][18000000000][123456789a][ABCDEF012][77] +[-12345678901][12345678901][42][7][-3] +[-17][4294967295][777][ok] +abcdef +xyz|3 6 2 3 3 +1 |+03| 0x11|k |0 +[ -1][99 ][-0000000000000000123][+5][ 6] +[-0000000042][0][0][0] +[-2147483648][2147483647] + ab | +12 +EOF +) +try_output 0 "$printf_c99_ans" << EOF +#include +#include +#include +int main(void) +{ + int n1 = 0, n2 = 0; + signed char hn = 0; + short sn = 0; + long long lln = 0; + int x = 0; + char buf[64] = "abcdefgh"; + printf("[%-5d][%-5x][%-8s][%-3c][%5c]\n", 42, 255, "ab", 'z', 'y'); + printf("[%+d][%+d][% d][% d][%+ d]\n", 5, -5, 5, -5, 7); + printf("[%#x][%#X][%#o][%#o][%#x]\n", 255, 255, 8, 0, 0); + printf("[%05d][%-05d][%05x][%+05d][% 05d]\n", -42, -42, 171, 42, 42); + printf("[%.3d][%.0d][%5.3d][%-6.2x][%.0x]\n", 7, 0, -7, 10, 0); + printf("[%#.3o][%#.0o][%08.3d][%X][%x]\n", 8, 0, 5, 0xabcdef, 0xABCDEF); + printf("[%*d][%-*d][%*d]", 6, 1, 4, 2, -4, 3); + printf("[%.*d][%.*s][%.*d]\n", 3, 4, 2, "hello", -1, 5); + printf("[%.2s][%10.3s][%-10.1s][%s][%.0s]\n", "abc", "abcdef", "xyz", "", "q"); + printf("[%p][%12p][%-12p]\n", (void *) 0x1234, (void *) 0x10, (void *) 0x20); + printf("[%p][%8p]\n", (void *) 0, (void *) 0); + printf("[%%][%5%]\n"); + printf("[%hhd][%hhu][%hd][%hu][%hx]\n", 300, 300, 70000, 70000, 70000); + printf("[%ld][%lu][%lx][%li][%lo]\n", -3L, 3000000000UL, 0xfeedL, -9L, 8L); + printf("[%lld][%llu][%llx][%llX][%lli]\n", -5000000000LL, 18000000000ULL, + 0x123456789aLL, 0xabcdef012LL, 77LL); + printf("[%jd][%ju][%zu][%zd][%td]\n", (intmax_t) -12345678901LL, + (uintmax_t) 12345678901ULL, (size_t) 42, (size_t) 7, (ptrdiff_t) -3); + printf("[%i][%u][%o][%c%c]\n", -17, 4294967295U, 511, 'o', 'k'); + printf("abc%ndef%n\n", &n1, &n2); + printf("xy%hhnz%hn%lln|", &hn, &sn, &lln); + printf("%d %d %d %d %d\n", n1, n2, hn, sn, (int) lln); + snprintf(buf, sizeof buf, "%-4d|%+.2d|%#6x|%-6s|%x", 1, 3, 17, "k", 0); + printf("%s\n", buf); + printf("[%20lld][%-20lld][%020lld][%+lld][% lld]\n", -1LL, 99LL, -123LL, 5LL, 6LL); + printf("[%.10lld][%#llx][%#llo][%llu]\n", -42LL, 0LL, 0LL, 0ULL); + printf("[%d][%d]\n", -2147483647 - 1, 2147483647); + x = printf("%5s%-5s|\n", "a", "b"); + printf("%d\n", x); + return 0; +} +EOF + +# The printf family converts %u, the long forms %ld, %lu and %lx, and the long +# long forms %lld, %llu, %llx and %llo, whose argument takes two words on a +# 32-bit target and starts at an even slot. +try_ 0 << EOF +int main(void) { + long long a = -1234567890123LL, c = 0x1122334455667788LL; + unsigned long long b = 18446744073709551615ULL; + char buf[160]; + snprintf(buf, sizeof buf, "%lld %llu %llx|%d %lld", a, b, c, 7, a); + if (strcmp(buf, "-1234567890123 18446744073709551615 1122334455667788|" + "7 -1234567890123")) + return 1; + sprintf(buf, "%ld %lu %u %lx %5u", -5L, 4000000000UL, 4000000000U, 255L, 42U); + if (strcmp(buf, "-5 4000000000 4000000000 ff 42")) + return 2; + sprintf(buf, "%020lld|%18lld|%#llx|%#llo|%llo", a, a, c, 8LL, 0LL); + if (strcmp(buf, "-0000001234567890123| -1234567890123|0x1122334455667788|" + "010|0")) + return 3; + sprintf(buf, "%lld", -9223372036854775807LL - 1); + if (strcmp(buf, "-9223372036854775808")) + return 4; + sprintf(buf, "%s %lld %s", "x", 5LL, "y"); + return strcmp(buf, "x 5 y") != 0; +} +EOF + +try_ 0 << EOF +int main() { + return '\0'; +} +EOF + +begin_category "Goto statements" "Testing goto and label statements" + +# label undeclaration +try_compile_error << EOF +int main() +{ + goto label; +} +EOF + +# label redefinition +try_compile_error << EOF +int main() +{ + goto label; +label: +label: +} +EOF + +# test label namespace +try_ 1 << EOF +int main() +{ + goto label; +label: + int label = 1; + return label; +} +EOF + +try_ 0 << EOF +int main() { + int x = 0; + goto skip; + x = 1; +skip: + return x; /* Should return 0 */ +} +EOF + +# Forward reference. Statements between a goto and its label are unreachable but +# perfectly legal, and gcc accepts this silently at -Wall -Wextra -pedantic. +# shecc used to abort on the unreachable "return 1;" -- this case asserted that +# abort as a compile error; it now asserts the correct result. +try_ 0 << EOF +int main() +{ + goto end; + return 1; +end: + return 0; +} +EOF + +# Simple loop +try_ 10 << EOF +int main() +{ + int vars0; + + vars0 = 0; +BB1: + if (!(vars0 < 10)) goto BB6; + vars0++; + goto BB1; +BB6: + return vars0; +} +EOF + +# Complex loop +ans="0 +0012345678910123456789201234567893012345678940123456789 +1 +0012345678910123456789201234567893012345678940123456789 +3 +0012345678910123456789201234567893012345678940123456789 +4 +0012345678910123456789201234567893012345678940123456789 +5 +0012345678910123456789201234567893012345678940123456789 +6 +0012345678910123456789201234567893012345678940123456789 +7 +0012345678910123456789201234567893012345678940123456789 +8 +0012345678910123456789201234567893012345678940123456789 +9 +0012345678910123456789201234567893012345678940123456789" +try_output 0 "$ans" << EOF +int main() +{ + int vars0; + int vars1; + int vars2; + int vars3; + + vars0 = 0; +BB1: + if (!(vars0 < 10)) goto BB47; + if (vars0 == 2) goto BB45; + printf("%d\n", vars0); + vars1 = 0; +BB10: + if (!(vars1 < 10)) goto BB27; + if (vars1 == 5) goto BB27; + printf("%d", vars1); + vars2 = 0; +BB19: + if (!(vars2 < 10)) goto BB25; + printf("%d", vars2); + vars2++; + goto BB19; +BB25: + vars1++; + goto BB10; +BB27: + printf("\n"); + vars3 = 5; +BB29: + if (vars3 == 2) goto BB29; + if (vars3 == 3) goto BB45; + vars3--; + if (vars3 > 0) goto BB29; +BB45: + vars0++; + goto BB1; +BB47: + return 0; +} +EOF + +# Category: Built-in macros +begin_category "Built-in Macros" "Testing macros defined by standard, e.g. __LINE__" + +try_output 0 "3" << EOF +int main() +{ + printf("%d", __LINE__); + return 0; +} +EOF + +try_ 1 << EOF +int main() +{ + char *file_name = __FILE__; + return !strcmp(file_name + strlen(file_name) - 2, ".c"); +} +EOF + +#line changes the logical source location observed by the standard built-ins. +try_ 1 << EOF +#line 70 +int main(void) { return __LINE__ == 70; } +EOF + +try_ 1 << EOF +#line 41 "generated-input.c" +int main(void) { return !strcmp(__FILE__, "generated-input.c"); } +EOF + +#line operands are macro-expanded in an isolated directive token stream. +try_ 2 << EOF +#define LINE_VALUE 90 +#define LINE_FILE "macro-generated.c" +#line LINE_VALUE LINE_FILE +int main(void) { return (__LINE__ == 90) + !strcmp(__FILE__, "macro-generated.c"); } +EOF + +try_ 1 << EOF +#define ID(x) x +#line ID(120) +int main(void) { return __LINE__ == 120; } +EOF + +try_compile_error << EOF +#line 0 +int main(void) { return 0; } +EOF + +# The line number is a decimal digit sequence: a leading zero is not octal, and +# a prefix, a suffix or a value past 2147483647 is rejected. +try_ 1 << EOF +#line 010 +int main(void) { return __LINE__ == 10; } +EOF + +try_compile_error << EOF +#line 0x10 +int main(void) { return 0; } +EOF + +try_compile_error << EOF +#line 10u +int main(void) { return 0; } +EOF + +try_compile_error << EOF +#line 4294967297 +int main(void) { return 0; } +EOF + +# C99 fixes the spelling and extent of these translation-time string literals. +# Their actual value is supplied once at configuration time so all bootstrap +# stages use precisely the same expansion. +try_ 1 << EOF +int main(void) +{ + return sizeof(__DATE__) == 12 && sizeof(__TIME__) == 9; +} +EOF + +# Reached through another macro's name, each still spells its own value. +try_ 1 << EOF +#define DATE_ALIAS __DATE__ +#define TIME_ALIAS() __TIME__ +int main(void) +{ + return sizeof(DATE_ALIAS) == 12 && sizeof(TIME_ALIAS()) == 9 && + !strcmp(DATE_ALIAS, __DATE__) && !strcmp(TIME_ALIAS(), __TIME__); +} +EOF + +# Category: Function-like Macros +begin_category "Function-like Macros" "Testing function-like macros and variadic macros" + +# An empty argument list is a valid invocation of a zero-parameter macro. +try_ 6 << EOF +#define SIX() 6 +int main(void) { return SIX(); } +EOF + +# An object-like replacement is expanded without consuming its following call. +try_ 9 << EOF +#define TARGET target +int target(void) { return 9; } +int main(void) { return TARGET(); } +EOF + +# An argument is macro-replaced before it is substituted (C99 6.10.3.1), so a +# macro used in its own argument expands too, directly or through another macro, +# while the rescanned replacement still does not recurse. +try_output 0 "3 3 5 15 9 12 ((((1) + 1)) + 1)" << EOF +#define A(x) ((x) + 1) +#define B(y) A(y) +#define C(a, b) A(a) * A(b) +#define SELF(x) SELF +#define REC(x) REC(x) +#define STR(x) #x +#define XSTR(x) STR(x) +int SELF = 9; +int REC(int v) { return v * 2; } +int main() +{ + printf("%d %d %d %d %d %d %s\n", A(A(1)), B(A(1)), A(B(A(2))), + C(A(1), C(1, A(0))), SELF(SELF(0)), REC(REC(3)), XSTR(A(A(1)))); + return 0; +} +EOF + +# stringification: '#' spells the argument as it was written +try_output 0 "hello world" << EOF +#define STR(x) #x +int main() +{ + printf("%s\n", STR(hello world)); + return 0; +} +EOF + +# '#' does not expand its operand, but an extra level of macro does +try_output 0 "VER 3" << EOF +#define STR(x) #x +#define XSTR(x) STR(x) +#define VER 3 +int main() +{ + printf("%s %s\n", STR(VER), XSTR(VER)); + return 0; +} +EOF + +# a quote or backslash in the argument survives stringification +try_output 0 '["q\\b"]' << EOF +#define STR(x) #x +int main() +{ + printf("[%s]\n", STR("q\\\\b")); + return 0; +} +EOF + +# an empty argument stringifies to an empty string +try_output 0 "[]" << EOF +#define STR(x) #x +int main() +{ + printf("[%s]\n", STR()); + return 0; +} +EOF + +# A '#' that a backslash-newline carries to column 1 is still inside the logical +# line before it, so it stringifies rather than opening a directive, while white +# space alone before a '#' leaves it a directive. +try_output 0 "[ab] 7" << 'EOF' +#define STR(x) \ +#x + \ +#define SEVEN 7 + #ifndef SEVEN + #error SEVEN + #endif +int main() +{ + printf("[%s] %d\n", STR(ab), SEVEN); + return 0; +} +EOF + +# token pasting builds an identifier +try_ 11 << EOF +#define CAT(a, b) a##b +int foobar() +{ + return 11; +} +int main() +{ + return CAT(foo, bar)(); +} +EOF + +# pasting chains left to right, and works on numbers +try_ 123 << EOF +#define JOIN3(a, b, c) a##b##c +int main() +{ + return JOIN3(1, 2, 3); +} +EOF + +# pasting in an object-like macro, and pasting an operator +try_ 42 << EOF +#define PLUSEQ +##= +#define OBJ pre##fix +int prefix = 41; +int main() +{ + int x = 1; + x PLUSEQ prefix; + return x; +} +EOF + +# an empty operand leaves the other side of '##' standing alone +try_ 3 << EOF +#define CAT(a, b) a##b +int main() { - int t = 0; - for (int i = 0; i < rows; i++) - for (int j = 0; j < 4; j++) - t += a[i][j]; - return t; + return CAT(1, ) + CAT(, 2); } +EOF + +# an omitted argument substitutes nothing rather than its own name +try_ 3 << EOF +#define TAIL(x, y) x y int main() { - int m[3][4]; - int c = 0; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 4; j++) { - m[i][j] = c; - c++; - } - return sum2(m, 3); + return TAIL(3, ); } EOF -# the sized form keeps working, and both agree -try_ 66 << EOF -int sum2(int a[3][4], int rows) +# a comma inside parentheses belongs to the argument, not the argument list +try_ 5 << EOF +#define ID(x) x +int add(int p, int q) { - int t = 0; - for (int i = 0; i < rows; i++) - for (int j = 0; j < 4; j++) - t += a[i][j]; - return t; + return p + q; } int main() { - int m[3][4]; - int c = 0; - for (int i = 0; i < 3; i++) - for (int j = 0; j < 4; j++) { - m[i][j] = c; - c++; - } - return sum2(m, 3); + return ID(add(2, 3)); } EOF -# a single omitted dimension is still a plain pointer -try_ 66 << EOF -int sum1(int a[], int n) +# '#' outside a macro definition is not a directive and must be rejected +try_compile_error << EOF +int main() { - int t = 0; - for (int i = 0; i < n; i++) - t += a[i]; - return t; + int a = 1 # 2; + return a; +} +EOF + +# '##' with nothing on its left is rejected +try_compile_error << EOF +#define P(a) ##a +int main() +{ + return P(1); } +EOF + +# a paste that does not form a single token is rejected +try_compile_error << EOF +#define Q(a, b) a##b int main() { - int m[12]; - for (int i = 0; i < 12; i++) - m[i] = i; - return sum1(m, 12); + int Q(x, +) = 1; + return 0; } EOF -# arrays +# function-like macro +try_ 1 << EOF +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +int main() +{ + int x = 0, y = 1; + return MAX(x, y); +} +EOF + +try_ 7 << EOF +#define M(a, b) a + b +int main() +{ + return M(1, 2) * 3; +} +EOF + +# function-like variadic macro +try_ 2 << EOF +#define M(m, n, ...) \ + do { \ + x = __VA_ARGS__; \ + } while (0) +int main() +{ + int x = 0; + M(0, 1, 2); + return x; +} +EOF + +# A call that stops after the named parameters binds __VA_ARGS__ to nothing, +# which substitutes, stringifies and pastes as the empty list. +try_ 15 << EOF +#define TAIL(a, ...) (a + 0 __VA_ARGS__) +#define SPELL(a, ...) #__VA_ARGS__ +#define JOIN(a, ...) a ## __VA_ARGS__ +int main() +{ + int v = 4; + return TAIL(3) + TAIL(1, +2) + sizeof(SPELL(9)) + JOIN(v) + JOIN(v, ); +} +EOF + +# GNU comma elision: ", ## __VA_ARGS__" deletes the comma when the variadic +# argument is empty and keeps it, without pasting, when it is not. try_ 12 << EOF -int nth_of(int *a, int i) { - return a[i]; +#define CALL(f, ...) f(0, ##__VA_ARGS__) +#define ALL(...) count(9, ##__VA_ARGS__) +int count(int first, ...) { return first; } +int sum(int a, ...) { int *p = &a; return a + p[1 * (__SIZEOF_POINTER__ / 4)]; } +int one(int a) { return a + 1; } +int main() +{ + return CALL(one) + CALL(sum, 2) + ALL() + ALL(1, 2) - 9; } +EOF -int main() { - int ary[5]; - int i; - int v0; - int v1; - int v2; +# macro parameter substitution works in expression contexts +try_ 15 << EOF +#define ADD_PARAMS(a, b) ((a) + (b)) +int main() +{ + int x = 5, y = 10; + return ADD_PARAMS(x, y); +} +EOF - for (i = 0; i < 5; i++) { - ary[i] = i * 2; - } +# macro with assignment operators +try_ 18 << EOF +#define ASSIGN_MACRO(variable, val) \ + variable = variable + val + 10 +int main() +{ + int x = 5; + ASSIGN_MACRO(x, 3); + return x; +} +EOF - v0 = nth_of(ary, 0); - v1 = nth_of(ary, 2); - v2 = nth_of(ary, 4); - return v0 + v1 + v2; +try_ 27 << EOF +#define COMPOUND_ASSIGN(variable, val) \ + variable += val + 10 +int main() +{ + int y = 10; + COMPOUND_ASSIGN(y, 7); + return y; } EOF -# Test short array -try_ 25 << EOF -int main() { - short arr[4] = {10, 15, 20, 25}; - return arr[3]; +try_ 42 << EOF +#define SET_VAR(var, value) var = value +int main() +{ + int z = 0; + SET_VAR(z, 42); + return z; } EOF -# 2D Array Tests with proper row-major indexing for multi-dimensional arrays -try_ 78 << EOF -int main() { - int matrix[3][4]; - int sum = 0; - int i, j; +try_output 0 "Wrapper: Hello World!" << EOF +#define WRAPPER(...) \ + do { \ + printf("Wrapper: "); \ + printf(__VA_ARGS__); \ + } while (0) +int main() +{ + WRAPPER("%s", "Hello World!"); + return 0; +} +EOF - /* Initialize array */ - for (i = 0; i < 3; i = i + 1) { - for (j = 0; j < 4; j = j + 1) { - matrix[i][j] = i * 4 + j + 1; - } - } +try_ 0 << EOF +#if 1 || 0 +#define A 0 +#elif 1 && 0 +#define A 1 +#else +#define A 2 +#endif +int main() +{ + return A; +} +EOF - /* Calculate sum (1+2+...+12 = 78) */ - for (i = 0; i < 3; i = i + 1) { - for (j = 0; j < 4; j = j + 1) { - sum = sum + matrix[i][j]; - } - } +# recursive macro expansion +try_ 4 << EOF +int A(int x) +{ + return 2; +} +#define A(x) x + B(x) +#define B(x) x + A(x) +int main() +{ + return A(1); +} +EOF - return sum; +# optimizers + +# common subexpression elimination (CSE) +try_ 1 << EOF +int i = 0; +void func() +{ + i = 1; +} +int main() +{ + char arr[2], t; + arr[0] = 0; + arr[1] = 1; + t = arr[i]; + func(); + t = arr[i]; + return t; +} +EOF + +# An operand whose address is taken can change between two identical operations +# without any instruction naming it: a store through the pointer, or by a callee +# handed the address, in the same function, a loop, or through a pointer kept in +# a record. The second operation must be computed again. +try_ 101 << EOF +int f(int a, int b) +{ + int x = a + b; + int *p = &a; + *p = 99; + int y = a + b; + return y; +} +int main(void) +{ + return f(1, 2); } EOF -# 2D array element access in expressions -try_ 17 << EOF -int main() { - int grid[2][3]; - - grid[0][0] = 5; - grid[0][1] = 10; - grid[0][2] = 15; - grid[1][0] = 20; - grid[1][1] = 25; - grid[1][2] = 30; - - /* Test complex expression with 2D array elements */ - return (grid[0][1] + grid[0][2]) / 2 + grid[1][0] / 4; /* (10+15)/2 + 20/4 = 12 + 5 = 17 */ +try_ 34 << EOF +void inc(int *p) +{ + *p = *p + 1; +} +int f(int a, int b) +{ + int x = a + b; + inc(&a); + int y = a + b; + return x * 10 + y; +} +int main(void) +{ + return f(1, 2); } EOF -# Actually fix the calculation error above - should return 17, not 25 -try_ 17 << EOF -int main() { - int grid[2][3]; - - grid[0][0] = 5; - grid[0][1] = 10; - grid[0][2] = 15; - grid[1][0] = 20; - grid[1][1] = 25; - grid[1][2] = 30; - - /* Test complex expression with 2D array elements */ - return (grid[0][1] + grid[0][2]) / 2 + grid[1][0] / 4; /* (10+15)/2 + 20/4 = 12 + 5 = 17 */ +try_ 56 << EOF +int f(int a, int b) +{ + int *p = &b; + int x = a * b; + *p = 5; + int y = a * b; + return x + y; +} +int main(void) +{ + return f(7, 3); } EOF -# 2D array as multiplication table -try_ 30 << EOF -int main() { - int table[5][6]; - int i, j; - - /* Create multiplication table */ - for (i = 0; i < 5; i = i + 1) { - for (j = 0; j < 6; j = j + 1) { - table[i][j] = (i + 1) * (j + 1); - } - } - - /* Check specific values and return 5*6 = 30 */ - if (table[2][3] != 12) return 1; /* 3*4 = 12 */ - if (table[4][5] != 30) return 2; /* 5*6 = 30 */ - - return table[4][5]; +try_ 10 << EOF +int f(int a, int b) +{ + int x = a < b; + int *p = &a; + *p = 10; + int y = a < b; + return x * 10 + y; +} +int main(void) +{ + return f(1, 2); } EOF -# 2D array with single row/column -try_ 12 << EOF -int main() { - int row[1][5]; - int col[5][1]; - int i; - - /* Initialize single row array */ - for (i = 0; i < 5; i = i + 1) { - row[0][i] = i + 1; - } - - /* Initialize single column array */ - for (i = 0; i < 5; i = i + 1) { - col[i][0] = i + 1; - } - - return row[0][2] + col[3][0] + row[0][4]; /* 3 + 4 + 5 = 12 */ +try_ 84 << EOF +struct holder { + int *q; +}; +int main(void) +{ + int a = 3, b = 4; + struct holder h; + h.q = &a; + int x = a + b; + *h.q = 10; + int y = a + b; + return x * 10 + y; } EOF -# Fix the test above - the comment was wrong -try_ 12 << EOF -int main() { - int row[1][5]; - int col[5][1]; - int i; - - /* Initialize single row array */ - for (i = 0; i < 5; i = i + 1) { - row[0][i] = i + 1; - } - - /* Initialize single column array */ - for (i = 0; i < 5; i = i + 1) { - col[i][0] = i + 1; +try_ 3 << EOF +int main(void) +{ + int a = 3, b = 4, s = 0; + int *p = &a; + for (int k = 0; k < 3; k++) { + int x = a + b; + *p = *p + 1; + int y = a + b; + s += y - x; } - - return row[0][2] + col[3][0] + row[0][4]; /* 3 + 4 + 5 = 12 */ + return s; } EOF -# 2D array of structs -try_ 42 << EOF -typedef struct { - int x; - int y; -} Point; - -int main() { - Point grid[2][2]; - - grid[0][0].x = 1; - grid[0][0].y = 2; - grid[0][1].x = 3; - grid[0][1].y = 4; - grid[1][0].x = 5; - grid[1][0].y = 6; - grid[1][1].x = 7; - grid[1][1].y = 8; - - /* Sum all x values: 1 + 3 + 5 + 7 = 16 */ - /* Sum all y values: 2 + 4 + 6 + 8 = 20 */ - /* Return total of x[1][1] * y[1][0] = 7 * 6 = 42 */ - return grid[1][1].x * grid[1][0].y; +# Reading the same element twice is a repeat only while nothing can have written +# memory in between: a store to that element, one through another pointer, a +# callee, a store on one path to the second read, or an assignment by name to +# the variable the pointer reaches. A compound assignment to the element must +# also keep the address its store goes to. +try_ 57 << EOF +int main(void) +{ + char buf[4]; + int i = 1; + buf[1] = 5; + int x = buf[i]; + buf[i] = 7; + int y = buf[i]; + return x * 10 + y; } EOF -# 2D char array (string array simulation) -try_ 65 << EOF -int main() { - char letters[3][3]; - - /* Store letters A-I in 3x3 grid */ - letters[0][0] = 'A'; /* 65 */ - letters[0][1] = 'B'; - letters[0][2] = 'C'; - letters[1][0] = 'D'; - letters[1][1] = 'E'; - letters[1][2] = 'F'; - letters[2][0] = 'G'; - letters[2][1] = 'H'; - letters[2][2] = 'I'; - - /* Return the first letter */ - return letters[0][0]; +try_ 57 << EOF +int main(void) +{ + char buf[4]; + char *s = buf, *t = buf; + int i = 1; + s[1] = 5; + int x = s[i]; + t[i] = 7; + int y = s[i]; + return x * 10 + y; } EOF -# 2D array boundary test -try_ 100 << EOF -int main() { - int data[10][10]; - int i, j; +try_ 19 << EOF +void put(char *s, int i) +{ + s[i] = 9; +} +int main(void) +{ + char buf[4]; + int i = 2; + buf[2] = 1; + int x = buf[i]; + put(buf, i); + int y = buf[i]; + return x * 10 + y; +} +EOF - /* Initialize entire array */ - for (i = 0; i < 10; i = i + 1) { - for (j = 0; j < 10; j = j + 1) { - data[i][j] = i * 10 + j; - } - } +try_ 59 << EOF +int f(char *s, int i, int k) +{ + int x = s[i]; + if (k) + s[i] = 9; + int y = s[i]; + return x * 10 + y; +} +int main(void) +{ + char buf[4] = {0, 5, 0, 0}; + return f(buf, 1, 1); +} +EOF - /* Check corner values */ - if (data[0][0] != 0) return 1; - if (data[9][9] != 99) return 2; - if (data[5][5] != 55) return 3; +try_ 57 << EOF +int main(void) +{ + char buf[4]; + char *s = buf; + int i = 1; + s[1] = 5; + int x = s[i]; + s[i] += 2; + return x * 10 + buf[1]; +} +EOF - /* Return sum of corners: 0 + 9 + 90 + 99 = 198 - wait let me recalculate */ - /* Actually the test says return 100, let's just return data[9][9] + 1 */ - return data[9][9] + 1; +try_ 56 << EOF +int main(void) +{ + char buf[4]; + char *s = buf; + int i = 1; + s[1] = 5; + int x = s[i]; + s[i]++; + return x * 10 + buf[1]; } EOF -# Mixed subscript and arrow / dot operators, excerpted and modified from issue -# #165 -try_output 0 "DDDDDDMMMEEE1" << EOF -#include -#include +try_ 15 << EOF +int f(int i) +{ + char c = 1; + char *s = &c; + int x = s[i]; + c = 5; + int y = s[i]; + return x * 10 + y; +} +int main(void) +{ + return f(0); +} +EOF -char a[100]; +try_ 15 << EOF +char g; +int f(int i) +{ + char *s = &g; + g = 1; + int x = s[i]; + g = 5; + int y = s[i]; + return x * 10 + y; +} +int main(void) +{ + return f(0); +} +EOF -typedef struct { - char *raw; -} data_t; +# constant folding +try_ 20 << EOF +int main() +{ + int a = 2; /* constant assingment */ + int b = a; /* assignment via constant representation */ + int c = a + b; + int d = c + 8; /* mixed assigment */ + return a + b + c + d; /* chained assignment */ +} +EOF -int main() { - strcpy(a, "DATA"); - data_t *data = malloc(sizeof(data_t)); - data->raw = a; - data_t data2; - data2.raw = a; - char *raw = data->raw; - char *raw2 = data2.raw; - /* mixed arrow / dot with subscript operators dereference */ - printf("%c", a[0]); - printf("%c", raw[0]); - printf("%c", data->raw[0]); - printf("%c", a[0]); - printf("%c", raw2[0]); - printf("%c", data2.raw[0]); - /* mixed arrow / dot with subscript operators assignment */ - data2.raw[0] = 'M'; - data->raw[1] = 'E'; - printf("%c", a[0]); - printf("%c", raw[0]); - printf("%c", data->raw[0]); - printf("%c", a[1]); - printf("%c", raw2[1]); - printf("%c", data2.raw[1]); - /* their addresses should be same */ - printf("%d", &data2.raw[0] == &data->raw[0]); - free(data); - return 0; +# Variables can be declared within a for-loop iteration +try_ 120 << EOF +int main() +{ + int fac = 1; + for (int i = 1; i <= 5; i++) { + fac = fac * i; + } + return fac; } EOF -# Category: Global Variables -begin_category "Global Variables" "Testing global variable initialization and access" - -# global initialization -try_ 20 << EOF -int a = 5 * 2; -int b = -4 * 3 + 7 + 9 / 3 * 5; +# Multiplication for signed integers +try_output 0 "35 -35 -35 35" << EOF int main() { - return a + b; + printf("%d %d %d %d\n", 5 * 7, 5 * (-7), (-5) * 7, (-5) * (-7)); + return 0; } EOF -# Category: Const Qualifiers -begin_category "Const Qualifiers" "Testing const qualifier support for variables and parameters" - -# Test 1: Basic const local variable -try_ 42 << EOF -int main() { - const int x = 42; - return x; +try_output 0 "-212121 -535050 336105 666666666" << EOF +int main() +{ + printf("%d %d %d %d\n", (-333) * 637, 1450 * (-369), 37345 * 9, (-111111111) * (-6)); + return 0; } EOF -# Test 2: Const global variable -try_ 100 << EOF -const int global_const = 100; -int main() { - return global_const; +try_output 0 "1073676289 -131071 30" << EOF +int main() +{ + printf("%d %d %d\n", 32767 * 32767, 65535 * 65535, 54 * 5 * 954437177); + return 0; } EOF -# Test 3: Multiple const variables -try_ 30 << EOF -int main() { - const int a = 10; - const int b = 20; - return a + b; +try_output 0 "-2 6 24" << EOF +int main() +{ + printf("%d %d %d\n", (-1) * 2, (-1) * 2 * (-3), (-1) * 2 * (-3) * 4); + return 0; } EOF -# Test 4: Const parameter in function -try_ 15 << EOF -int add_five(const int x) { - return x + 5; +# Division and modulo for signed integers +try_output 0 "-1 -2" << EOF +int main() +{ + printf("%d %d", -6 / 4, -6 % 4); + return 0; } -int main() { - return add_five(10); +EOF + +try_output 0 "-3 1" << EOF +int main() +{ + printf("%d %d", 7 / -2, 7 % -2); + return 0; } EOF -# Test 5: Const pointer value (simplified) -try_ 25 << EOF -int main() { - const int value = 25; - const int *ptr = &value; - return *ptr; +try_output 0 "12 -1" << EOF +int main() +{ + printf("%d %d", -109 / -9, -109 % -9); + return 0; } EOF -# Test 6: Non-const pointer to const data -try_ 35 << EOF -int main() { - const int value = 35; - int *ptr = &value; - return *ptr; +# octal(155) = dec(109), expect same output with above test suite +try_output 0 "12 -1" << EOF +int main() +{ + printf("%d %d", -0155 / -9, -0155 % -9); + return 0; } EOF -# Test 7: Const in arithmetic expressions -try_ 60 << EOF -int main() { - const int x = 20; - const int y = 30; - const int z = 10; - return x + y + z; +try_output 0 "1365 0" << EOF +int main() +{ + printf("%d %d", 1365 / 1, 1365 % 1); + return 0; } EOF -# Test 8: Const with initialization from expression -try_ 50 << EOF -int main() { - int a = 10; - const int b = a * 5; - return b; +try_output 0 "-126322567 -8" << EOF +int main() +{ + printf("%d %d", -2147483647 / 17, -2147483647 % 17); + return 0; } EOF -# Test 9: Function returning through const variable -try_ 77 << EOF -int compute() { - const int result = 77; - return result; +try_output 0 "-1 -1" << EOF +int main() +{ + printf("%d %d", -2147483648 / 2147483647, -2147483648 % 2147483647); + return 0; } -int main() { - return compute(); +EOF + +try_output 0 "-2147483648 0" << EOF +int main() +{ + printf("%d %d", -2147483648 / 1, -2147483648 % 1); + return 0; } EOF -# Test 10: Const array element access -try_ 30 << EOF -int main() { - const int arr[3] = {10, 20, 30}; - return arr[2]; +try_output 0 "-134217728 0" << EOF +int main() +{ + printf("%d %d", -2147483648 / 16, -2147483648 % 16); + return 0; } EOF -# Test 11: Mixed const and non-const -try_ 45 << EOF -int main() { - const int x = 15; - int y = 20; - const int z = 10; - return x + y + z; +try_output 0 "134217728 0" << EOF +int main() +{ + printf("%d %d", -2147483648 / -16, -2147483648 % -16); + return 0; } EOF -# Test 12: Const with conditional -try_ 40 << EOF -int main() { - const int x = 40; - const int y = 50; - return (x < y) ? x : y; +try_output 0 "1 0" << EOF +int main() +{ + printf("%d %d", -2147483648 / -2147483648, -2147483648 % -2147483648); + return 0; } EOF -# Test 13: Const value from struct (simplified) -try_ 99 << EOF -struct Point { - int x; - int y; -}; -int main() { - struct Point p = {99, 100}; - const int val = p.x; - return val; +try_output 0 "-8910720 -128" << EOF +int main() +{ + printf("%d %d", -2147483648 / 241, -2147483648 % 241); + return 0; } EOF -# Test 14: Const char array (string) -try_ 72 << EOF -int main() { - const char str[] = "Hello"; - return str[0]; /* 'H' = 72 */ +try_output 0 "1" << EOF +int main() +{ + printf("%d", 6 / -2 / -3); + return 0; } EOF -# Test 15: Multiple const on same line -try_ 55 << EOF -int main() { - const int a = 10, b = 20, c = 25; - return a + b + c; +try_output 0 "0" << EOF +int main() +{ + printf("%d", 477 / 37 % -3); + return 0; } EOF -# Test 16: Const with typedef -try_ 88 << EOF -typedef int myint; -int main() { - const myint value = 88; - return value; +try_output 0 "12" << EOF +int main() +{ + printf("%d", 477 / (37 + 1 / -3)); + return 0; } EOF -# Test 17: Const void pointer -try_ 12 << EOF -int main() { - int val = 12; - const void *ptr = &val; - const int *iptr = ptr; - return *iptr; +try_output 0 "-39" << EOF +int main() +{ + printf("%d", 477 / (37 / -3)); + return 0; } EOF -# Test 18: Nested const usage -try_ 18 << EOF -int get_value(const int x) { - const int multiplier = 2; - return x * multiplier; +try_output 0 "2 3" << EOF +int div(int a, int b) +{ + return a / b; } -int main() { - const int input = 9; - return get_value(input); + +int mod(int a, int b) +{ + return a % b; } -EOF -# Test 19: Const with pointer arithmetic -try_ 30 << EOF -int main() { - const int arr[] = {10, 20, 30, 40}; - const int *ptr = arr; - ptr = ptr + 2; - return *ptr; +int main() +{ + int a = div(4 + 5 + 6, 1 + 2 + 3); + int b = mod(4 + 5 + 6, 1 + 2 + 3); + printf("%d %d", a, b); + return 0; } EOF -# Test 20: Const with literal value -try_ 3 << EOF -int main() { - const int x = 3; - return x; +try_output 0 "-1422 -3094" << EOF +int div(int a, int b) +{ + return a / b; } -EOF - -# Category: Ternary Operator -begin_category "Ternary Operator" "Testing conditional ?: operator" -# conditional operator -expr 10 "1 ? 10 : 5" -expr 25 "0 ? 10 : 25" +int mod(int a, int b) +{ + return a % b; +} -# Category: Compound Assignment -begin_category "Compound Assignment" "Testing +=, -=, *=, /=, %=, <<=, >>=, ^= operators" +int main() +{ + int a = div(-4449688, 3127); + int b = mod(-4449688, 3127); + printf("%d %d", a, b); + return 0; +} +EOF -# compound assignemnt -items 5 "int a; a = 2; a += 3; return a;" -items 5 "int a; a = 10; a -= 5; return a;" -items 4 "int a; a = 2; a *= 2; return a;" -items 33 "int a; a = 100; a /= 3; return a;" -items 1 "int a; a = 100; a %= 3; return a;" -items 4 "int a; a = 2; a <<= 1; return a;" -items 2 "int a; a = 4; a >>= 1; return a;" -items 1 "int a; a = 1; a ^= 0; return a;" -items 20 "int *p; int a[3]; a[0] = 10; a[1] = 20; a[2] = 30; p = a; p+=1; return p[0];" -items 8 "short s; s = 5; s += 3; return s;" -items 15 "short s; s = 20; s -= 5; return s;" -items 24 "short s; s = 6; s *= 4; return s;" +try_output 0 "-2267573 102" << EOF +int div(int a, int b) +{ + return a / b; +} -# Category: Sizeof Operator -begin_category "Sizeof Operator" "Testing sizeof operator on various types" +int mod(int a, int b) +{ + return a % b; +} -# sizeof -expr 0 "sizeof(void)" -expr 1 "sizeof(_Bool)" -expr 1 "sizeof(char)" -expr 2 "sizeof(short)" -expr 4 "sizeof(int)" -# sizeof pointers -expr $PTR_SZ "sizeof(void*)" -expr $PTR_SZ "sizeof(_Bool*)" -expr $PTR_SZ "sizeof(char*)" -expr $PTR_SZ "sizeof(short*)" -expr $PTR_SZ "sizeof(int*)" -# sizeof multi-level pointer -expr $PTR_SZ "sizeof(void**)" -expr $PTR_SZ "sizeof(_Bool**)" -expr $PTR_SZ "sizeof(char**)" -expr $PTR_SZ "sizeof(short**)" -expr $PTR_SZ "sizeof(int**)" -# sizeof struct -try_ $PTR_SZ << EOF -typedef struct { - int a; - int b; -} struct_t; -int main() { return sizeof(struct_t*); } +int main() +{ + int a = div(333333333, -147); + int b = mod(333333333, -147); + printf("%d %d", a, b); + return 0; +} EOF -try_ 6 << EOF -typedef struct { - int x; - short y; -} struct_t; +try_output 0 "104643 -134" << EOF +int div(int a, int b) +{ + return a / b; +} -int main() { return sizeof(struct_t); } -EOF +int mod(int a, int b) +{ + return a % b; +} -# sizeof enum -try_ $PTR_SZ << EOF -typedef enum { - A, - B -} enum_t; -int main() { return sizeof(enum_t*); } +int main() +{ + int a = div(-104747777, -1001); + int b = mod(-104747777, -1001); + printf("%d %d", a, b); + return 0; +} EOF -# sizeof with expressions -items 4 "int x = 42; return sizeof(x);" -items 4 "int arr[5]; return sizeof(arr[0]);" -items 4 "int x = 10; int *ptr = &x; return sizeof(*ptr);" -items 1 "char c = 'A'; return sizeof(c);" -items 2 "short s = 100; return sizeof(s);" -items 4 "int a = 1, b = 2; return sizeof(a + b);" - -# sizeof with complex expressions -try_ 4 << EOF -int main() { - int arr[10]; - int i = 5; - return sizeof(arr[i]); +# _Bool size should be equivalent to char, which is 1 byte +try_output 0 "1" << EOF +int main() +{ + printf("%d", sizeof(bool)); + return 0; } EOF -try_ 4 << EOF -int main() { - int x = 100; - int *p = &x; - int **pp = &p; - return sizeof(**pp); +# Logical-and +try_output 0 "1 0 0 0" << EOF +int main() +{ + int a = 7, b = -15; + int res = a && b; + printf("%d ", res); + a = 0; + res = a && b; + printf("%d ", res); + a = -79; + b = 0; + res = a && b; + printf("%d ", res); + a = 0; + b = 0; + res = a && b; + printf("%d", res); + return 0; } EOF -try_ 4 << EOF -int main() { - int values[3]; - values[1] = 2; - values[2] = 3; - return sizeof(values[1] + values[2]); +# Logical-and, if statement +try_output 0 "6" << EOF +int main() +{ + int a = 4, b = 10; + if (a && b) + printf("%d", b - a); + return 0; } EOF -# sizeof with function calls -try_ 4 << EOF -int get_value() { return 42; } -int main() { - return sizeof(get_value()); +# Logical-and, for loop condition +try_output 0 "10" << EOF +int main() +{ + int a = 0; + for (int i = 0; i < 10 && a < 10; i++) { + a += 2; + } + printf("%d", a); + return 0; } EOF -# sizeof with ternary expressions -try_ 4 << EOF -int main() { - int a = 5, b = 10; - return sizeof(a > b ? a : b); +# Logical-and, while loop condition expression +try_output 0 "5" << EOF +int main() +{ + int a = 10, b = 1; + while (a > 5 && b){ + a--; + } + printf("%d", a); + return 0; } EOF -# Category: Switch Statements -begin_category "Switch Statements" "Testing switch-case control flow" - -# switch-case -items 10 "int a; a = 0; switch (3) { case 0: return 2; case 3: a = 10; break; case 1: return 0; } return a;" -items 10 "int a; a = 0; switch (3) { case 0: return 2; default: a = 10; break; } return a;" - -# Category: Enumerations -begin_category "Enumerations" "Testing enum declarations and usage" - -# enum -try_ 6 << EOF -typedef enum { enum1 = 5, enum2 } enum_t; -int main() { enum_t v = enum2; return v; } +# Logical-and, do-while loop condition expression +try_output 0 "10" << EOF +int main() +{ + int a = 1, b = 5; + do { + a++; + } while(a < 10 && b == 5); + printf("%d", a); + return 0; +} EOF -# Category: Memory Management -begin_category "Memory Management" "Testing malloc, free, and dynamic memory allocation" - -if [ "$LINK_MODE" = "static" ]; then - # malloc and free - try_ 1 << EOF +# Logical-and, Left-to-right evaluation +try_output 0 "x > 0, 1" << EOF +int func(int x) +{ + if (x > 0) { + printf("x > 0, "); + } + return x; +} int main() { - /* change test bench if different scheme apply */ - int *a = malloc(sizeof(int) * 5); - free(a); - if (a == NULL) - abort(); - int *b = malloc(sizeof(int) * 3); - - /* "malloc" will reuse memory free'd by "free(a)" */ - return a == b; + int ret = 0; + ret = 1 && func(5); + if (ret) + printf("%d", ret); + ret = 0 && func(5); + if (ret) + printf("%d", ret); + return 0; } EOF -else - echo "Skip test cases because of using dynamic linking mode" -fi # "LINK_MODE" = "static" -try_ 1 << EOF +# global character initialization +try_ 198 << EOF +char ch1 = 'A'; +char ch2 = ('B'); +char ch3 = (('C')); int main() { - char *ptr = "hello"; - return (0 == strcmp(ptr, "hello")) == (!strcmp(ptr, "hello")); + return ch1 + ch2 + ch3; } EOF -# Category: Preprocessor Directives -begin_category "Preprocessor Directives" "Testing #define, #ifdef, #ifndef, #if, #elif, #else, #endif" +# global string initialization and modification +try_output 0 "Hello World!" << EOF +char *data = "Hello World!"; -# #ifdef...#else...#endif -try_ 0 << EOF -#define A 0 -#define B 200 -int main() +int main(void) { - int x; -#ifdef A - x = A; -#else - x = B; -#endif - return x; + printf(data); + return 0; } EOF -# #ifndef...#else...#endif -try_ 0 << EOF -#ifndef A -#define A 0 -#else -#define A 1 -#endif - -#ifndef A -#define B 1 -#else -#define B 0 -#endif +# global initialization with logical and equality operation +try_ 4 << EOF +int b1 = 1 && 1; +int b2 = 1 || 0; +int b3 = 1 == 1; +int b4 = 1 != 2; int main() { - return A + B; + return b1 + b2 + b3 + b4; } EOF -# include guard test, simulates inclusion of a file named defs.h and global.c -try_ 0 << EOF -/* #include "defs.h" */ -#ifndef DEFS_H -#define DEFS_H +# Logical-or: simplest case +expr 1 "41 || 20" -#define A 1 +# Logical-or: control flow -#endif -/* end if "defs.h" inclusion */ +ans="0 20 +0" -/* #include "global.c" */ -#ifndef GLOBAL_C -#define GLOBAL_C +try_output 0 "$ans" << EOF +int main() +{ + int a = 0; + int b = 20; -#define B 1 + if (a || b) + printf("%d %d\n", a, b); -/* [global.c] #include "defs.h" */ -#ifndef DEFS_H -#define DEFS_H + b = 0; -#define A 2 + if (a || b) + printf("%d %d\n", a, b); + else + printf("0\n"); -#endif -/* end if "defs.h" inclusion */ -#endif -/* end if "global.c" inclusion */ + return 0; +} +EOF + +# Logical-or: for loop +ans="a-- +a-- +b-- +b-- +b-- +b-- +b-- +b-- +b-- +b-- +0 0 45" +try_output 0 "$ans" << EOF int main() { - return A - B; + int a = 2, b = 8, c = 0; + for (int i = 0; a || b; i++) { + if (a) { + c += i; + a--; + printf("a--\n"); + continue; + } + if (b) { + c += i; + b--; + printf("b--\n"); + continue; + } + } + printf("%d %d %d\n", a, b, c); + + return 0; } EOF -# #if defined(...) ... #elif defined(...) ... #else ... #endif -try_ 0 << EOF -#define A 0 -#define B 0xDEAD +# Logical-or: while loop +ans="a -= 2 +a -= 2 +b -= 3 +b -= 3 +b -= 3 +-1 0 13" + +try_output 0 "$ans" << EOF int main() { - int x; -#if defined(A) - x = A; -#elif defined(B) - x = B; -#else - x = 0xCAFE; -#endif - return x; + int a = 3, b = 9, c = 0; + while (a > 0 || b > 0) { + if (a > 0) { + c += 2; + a -= 2; + printf("a -= 2\n"); + continue; + } + if (b > 0) { + c += 3; + b -= 3; + printf("b -= 3\n"); + continue; + } + } + printf("%d %d %d\n", a, b, c); + + return 0; } EOF -# #define ... #undef -try_output 0 "1" << EOF -#define A 1 -void log() -{ - printf("%d", A); -} -#undef A -#define A 0 +# Logical-or: do-while loop +ans="do: a -= 2 +do: a -= 2 +do: a -= 2 +do: b -= 5 +do: b -= 5 +do: b -= 5 +do: b -= 5 +-1 -4 -26" + +try_output 0 "$ans" << EOF int main() { - log(); - return A; + int a = 5, b = 16, c = 0; + do { + printf("do: "); + if (a > 0) { + c -= 2; + a -= 2; + printf("a -= 2\n"); + } else if (b > 0) { + c -= 5; + b -= 5; + printf("b -= 5\n"); + } + } while (a > 0 || b > 0); + printf("%d %d %d\n", a, b, c); + + return 0; } EOF -# An empty replacement list expands to nothing, in both macro shapes. Producing -# no tokens used to hand the caller a pointer into the dead frame that expanded -# them, which spliced the token list into a cycle the parser never left. -try_output 42 "" << EOF -#define EMPTY -#define NOTHING(x) -EMPTY int main(void) +# Logical-or: test the short-circuit principle +ans="10 > 0 +10 0 +20 > 0 +0 20 +get 0" + +try_output 0 "$ans" << EOF +int func(int x) { - NOTHING(1) - EMPTY return 42; + if (x > 0) + printf("%d > 0\n", x); + return x; } -EOF -try_output 0 "ab" << EOF -#define BLANK -#define JOIN(a, b) printf(a); BLANK printf(b); -int main(void) +int main() { - JOIN("a", "b") - return 0; -} -EOF + int a = 10, b = 0, c = 20, d = -100; + if (func(a) || func(b)) + printf("%d %d\n", a, b); -# format -try_output 0 "2147483647" << EOF -int main() { - printf("%d", 2147483647); - return 0; -} -EOF + if (func(b) || func(c)) + printf("%d %d\n", b, c); + + if (func(d + 100) || func(b)) + printf("%d %d\n", b, c); + else + printf("get 0\n"); -try_output 0 "-2147483648" << EOF -int main() { - printf("%d", -2147483648); - return 0; -} -EOF -try_output 0 "-2147483647" << EOF -int main() { - printf("%d", -2147483647); return 0; } EOF -try_output 0 "-214748364" << EOF -int main() { - printf("%d", -214748364); - return 0; -} -EOF +# Logical-or and logical-and: More complex use cases +ans="0 +1 +1 +1 +1 +1 +1 +1 +0 +1 +1 +1 +1 +1 +0 +1 +0 +func(10): 10 > 0 +func(20): 20 > 0 +func(0): 0 <= 0 +0 +func(10): 10 > 0 +0 +func(10): 10 > 0 +0 +func(0): 0 <= 0 +func(10): 10 > 0 +func(-100): -100 <= 0 +1 +func(0): 0 <= 0 +func(0): 0 <= 0 +0 +func(0): 0 <= 0 +func(0): 0 <= 0 +0 +func(0): 0 <= 0 +func(10): 10 > 0 +func(-100): -100 <= 0 +1 +func(10): 10 > 0 +func(-100): -100 <= 0 +1 +func(10): 10 > 0 +func(-100): -100 <= 0 +1" -try_output 0 " -214748364" << EOF -int main() { - printf("%11d", -214748364); - return 0; +try_output 0 "$ans" << EOF +int func(int x) +{ + if (x > 0) + printf("func(%d): %d > 0\n", x, x); + else + printf("func(%d): %d <= 0\n", x, x); + return x; } -EOF -try_output 0 " -214748364" << EOF -int main() { - printf("%16d", -214748364); - return 0; -} -EOF +int main() +{ + int a = 10, b = 20, c = 0, d = -100; + printf("%d\n", a && b && c && d); + printf("%d\n", a || b && c && d); + printf("%d\n", a && b || c && d); + printf("%d\n", a && b && c || d); + printf("%d\n", a || b || c && d); + printf("%d\n", a || b && c || d); + printf("%d\n", a && b || c || d); + printf("%d\n", a || b || c || d); -try_output 0 "$(printf '%97s123')" << EOF -int main() { - printf("%100d", 123); - return 0; -} -EOF + printf("%d\n", (a || b) && c && d); + printf("%d\n", a && (b || c) && d); + printf("%d\n", a && b && (c || d)); + printf("%d\n", (a || b || c) && d); + printf("%d\n", (a || b) && (c || d)); + printf("%d\n", a && (b || c || d)); + printf("%d\n", a * 0 && (b || c || d)); + printf("%d\n", a * 2 && (b || c || d)); + printf("%d\n", a && (b * 0 || c || d * 0)); -try_output 0 "%1" << EOF -int main() { - printf("%%%d", 1); - return 0; -} -EOF + printf("%d\n", func(a) && func(b) && func(c)); + printf("%d\n", func(a) - a && func(b) && func(c)); + printf("%d\n", func(a) - a && func(b) && func(c) + 1); + printf("%d\n", func(c) || func(a) && func(d)); + printf("%d\n", func(c) || func(c) && func(d)); + printf("%d\n", (func(c) || func(c)) && func(d + 100)); + printf("%d\n", func(c) || func(a) && func(d)); + printf("%d\n", func(a) && (func(d) || func(c))); + printf("%d\n", func(a) * 2 && (func(d) || func(c))); -try_output 0 "144" << EOF -int main() { - printf("%o", 100); return 0; } EOF -try_output 0 "0144" << EOF +if [ "$LINK_MODE" = "static" ]; then + + # printf family, including truncation and zero size input + try_output 11 "Hello World" << EOF int main() { - printf("%#o", 100); - return 0; + int written = printf("Hello World"); + return written; } EOF -try_output 0 "7f" << EOF -int main() { - printf("%x", 127); - return 0; + # tests printf returns EBADF (errno 9) when stdout is closed + try_output 1 "" << EOF +int main() +{ + __syscall(__syscall_close, 1); + int written = printf("Hello\n"); + return written == -9; } EOF -try_output 0 "0x7f" << EOF + try_output 11 "Hello World" << EOF int main() { - printf("%#x", 127); - return 0; + char buffer[50]; + int written = sprintf(buffer, "Hello World"); + printf("%s", buffer); + return written; } EOF -fmt_ans="0x0000000000000000000000ff00cde1 - 0xff00cde1 -000000000000000000000000ff00cde1 - ff00cde1 -0xff00cde1 -ff00cde1 -0x00ff00cde1 - 0xff00cde1 -0000ff00cde1 - ff00cde1 -00000000000000000000037700146741 - 037700146741 -00000000000000000000037700146741 - 37700146741 -037700146741 -37700146741 -037700146741 -037700146741 -037700146741 - 37700146741 --0000000000000000000000016724511 - -16724511 --16724511 --00016724511 - -16724511 -0x0000000000000000000000fffff204 - 0xfffff204 -000000000000000000000000fffff204 - fffff204 -0xfffff204 -fffff204 -0x00fffff204 - 0xfffff204 -0000fffff204 - fffff204 -00000000000000000000037777771004 - 037777771004 -00000000000000000000037777771004 - 37777771004 -037777771004 -37777771004 -037777771004 -037777771004 -037777771004 - 37777771004 --0000000000000000000000000003580 - -3580 --3580 --00000003580 - -3580 -0x00000000000000000000000001000c - 0x1000c -0000000000000000000000000001000c - 1000c -0x1000c -1000c -0x000001000c - 0x1000c -00000001000c - 1000c -00000000000000000000000000200014 - 0200014 -00000000000000000000000000200014 - 200014 -0200014 -200014 -000000200014 - 0200014 -000000200014 - 200014 -00000000000000000000000000065548 - 65548 -65548 -000000065548 - 65548 -00000000000000000000000000000000 - 0 -00000000000000000000000000000000 - 0 -0 -0 -000000000000 - 0 -000000000000 - 0 -00000000000000000000000000000000 - 0 -00000000000000000000000000000000 - 0 -0 -0 -000000000000 - 0 -000000000000 - 0 -00000000000000000000000000000000 - 0 -0 -000000000000 - 0" + try_output 16 "Hello World 1123" << EOF +int main() { + char buffer[50]; + int written = sprintf(buffer, "Hello %s %d", "World", 1123); + printf("%s", buffer); + return written; +} +EOF -try_output 0 "$fmt_ans" << EOF -void printf_conversion(int num) { - printf("%#032x\n%#32x\n%032x\n%32x\n%#x\n%x\n", num, num, num, num, num, num); - printf("%#012x\n%#12x\n%012x\n%12x\n", num, num, num, num); - printf("%#032o\n%#32o\n%032o\n%32o\n%#o\n%o\n", num, num, num, num, num, num); - printf("%#012o\n%#12o\n%012o\n%12o\n", num, num, num, num); - printf("%032d\n%32d\n%d\n", num, num, num); - printf("%012d\n%12d\n", num, num); + # The following cases validate the behavior and return value of snprintf(). + # + # This case is a normal case and outputs the complete string because the + # given buffer size is large enough. + try_output 16 "Hello World 1123" << EOF +int main() { + char buffer[50]; + int written = snprintf(buffer, 50, "Hello %s %d", "World", 1123); + printf("%s", buffer); + return written; } +EOF + # If n is zero, nothing is written. + # + # Thus, the output should be the string containing 19 characters for this + # test case. + try_output 11 "0000000000000000000" << EOF int main() { - int a = 0xFF00CDE1, b = 0xFFFFF204, c = 65548, d = 0; - printf_conversion(a); - printf_conversion(b); - printf_conversion(c); - printf_conversion(d); - return 0; + char buffer[20]; + for (int i = 0; i < 19; i++) + buffer[i] = '0'; + buffer[19] = 0; + int written = snprintf(buffer, 0, "Number: %d", -37); + printf("%s", buffer); + return written; } EOF -try_ 0 << EOF + # In this case, snprintf() only writes at most 10 bytes (including '\0'), + # but the return value is 11, which corresponds to the length of "Number: + # -37". + try_output 11 "Number: -" << EOF int main() { - return '\0'; + char buffer[10]; + for (int i = 0; i < 9; i++) + buffer[i] = '0'; + buffer[9] = 0; + int written = snprintf(buffer, 10, "Number: %d", -37); + printf("%s", buffer); + return written; } EOF -begin_category "Goto statements" "Testing goto and label statements" + try_output 14 " 4e 75 6d 62 65 72 3a 20 2d 0 30 30 30 30 30 30 30 30 30 0" << EOF +int main() +{ + char buffer[20]; + for (int i = 0; i < 19; i++) + buffer[i] = '0'; + buffer[19] = 0; -# label undeclaration -try_compile_error << EOF + int written = snprintf(buffer, 10, "Number: %06d", -35337); + + for (int i = 0; i < 20; i++) + printf(" %x", buffer[i]); + return written; +} +EOF + + # A complex test case for snprintf(). + ans="written = 24 +buffer = buf - 00000 +written = 13 +buffer = aaaa - 0 +written = 19 +buffer = aaaa - 000000777777 +written = 14 +buffer = aaaa - 000000777777 + 61 61 61 61 20 2d 20 30 30 30 30 30 30 37 37 37 37 37 37 0 30 30 30 30 30 30 30 30 30 0" + try_output 0 "$ans" << EOF int main() { - goto label; + char buffer[30]; + for (int i = 0; i < 29; i++) + buffer[i] = '0'; + buffer[29] = 0; + + int written = snprintf(buffer, 12, "%s - %018d", "buf", 35133127); + printf("written = %d\nbuffer = %s\n", written, buffer); + written = snprintf(buffer, 9, "%s - %#06x", "aaaa", 0xFF); + printf("written = %d\nbuffer = %s\n", written, buffer); + written = snprintf(buffer, 30, "%s - %#012o", "aaaa", 0777777); + printf("written = %d\nbuffer = %s\n", written, buffer); + written = snprintf(buffer, 0, "%s - %#05x", "bbbbb", 0xAAFF); + printf("written = %d\nbuffer = %s\n", written, buffer); + + for (int i = 0; i < 30; i++) + printf(" %x", buffer[i]); + printf("\n"); + return 0; } EOF -# label redefinition -try_compile_error << EOF + # test the return value when calling fputc(). + # + # Since the FILE data type is defined as an int in the built-in C library, + # and most of the functions such as fputc(), fgetc(), fclose() and fgets() + # directly treat the "stream" parameter (of type FILE *) as a file + # descriptor for performing input/output operations, the following test + # cases define "stdout" as descriptor 1 cast to FILE *, the standard output. + try_output 0 "awritten = a" << EOF +#define stdout ((FILE *) 1) int main() { - goto label; -label: -label: + int c = fputc('a', stdout); + printf("written = %c", c); + return 0; } EOF -# test label namespace -try_ 1 << EOF + try_output 1 "" << EOF +#define stdout ((FILE *) 1) int main() { - goto label; -label: - int label = 1; - return label; + __syscall(__syscall_close, 1); + int c = fputc('a', stdout); + return c == -1; } EOF +else + echo "Skip test cases because of using dynamic linking mode" +fi # "LINK_MODE" = "static" -try_ 0 << EOF -int main() { - int x = 0; - goto skip; - x = 1; -skip: - return x; /* Should return 0 */ +# The standard streams from lib/c.h, in both link modes. Under dynamic linking +# the host libc dereferences them, so a descriptor number would fault there. +try_output 0 "a1b" << EOF +int main() +{ + fputc('a', stdout); + fprintf(stdout, "%d", 1); + printf("b"); + fflush(stdout); + fprintf(stderr, ""); + return fflush(stderr) != 0 || stdin == stdout || stdout == stderr; } EOF -# Forward reference. Statements between a goto and its label are unreachable but -# perfectly legal, and gcc accepts this silently at -Wall -Wextra -pedantic. -# shecc used to abort on the unreachable "return 1;" -- this case asserted that -# abort as a compile error; it now asserts the correct result. -try_ 0 << EOF +# tests integer type conversion excerpted and modified from issue #166 +try_output 0 "a = -127, b = -78, c = -93, d = -44" << EOF int main() { - goto end; - return 1; -end: + char a = 0x11, b = 0x22, c = 0x33, d = 0x44; + a += 6000; + b += 400; + c -= 400; + d -= 6000; + printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); return 0; } EOF -# Simple loop -try_ 10 << EOF +try_output 0 "-1 -1" << EOF int main() { - int vars0; + char a = 0xFF; + int b = a; + printf("%d %d\n", a, b); + return 0; +} +EOF - vars0 = 0; -BB1: - if (!(vars0 < 10)) goto BB6; - vars0++; - goto BB1; -BB6: - return vars0; +# memcmp() orders bytes as unsigned char, so 0x80 sorts above 0x01. +try_ 0 << EOF +int main(void) +{ + char high[1] = {(char) 0x80}, low[1] = {1}; + return !(memcmp(high, low, 1) > 0 && memcmp(low, high, 1) < 0); } EOF -# Complex loop -ans="0 -0012345678910123456789201234567893012345678940123456789 -1 -0012345678910123456789201234567893012345678940123456789 -3 -0012345678910123456789201234567893012345678940123456789 -4 -0012345678910123456789201234567893012345678940123456789 -5 -0012345678910123456789201234567893012345678940123456789 -6 -0012345678910123456789201234567893012345678940123456789 -7 -0012345678910123456789201234567893012345678940123456789 -8 -0012345678910123456789201234567893012345678940123456789 -9 -0012345678910123456789201234567893012345678940123456789" +# Test memset() +ans=" 7d 7d 7d 7d 7d 7d 7d 7d 7d 7d 7d 00 00 00 00 00 + 00 00 00 00 00 00 7d 7d 7d 7d 7d 00 00 00 00 00 + 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a" try_output 0 "$ans" << EOF -int main() +void print_array(char *ptr, int sz) { - int vars0; - int vars1; - int vars2; - int vars3; - - vars0 = 0; -BB1: - if (!(vars0 < 10)) goto BB47; - if (vars0 == 2) goto BB45; - printf("%d\n", vars0); - vars1 = 0; -BB10: - if (!(vars1 < 10)) goto BB27; - if (vars1 == 5) goto BB27; - printf("%d", vars1); - vars2 = 0; -BB19: - if (!(vars2 < 10)) goto BB25; - printf("%d", vars2); - vars2++; - goto BB19; -BB25: - vars1++; - goto BB10; -BB27: + for (int i = 0; i < sz; i++) + printf(" %02x", ptr[i]); printf("\n"); - vars3 = 5; -BB29: - if (vars3 == 2) goto BB29; - if (vars3 == 3) goto BB45; - vars3--; - if (vars3 > 0) goto BB29; -BB45: - vars0++; - goto BB1; -BB47: +} + +int main(void) +{ + int sz = sizeof(char) * 16; + char *ptr = malloc(sz); + + if (ptr != memset(ptr, 0x7D, sizeof(char) * 11)) + exit(1); + print_array(ptr, sz); + if (ptr != memset(ptr, 0, sizeof(char) * 6)) + exit(1); + print_array(ptr, sz); + if (ptr != memset(ptr, 0x3A, sz)) + exit(1); + print_array(ptr, sz); + + free(ptr); return 0; } EOF -# Category: Built-in macros -begin_category "Built-in Macros" "Testing macros defined by standard, e.g. __LINE__" - -try_output 0 "3" << EOF -int main() +# memcpy() takes and returns void *, as memset() does, so a matching +# redeclaration and a void * function pointer both agree with the library. +try_ 0 << EOF +void *memcpy(void *dest, const void *src, int count); +struct pair { + int a, b; +}; +int main(void) { - printf("%d", __LINE__); + struct pair x = {3, 4}, y; + int src[2] = {5, 6}, dst[2]; + void *(*copy)(void *, const void *, int) = memcpy; + if (memcpy(&y, &x, sizeof(x)) != &y || y.b != 4) + return 1; + if (copy(dst, src, sizeof(src)) != dst || dst[1] != 6) + return 2; return 0; } EOF -try_ 1 << EOF -int main() +# The address of a library function, which under dynamic linking is its PLT +# entry: equal wherever it is taken, and callable from a local, a file-scope +# initializer and a struct or array member. The global read after each call +# needs the global base intact once the library returns. +try_ 0 << EOF +int (*global_len)(const char *) = strlen; +struct ops { + int (*len)(const char *); + void *(*set)(void *, int, int); +}; +struct ops global_ops = {strlen, memset}; +int (*global_table[2])(const char *) = {strlen, &strlen}; +int counter = 40; +int main(void) { - char *file_name = __FILE__; - return !strcmp(file_name + strlen(file_name) - 2, ".c"); -} -EOF - -# Category: Function-like Macros -begin_category "Function-like Macros" "Testing function-like macros and variadic macros" + int (*local_len)(const char *) = &strlen; + struct ops local_ops = {strlen, memset}; + int word = 0; -# stringification: '#' spells the argument as it was written -try_output 0 "hello world" << EOF -#define STR(x) #x -int main() -{ - printf("%s\n", STR(hello world)); + if (!local_len || &memset != &memset || local_len != global_len) + return 1; + if (global_ops.len != global_table[1] || local_ops.set != global_ops.set) + return 2; + if (local_len("abc") + counter != 43 || global_len("ab") + counter != 42) + return 3; + if (global_table[0]("abcd") != 4 || local_ops.len("a") + counter != 41) + return 4; + global_ops.set(&word, 1, 1); + if (word != 1 || counter != 40) + return 5; return 0; } EOF -# '#' does not expand its operand, but an extra level of macro does -try_output 0 "VER 3" << EOF -#define STR(x) #x -#define XSTR(x) STR(x) -#define VER 3 +try_output 0 "2748 6719 105884 0" << EOF int main() { - printf("%s %s\n", STR(VER), XSTR(VER)); + int a = 0XABC; + int b = 0X1a3f; + int c = 0XDEaD + 0xBeEF; + int d = 0X0; + printf("%d %d %d %d", a, b, c, d); return 0; } EOF -# a quote or backslash in the argument survives stringification -try_output 0 '["q\\b"]' << EOF -#define STR(x) #x +try_compile_error << EOF int main() { - printf("[%s]\n", STR("q\\\\b")); + int x = 0X; return 0; } EOF -# an empty argument stringifies to an empty string -try_output 0 "[]" << EOF -#define STR(x) #x +try_compile_error << EOF int main() { - printf("[%s]\n", STR()); + int x = 0XGHI; return 0; } EOF -# token pasting builds an identifier -try_ 11 << EOF -#define CAT(a, b) a##b -int foobar() -{ - return 11; -} -int main() -{ - return CAT(foo, bar)(); -} -EOF +# Binary literal tests (0b/0B prefix) Test basic binary literals +expr 0 "0b0" +expr 1 "0b1" +expr 2 "0b10" +expr 3 "0b11" +expr 4 "0b100" +expr 8 "0b1000" +expr 15 "0b1111" +expr 16 "0b10000" +expr 255 "0b11111111" -# pasting chains left to right, and works on numbers -try_ 123 << EOF -#define JOIN3(a, b, c) a##b##c -int main() -{ - return JOIN3(1, 2, 3); -} -EOF +# Test uppercase B prefix +expr 10 "0B1010" +expr 240 "0B11110000" -# pasting in an object-like macro, and pasting an operator -try_ 42 << EOF -#define PLUSEQ +##= -#define OBJ pre##fix -int prefix = 41; -int main() -{ - int x = 1; - x PLUSEQ prefix; - return x; -} -EOF +# Test binary literals in arithmetic expressions +expr 15 "0b1100 | 0b0011" +expr 0 "0b1100 & 0b0011" +expr 15 "0b1100 ^ 0b0011" +expr 24 "0b110 << 2" +expr 3 "0b1100 >> 2" -# an empty operand leaves the other side of '##' standing alone -try_ 3 << EOF -#define CAT(a, b) a##b -int main() -{ - return CAT(1, ) + CAT(, 2); -} -EOF +# Test binary literals in variables +items 10 "int a = 0b1010; return a;" +items 255 "int b = 0B11111111; return b;" +items 45 "int x = 0b101101; return x;" -# an omitted argument substitutes nothing rather than its own name -try_ 3 << EOF -#define TAIL(x, y) x y -int main() -{ - return TAIL(3, ); +# Test binary literals in complex expressions +items 54 "int a = 0b1111; int b = 0b0011; return (a + b) * 3;" +items 160 "int mask = 0b11110000; int value = 0b10101010; return value & mask;" + +# Test combination of different number bases +expr 45 "0b1111 + 0xF + 017" # 15 + 15 + 15 = 45 +expr 90 "0b110000 + 0x10 + 032" # 48 + 16 + 26 = 90 + +# Test binary literals in comparisons +expr 1 "0b1010 == 10" +expr 1 "0b11111111 == 255" +expr 0 "0b1000 != 8" +expr 1 "0b10000 > 0xF" +expr 1 "0B1111 < 020" # 15 < 16 (octal) + +# Test binary literals with large values +try_large 1023 << EOF +int test_function() { + return 0b1111111111; /* 10 bits set = 1023 */ } EOF -# a comma inside parentheses belongs to the argument, not the argument list -try_ 5 << EOF -#define ID(x) x -int add(int p, int q) -{ - return p + q; -} -int main() -{ - return ID(add(2, 3)); +try_large 65535 << EOF +int test_function() { + return 0b1111111111111111; /* 16 bits set = 65535 */ } EOF -# '#' outside a macro definition is not a directive and must be rejected +# Test invalid binary literal errors try_compile_error << EOF int main() { - int a = 1 # 2; - return a; + int x = 0b; /* No binary digits */ + return 0; } EOF -# '##' with nothing on its left is rejected try_compile_error << EOF -#define P(a) ##a int main() { - return P(1); + int x = 0b2; /* Invalid binary digit */ + return 0; } EOF -# a paste that does not form a single token is rejected try_compile_error << EOF -#define Q(a, b) a##b int main() { - int Q(x, +) = 1; + int x = 0B9; /* Invalid binary digit */ return 0; } EOF -# function-like macro -try_ 1 << EOF -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -int main() -{ - int x = 0, y = 1; - return MAX(x, y); +# New escape sequence tests (\a, \b, \v, \f) Test character literals with new +# escape sequences +try_ 7 << EOF +int main() { + char bell = '\a'; /* ASCII 7 - bell/alert */ + return bell; } EOF -try_ 7 << EOF -#define M(a, b) a + b -int main() -{ - return M(1, 2) * 3; +try_ 8 << EOF +int main() { + char backspace = '\b'; /* ASCII 8 - backspace */ + return backspace; } EOF -# function-like variadic macro -try_ 2 << EOF -#define M(m, n, ...) \ - do { \ - x = __VA_ARGS__; \ - } while (0) -int main() -{ - int x = 0; - M(0, 1, 2); - return x; +try_ 11 << EOF +int main() { + char vtab = '\v'; /* ASCII 11 - vertical tab */ + return vtab; } EOF -# macro parameter substitution works in expression contexts -try_ 15 << EOF -#define ADD_PARAMS(a, b) ((a) + (b)) -int main() -{ - int x = 5, y = 10; - return ADD_PARAMS(x, y); +try_ 12 << EOF +int main() { + char formfeed = '\f'; /* ASCII 12 - form feed */ + return formfeed; } EOF -# macro with assignment operators -try_ 18 << EOF -#define ASSIGN_MACRO(variable, val) \ - variable = variable + val + 10 -int main() -{ - int x = 5; - ASSIGN_MACRO(x, 3); - return x; +# Test all escape sequences together +try_output 0 "7 8 11 12" << EOF +int main() { + printf("%d %d %d %d", '\a', '\b', '\v', '\f'); + return 0; } EOF -try_ 27 << EOF -#define COMPOUND_ASSIGN(variable, val) \ - variable += val + 10 -int main() -{ - int y = 10; - COMPOUND_ASSIGN(y, 7); - return y; +# Test escape sequences in strings +try_ 65 << EOF +int main() { + char *str = "A\a\b\v\f"; + return str[0]; /* Should return 'A' = 65 */ +} +EOF + +try_ 7 << EOF +int main() { + char *str = "A\a\b\v\f"; + return str[1]; /* Should return '\a' = 7 */ } EOF -try_ 42 << EOF -#define SET_VAR(var, value) var = value -int main() -{ - int z = 0; - SET_VAR(z, 42); - return z; +try_ 8 << EOF +int main() { + char *str = "A\a\b\v\f"; + return str[2]; /* Should return '\b' = 8 */ } EOF -try_output 0 "Wrapper: Hello World!" << EOF -#define WRAPPER(...) \ - do { \ - printf("Wrapper: "); \ - printf(__VA_ARGS__); \ - } while (0) -int main() -{ - WRAPPER("%s", "Hello World!"); +# Test that existing escape sequences still work +try_output 0 "10 9 13 0" << EOF +int main() { + printf("%d %d %d %d", '\n', '\t', '\r', '\0'); return 0; } EOF -try_ 0 << EOF -#if 1 || 0 -#define A 0 -#elif 1 && 0 -#define A 1 -#else -#define A 2 -#endif -int main() -{ - return A; +# Test additional escape sequences (\?, \e, unknown escapes) +try_ 63 << EOF +int main() { + return '\?'; /* Should return 63 (ASCII '?') */ } EOF -# recursive macro expansion -try_ 4 << EOF -int A(int x) -{ - return 2; -} -#define A(x) x + B(x) -#define B(x) x + A(x) -int main() -{ - return A(1); +try_ 27 << EOF +int main() { + return '\e'; /* GNU extension: ESC character (ASCII 27) */ } EOF -# optimizers - -# common subexpression elimination (CSE) -try_ 1 << EOF -int i = 0; -void func() -{ - i = 1; -} -int main() -{ - char arr[2], t; - arr[0] = 0; - arr[1] = 1; - t = arr[i]; - func(); - t = arr[i]; - return t; +try_ 122 << EOF +int main() { + return '\z'; /* Unknown escape should return 'z' (ASCII 122) */ } EOF -# constant folding -try_ 20 << EOF -int main() -{ - int a = 2; /* constant assingment */ - int b = a; /* assignment via constant representation */ - int c = a + b; - int d = c + 8; /* mixed assigment */ - return a + b + c + d; /* chained assignment */ +# Test hexadecimal escape sequences +try_ 65 << EOF +int main() { + return '\x41'; /* Should return 65 (ASCII 'A') */ } EOF -# Variables can be declared within a for-loop iteration -try_ 120 << EOF -int main() -{ - int fac = 1; - for (int i = 1; i <= 5; i++) { - fac = fac * i; - } - return fac; +try_ 72 << EOF +int main() { + return '\x48'; /* Should return 72 (ASCII 'H') */ } EOF -# Multiplication for signed integers -try_output 0 "35 -35 -35 35" << EOF -int main() -{ - printf("%d %d %d %d\n", 5 * 7, 5 * (-7), (-5) * 7, (-5) * (-7)); - return 0; +# C99 hexadecimal escapes consume every following hex digit, and in a narrow +# literal the value must fit an unsigned char. A narrow literal joined to a wide +# one is wide, so its escape may exceed a byte. +try_ 1 << EOF +int main() { + char *s = "\\x0041"; + return s[0] == 'A' && s[1] == 0; } EOF - -try_output 0 "-212121 -535050 336105 666666666" << EOF -int main() -{ - printf("%d %d %d %d\n", (-333) * 637, 1450 * (-369), 37345 * 9, (-111111111) * (-6)); - return 0; -} +try_compile_error_message "Hexadecimal escape sequence out of range" << 'EOF' +int main(void) { char *s = "\x100"; return s[0]; } EOF - -try_output 0 "1073676289 -131071 30" << EOF -int main() -{ - printf("%d %d %d\n", 32767 * 32767, 65535 * 65535, 54 * 5 * 954437177); - return 0; +try_compile_error_message "Hexadecimal escape sequence out of range" << 'EOF' +char joined[] = "a" "\x123" "b"; +int main(void) { return joined[0]; } +EOF +try_ 0 << 'EOF' +int main(void) { + wchar_t *joined = "\x100" L"b"; + wchar_t *wide = L"\x1234"; + return joined[0] != 0x100 || joined[1] != 'b' || wide[0] != 0x1234; } EOF -try_output 0 "-2 6 24" << EOF -int main() -{ - printf("%d %d %d\n", (-1) * 2, (-1) * 2 * (-3), (-1) * 2 * (-3) * 4); - return 0; +# Test octal escape sequences +try_ 65 << EOF +int main() { + return '\101'; /* Should return 65 (octal 101 = ASCII 'A') */ } EOF -# Division and modulo for signed integers -try_output 0 "-1 -2" << EOF -int main() -{ - printf("%d %d", -6 / 4, -6 % 4); - return 0; +try_ 10 << EOF +int main() { + return '\12'; /* Should return 10 (octal 12 = newline) */ } EOF -try_output 0 "-3 1" << EOF -int main() -{ - printf("%d %d", 7 / -2, 7 % -2); - return 0; +try_ 8 << EOF +int main() { + return '\10'; /* Should return 8 (octal 10 = backspace) */ } EOF -try_output 0 "12 -1" << EOF -int main() -{ - printf("%d %d", -109 / -9, -109 % -9); - return 0; -} +# C99 permits at most three octal digits in one escape; the trailing 2 is a +# second character in this implementation-defined packed multicharacter value. +try_ 1 << EOF +int main(void) { return '\1012' == 0x4132; } EOF -# octal(155) = dec(109), expect same output with above test suite -try_output 0 "12 -1" << EOF -int main() -{ - printf("%d %d", -0155 / -9, -0155 % -9); +# Test hex escapes in strings +try_output 0 "Hello World" << EOF +int main() { + char *s = "\x48\x65\x6C\x6C\x6F \x57\x6F\x72\x6C\x64"; + printf("%s", s); return 0; } EOF -try_output 0 "1365 0" << EOF -int main() -{ - printf("%d %d", 1365 / 1, 1365 % 1); +# Test octal escapes in strings +try_output 0 "ABC" << EOF +int main() { + char *s = "\101\102\103"; + printf("%s", s); return 0; } EOF -try_output 0 "-126322567 -8" << EOF -int main() -{ - printf("%d %d", -2147483647 / 17, -2147483647 % 17); +# Test escape sequences in printf Note: The bell character (\a) is non-printable +# but present in output +try_output 0 "$(printf 'Bell: \a Tab:\t Newline:\n')" << EOF +int main() { + printf("Bell: %c Tab:%c Newline:%c", '\a', '\t', '\n'); return 0; } EOF -try_output 0 "-1 -1" << EOF -int main() -{ - printf("%d %d", -2147483648 / 2147483647, -2147483648 % 2147483647); +# Test adjacent string literal concatenation +try_output 0 "Hello World" << EOF +int main() { + char *s = "Hello " "World"; + printf("%s", s); return 0; } EOF -try_output 0 "-2147483648 0" << EOF -int main() -{ - printf("%d %d", -2147483648 / 1, -2147483648 % 1); +try_output 0 "Testing string concatenation" << EOF +int main() { + char *s = "Testing " "string " "concatenation"; + printf("%s", s); return 0; } EOF -try_output 0 "-134217728 0" << EOF -int main() -{ - printf("%d %d", -2147483648 / 16, -2147483648 % 16); +try_output 0 "Multiple adjacent strings work!" << EOF +int main() { + char *s = "Multiple " "adjacent " "strings " "work!"; + printf("%s", s); return 0; } EOF -try_output 0 "134217728 0" << EOF -int main() -{ - printf("%d %d", -2147483648 / -16, -2147483648 % -16); - return 0; +# A joined literal may be longer than any single token. +try_ 2 << EOF +char joined_array[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; +int main(void) { + const char *joined = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + return (sizeof(joined_array) == 601) + (strlen(joined) == 600); } EOF -try_output 0 "1 0" << EOF -int main() -{ - printf("%d %d", -2147483648 / -2147483648, -2147483648 % -2147483648); - return 0; +# An escaped backslash does not escape the quote that follows it. +try_ 0 << 'EOF' +char trailing[] = "\\"; +int main(void) { + char *pair = "a\\\\" "b\\"; + const char *quote = "\\\""; + return (sizeof trailing != 2 || trailing[0] != '\\') + + (pair[1] != '\\' || pair[2] != '\\' || pair[3] != 'b' || + pair[4] != '\\' || pair[5] != 0) * + 2 + + (quote[0] != '\\' || quote[1] != '"' || quote[2] != 0) * 4; } EOF -try_output 0 "-8910720 -128" << EOF -int main() -{ - printf("%d %d", -2147483648 / 241, -2147483648 % 241); - return 0; +# Escapes end with their own literal: a digit that starts the next literal is a +# character of its own, not a further digit of a hexadecimal or short octal +# escape that ended the previous one. +try_ 0 << 'EOF' +char hex[] = "\x1" "2"; +char oct[] = "\1" "2"; +char oct2[] = "\12" "3"; +char full[] = "\123" "4"; +char escaped[] = "\\x1" "2"; +char chain[] = "\x1" "2" "3"; +int main(void) { + wchar_t *wide = L"\x1" L"f"; + char *mixed = "\7" "7" "\x2" "A"; + return (sizeof hex != 3 || hex[0] != 1 || hex[1] != '2') + + (sizeof oct != 3 || oct[0] != 1 || oct[1] != '2') * 2 + + (sizeof oct2 != 3 || oct2[0] != 10 || oct2[1] != '3') * 4 + + (sizeof full != 3 || full[0] != 0123 || full[1] != '4') * 8 + + (sizeof escaped != 5 || escaped[1] != 'x' || escaped[3] != '2') * + 16 + + (sizeof chain != 4 || chain[0] != 1 || chain[2] != '3') * 32 + + (wide[0] != 1 || wide[1] != 'f' || wide[2] != 0) * 64 + + (mixed[0] != 7 || mixed[1] != '7' || mixed[2] != 2 || + mixed[3] != 'A') * + 128; } EOF -try_output 0 "1" << EOF -int main() +# va_list and variadic function tests Note: These tests demonstrate both direct +# pointer arithmetic and va_list typedef forwarding between functions, now fully +# supported. + +# Test 1: Sum calculation using variadic arguments +try_output 0 "Sum: 15" << EOF +int calculate_sum(int count, ...) { - printf("%d", 6 / -2 / -3); - return 0; + int sum = 0; + int i; + int *p; + + p = &count; + p += $VS; + + for (i = 0; i < count; i++) + sum += p[i * $VS]; + + return sum; } -EOF -try_output 0 "0" << EOF int main() { - printf("%d", 477 / 37 % -3); + int result = calculate_sum(5, 1, 2, 3, 4, 5); + printf("Sum: %d", result); return 0; } EOF -try_output 0 "12" << EOF -int main() +# Test 2: Multiple integer arguments +try_output 0 "Multi: 10 20 255" << EOF +void multi_arg_test(int first, ...) { - printf("%d", 477 / (37 + 1 / -3)); - return 0; + int *p; + int val1, val2; + + /* Point to variadic arguments */ + p = &first; + p += $VS; + + /* Get integer values */ + val1 = p[0]; + val2 = p[1 * $VS]; + + printf("Multi: %d %d %d", first, val1, val2); } -EOF -try_output 0 "-39" << EOF int main() { - printf("%d", 477 / (37 / -3)); + multi_arg_test(10, 20, 255); return 0; } EOF -try_output 0 "2 3" << EOF -int div(int a, int b) +# Test 3: Variable argument count with different values +try_output 0 "Args: 1=100 2=200 3=300" << EOF +void print_args(int count, ...) { - return a / b; -} + int *p = &count; + int i; -int mod(int a, int b) -{ - return a % b; + p += $VS; + printf("Args:"); + for (i = 0; i < count; i++) + printf(" %d=%d", i + 1, p[i * $VS]); } int main() { - int a = div(4 + 5 + 6, 1 + 2 + 3); - int b = mod(4 + 5 + 6, 1 + 2 + 3); - printf("%d %d", a, b); + print_args(3, 100, 200, 300); return 0; } EOF -try_output 0 "-1422 -3094" << EOF -int div(int a, int b) +# Test 4: Mixed argument types (integers with different sizes) +try_output 0 "Values: 42 -17 0 999" << EOF +void mixed_args(int first, ...) { - return a / b; -} + int *p = &first; -int mod(int a, int b) -{ - return a % b; + printf("Values: %d", first); + p += $VS; + printf(" %d", *p); + p += $VS; + printf(" %d", *p); + p += $VS; + printf(" %d", *p); } int main() { - int a = div(-4449688, 3127); - int b = mod(-4449688, 3127); - printf("%d %d", a, b); + mixed_args(42, -17, 0, 999); return 0; } EOF -try_output 0 "-2267573 102" << EOF -int div(int a, int b) +# Test 5: Minimum and maximum finder +try_output 0 "Min: 5, Max: 50" << EOF +void find_min_max(int count, ...) { - return a / b; -} + int *p = &count; + int i, min, max; -int mod(int a, int b) -{ - return a % b; + p += $VS; + min = p[0]; + max = p[0]; + + for (i = 1; i < count; i++) { + if (p[i * $VS] < min) min = p[i * $VS]; + if (p[i * $VS] > max) max = p[i * $VS]; + } + + printf("Min: %d, Max: %d", min, max); } int main() { - int a = div(333333333, -147); - int b = mod(333333333, -147); - printf("%d %d", a, b); + find_min_max(4, 10, 50, 5, 25); return 0; } EOF -try_output 0 "104643 -134" << EOF -int div(int a, int b) -{ - return a / b; -} - -int mod(int a, int b) +# Test 6: Simple printf-like function +try_output 0 "ERROR: Failed with code 42" << EOF +void error_log(int code, ...) { - return a % b; + printf("ERROR: Failed with code %d", code); } int main() { - int a = div(-104747777, -1001); - int b = mod(-104747777, -1001); - printf("%d %d", a, b); + error_log(42); return 0; } EOF -# _Bool size should be equivalent to char, which is 1 byte -try_output 0 "1" << EOF -int main() +# Test 7: Function with single variadic argument +try_output 0 "Single extra: 123" << EOF +void single_extra(int base, ...) { - printf("%d", sizeof(bool)); - return 0; + int *p = &base; + p += $VS; + printf("Single extra: %d", *p); } -EOF -# Logical-and -try_output 0 "1 0 0 0" << EOF int main() { - int a = 7, b = -15; - int res = a && b; - printf("%d ", res); - a = 0; - res = a && b; - printf("%d ", res); - a = -79; - b = 0; - res = a && b; - printf("%d ", res); - a = 0; - b = 0; - res = a && b; - printf("%d", res); + single_extra(0, 123); return 0; } EOF -# Logical-and, if statement -try_output 0 "6" << EOF +# Test 8: Zero additional arguments +try_output 0 "Only required: 77" << EOF +void only_required(int value, ...) +{ + printf("Only required: %d", value); +} + int main() { - int a = 4, b = 10; - if (a && b) - printf("%d", b - a); + only_required(77); return 0; } EOF -# Logical-and, for loop condition -try_output 0 "10" << EOF -int main() +# Test 9: Arithmetic operations on variadic arguments +try_output 0 "Result: 25" << EOF +int arithmetic_va(int count, ...) { - int a = 0; - for (int i = 0; i < 10 && a < 10; i++) { - a += 2; + int *p = &count; + int result = 0; + int i; + + p += $VS; + for (i = 0; i < count; i++) { + if (i % 2 == 0) + result += p[i * $VS]; + else + result -= p[i * $VS]; } - printf("%d", a); - return 0; + return result; } -EOF -# Logical-and, while loop condition expression -try_output 0 "5" << EOF int main() { - int a = 10, b = 1; - while (a > 5 && b){ - a--; - } - printf("%d", a); + int res = arithmetic_va(4, 20, 5, 15, 5); /* 20 - 5 + 15 - 5 = 25 */ + printf("Result: %d", res); return 0; } EOF -# Logical-and, do-while loop condition expression -try_output 0 "10" << EOF +# Test 10: Simple working variadic function test +try_output 0 "Variadic: 60" << EOF +int sum_three(int a, ...) +{ + int *p = &a; + int v1 = p[0]; + int v2 = p[1 * $VS]; + int v3 = p[2 * $VS]; + return v1 + v2 + v3; +} + int main() { - int a = 1, b = 5; - do { - a++; - } while(a < 10 && b == 5); - printf("%d", a); + printf("Variadic: %d", sum_three(10, 20, 30)); return 0; } EOF -# Logical-and, Left-to-right evaluation -try_output 0 "x > 0, 1" << EOF -int func(int x) +# va_list typedef forwarding tests These tests demonstrate va_list typedef +# forwarding between functions + +# Test 11: Basic va_list typedef forwarding +try_output 0 "Test: 42" << EOF +typedef int *va_list; + +void print_with_va_list(va_list args) { - if (x > 0) { - printf("x > 0, "); - } - return x; + printf("Test: %d", args[0]); } + int main() { - int ret = 0; - ret = 1 && func(5); - if (ret) - printf("%d", ret); - ret = 0 && func(5); - if (ret) - printf("%d", ret); + int values[3]; + values[0] = 42; + values[1] = 100; + values[2] = 200; + va_list myargs = values; + print_with_va_list(myargs); return 0; } EOF -# global character initialization -try_ 198 << EOF -char ch1 = 'A'; -char ch2 = ('B'); -char ch3 = (('C')); +# Test 12: va_list array indexing +try_output 0 "args[0] = 42" << EOF +typedef int *va_list; + int main() { - return ch1 + ch2 + ch3; + int x = 42; + va_list args = &x; + printf("args[0] = %d", args[0]); + return 0; } EOF -# global string initialization and modification -try_output 0 "Hello World!" << EOF -char *data = "Hello World!"; - -int main(void) +# Test 13: Built-in va_list usage (from lib/c.c) +try_output 0 "Built-in: 777" << EOF +int main() { - printf(data); + int test_val = 777; + va_list args = &test_val; + printf("Built-in: %d", args[0]); return 0; } EOF -# global initialization with logical and equality operation -try_ 4 << EOF -int b1 = 1 && 1; -int b2 = 1 || 0; -int b3 = 1 == 1; -int b4 = 1 != 2; -int main() +# typedef pointer tests +try_ 42 << EOF +typedef int *int_ptr; + +int main(void) { - return b1 + b2 + b3 + b4; + int x = 42; + int_ptr p = &x; + return *p; } EOF -# Logical-or: simplest case -expr 1 "41 || 20" - -# Logical-or: control flow - -ans="0 20 -0" +try_output 0 "Hello" << EOF +typedef char *string; -try_output 0 "$ans" << EOF -int main() +int main(void) { - int a = 0; - int b = 20; - - if (a || b) - printf("%d %d\n", a, b); - - b = 0; - - if (a || b) - printf("%d %d\n", a, b); - else - printf("0\n"); - + char buf[] = "Hello"; + string str = buf; + printf("%s", str); return 0; } EOF -# Logical-or: for loop -ans="a-- -a-- -b-- -b-- -b-- -b-- -b-- -b-- -b-- -b-- -0 0 45" +try_output 0 "Pointer arithmetic: 10 20 30" << EOF +typedef int *int_ptr; -try_output 0 "$ans" << EOF -int main() +int main(void) { - int a = 2, b = 8, c = 0; - for (int i = 0; a || b; i++) { - if (a) { - c += i; - a--; - printf("a--\n"); - continue; - } - if (b) { - c += i; - b--; - printf("b--\n"); - continue; - } - } - printf("%d %d %d\n", a, b, c); + int a = 10, b = 20, c = 30; + int_ptr ptr = &a; + printf("Pointer arithmetic:"); + printf(" %d", *ptr); + ptr = &b; + printf(" %d", *ptr); + ptr = &c; + printf(" %d", *ptr); return 0; } EOF -# Logical-or: while loop -ans="a -= 2 -a -= 2 -b -= 3 -b -= 3 -b -= 3 --1 0 13" +try_output 0 "Value: 42" << EOF +typedef int *int_ptr; -try_output 0 "$ans" << EOF -int main() +int main(void) { - int a = 3, b = 9, c = 0; - while (a > 0 || b > 0) { - if (a > 0) { - c += 2; - a -= 2; - printf("a -= 2\n"); - continue; - } - if (b > 0) { - c += 3; - b -= 3; - printf("b -= 3\n"); - continue; - } - } - printf("%d %d %d\n", a, b, c); - + int value = 42; + int_ptr iptr = &value; + printf("Value: %d", *iptr); return 0; } EOF -# Logical-or: do-while loop -ans="do: a -= 2 -do: a -= 2 -do: a -= 2 -do: b -= 5 -do: b -= 5 -do: b -= 5 -do: b -= 5 --1 -4 -26" +# Complex pointer arithmetic tests Testing enhanced parser capability to handle +# expressions like *(ptr + offset) -try_output 0 "$ans" << EOF +# Test 1: Basic pointer arithmetic on RHS +try_output 0 "Values: 10 20 30" << EOF int main() { - int a = 5, b = 16, c = 0; - do { - printf("do: "); - if (a > 0) { - c -= 2; - a -= 2; - printf("a -= 2\n"); - } else if (b > 0) { - c -= 5; - b -= 5; - printf("b -= 5\n"); - } - } while (a > 0 || b > 0); - printf("%d %d %d\n", a, b, c); - + int arr[3]; + arr[0] = 10; + arr[1] = 20; + arr[2] = 30; + int *ptr = arr; + printf("Values: %d %d %d", *(ptr + 0), *(ptr + 1), *(ptr + 2)); return 0; } EOF -# Logical-or: test the short-circuit principle -ans="10 > 0 -10 0 -20 > 0 -0 20 -get 0" - -try_output 0 "$ans" << EOF -int func(int x) -{ - if (x > 0) - printf("%d > 0\n", x); - return x; -} - +# Test 2: Complex pointer arithmetic with variables on RHS +try_output 0 "Complex: 25 35 45" << EOF int main() { - int a = 10, b = 0, c = 20, d = -100; - if (func(a) || func(b)) - printf("%d %d\n", a, b); - - if (func(b) || func(c)) - printf("%d %d\n", b, c); - - if (func(d + 100) || func(b)) - printf("%d %d\n", b, c); - else - printf("get 0\n"); - - + int data[5]; + data[0] = 5; + data[1] = 15; + data[2] = 25; + data[3] = 35; + data[4] = 45; + int *p = data; + int offset = 2; + printf("Complex: %d %d %d", *(p + offset), *(p + offset + 1), *(p + (offset + 2))); return 0; } EOF -# Logical-or and logical-and: More complex use cases -ans="0 -1 -1 -1 -1 -1 -1 -1 -0 -1 -1 -1 -1 -1 -0 -1 -0 -func(10): 10 > 0 -func(20): 20 > 0 -func(0): 0 <= 0 -0 -func(10): 10 > 0 -0 -func(10): 10 > 0 -0 -func(0): 0 <= 0 -func(10): 10 > 0 -func(-100): -100 <= 0 -1 -func(0): 0 <= 0 -func(0): 0 <= 0 -0 -func(0): 0 <= 0 -func(0): 0 <= 0 -0 -func(0): 0 <= 0 -func(10): 10 > 0 -func(-100): -100 <= 0 -1 -func(10): 10 > 0 -func(-100): -100 <= 0 -1 -func(10): 10 > 0 -func(-100): -100 <= 0 -1" - -try_output 0 "$ans" << EOF -int func(int x) +# Test 3: Pointer arithmetic with negative offsets on RHS +try_output 0 "Negative: 30 20 10" << EOF +int main() { - if (x > 0) - printf("func(%d): %d > 0\n", x, x); - else - printf("func(%d): %d <= 0\n", x, x); - return x; + int values[3]; + values[0] = 10; + values[1] = 20; + values[2] = 30; + int *ptr = &values[2]; /* Point to last element */ + printf("Negative: %d %d %d", ptr[0], ptr[-1], ptr[-2]); + return 0; } +EOF +# Test 4: Multiple levels of pointer arithmetic on RHS +try_output 0 "Multi: 100 200 300" << EOF int main() { - int a = 10, b = 20, c = 0, d = -100; - printf("%d\n", a && b && c && d); - printf("%d\n", a || b && c && d); - printf("%d\n", a && b || c && d); - printf("%d\n", a && b && c || d); - printf("%d\n", a || b || c && d); - printf("%d\n", a || b && c || d); - printf("%d\n", a && b || c || d); - printf("%d\n", a || b || c || d); - - printf("%d\n", (a || b) && c && d); - printf("%d\n", a && (b || c) && d); - printf("%d\n", a && b && (c || d)); - printf("%d\n", (a || b || c) && d); - printf("%d\n", (a || b) && (c || d)); - printf("%d\n", a && (b || c || d)); - printf("%d\n", a * 0 && (b || c || d)); - printf("%d\n", a * 2 && (b || c || d)); - printf("%d\n", a && (b * 0 || c || d * 0)); - - printf("%d\n", func(a) && func(b) && func(c)); - printf("%d\n", func(a) - a && func(b) && func(c)); - printf("%d\n", func(a) - a && func(b) && func(c) + 1); - printf("%d\n", func(c) || func(a) && func(d)); - printf("%d\n", func(c) || func(c) && func(d)); - printf("%d\n", (func(c) || func(c)) && func(d + 100)); - printf("%d\n", func(c) || func(a) && func(d)); - printf("%d\n", func(a) && (func(d) || func(c))); - printf("%d\n", func(a) * 2 && (func(d) || func(c))); - + int matrix[3]; + matrix[0] = 100; + matrix[1] = 200; + matrix[2] = 300; + int *base = matrix; + int i = 1, j = 2; + printf("Multi: %d %d %d", *(base + 0), *(base + i), *(base + j)); return 0; } EOF -if [ "$LINK_MODE" = "static" ]; then - - # printf family, including truncation and zero size input - try_output 11 "Hello World" << EOF -int main() { - int written = printf("Hello World"); - return written; +# Test 5: Complex expressions in pointer arithmetic on RHS +try_output 0 "Expr: 42 84 126" << EOF +int main() +{ + int nums[6]; + nums[0] = 0; + nums[1] = 42; + nums[2] = 84; + nums[3] = 126; + nums[4] = 168; + nums[5] = 210; + int *p = nums; + int step = 1; + printf("Expr: %d %d %d", *(p + 1), *(p + 2), *(p + 3)); + return 0; } EOF - # tests printf returns EBADF (errno 9) when stdout is closed - try_output 1 "" << EOF +# Test 6: Pointer arithmetic on LHS for assignment +try_ 42 << EOF int main() { - __syscall(__syscall_close, 1); - int written = printf("Hello\n"); - return written == -9; + int arr[3]; + arr[0] = 0; + arr[1] = 0; + arr[2] = 0; + int *ptr = arr; + ptr[0] = 10; + ptr[1] = 20; + ptr[2] = 12; + return ptr[0] + ptr[1] + ptr[2]; } EOF - try_output 11 "Hello World" << EOF -int main() { - char buffer[50]; - int written = sprintf(buffer, "Hello World"); - printf("%s", buffer); - return written; +# Test 7: Complex LHS assignment with variables +try_output 0 "LHS: 5 15 25" << EOF +int main() +{ + int data[3]; + data[0] = 0; + data[1] = 0; + data[2] = 0; + int *p = data; + int offset = 1; + p[0] = 5; + p[offset] = 15; + p[offset + 1] = 25; + printf("LHS: %d %d %d", data[0], data[1], data[2]); + return 0; } EOF - try_output 16 "Hello World 1123" << EOF -int main() { - char buffer[50]; - int written = sprintf(buffer, "Hello %s %d", "World", 1123); - printf("%s", buffer); - return written; +# Test 8: LHS assignment with negative offsets +try_output 0 "Reverse: 10 20 30" << EOF +int main() +{ + int vals[3]; + vals[0] = 0; + vals[1] = 0; + vals[2] = 0; + int *ptr = &vals[2]; /* Point to last element */ + ptr[-2] = 10; + ptr[-1] = 20; + ptr[0] = 30; + printf("Reverse: %d %d %d", vals[0], vals[1], vals[2]); + return 0; } EOF - # The following cases validate the behavior and return value of snprintf(). - # - # This case is a normal case and outputs the complete string because the - # given buffer size is large enough. - try_output 16 "Hello World 1123" << EOF -int main() { - char buffer[50]; - int written = snprintf(buffer, 50, "Hello %s %d", "World", 1123); - printf("%s", buffer); - return written; +# Test 9: Multi-level pointer dereference with arithmetic +try_ 9 << EOF +int main() +{ + int value = 777; + int *ptr1 = &value; + int **ptr2 = &ptr1; + int ***ptr3 = &ptr2; + return ***(ptr3 + 0); } EOF - # If n is zero, nothing is written. - # - # Thus, the output should be the string containing 19 characters for this - # test case. - try_output 11 "0000000000000000000" << EOF -int main() { - char buffer[20]; - for (int i = 0; i < 19; i++) - buffer[i] = '0'; - buffer[19] = 0; - int written = snprintf(buffer, 0, "Number: %d", -37); - printf("%s", buffer); - return written; +# Test 10: Complex multi-level pointer arithmetic +try_output 0 "Complex multi: 100 200" << EOF +int main() +{ + int arr[2]; + arr[0] = 100; + arr[1] = 200; + int *ptrs[2]; + ptrs[0] = &arr[0]; + ptrs[1] = &arr[1]; + int **pptr = ptrs; + printf("Complex multi: %d %d", **(pptr + 0), **(pptr + 1)); + return 0; } EOF - # In this case, snprintf() only writes at most 10 bytes (including '\0'), - # but the return value is 11, which corresponds to the length of "Number: - # -37". - try_output 11 "Number: -" << EOF -int main() { - char buffer[10]; - for (int i = 0; i < 9; i++) - buffer[i] = '0'; - buffer[9] = 0; - int written = snprintf(buffer, 10, "Number: %d", -37); - printf("%s", buffer); - return written; +# Test 11: Mixed pointer arithmetic and array indexing +try_output 0 "Mixed: 11 22 33" << EOF +int main() +{ + int matrix[3]; + matrix[0] = 11; + matrix[1] = 22; + matrix[2] = 33; + int *p = matrix; + printf("Mixed: %d %d %d", p[0], *(p + 1), matrix[2]); + return 0; } EOF - try_output 14 " 4e 75 6d 62 65 72 3a 20 2d 0 30 30 30 30 30 30 30 30 30 0" << EOF +# Test 12: Pointer arithmetic in function calls +try_output 0 "Function: 45" << EOF +int get_value(int *ptr, int offset) +{ + return *(ptr + offset); +} + int main() { - char buffer[20]; - for (int i = 0; i < 19; i++) - buffer[i] = '0'; - buffer[19] = 0; + int data[3]; + data[0] = 15; + data[1] = 30; + data[2] = 45; + printf("Function: %d", get_value(data, 2)); + return 0; +} +EOF - int written = snprintf(buffer, 10, "Number: %06d", -35337); +# Test 13: Complex pointer arithmetic with structure members +try_output 0 "Struct: 10 20" << EOF +typedef struct { + int x; + int y; +} point_t; - for (int i = 0; i < 20; i++) - printf(" %x", buffer[i]); - return written; +int main() +{ + point_t points[2]; + points[0].x = 10; + points[0].y = 20; + points[1].x = 30; + points[1].y = 40; + point_t *p = points; + printf("Struct: %d %d", p->x, p->y); + return 0; } EOF - # A complex test case for snprintf(). - ans="written = 24 -buffer = buf - 00000 -written = 13 -buffer = aaaa - 0 -written = 19 -buffer = aaaa - 000000777777 -written = 14 -buffer = aaaa - 000000777777 - 61 61 61 61 20 2d 20 30 30 30 30 30 30 37 37 37 37 37 37 0 30 30 30 30 30 30 30 30 30 0" - try_output 0 "$ans" << EOF +# Test 14: Arithmetic with pointer dereferencing in expressions +try_output 0 "Arithmetic: 35" << EOF +int main() +{ + int nums[3]; + nums[0] = 10; + nums[1] = 15; + nums[2] = 20; + int *p = nums; + int result = *(p + 0) + *(p + 1) + *(p + 2) - 10; + printf("Arithmetic: %d", result); + return 0; +} +EOF + +# Test 15: Complex LHS with compound assignment operators +try_output 0 "Compound: 15 25 35" << EOF int main() { - char buffer[30]; - for (int i = 0; i < 29; i++) - buffer[i] = '0'; - buffer[29] = 0; - - int written = snprintf(buffer, 12, "%s - %018d", "buf", 35133127); - printf("written = %d\nbuffer = %s\n", written, buffer); - written = snprintf(buffer, 9, "%s - %#06x", "aaaa", 0xFF); - printf("written = %d\nbuffer = %s\n", written, buffer); - written = snprintf(buffer, 30, "%s - %#012o", "aaaa", 0777777); - printf("written = %d\nbuffer = %s\n", written, buffer); - written = snprintf(buffer, 0, "%s - %#05x", "bbbbb", 0xAAFF); - printf("written = %d\nbuffer = %s\n", written, buffer); - - for (int i = 0; i < 30; i++) - printf(" %x", buffer[i]); - printf("\n"); + int arr[3]; + arr[0] = 10; + arr[1] = 20; + arr[2] = 30; + int *ptr = arr; + ptr[0] += 5; + ptr[1] += 5; + ptr[2] += 5; + printf("Compound: %d %d %d", arr[0], arr[1], arr[2]); return 0; } EOF - # test the return value when calling fputc(). - # - # Since the FILE data type is defined as an int in the built-in C library, - # and most of the functions such as fputc(), fgetc(), fclose() and fgets() - # directly treat the "stream" parameter (of type FILE *) as a file - # descriptor for performing input/output operations, the following test - # cases define "stdout" as 1, which is the file descriptor for the standard - # output. - try_output 0 "awritten = a" << EOF -#define stdout 1 +# Test 16: Pointer arithmetic with character arrays +try_output 0 "Chars: ABC" << EOF int main() { - int c = fputc('a', stdout); - printf("written = %c", c); - return 0; + char str[4]; + str[0] = 'A'; + str[1] = 'B'; + str[2] = 'C'; + str[3] = '\0'; + char *p = str; + printf("Chars: %c%c%c", *(p + 0), *(p + 1), *(p + 2)); + return 0; } EOF - try_output 1 "" << EOF -#define stdout 1 +# Test 17: Complex nested pointer arithmetic +try_output 0 "Nested: 42" << EOF int main() { - __syscall(__syscall_close, 1); - int c = fputc('a', stdout); - return c == -1; + int data[5]; + data[0] = 0; + data[1] = 10; + data[2] = 20; + data[3] = 42; + data[4] = 50; + int *base = data; + int offset1 = 2, offset2 = 1; + printf("Nested: %d", *(base + offset1 + offset2)); + return 0; } EOF -else - echo "Skip test cases because of using dynamic linking mode" -fi # "LINK_MODE" = "static" -# tests integer type conversion excerpted and modified from issue #166 -try_output 0 "a = -127, b = -78, c = -93, d = -44" << EOF +# Test 18: Pointer arithmetic with conditional expressions +try_output 0 "Conditional: 100" << EOF int main() { - char a = 0x11, b = 0x22, c = 0x33, d = 0x44; - a += 6000; - b += 400; - c -= 400; - d -= 6000; - printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); + int vals[2]; + vals[0] = 50; + vals[1] = 100; + int *p = vals; + int flag = 1; + printf("Conditional: %d", *(p + (flag ? 1 : 0))); return 0; } EOF -try_output 0 "-1 -1" << EOF +# Test 19: Complex triple dereference with arithmetic +try_output 0 "Triple deref: 777" << EOF int main() { - char a = 0xFF; - int b = a; - printf("%d %d\n", a, b); + int value = 777; + int *ptr1 = &value; + int **ptr2 = &ptr1; + int ***ptr3 = &ptr2; + printf("Triple deref: %d", ***(ptr3 + 0)); return 0; } EOF -# Test memset() -ans=" 7d 7d 7d 7d 7d 7d 7d 7d 7d 7d 7d 00 00 00 00 00 - 00 00 00 00 00 00 7d 7d 7d 7d 7d 00 00 00 00 00 - 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a 3a" -try_output 0 "$ans" << EOF -void print_array(char *ptr, int sz) +# Test 20: Complex double dereference with arithmetic +try_output 0 "Double deref: 888" << EOF +int main() { - for (int i = 0; i < sz; i++) - printf(" %02x", ptr[i]); - printf("\n"); + int value = 888; + int *ptr1 = &value; + int **ptr2 = &ptr1; + printf("Double deref: %d", **(ptr2 + 0)); + return 0; } +EOF -int main(void) +# Test 21: Complex nested parentheses with multiple dereference +try_output 0 "Nested parens: 999" << EOF +int main() { - int sz = sizeof(char) * 16; - char *ptr = malloc(sz); - - if (ptr != memset(ptr, 0x7D, sizeof(char) * 11)) - exit(1); - print_array(ptr, sz); - if (ptr != memset(ptr, 0, sizeof(char) * 6)) - exit(1); - print_array(ptr, sz); - if (ptr != memset(ptr, 0x3A, sz)) - exit(1); - print_array(ptr, sz); - - free(ptr); + int value = 999; + int *ptr1 = &value; + int **ptr2 = &ptr1; + int ***ptr3 = &ptr2; + printf("Nested parens: %d", ***((ptr3 + 0))); return 0; } EOF -try_output 0 "2748 6719 105884 0" << EOF +# Test 22: Variable offset in complex dereference +try_output 0 "Variable offset: 555" << EOF int main() { - int a = 0XABC; - int b = 0X1a3f; - int c = 0XDEaD + 0xBeEF; - int d = 0X0; - printf("%d %d %d %d", a, b, c, d); + int value = 555; + int *ptr1 = &value; + int **ptr2 = &ptr1; + int ***ptr3 = &ptr2; + int offset = 0; + printf("Variable offset: %d", ***(ptr3 + offset)); return 0; } EOF -try_compile_error << EOF +# Test 23: Array of pointers with complex dereference +try_output 0 "Array ptr: 111 222 333" << EOF int main() { - int x = 0X; + int a = 111, b = 222, c = 333; + int *arr[3]; + arr[0] = &a; + arr[1] = &b; + arr[2] = &c; + int **parr = arr; + printf("Array ptr: %d %d %d", **(parr + 0), **(parr + 1), **(parr + 2)); return 0; } EOF -try_compile_error << EOF +# Test 24: Mixed single and multiple dereference +try_output 0 "Mixed: 666 666 666" << EOF int main() { - int x = 0XGHI; + int value = 666; + int *ptr1 = &value; + int **ptr2 = &ptr1; + printf("Mixed: %d %d %d", *ptr1, **ptr2, **(ptr2 + 0)); return 0; } EOF -# Binary literal tests (0b/0B prefix) Test basic binary literals -expr 0 "0b0" -expr 1 "0b1" -expr 2 "0b10" -expr 3 "0b11" -expr 4 "0b100" -expr 8 "0b1000" -expr 15 "0b1111" -expr 16 "0b10000" -expr 255 "0b11111111" +# Test compound literals: basic int/char and arrays +try_ 42 << EOF +int main() { + /* Basic int compound literal */ + return (int){42}; +} +EOF -# Test uppercase B prefix -expr 10 "0B1010" -expr 240 "0B11110000" +try_ 65 << EOF +int main() { + /* Basic char compound literal */ + return (char){65}; +} +EOF -# Test binary literals in arithmetic expressions -expr 15 "0b1100 | 0b0011" -expr 0 "0b1100 & 0b0011" -expr 15 "0b1100 ^ 0b0011" -expr 24 "0b110 << 2" -expr 3 "0b1100 >> 2" +try_ 25 << EOF +int main() { + /* Single element array compound literal */ + return (int[]){25}; +} +EOF -# Test binary literals in variables -items 10 "int a = 0b1010; return a;" -items 255 "int b = 0B11111111; return b;" -items 45 "int x = 0b101101; return x;" +try_ 10 << EOF +int main() { + /* Multi-element array compound literal - returns first element */ + return (int[]){10, 20, 30}; +} +EOF -# Test binary literals in complex expressions -items 54 "int a = 0b1111; int b = 0b0011; return (a + b) * 3;" -items 160 "int mask = 0b11110000; int value = 0b10101010; return value & mask;" +try_ 100 << EOF +int main() { + /* Array compound literal assignment */ + int x = (int[]){100, 200, 300}; + return x; +} +EOF -# Test combination of different number bases -expr 45 "0b1111 + 0xF + 017" # 15 + 15 + 15 = 45 -expr 90 "0b110000 + 0x10 + 032" # 48 + 16 + 26 = 90 +try_ 50 << EOF +int main() { + /* Char array compound literal */ + return (char[]){50, 60, 70}; +} +EOF -# Test binary literals in comparisons -expr 1 "0b1010 == 10" -expr 1 "0b11111111 == 255" -expr 0 "0b1000 != 8" -expr 1 "0b10000 > 0xF" -expr 1 "0B1111 < 020" # 15 < 16 (octal) +# Test compound literals: advanced features +try_ 35 << EOF +int main() { + /* Compound literals in arithmetic expressions */ + return (int){10} + (int){20} + (int[]){5, 15, 25}; +} +EOF -# Test binary literals with large values -try_large 1023 << EOF -int test_function() { - return 0b1111111111; /* 10 bits set = 1023 */ +try_ 42 << EOF +int add_values(int a, int b) { return a + b; } +int main() { + /* Compound literals as function arguments */ + return add_values((int){30}, (int){12}); } EOF -try_large 65535 << EOF -int test_function() { - return 0b1111111111111111; /* 16 bits set = 65535 */ +try_ 75 << EOF +int main() { + /* Multiple array compound literals */ + int a = (int[]){25, 35, 45}; + int b = (int[]){50, 60, 70}; + return a + b; /* 25 + 50 = 75 */ } EOF -# Test invalid binary literal errors -try_compile_error << EOF -int main() -{ - int x = 0b; /* No binary digits */ - return 0; +try_ 200 << EOF +int main(void) { + char *s = (char[]){'A', 'B', 'C', 'D', 'E'}; + return s[0] + s[1] + s[4]; /* 65 + 66 + 69 */ } EOF -try_compile_error << EOF -int main() -{ - int x = 0b2; /* Invalid binary digit */ - return 0; +try_ 6 << EOF +int main(void) { + short *s = (short[]){1, 2, 3, 4, 5}; + return s[0] + s[4]; } EOF -try_compile_error << EOF -int main() -{ - int x = 0B9; /* Invalid binary digit */ - return 0; +try_ 60 << EOF +int main(void) { + int arr[] = {10, 20, 30, 40, 50}; + int *selected = 1 ? arr : (int[]){1, 2, 3, 4, 5}; + return selected[0] + selected[4]; } EOF -# New escape sequence tests (\a, \b, \v, \f) Test character literals with new -# escape sequences -try_ 7 << EOF -int main() { - char bell = '\a'; /* ASCII 7 - bell/alert */ - return bell; +try_ 6 << EOF +int main(void) { + int arr[] = {10, 20, 30, 40, 50}; + int *selected = 0 ? arr : (int[]){1, 2, 3, 4, 5}; + return selected[0] + selected[4]; } EOF -try_ 8 << EOF +try_ 120 << EOF int main() { - char backspace = '\b'; /* ASCII 8 - backspace */ - return backspace; + /* Complex expression with mixed compound literals */ + return (int){40} + (char){80} + (int[]){0, 0, 0}; /* 40 + 80 + 0 = 120 */ } EOF -try_ 11 << EOF +try_ 200 << EOF int main() { - char vtab = '\v'; /* ASCII 11 - vertical tab */ - return vtab; + /* Compound literal with larger numbers */ + return (int[]){200, 300, 400}; } EOF -try_ 12 << EOF +# Test compound literals: edge cases +try_ 0 << EOF int main() { - char formfeed = '\f'; /* ASCII 12 - form feed */ - return formfeed; + /* Empty compound literal */ + return (int){}; } EOF -# Test all escape sequences together -try_output 0 "7 8 11 12" << EOF +try_ 0 << EOF int main() { - printf("%d %d %d %d", '\a', '\b', '\v', '\f'); - return 0; + /* Empty array compound literal */ + return (int[]){}; } EOF -# Test escape sequences in strings -try_ 65 << EOF +try_ 90 << EOF int main() { - char *str = "A\a\b\v\f"; - return str[0]; /* Should return 'A' = 65 */ + /* Multiple compound literals in expression */ + return (int[]){30, 60} + (int[]){60, 30}; /* 30 + 60 = 90 */ } EOF -try_ 7 << EOF +try_ 255 << EOF int main() { - char *str = "A\a\b\v\f"; - return str[1]; /* Should return '\a' = 7 */ + /* Large char compound literal */ + return (char){255}; } EOF -try_ 8 << EOF +try_ 150 << EOF int main() { - char *str = "A\a\b\v\f"; - return str[2]; /* Should return '\b' = 8 */ + /* Mixed compound literal expressions */ + int a = (int){50}; + int b = (int[]){100, 200, 300}; + return a + b; /* 50 + 100 = 150 */ } EOF -# Test that existing escape sequences still work -try_output 0 "10 9 13 0" << EOF +# Array literal decay in initializer for pointer variable +try_ 0 << EOF +int main(void) { + int *p = (int[]){42, 43, 44}; + return *p == 42 ? 0 : 1; +} +EOF + +# Test pointer compound literals +try_ 0 << EOF +int main() +{ + /* Test NULL pointer compound literal */ + int *p = (int*){}; + return p ? 1 : 0; +} +EOF + +try_ 0 << EOF +int main() +{ + /* Test pointer compound literal with zero */ + int *p = (int*){0}; + return p ? 1 : 0; +} +EOF + +# Test char pointer compound literals +try_ 0 << EOF +int main() +{ + char *p = (char*){}; + return p ? 1 : 0; +} +EOF + +# Test typedef pointer compound literals +try_ 0 << EOF +typedef int* IntPtr; + +int main() +{ + IntPtr p = (IntPtr){0}; + return p ? 1 : 0; +} +EOF + +# Additional struct initialization tests from refine-parser Test: Local struct +# initialization (working with field-by-field assignment) +try_ 42 << EOF +typedef struct { + int x; + int y; +} point_t; + int main() { - printf("%d %d %d %d", '\n', '\t', '\r', '\0'); - return 0; + point_t p; + p.x = 10; + p.y = 32; + return p.x + p.y; /* Returns 42 */ } EOF -# Test additional escape sequences (\?, \e, unknown escapes) -try_ 63 << EOF +# Test: Simple array initialization +try_ 15 << EOF int main() { - return '\?'; /* Should return 63 (ASCII '?') */ + int nums[3]; + nums[0] = 1; + nums[1] = 5; + nums[2] = 9; + return nums[0] + nums[1] + nums[2]; /* Returns 15 */ } EOF -try_ 27 << EOF +# Test: Character array with integer values +try_ 24 << EOF int main() { - return '\e'; /* GNU extension: ESC character (ASCII 27) */ + char arr[3]; + arr[0] = 5; + arr[1] = 9; + arr[2] = 10; + return arr[0] + arr[1] + arr[2]; /* Returns 24 (5+9+10) */ } EOF -try_ 122 << EOF +# Test: Simple 3-element array +try_ 6 << EOF int main() { - return '\z'; /* Unknown escape should return 'z' (ASCII 122) */ + int arr[3]; + arr[0] = 1; + arr[1] = 2; + arr[2] = 3; + return arr[0] + arr[1] + arr[2]; /* Returns 6 (1+2+3) */ } EOF -# Test hexadecimal escape sequences -try_ 65 << EOF +# Test: Mixed scalar fields in struct +try_ 42 << EOF +typedef struct { + int scalar; + int x, y; +} mixed_t; + int main() { - return '\x41'; /* Should return 65 (ASCII 'A') */ + mixed_t m; + m.scalar = 0; + m.x = 10; + m.y = 32; + return m.x + m.y; /* Returns 42 */ } EOF -try_ 72 << EOF +# Union support tests Basic union declaration and field access +try_ 42 << EOF +typedef union { + int i; + char c; +} basic_union_t; + int main() { - return '\x48'; /* Should return 72 (ASCII 'H') */ + basic_union_t u; + u.i = 42; + return u.i; /* Returns 42 */ } EOF -# Test octal escape sequences +# Union field access - different types sharing same memory try_ 65 << EOF +typedef union { + int i; + char c; +} char_int_union_t; + int main() { - return '\101'; /* Should return 65 (octal 101 = ASCII 'A') */ + char_int_union_t u; + u.c = 65; /* ASCII 'A' */ + return u.c; /* Returns 65 */ } EOF -try_ 10 << EOF +# Union with multiple integer fields +try_ 100 << EOF +typedef union { + int value; + int number; + int data; +} multi_int_union_t; + int main() { - return '\12'; /* Should return 10 (octal 12 = newline) */ + multi_int_union_t u; + u.value = 100; + return u.number; /* Returns 100 - same memory location */ } EOF -try_ 8 << EOF +# Union size calculation - should be size of largest member +try_ 4 << EOF +typedef union { + int i; /* 4 bytes */ + char c; /* 1 byte */ +} size_union_t; + int main() { - return '\10'; /* Should return 8 (octal 10 = backspace) */ + return sizeof(size_union_t); /* Returns 4 (size of int) */ } EOF -# Test hex escapes in strings -try_output 0 "Hello World" << EOF +try_ 2 << EOF +typedef union { + short s; /* 2 bytes */ + char c; /* 1 byte */ +} size_union_t; + int main() { - char *s = "\x48\x65\x6C\x6C\x6F \x57\x6F\x72\x6C\x64"; - printf("%s", s); - return 0; + return sizeof(size_union_t); /* Returns 2 (size of short) */ } EOF -# Test octal escapes in strings -try_output 0 "ABC" << EOF -int main() { - char *s = "\101\102\103"; - printf("%s", s); - return 0; -} +# Union extent is padded to the strictest member alignment, even when its +# largest member is an odd-sized character array. +try_ 8 << EOF +typedef union { + char bytes[5]; + int value; +} padded_union_t; +int main(void) { return sizeof(padded_union_t); } EOF -# Test escape sequences in printf Note: The bell character (\a) is non-printable -# but present in output -try_output 0 "$(printf 'Bell: \a Tab:\t Newline:\n')" << EOF +# Union with different data types +try_output 0 "Value as int: 1094795585, as char: 65" << EOF +typedef union { + int i; + char c; +} data_union_t; + int main() { - printf("Bell: %c Tab:%c Newline:%c", '\a', '\t', '\n'); + data_union_t u; + u.i = 1094795585; /* 0x41414141 in hex - four 'A' characters */ + printf("Value as int: %d, as char: %d", u.i, u.c); return 0; } EOF -# Test adjacent string literal concatenation -try_output 0 "Hello World" << EOF +# Nested union in struct +try_ 50 << EOF +typedef union { + int value; + char byte; +} nested_union_t; + +typedef struct { + int id; + nested_union_t data; +} container_t; + int main() { - char *s = "Hello " "World"; - printf("%s", s); - return 0; + container_t c; + c.id = 10; + c.data.value = 40; + return c.id + c.data.value; /* Returns 50 */ } EOF -try_output 0 "Testing string concatenation" << EOF +# Array of unions +try_ 30 << EOF +typedef union { + int i; + char c; +} array_union_t; + int main() { - char *s = "Testing " "string " "concatenation"; - printf("%s", s); - return 0; + array_union_t arr[3]; + arr[0].i = 10; + arr[1].i = 20; + arr[2].i = 0; /* Will be overridden */ + arr[2].c = 0; /* Sets to 0 */ + return arr[0].i + arr[1].i + arr[2].i; /* Returns 30 */ } EOF -try_output 0 "Multiple adjacent strings work!" << EOF +# Union with pointer fields +try_ 42 << EOF +typedef union { + int *int_ptr; + char *char_ptr; +} ptr_union_t; + int main() { - char *s = "Multiple " "adjacent " "strings " "work!"; - printf("%s", s); - return 0; + int value = 42; + ptr_union_t u; + u.int_ptr = &value; + return *(u.int_ptr); /* Returns 42 */ } EOF -# va_list and variadic function tests Note: These tests demonstrate both direct -# pointer arithmetic and va_list typedef forwarding between functions, now fully -# supported. - -# Test 1: Sum calculation using variadic arguments -try_output 0 "Sum: 15" << EOF -int calculate_sum(int count, ...) -{ - int sum = 0; - int i; - int *p; - - p = &count; - p += $VS; +# Complex union with struct member +try_ 77 << EOF +typedef struct { + int x; + int y; +} point_t; - for (i = 0; i < count; i++) - sum += p[i * $VS]; +typedef union { + point_t pt; + int values[2]; +} point_union_t; - return sum; +int main() { + point_union_t u; + u.pt.x = 30; + u.pt.y = 47; + return u.values[0] + u.values[1]; /* Returns 77 (30+47) */ } +EOF -int main() -{ - int result = calculate_sum(5, 1, 2, 3, 4, 5); - printf("Sum: %d", result); +# Union assignment and memory sharing (endianness-neutral) +try_output 0 "Union works: 100" << EOF +typedef union { + int i; + char bytes[4]; +} byte_union_t; + +int main() { + byte_union_t u; + u.i = 100; + printf("Union works: %d", u.i); return 0; } EOF -# Test 2: Multiple integer arguments -try_output 0 "Multi: 10 20 255" << EOF -void multi_arg_test(int first, ...) -{ - int *p; - int val1, val2; - - /* Point to variadic arguments */ - p = &first; - p += $VS; - - /* Get integer values */ - val1 = p[0]; - val2 = p[1 * $VS]; +# Union with typedef pointer +try_ 99 << EOF +typedef int *int_ptr_t; - printf("Multi: %d %d %d", first, val1, val2); -} +typedef union { + int_ptr_t ptr; + int direct; +} typedef_ptr_union_t; -int main() -{ - multi_arg_test(10, 20, 255); - return 0; +int main() { + int value = 99; + typedef_ptr_union_t u; + u.ptr = &value; + return *(u.ptr); /* Returns 99 */ } EOF -# Test 3: Variable argument count with different values -try_output 0 "Args: 1=100 2=200 3=300" << EOF -void print_args(int count, ...) -{ - int *p = &count; - int i; - - p += $VS; - printf("Args:"); - for (i = 0; i < count; i++) - printf(" %d=%d", i + 1, p[i * $VS]); -} +# Union initialization with different members +try_ 25 << EOF +typedef union { + int integer; + char character; +} init_union_t; -int main() -{ - print_args(3, 100, 200, 300); - return 0; +int main() { + init_union_t u1, u2; + u1.integer = 25; + u2.character = 25; + return u1.integer; /* Returns 25 */ } EOF -# Test 4: Mixed argument types (integers with different sizes) -try_output 0 "Values: 42 -17 0 999" << EOF -void mixed_args(int first, ...) -{ - int *p = &first; +# Union with function pointers +try_ 15 << EOF +int add_func(int a, int b) { return a + b; } +int mult_func(int a, int b) { return a * b; } - printf("Values: %d", first); - p += $VS; - printf(" %d", *p); - p += $VS; - printf(" %d", *p); - p += $VS; - printf(" %d", *p); -} +typedef union { + int (*add_ptr)(int, int); + int (*mult_ptr)(int, int); +} func_union_t; -int main() -{ - mixed_args(42, -17, 0, 999); - return 0; +int main() { + func_union_t u; + u.add_ptr = add_func; + return u.add_ptr(7, 8); /* Returns 15 */ } EOF -# Test 5: Minimum and maximum finder -try_output 0 "Min: 5, Max: 50" << EOF -void find_min_max(int count, ...) -{ - int *p = &count; - int i, min, max; - - p += $VS; - min = p[0]; - max = p[0]; - - for (i = 1; i < count; i++) { - if (p[i * $VS] < min) min = p[i * $VS]; - if (p[i * $VS] > max) max = p[i * $VS]; - } +# Sizeof union with mixed types. The largest member is the pointer, so the union +# is one pointer wide: 4 on the 32-bit targets, 8 on LP64. +try_ $PTR_SZ << EOF +typedef union { + char c; + int i; + char *p; +} mixed_union_t; - printf("Min: %d, Max: %d", min, max); +int main() { + return sizeof(mixed_union_t); /* size of the largest member */ } +EOF -int main() -{ - find_min_max(4, 10, 50, 5, 25); - return 0; +# Union field modification +try_ 200 << EOF +typedef union { + int total; + int sum; +} modify_union_t; + +int main() { + modify_union_t u; + u.total = 100; + u.sum += 100; /* Modifies same memory location */ + return u.total; /* Returns 200 */ } EOF -# Test 6: Simple printf-like function -try_output 0 "ERROR: Failed with code 42" << EOF -void error_log(int code, ...) -{ - printf("ERROR: Failed with code %d", code); -} +# Named union inside struct +try_ 88 << EOF +typedef union { + int value; + char byte; +} inner_union_t; -int main() -{ - error_log(42); - return 0; +typedef struct { + int id; + inner_union_t data; +} named_union_container_t; + +int main() { + named_union_container_t c; + c.id = 8; + c.data.value = 80; + return c.id + c.data.value; /* Returns 88 */ } EOF -# Test 7: Function with single variadic argument -try_output 0 "Single extra: 123" << EOF -void single_extra(int base, ...) -{ - int *p = &base; - p += $VS; - printf("Single extra: %d", *p); -} +# Union with array members +try_ 15 << EOF +typedef union { + int array[3]; + char bytes[12]; +} array_union_t; -int main() -{ - single_extra(0, 123); - return 0; +int main() { + array_union_t u; + u.array[0] = 5; + u.array[1] = 10; + u.array[2] = 0; + return u.array[0] + u.array[1] + u.array[2]; /* Returns 15 */ } EOF -# Test 8: Zero additional arguments -try_output 0 "Only required: 77" << EOF -void only_required(int value, ...) -{ - printf("Only required: %d", value); -} +# Complex union with nested structures +try_ 33 << EOF +typedef struct { + int a; + int b; +} pair_t; -int main() -{ - only_required(77); - return 0; +typedef union { + pair_t pair; + int values[2]; + char bytes[8]; +} complex_union_t; + +int main() { + complex_union_t u; + u.pair.a = 11; + u.pair.b = 22; + return u.values[0] + u.values[1]; /* Returns 33 */ } EOF -# Test 9: Arithmetic operations on variadic arguments -try_output 0 "Result: 25" << EOF -int arithmetic_va(int count, ...) -{ - int *p = &count; - int result = 0; +# Union as function parameter +try_ 60 << EOF +typedef union { int i; + char c; +} param_union_t; - p += $VS; - for (i = 0; i < count; i++) { - if (i % 2 == 0) - result += p[i * $VS]; - else - result -= p[i * $VS]; - } - return result; +int process_union(param_union_t u) { + return u.i; } -int main() -{ - int res = arithmetic_va(4, 20, 5, 15, 5); /* 20 - 5 + 15 - 5 = 25 */ - printf("Result: %d", res); - return 0; +int main() { + param_union_t u; + u.i = 60; + return process_union(u); /* Returns 60 */ } EOF -# Test 10: Simple working variadic function test -try_output 0 "Variadic: 60" << EOF -int sum_three(int a, ...) -{ - int *p = &a; - int v1 = p[0]; - int v2 = p[1 * $VS]; - int v3 = p[2 * $VS]; - return v1 + v2 + v3; +# Union as return type +try_ 45 << EOF +typedef union { + int value; + char byte; +} return_union_t; + +return_union_t create_union(int val) { + return_union_t u; + u.value = val; + return u; } -int main() -{ - printf("Variadic: %d", sum_three(10, 20, 30)); - return 0; +int main() { + return_union_t result = create_union(45); + return result.value; /* Returns 45 */ } EOF -# va_list typedef forwarding tests These tests demonstrate va_list typedef -# forwarding between functions - -# Test 11: Basic va_list typedef forwarding -try_output 0 "Test: 42" << EOF -typedef int *va_list; +# Multiple union declarations +try_ 120 << EOF +typedef union { + int x; + char c; +} union1_t; -void print_with_va_list(va_list args) -{ - printf("Test: %d", args[0]); -} +typedef union { + int y; + char d; +} union2_t; -int main() -{ - int values[3]; - values[0] = 42; - values[1] = 100; - values[2] = 200; - va_list myargs = values; - print_with_va_list(myargs); - return 0; +int main() { + union1_t u1; + union2_t u2; + u1.x = 50; + u2.y = 70; + return u1.x + u2.y; /* Returns 120 */ } EOF -# Test 12: va_list array indexing -try_output 0 "args[0] = 42" << EOF -typedef int *va_list; +# Type Casting Tests +echo "Testing type casting functionality..." -int main() -{ - int x = 42; - va_list args = &x; - printf("args[0] = %d", args[0]); - return 0; -} -EOF +declare -a cast_tests=( + "42 int var; var = (int)42; return var;" + "10 int var; var = (short)10; return var;" + "5 short s; s = (short)5; return s;" + "20 short s; s = (int)20; return s;" + "15 short sa = 10; short sb = (short)5; return sa + sb;" + "30 int ia = 10; int ib = (int)20; return ia + ib;" +) -# Test 13: Built-in va_list usage (from lib/c.c) -try_output 0 "Built-in: 777" << EOF -int main() -{ - int test_val = 777; - va_list args = &test_val; - printf("Built-in: %d", args[0]); - return 0; +run_items_tests cast_tests + +# Basic int to char cast +try_ 65 << EOF +int main() { + int x = 65; + char c = (char)x; + return c; /* Returns 65 ('A') */ } EOF -# typedef pointer tests +# Char to int cast try_ 42 << EOF -typedef int *int_ptr; - -int main(void) -{ - int x = 42; - int_ptr p = &x; - return *p; +int main() { + char c = 42; + int x = (int)c; + return x; /* Returns 42 */ } EOF -try_output 0 "Hello" << EOF -typedef char *string; - -int main(void) -{ - char buf[] = "Hello"; - string str = buf; - printf("%s", str); - return 0; +# Cast in expressions +try_ 130 << EOF +int main() { + int a = 65; + int b = 65; + return (char)a + (char)b; /* Returns 130 */ } EOF -try_output 0 "Pointer arithmetic: 10 20 30" << EOF -typedef int *int_ptr; - -int main(void) -{ - int a = 10, b = 20, c = 30; - int_ptr ptr = &a; - - printf("Pointer arithmetic:"); - printf(" %d", *ptr); - ptr = &b; - printf(" %d", *ptr); - ptr = &c; - printf(" %d", *ptr); - return 0; +# Cast with arithmetic +try_ 42 << EOF +int main() { + int x = 50; + return (int)((char)(x - 8)); /* Returns 42 */ } EOF -try_output 0 "Value: 42" << EOF -typedef int *int_ptr; +# Multiple casts in sequence +try_ 100 << EOF +int main() { + int x = 100; + char c = (char)x; + int y = (int)c; + return y; /* Returns 100 */ +} +EOF -int main(void) -{ - int value = 42; - int_ptr iptr = &value; - printf("Value: %d", *iptr); - return 0; +# Cast with function parameters +try_ 88 << EOF +int test_func(char c) { + return (int)c; +} + +int main() { + int x = 88; + return test_func((char)x); /* Returns 88 */ } EOF -# Complex pointer arithmetic tests Testing enhanced parser capability to handle -# expressions like *(ptr + offset) +# Cast in return statement +try_ 123 << EOF +int get_char() { + int x = 123; + return (char)x; +} -# Test 1: Basic pointer arithmetic on RHS -try_output 0 "Values: 10 20 30" << EOF -int main() -{ - int arr[3]; - arr[0] = 10; - arr[1] = 20; - arr[2] = 30; - int *ptr = arr; - printf("Values: %d %d %d", *(ptr + 0), *(ptr + 1), *(ptr + 2)); - return 0; +int main() { + return get_char(); /* Returns 123 */ } EOF -# Test 2: Complex pointer arithmetic with variables on RHS -try_output 0 "Complex: 25 35 45" << EOF -int main() -{ - int data[5]; - data[0] = 5; - data[1] = 15; - data[2] = 25; - data[3] = 35; - data[4] = 45; - int *p = data; - int offset = 2; - printf("Complex: %d %d %d", *(p + offset), *(p + offset + 1), *(p + (offset + 2))); - return 0; +# Nested casts +try_ 200 << EOF +int main() { + int x = 200; + return (int)((char)((int)x)); /* Returns 200 */ } EOF -# Test 3: Pointer arithmetic with negative offsets on RHS -try_output 0 "Negative: 30 20 10" << EOF -int main() -{ - int values[3]; - values[0] = 10; - values[1] = 20; - values[2] = 30; - int *ptr = &values[2]; /* Point to last element */ - printf("Negative: %d %d %d", ptr[0], ptr[-1], ptr[-2]); - return 0; +# Cast with assignment +try_ 150 << EOF +int main() { + int x = 150; + char c; + c = (char)x; + return (int)c; /* Returns 150 */ } EOF -# Test 4: Multiple levels of pointer arithmetic on RHS -try_output 0 "Multi: 100 200 300" << EOF -int main() -{ - int matrix[3]; - matrix[0] = 100; - matrix[1] = 200; - matrix[2] = 300; - int *base = matrix; - int i = 1, j = 2; - printf("Multi: %d %d %d", *(base + 0), *(base + i), *(base + j)); +# String literal and escape coverage (additional) +try_output 0 "AZ" << 'EOF' +int main() { + printf("%s", "\x41Z"); /* hex escape then normal char */ return 0; } EOF -# Test 5: Complex expressions in pointer arithmetic on RHS -try_output 0 "Expr: 42 84 126" << EOF -int main() -{ - int nums[6]; - nums[0] = 0; - nums[1] = 42; - nums[2] = 84; - nums[3] = 126; - nums[4] = 168; - nums[5] = 210; - int *p = nums; - int step = 1; - printf("Expr: %d %d %d", *(p + 1), *(p + 2), *(p + 3)); +try_output 0 "AZ" << 'EOF' +int main() { + printf("%s", "A\132"); /* octal escape for 'Z' */ return 0; } EOF -# Test 6: Pointer arithmetic on LHS for assignment -try_ 42 << EOF -int main() -{ - int arr[3]; - arr[0] = 0; - arr[1] = 0; - arr[2] = 0; - int *ptr = arr; - ptr[0] = 10; - ptr[1] = 20; - ptr[2] = 12; - return ptr[0] + ptr[1] + ptr[2]; +# Cast zero value +try_ 0 << EOF +int main() { + int x = 0; + char c = (char)x; + return (int)c; /* Returns 0 */ } EOF -# Test 7: Complex LHS assignment with variables -try_output 0 "LHS: 5 15 25" << EOF -int main() -{ - int data[3]; - data[0] = 0; - data[1] = 0; - data[2] = 0; - int *p = data; - int offset = 1; - p[0] = 5; - p[offset] = 15; - p[offset + 1] = 25; - printf("LHS: %d %d %d", data[0], data[1], data[2]); - return 0; +# Local array initializers - verify compilation and correct values Test 1: +# Implicit size array with single element +try_ 1 << 'EOF' +int main() { + int a[] = {1}; + return a[0]; /* Should return 1 */ } EOF -# Test 8: LHS assignment with negative offsets -try_output 0 "Reverse: 10 20 30" << EOF -int main() -{ - int vals[3]; - vals[0] = 0; - vals[1] = 0; - vals[2] = 0; - int *ptr = &vals[2]; /* Point to last element */ - ptr[-2] = 10; - ptr[-1] = 20; - ptr[0] = 30; - printf("Reverse: %d %d %d", vals[0], vals[1], vals[2]); - return 0; +# Test 2: Explicit size array with single element +try_ 42 << 'EOF' +int main() { + int a[1] = {42}; + return a[0]; /* Should return 42 */ } EOF -# Test 9: Multi-level pointer dereference with arithmetic -try_ 9 << EOF -int main() -{ - int value = 777; - int *ptr1 = &value; - int **ptr2 = &ptr1; - int ***ptr3 = &ptr2; - return ***(ptr3 + 0); +# Test 3: Multiple elements - verify all are initialized +try_ 6 << 'EOF' +int main() { + int a[3] = {1, 2, 3}; + return a[0] + a[1] + a[2]; /* Should return 1+2+3=6 */ } EOF -# Test 10: Complex multi-level pointer arithmetic -try_output 0 "Complex multi: 100 200" << EOF -int main() -{ - int arr[2]; - arr[0] = 100; - arr[1] = 200; - int *ptrs[2]; - ptrs[0] = &arr[0]; - ptrs[1] = &arr[1]; - int **pptr = ptrs; - printf("Complex multi: %d %d", **(pptr + 0), **(pptr + 1)); - return 0; +# Test 4: Character array initialization +try_ 97 << 'EOF' +int main() { + char s[] = {'a', 'b', 'c'}; + return s[0]; /* Should return ASCII value of 'a' = 97 */ } EOF -# Test 11: Mixed pointer arithmetic and array indexing -try_output 0 "Mixed: 11 22 33" << EOF -int main() -{ - int matrix[3]; - matrix[0] = 11; - matrix[1] = 22; - matrix[2] = 33; - int *p = matrix; - printf("Mixed: %d %d %d", p[0], *(p + 1), matrix[2]); - return 0; +# Test 5: Empty initializer (all zeros) +try_ 0 << 'EOF' +int main() { + int a[5] = {}; + return a[0] + a[1] + a[2] + a[3] + a[4]; /* Should return 0 */ } EOF -# Test 12: Pointer arithmetic in function calls -try_output 0 "Function: 45" << EOF -int get_value(int *ptr, int offset) -{ - return *(ptr + offset); +# Test 6: Partial initialization (remaining should be zero) +try_ 15 << 'EOF' +int main() { + int a[5] = {5, 10}; + return a[0] + a[1] + a[2] + a[3] + a[4]; /* Should return 5+10+0+0+0=15 */ } +EOF -int main() -{ - int data[3]; - data[0] = 15; - data[1] = 30; - data[2] = 45; - printf("Function: %d", get_value(data, 2)); - return 0; +# Test 7: Pass initialized array to function +try_ 30 << 'EOF' +int sum(int *p, int n) { + int total = 0; + for (int i = 0; i < n; i++) + total += p[i]; + return total; +} +int main() { + int a[] = {5, 10, 15}; + return sum(a, 3); /* Should return 5+10+15=30 */ +} +EOF + +# Test 8: Nested scope with array initialization +try_ 100 << 'EOF' +int main() { + { + int values[] = {25, 25, 25, 25}; + return values[0] + values[1] + values[2] + values[3]; + } } EOF -# Test 13: Complex pointer arithmetic with structure members -try_output 0 "Struct: 10 20" << EOF -typedef struct { +# Struct Variable Declaration Tests (Bug Fix Validation) +echo "Testing struct variable declaration functionality..." + +# Test 1: Basic struct variable declaration (the original bug case) +try_ 0 << EOF +struct point { int x; int y; -} point_t; +}; -int main() -{ - point_t points[2]; - points[0].x = 10; - points[0].y = 20; - points[1].x = 30; - points[1].y = 40; - point_t *p = points; - printf("Struct: %d %d", p->x, p->y); +int main() { + struct point p; return 0; } EOF -# Test 14: Arithmetic with pointer dereferencing in expressions -try_output 0 "Arithmetic: 35" << EOF -int main() -{ - int nums[3]; - nums[0] = 10; - nums[1] = 15; - nums[2] = 20; - int *p = nums; - int result = *(p + 0) + *(p + 1) + *(p + 2) - 10; - printf("Arithmetic: %d", result); - return 0; -} -EOF +# Test 2: Struct variable declaration with initialization +try_ 42 << EOF +struct point { + int x; + int y; +}; -# Test 15: Complex LHS with compound assignment operators -try_output 0 "Compound: 15 25 35" << EOF -int main() -{ - int arr[3]; - arr[0] = 10; - arr[1] = 20; - arr[2] = 30; - int *ptr = arr; - ptr[0] += 5; - ptr[1] += 5; - ptr[2] += 5; - printf("Compound: %d %d %d", arr[0], arr[1], arr[2]); - return 0; +int main() { + struct point p; + p.x = 20; + p.y = 22; + return p.x + p.y; } EOF -# Test 16: Pointer arithmetic with character arrays -try_output 0 "Chars: ABC" << EOF -int main() -{ - char str[4]; - str[0] = 'A'; - str[1] = 'B'; - str[2] = 'C'; - str[3] = '\0'; - char *p = str; - printf("Chars: %c%c%c", *(p + 0), *(p + 1), *(p + 2)); - return 0; +# Test 3: Multiple struct variable declarations +try_ 20 << EOF +struct point { + int x; + int y; +}; + +int main() { + struct point p1; + struct point p2; + p1.x = 10; + p1.y = 15; + p2.x = 3; + p2.y = 2; + return p1.x + p1.y - p2.x - p2.y; } EOF -# Test 17: Complex nested pointer arithmetic -try_output 0 "Nested: 42" << EOF -int main() -{ - int data[5]; - data[0] = 0; - data[1] = 10; - data[2] = 20; - data[3] = 42; - data[4] = 50; - int *base = data; - int offset1 = 2, offset2 = 1; - printf("Nested: %d", *(base + offset1 + offset2)); - return 0; +# Test 4: Struct variable declaration in nested scope +try_ 100 << EOF +struct data { + int value; +}; + +int main() { + { + struct data d; + d.value = 100; + return d.value; + } } EOF -# Test 18: Pointer arithmetic with conditional expressions -try_output 0 "Conditional: 100" << EOF -int main() -{ - int vals[2]; - vals[0] = 50; - vals[1] = 100; - int *p = vals; - int flag = 1; - printf("Conditional: %d", *(p + (flag ? 1 : 0))); - return 0; +# Test 5: Struct with char fields +try_ 142 << EOF +struct character { + char first; + char second; +}; + +int main() { + struct character ch; + ch.first = 'A'; + ch.second = 'M'; + return ch.first + ch.second; /* 65 + 77 = 142 */ } EOF -# Test 19: Complex triple dereference with arithmetic -try_output 0 "Triple deref: 777" << EOF -int main() -{ - int value = 777; - int *ptr1 = &value; - int **ptr2 = &ptr1; - int ***ptr3 = &ptr2; - printf("Triple deref: %d", ***(ptr3 + 0)); - return 0; +# Test 6: Struct with typedef (working pattern) +try_ 55 << EOF +typedef struct { + int data; + void *next; +} node_t; + +int main() { + node_t n; + n.data = 55; + n.next = 0; + return n.data; } EOF -# Test 20: Complex double dereference with arithmetic -try_output 0 "Double deref: 888" << EOF -int main() -{ - int value = 888; - int *ptr1 = &value; - int **ptr2 = &ptr1; - printf("Double deref: %d", **(ptr2 + 0)); - return 0; +# Test 7: Struct with pointer arithmetic +try_ 25 << EOF +typedef struct { + int width; + int height; +} rect_t; + +int main() { + rect_t rectangle; + rectangle.width = 5; + rectangle.height = 5; + return rectangle.width * rectangle.height; } EOF -# Test 21: Complex nested parentheses with multiple dereference -try_output 0 "Nested parens: 999" << EOF -int main() -{ - int value = 999; - int *ptr1 = &value; - int **ptr2 = &ptr1; - int ***ptr3 = &ptr2; - printf("Nested parens: %d", ***((ptr3 + 0))); - return 0; +# Test 8: Struct variable with multiple fields +try_ 15 << EOF +typedef struct { + int first; + int second; +} container_t; + +int main() { + container_t c; + c.first = 10; + c.second = 5; + return c.first + c.second; /* 10 + 5 = 15 */ } EOF -# Test 22: Variable offset in complex dereference -try_output 0 "Variable offset: 555" << EOF -int main() -{ - int value = 555; - int *ptr1 = &value; - int **ptr2 = &ptr1; - int ***ptr3 = &ptr2; - int offset = 0; - printf("Variable offset: %d", ***(ptr3 + offset)); - return 0; +# Test 9: Simple struct array access +try_ 10 << EOF +typedef struct { + int x; + int y; +} point_t; + +int main() { + point_t p; + p.x = 3; + p.y = 7; + return p.x + p.y; /* 3+7 = 10 */ } EOF -# Test 23: Array of pointers with complex dereference -try_output 0 "Array ptr: 111 222 333" << EOF -int main() -{ - int a = 111, b = 222, c = 333; - int *arr[3]; - arr[0] = &a; - arr[1] = &b; - arr[2] = &c; - int **parr = arr; - printf("Array ptr: %d %d %d", **(parr + 0), **(parr + 1), **(parr + 2)); - return 0; +# Test 10: Struct variable declaration mixed with other declarations +try_ 88 << EOF +typedef struct { + int x; + int y; +} coord_t; + +int main() { + int a = 10; + coord_t pos; + int b = 20; + coord_t vel; + pos.x = 15; + pos.y = 18; + vel.x = 25; + vel.y = 20; + return a + b + pos.x + pos.y + vel.x; /* 10+20+15+18+25 = 88 */ } EOF -# Test 24: Mixed single and multiple dereference -try_output 0 "Mixed: 666 666 666" << EOF -int main() -{ - int value = 666; - int *ptr1 = &value; - int **ptr2 = &ptr1; - printf("Mixed: %d %d %d", *ptr1, **ptr2, **(ptr2 + 0)); +# Pointer dereference assignment tests Test Case 1: Simple pointer dereference +# assignment +try_ 0 << EOF +void f(int *ap) { + *ap = 0; // Should work now +} +int main() { return 0; } EOF -# Test compound literals: basic int/char and arrays -try_ 42 << EOF +# Test Case 2: Double pointer assignment +try_ 0 << EOF +void f(int **ap) { + *ap = 0; // Should work now +} int main() { - /* Basic int compound literal */ - return (int){42}; + return 0; } EOF -try_ 65 << EOF +# Test Case 3: va_list Implementation (Original Context) +try_ 0 << EOF +typedef int *va_list; +void va_start(va_list *ap, void *last) { + *ap = (int *)(&last + 1); // Should work now +} int main() { - /* Basic char compound literal */ - return (char){65}; + return 0; } EOF -try_ 25 << EOF +# Test Case 4: Compilation test - pointer assignment with local variable +try_ 0 << EOF +void modify(int *p) { + *p = 42; // Tests pointer dereference assignment compilation +} int main() { - /* Single element array compound literal */ - return (int[]){25}; + int x = 10; + // Test compilation of pointer assignment - execution may have issues + // but compilation should succeed + return 0; } EOF -try_ 10 << EOF +# Test Case 5: Compilation test - multiple pointer assignments +try_ 0 << EOF +void assign_values(int *a, int *b, int *c) { + *a = 5; // Multiple pointer dereference assignments + *b = 4; + *c = 6; +} int main() { - /* Multi-element array compound literal - returns first element */ - return (int[]){10, 20, 30}; + // Test compilation success for multiple pointer assignments + return 0; } EOF -try_ 100 << EOF +# Test Case 6: Compilation test - pointer arithmetic assignment +try_ 0 << EOF +void fill_array(int *arr, int size) { + int i; + for (i = 0; i < size; i++) { + *(arr + i) = i; // Pointer arithmetic assignment + } +} int main() { - /* Array compound literal assignment */ - int x = (int[]){100, 200, 300}; - return x; + // Test compilation of pointer arithmetic assignments + return 0; } EOF -try_ 50 << EOF +# Test Case 7: Compilation test - nested pointer dereference +try_ 0 << EOF +void set_nested(int ***ptr) { + ***ptr = 99; // Triple pointer dereference assignment +} int main() { - /* Char array compound literal */ - return (char[]){50, 60, 70}; + // Test compilation of nested pointer assignments + return 0; } EOF -# Test compound literals: advanced features -try_ 35 << EOF +# Test Case 8: Compilation test - assignment with arithmetic operations +try_ 0 << EOF +void complex_assign(int *ptr) { + *ptr = *ptr + 42; // Dereference on both sides + *ptr = (*ptr * 2) + 1; // Complex arithmetic +} int main() { - /* Compound literals in arithmetic expressions */ - return (int){10} + (int){20} + (int[]){5, 15, 25}; + // Test compilation of complex pointer assignments + return 0; } EOF -try_ 42 << EOF -int add_values(int a, int b) { return a + b; } -int main() { - /* Compound literals as function arguments */ - return add_values((int){30}, (int){12}); +begin_category "Function parsing" "Forward declaration and implementation" + +# One declaration may list several prototypes, mixed with object declarators, at +# file scope and in a block extern declaration. A definition stands alone. +try_ 0 << EOF +int f(void), g(int); +int h(int), value = 4, *ptr, k(void); +struct S { int a; }; +struct S make(int), other; +union U { int a; } umake(void), uobj; +enum E { E1 = 1 } emake(void), eobj = E1; +static int sf(int), sg(void); +int f(void) { return 1; } +int g(int x) { return x + 1; } +int h(int x) { return x * 2; } +int k(void) { return value; } +struct S make(int v) { struct S s; s.a = v; return s; } +union U umake(void) { union U u; u.a = 6; return u; } +enum E emake(void) { return E1; } +static int sf(int x) { return x - 1; } +static int sg(void) { return 3; } +int main(void) +{ + extern int y, late(void), z; + struct S m = make(5); + union U u = umake(); + ptr = &value; + other.a = 2; + return f() + g(2) + h(3) + k() + *ptr + m.a + other.a + sf(1) + sg() + + u.a + emake() + eobj + late() + y + z - 46; } +int y = 2, z = 3; +int late(void) { return 5; } +EOF +try_compile_error_message "function definition must be the only declarator" << EOF +int value, defined(void) { return 0; } +int main(void) { return 0; } +EOF +try_compile_error_message "function definition must be the only declarator" << EOF +int declared(void), defined(void) { return 0; } +int main(void) { return 0; } +EOF +try_compile_error << EOF +int clash(void), clash; +int main(void) { return 0; } +EOF + +# A block-scope function declaration has external linkage without extern (C99 +# 6.2.2p5), so plain prototypes may appear alone, several per declaration, mixed +# with objects, and with record, pointer or typedef return types. The name stays +# visible only in its block and hides an outer object there. +try_ 41 << EOF +struct S { int a; }; +typedef int T; +typedef char *str_t; +int outer(void) { int twice(int); return twice(3); } +int main(void) +{ + int f = 1; + struct S make(int); + char *text(void); + T add(int), value = 3; + str_t name(void); + unsigned count(void), *slot(void); + int x = 2, *ptr(void), y = 4; + struct S v = {1}, other(void); + { + int f(void); + if (f() != 7) + return 1; + } + struct S m = make(5); + struct S o = other(); + char *t = text(); + unsigned *k = slot(); + str_t n = name(); + int *p = ptr(); + return m.a + o.a + *t + add(1) + value + count() + *k + n[0] + x + *p + y + + v.a + f + outer(); +} +int f(void) { return 7; } +int twice(int v) { return v * 2; } +struct S make(int v) { struct S s; s.a = v; return s; } +struct S other(void) { struct S s; s.a = 2; return s; } +char *text(void) { return "\001"; } +T add(int v) { return v; } +str_t name(void) { return "\002"; } +unsigned count(void) { return 2; } +unsigned *slot(void) { static unsigned k = 7; return &k; } +int *ptr(void) { static int k = 4; return &k; } +EOF +try_ 9 << EOF +int main(void) { int f(void), g = f(); return g; } +int f(void) { return 9; } +EOF +try_compile_error << EOF +int main(void) { { int hidden(void); } return hidden(); } +int hidden(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { int f(void); return f(); } +char f(void) { return 1; } +EOF +try_compile_error_message "invalid storage class for block function declaration" << EOF +int main(void) { static int f(void); return 0; } +EOF +try_compile_error_message "invalid storage class for block function declaration" << EOF +int main(void) { register int x, f(void); return 0; } EOF -try_ 75 << EOF -int main() { - /* Multiple array compound literals */ - int a = (int[]){25, 35, 45}; - int b = (int[]){50, 60, 70}; - return a + b; /* 25 + 50 = 75 */ -} +# C99 admits a function definition only at file scope. A nested one is rejected +# however its declaration is spelled, rather than lowered inside its caller. +try_compile_error_message "function definition is not allowed at block scope" << EOF +int main(void) { extern int f(void) { return 1; } return f(); } +EOF +try_compile_error_message "function definition is not allowed at block scope" << EOF +int main(void) { int f(void) { return 1; } return f(); } +EOF +try_compile_error_message "function definition is not allowed at block scope" << EOF +struct S { int a; }; +int main(void) { int x; struct S make(void) { struct S s; return s; } return x; } +EOF +try_compile_error_message "function definition is not allowed at block scope" << EOF +int main(void) { typedef int F(void); F f { return 1; } return 0; } EOF -try_ 200 << EOF +# C99 functions may return object or void types, but never an array or another +# function. Keep declaration-only forms covered because no function body is +# needed for the constraint to apply. +try_compile_error << EOF +int returns_array(void)[2]; +EOF +try_compile_error << EOF +int returns_function(void)(void); +EOF +try_compile_error << EOF +typedef int array_t[2]; +array_t typedef_returns_array(void); +EOF +try_compile_error << EOF +typedef int function_t(void); +function_t typedef_returns_function(void); +EOF +try_compile_flag --std=c99 << EOF +typedef int row_t[2]; +typedef row_t *row_ptr_t; +row_ptr_t returns_row(void); +EOF +try_ 7 << EOF +int callback(void) { return 7; } +int main(void) { return (*callback)(); } +EOF +try_ 3 << EOF +int callback(void) { return 7; } +_Bool returns_callback(void) { return callback; } int main(void) { - char *s = (char[]){'A', 'B', 'C', 'D', 'E'}; - return s[0] + s[1] + s[4]; /* 65 + 66 + 69 */ + _Bool initialized = callback; + _Bool assigned = 0; + assigned = callback; + return initialized + assigned + returns_callback(); } EOF - -try_ 6 << EOF +try_ 7 << EOF +struct pair { int left; int right; }; +struct pair make_pair(int left, int right) { + struct pair value = {left, right}; + return value; +} int main(void) { - short *s = (short[]){1, 2, 3, 4, 5}; - return s[0] + s[4]; + struct pair value = make_pair(3, 4); + return value.left + value.right; } EOF - -try_ 60 << EOF +try_ 0 << EOF +struct pair { int left; int right; }; +void use(void) { struct pair (*callback)(void); } +int main(void) { use(); return 0; } +EOF +try_ 12 << EOF +struct triple { int first; int second; int third; }; +struct triple make_triple(int first, int second, int third) { + struct triple value = {first, second, third}; + return value; +} int main(void) { - int arr[] = {10, 20, 30, 40, 50}; - int *selected = 1 ? arr : (int[]){1, 2, 3, 4, 5}; - return selected[0] + selected[4]; + struct triple value = make_triple(3, 4, 5); + return value.first + value.second + value.third; } EOF - -try_ 6 << EOF +try_ 44 << EOF +struct pair { int left; int right; }; +struct pair make_pair(int left, int right) { + struct pair value = {left, right}; + return value; +} +int sum_pair(struct pair value) { return value.left + value.right; } int main(void) { - int arr[] = {10, 20, 30, 40, 50}; - int *selected = 0 ? arr : (int[]){1, 2, 3, 4, 5}; - return selected[0] + selected[4]; + struct pair (*callback)(int, int) = make_pair; + struct pair (*assigned)(int, int); + struct pair indirect = callback(7, 8); + assigned = make_pair; + struct pair assigned_result = assigned(2, 3); + struct pair parenthesized = (make_pair)(9, 10); + struct pair dereferenced = (*callback)(1, 2); + make_pair(0, 0); + return sum_pair(make_pair(12, 13)) + indirect.left + + parenthesized.left + dereferenced.left + assigned_result.left; } EOF - -try_ 120 << EOF -int main() { - /* Complex expression with mixed compound literals */ - return (int){40} + (char){80} + (int[]){0, 0, 0}; /* 40 + 80 + 0 = 120 */ +try_ 40 << EOF +union number { int value; char bytes[4]; }; +union number make_number(int value) { + union number result; + result.value = value; + return result; +} +int take_number(union number value) { return value.value; } +int main(void) { + union number (*callback)(int) = make_number; + union number indirect = callback(17); + return indirect.value + take_number(make_number(23)); } EOF -try_ 200 << EOF -int main() { - /* Compound literal with larger numbers */ - return (int[]){200, 300, 400}; +# Record-returning calls also go through file-scope pointers, record members, +# array elements and parameters. Every function whose address is taken must be +# defined, so each such pointer names a target using the internal return +# convention. A pointer object is pointer-sized whatever it returns: `tag_fn` +# once took one byte's slot and overlapped the array after it. +try_ 0 << 'EOF' +struct triple { int a; int b; int c; }; +struct tag { char x; }; +struct triple make(void) { struct triple s = {1, 2, 3}; return s; } +struct triple scaled(int k); +struct tag make_tag(void) { struct tag t = {'q'}; return t; } +unsigned char small(void) { return 200; } +struct triple (*g_make)(void) = make; +struct triple (*g_scaled)(int); +struct tag (*tag_fn)(void) = make_tag; +struct triple (*g_table[2])(int) = {scaled, scaled}; +unsigned char (*small_fn)(void) = small; +int after[2] = {11, 22}; +struct holder { + int id; + struct triple (*fn)(int); + struct tag (*tags[2])(void); +}; +struct holder g_holder = {9, scaled, {make_tag, make_tag}}; +static struct triple (*s_make)(void) = make; +struct triple apply(struct triple (*f)(int), int k) { return f(k); } +int block_static(void) +{ + static struct triple (*fn)(int) = scaled; + static struct tag (*tags[2])(void) = {make_tag, make_tag}; + struct triple (*local)(int) = g_scaled; + return fn(2).c != 6 || tags[1]().x != 'q' || local(1).b != 2; +} +int main(void) +{ + struct holder *h = &g_holder; + struct triple v = g_make(); + g_scaled = scaled; + struct triple w = g_scaled(4); + struct tag t = tag_fn(); + if (v.a != 1 || v.b != 2 || v.c != 3 || w.a != 4 || w.c != 12) + return 1; + if (t.x != 'q' || after[0] != 11 || after[1] != 22 || small_fn() != 200) + return 2; + if (g_holder.fn(5).c != 15 || h->fn(7).b != 14 || h->tags[1]().x != 'q') + return 3; + if (g_table[1](6).b != 12 || s_make().c != 3 || (*g_make)().a != 1) + return 4; + if (apply(scaled, 3).c != 9 || apply(g_table[0], 2).a != 2) + return 5; + h->tags[0] = make_tag; + g_table[0] = g_scaled; + if (g_table[0](8).a != 8 || h->tags[0]().x != 'q') + return 6; + return block_static() ? 7 : 0; } +struct triple scaled(int k) +{ + struct triple s = {k, k * 2, k * 3}; + return s; +} +EOF +try_compile_error << 'EOF' +struct pair { int value; }; +extern struct pair foreign_pair(void); +struct pair (*callback)(void) = foreign_pair; +int main(void) { return callback().value; } EOF -# Test compound literals: edge cases +# A record returned by value is an rvalue whose members can still be selected +# (C99 6.5.2.3), whether the record is small or large. try_ 0 << EOF -int main() { - /* Empty compound literal */ - return (int){}; +struct inner { char tag; int b; }; +struct small { int a; int c; }; +struct point { int x, y; }; +struct node { int value; struct node *next; }; +struct big { + int a; + struct inner inner; + int arr[3]; + int grid[2][3]; + struct point pts[2]; + unsigned flags : 3; + unsigned more : 5; + struct node *next; + long long wide; + char name[8]; +}; +typedef union { int value; char bytes[4]; } number_t; +static struct node tail = { 42, 0 }; +struct small make_small(int x) { struct small s = { x, x * 2 }; return s; } +number_t make_number(int value) { number_t n; n.value = value; return n; } +struct big make_big(int x) { + struct big b = { 0 }; + b.a = x; + b.inner.tag = 'q'; + b.inner.b = x + 1; + b.arr[0] = 1; b.arr[1] = x * 3; b.arr[2] = 5; + b.grid[1][0] = 10; b.grid[1][2] = 12; + b.pts[1].x = 7; b.pts[1].y = 8; + b.flags = 5; b.more = 17; + b.next = &tail; + b.wide = 0x100000003LL; + b.name[0] = 'h'; b.name[1] = 'i'; + return b; +} +int sum(int a, int b) { return a + b; } +int main(void) { + int x; + struct big (*maker)(int) = make_big; + x = make_small(4).c; + struct inner in = make_big(9).inner; + struct point pt = make_big(0).pts[1]; + return x != 8 || make_big(3).a != 3 || make_big(4).inner.b != 5 || + make_big(4).inner.tag != 'q' || + make_big(2).arr[1] * 10 + make_small(1).a != 61 || + make_big(1).grid[1][2] != 12 || *(make_big(1).grid[1] + 2) != 12 || + in.b != 10 || in.tag != 'q' || pt.x != 7 || pt.y != 8 || + make_big(0).pts[1].y != 8 || make_big(0).flags != 5 || + make_big(0).more != 17 || make_big(0).next->value != 42 || + make_big(0).wide != 0x100000003LL || + sum(make_small(2).a, make_big(5).arr[1]) != 17 || + maker(6).arr[1] != 18 || (*maker)(6).inner.b != 7 || + -make_small(5).a != -5 || !make_small(0).a != 1 || + make_big(0).name[1] != 'i' || *(make_big(0).arr + 2) != 5 || + (make_small(3).a ? make_big(2).inner.b : 0) != 3 || + (make_number(0x01020304).value & 0xff) != 4 || + sizeof(make_big(0).arr) != 3 * sizeof(int) || + sizeof make_big(0).grid[1] != 3 * sizeof(int) || + sizeof make_small(0).a != sizeof(int); +} +EOF +try_compile_error_message "member of a function call result is not assignable" << EOF +struct pair { int left; int right; }; +struct pair make_pair(void) { struct pair value = {1, 2}; return value; } +int main(void) { make_pair().left = 3; return 0; } +EOF + +# A member of what a call returns is an lvalue: it may be assigned, compounded +# and updated, whether it is reached through the returned pointer, through a +# pointer member of that record, or through a subscript of the result. +try_ 0 << 'EOF' +struct pair { int x, y; }; +struct pair pairs[2] = {{1, 2}, {3, 4}}; +struct pair *first(void) { return pairs; } +struct node; +typedef struct node *link; +struct node { int v; link next; }; +struct node tail = {2, 0}; +struct node head = {1, &tail}; +struct node *list(void) { return &head; } +typedef struct pair duo[2]; +duo *duos(void) { return &pairs; } +struct holder { int slots[3]; }; +struct holder holder = {{1, 2, 3}}; +struct holder *held(void) { return &holder; } +struct pair *(*callback)(void) = first; +int main(void) +{ + int value; + + first()->y = 8; + first()->y += 1; + (*first()).x = 5; + if (pairs[0].y != 9 || pairs[0].x != 5) + return 1; + list()->next->v = 1; + list()->next->v += 4; + if (tail.v != 5) + return 2; + duos()[0][1].x = 7; + held()->slots[1] = 9; + callback()->x = 6; + if (pairs[1].x != 7 || holder.slots[1] != 9 || pairs[0].x != 6) + return 3; + value = (first()->y = 12); + first()->y++; + ++first()->y; + if (value != 12 || pairs[0].y != 14) + return 4; + return 0; } EOF +try_compile_error_message "Assignment requires a modifiable lvalue" << 'EOF' +struct holder { int slots[3]; }; +struct holder *held(void); +int main(void) { held()->slots = 0; return 0; } +EOF +try_compile_error_message "assignment of read-only location" << 'EOF' +struct pair { const int x; int y; }; +struct pair *first(void); +int main(void) { first()->x = 1; return 0; } +EOF -try_ 0 << EOF -int main() { - /* Empty array compound literal */ - return (int[]){}; +# A function-pointer member of a call result, or of a record reached through a +# dereference, is called like any other function pointer. +try_ 17 << EOF +struct ops { int (*apply)(int); int bias; }; +int twice(int value) { return value * 2; } +struct ops make_ops(void) { + struct ops ops = {0, 1}; + ops.apply = twice; + return ops; +} +int main(void) { + struct ops ops = make_ops(); + struct ops *q = &ops; + return make_ops().apply(3) + (*q).apply(4) + (make_ops()).apply(1) + + make_ops().bias; +} +EOF + +# A pointer member whose star is hidden in a typedef is followed by -> after a +# call result or a dereferenced value too, and keeps the pointee's qualifier. +try_ 0 << 'EOF' +struct node; +typedef struct node *link; +struct node { int v; link next; }; +typedef struct { char c; short s; } pair; +typedef pair *pair_ptr; +struct holder { char tag; pair_ptr p; const pair_ptr q; pair_ptr ps[2]; }; +struct node n3 = {3, 0}; +struct node n2 = {2, &n3}; +struct node n1 = {1, &n2}; +pair p0 = {10, 20}; +pair p1 = {30, 40}; +struct holder h = {'x', &p0, &p1, {&p0, &p1}}; +struct node *head(void) { return &n1; } +struct holder *holder(void) { return &h; } +struct holder holder_value(void) { return h; } +int main(void) +{ + struct node *p = &n1; + if (head()->next->v != 2 || head()->next->next->v != 3 || p->next->next->v != 3) + return 1; + if ((*head()->next).v != 2 || (*p).next->v != 2 || head()->next->next->next) + return 2; + if (holder()->p->s != 20 || holder_value().q->c != 30 || holder()->ps[1]->s != 40) + return 3; + ++head()->next->v; + holder()->q->s++; + if (n2.v != 3 || p1.s != 41 || sizeof(head()->next->v) != sizeof(int)) + return 4; + return 0; +} +EOF +try_compile_error_message "assignment of read-only location" << 'EOF' +struct node { int v; }; +typedef const struct node *const_link; +struct holder { const_link p; }; +struct holder h; +struct holder *holder(void) { return &h; } +int main(void) { ++holder()->p->v; return 0; } +EOF +try_compile_error_message "Invalid record member access" << 'EOF' +struct node { int v; }; +typedef struct node *link; +struct holder { link *pp; }; +struct holder h; +struct holder *holder(void) { return &h; } +int main(void) { return holder()->pp->v; } +EOF +try_compile_error_message "Unknown struct or union member" << EOF +struct pair { int left; int right; }; +struct pair make_pair(void) { struct pair value = {1, 2}; return value; } +int main(void) { return make_pair().middle; } +EOF +try_compile_error << EOF +struct pair { int value; }; +extern struct pair foreign_pair(void); +int main(void) { foreign_pair(); return 0; } +EOF +try_compile_error << EOF +struct pair { int value; }; +extern struct pair foreign_pair(void); +int main(void) { + struct pair (*callback)(void) = foreign_pair; + callback(); + return 0; } EOF - -try_ 90 << EOF -int main() { - /* Multiple compound literals in expression */ - return (int[]){30, 60} + (int[]){60, 30}; /* 30 + 60 = 90 */ +try_compile_error << EOF +struct pair { int value; }; +struct pair local_pair(void) { struct pair value = {1}; return value; } +extern struct pair foreign_pair(void); +int main(int choose_foreign) { + struct pair (*callback)(void); + if (choose_foreign) callback = foreign_pair; + else callback = local_pair; + callback(); + return 0; } EOF - -try_ 255 << EOF -int main() { - /* Large char compound literal */ - return (char){255}; +try_ 9 << EOF +typedef int row[2]; +row *rows(void) { static row value = {4, 5}; return &value; } +int main(void) { return rows()[0][0] + rows()[0][1]; } +EOF +try_ 6 << EOF +typedef int grid[2][3]; +grid *grids(void) { + static grid value = {{1, 2, 3}, {4, 5, 6}}; + return &value; } +int main(void) { return grids()[0][1][2]; } EOF - -try_ 150 << EOF -int main() { - /* Mixed compound literal expressions */ - int a = (int){50}; - int b = (int[]){100, 200, 300}; - return a + b; /* 50 + 100 = 150 */ +try_ 24 << EOF +typedef int hyper_t[2][2][3][2]; +hyper_t *hypers(void) { + static hyper_t value = {0}; + value[1][1][2][1] = 24; + return &value; } +int main(void) { return hypers()[0][1][1][2][1]; } EOF - -# Array literal decay in initializer for pointer variable -try_ 0 << EOF -int main(void) { - int *p = (int[]){42, 43, 44}; - return *p == 42 ? 0 : 1; +try_ 9 << EOF +typedef int row[2]; +row *rows(void) { static row value = {4, 5}; return &value; } +int main(void) { rows()[0][1] = 9; return rows()[0][1]; } +EOF +try_ 4 << EOF +typedef int *row[2]; +static int second = 4; +row *pointers(void) { + static int first = 3; + static row value = {&first, &second}; + return &value; } +int main(void) { return pointers()[0][1] == &second ? 4 : 0; } EOF -# Test pointer compound literals -try_ 0 << EOF -int main() +# A call returning a pointer to a row of pointers keeps the element depth where +# the typedef spells it. Dereferencing the result yields that row, whose +# elements are pointers to read, not rows to select again. +try_ 0 << 'EOF' +typedef int *pointer_row[2]; +static int first = 3, second = 4; +pointer_row values = {&first, &second}; +pointer_row *rows(void) { return &values; } +int main(void) { - /* Test NULL pointer compound literal */ - int *p = (int*){}; - return p ? 1 : 0; + int **loaded = *rows(); + if (*(*rows())[1] != 4 || (*rows())[0] != &first) + return 1; + if (*loaded[1] != 4 || sizeof((*rows())[1]) != sizeof(int *)) + return 2; + if (sizeof(*rows()) != 2 * sizeof(int *) || *rows()[0][1] != 4) + return 3; + (*rows())[1] = &first; + if (*values[1] != 3) + return 4; + return 0; +} +EOF +try_compile_error_message "Expected a global object or function after '&'" << EOF +typedef int *row[2]; +row *invalid_pointers(void) { + int first = 3; + static int second = 4; + static row value = {&first, &second}; + return &value; } +int main(void) { return 0; } EOF -try_ 0 << EOF -int main() +# A static array named in an aggregate initializer decays to an address constant +# (C99 6.6p7), optionally offset, in array and record initializers at file scope +# and in block-scope statics. An automatic array is not a constant. +try_ 0 << 'EOF' +static int g[2] = {5, 6}; +int m[2][3] = {{1, 2, 3}, {4, 5, 6}}; +struct refs { int *p; int *q; char *n; }; +struct refs g_s = {g, g + 1, "gs"}; +int *g_a[] = {g + 1, m[1]}; +int main(void) { - /* Test pointer compound literal with zero */ - int *p = (int*){0}; - return p ? 1 : 0; + static int ls[3] = {7, 8, 9}; + static int *a[] = {g, g + 1, ls, ls + 2}; + static int *rows[] = {m[0], m[1] + 1}; + static char *names[] = {"a", "b" "c"}; + static struct refs s = {g + 1, ls, "nm"}; + static struct refs sa[2] = {{g, 0, "x"}, {ls + 1, g, "y"}}; + struct refs ds = {g, ls + 1, "z"}; + if (sizeof a != 4 * sizeof(int *) || sizeof names != 2 * sizeof(char *)) + return 1; + if (*a[0] != 5 || *a[1] != 6 || *a[2] != 7 || *a[3] != 9) + return 2; + if (rows[0][2] != 3 || *rows[1] != 5 || names[1][1] != 'c') + return 3; + if (*s.p != 6 || *s.q != 7 || s.n[1] != 'm') + return 4; + if (*sa[1].p != 8 || sa[1].q != g || sa[0].n[0] != 'x' || *ds.q != 8) + return 5; + if (g_s.p != g || *g_s.q != 6 || g_s.n[1] != 's' || *g_a[0] != 6 || + g_a[1][2] != 6) + return 6; + return 0; } EOF - -# Test char pointer compound literals -try_ 0 << EOF -int main() +try_compile_error << 'EOF' +int main(void) { - char *p = (char*){}; - return p ? 1 : 0; + int local[2]; + static int *slots[] = {local}; + return 0; } EOF - -# Test typedef pointer compound literals -try_ 0 << EOF -typedef int* IntPtr; - -int main() +try_compile_error << 'EOF' +int main(void) { - IntPtr p = (IntPtr){0}; - return p ? 1 : 0; + int local[2]; + static struct { int *p; } s = {local}; + return 0; } EOF -# Additional struct initialization tests from refine-parser Test: Local struct -# initialization (working with field-by-field assignment) -try_ 42 << EOF -typedef struct { - int x; - int y; -} point_t; - -int main() { - point_t p; - p.x = 10; - p.y = 32; - return p.x + p.y; /* Returns 42 */ +# Subscripts and offsets of an aggregate address constant resolve in the +# declaration's scope too, so a block-scope enumerator is a valid constant. +try_ 18 << EOF +int sum(void) { + enum { K = 1 }; + static int values[3] = {5, 6, 7}; + static int *slots[2] = {&values[K], &values[0] + K}; + static int *single = &values[K]; + return *slots[0] + *slots[1] + *single; } +int main(void) { return sum(); } EOF - -# Test: Simple array initialization try_ 15 << EOF -int main() { - int nums[3]; - nums[0] = 1; - nums[1] = 5; - nums[2] = 9; - return nums[0] + nums[1] + nums[2]; /* Returns 15 */ +typedef int row[2]; +row *rows(void) { static row value = {4, 5}; return &value; } +int main(void) { + int old; + rows()[0][1] += 2; + old = rows()[0][1]++; + return old + rows()[0][1]; } EOF - -# Test: Character array with integer values -try_ 24 << EOF -int main() { - char arr[3]; - arr[0] = 5; - arr[1] = 9; - arr[2] = 10; - return arr[0] + arr[1] + arr[2]; /* Returns 24 (5+9+10) */ -} +try_ 6 << EOF +typedef int row[2]; +row *rows(void) { static row value = {4, 5}; return &value; } +int main(void) { return ++rows()[0][1]; } +EOF +try_compile_error << EOF +typedef const int row[2]; +row *rows(void); +int main(void) { rows()[0][1] = 1; return 0; } +EOF +try_compile_error << EOF +typedef int row[2]; +const row *rows(void); +int main(void) { rows()[0][1] = 1; return 0; } EOF - -# Test: Simple 3-element array try_ 6 << EOF -int main() { - int arr[3]; - arr[0] = 1; - arr[1] = 2; - arr[2] = 3; - return arr[0] + arr[1] + arr[2]; /* Returns 6 (1+2+3) */ -} +typedef int row[2]; +row *rows(void) { static row value = {4, 5}; return &value; } +int main(void) { return ++(rows())[0][1]; } EOF - -# Test: Mixed scalar fields in struct -try_ 42 << EOF -typedef struct { - int scalar; - int x, y; -} mixed_t; - -int main() { - mixed_t m; - m.scalar = 0; - m.x = 10; - m.y = 32; - return m.x + m.y; /* Returns 42 */ -} +try_ 6 << EOF +typedef int row[2]; +row *rows(void) { static row value = {4, 5}; return &value; } +int main(void) { row *(*callback)(void) = rows; return ++callback()[0][1]; } EOF - -# Union support tests Basic union declaration and field access -try_ 42 << EOF -typedef union { - int i; - char c; -} basic_union_t; - -int main() { - basic_union_t u; - u.i = 42; - return u.i; /* Returns 42 */ +try_ 6 << EOF +typedef int (*row[2])(int); +int increment(int value) { return value + 1; } +int add_two(int value) { return value + 2; } +static int (*callback_storage[2])(int) = {increment, add_two}; +row *callbacks(void) { + return &callback_storage; } +int main(void) { return callbacks()[0][1](4); } EOF -# Union field access - different types sharing same memory -try_ 65 << EOF -typedef union { - int i; - char c; -} char_int_union_t; - -int main() { - char_int_union_t u; - u.c = 65; /* ASCII 'A' */ - return u.c; /* Returns 65 */ +# Fewer subscripts on a call returning a pointer to an array leave an array, not +# one scalar: it keeps its remaining bounds for sizeof, for its own subscripts +# and for pointer arithmetic, and so does a dereferenced call result. +try_ 0 << 'EOF' +typedef int grid[2][3]; +typedef int cube[2][2][3]; +typedef int *pointer_row[2]; +grid *grids(void) +{ + static grid value = {{1, 2, 3}, {4, 5, 6}}; + return &value; +} +cube *cubes(void) +{ + static cube value; + value[1][1][2] = 9; + value[1][0][1] = 7; + return &value; +} +static int first = 3, second = 4; +pointer_row *pointers(void) +{ + static pointer_row value = {&first, &second}; + return &value; +} +struct slots { int *a[2]; } holder; +struct slots *slots(void) { return &holder; } +int main(void) +{ + int *row = grids()[0][1]; + int (*rows)[3] = grids()[0]; + int (*plane)[3] = cubes()[0][1]; + int **pp = pointers()[0]; + if (row[2] != 6 || rows[1][0] != 4 || plane[1][2] != 9) + return 1; + if (*grids()[0][1] != 4 || (*grids()[0])[2] != 3 || *(grids()[0] + 1)[0] != 4) + return 2; + if (grids()[0] + 1 - grids()[0] != 1 || *pp[1] != 4 || cubes()[0][1][0][1] != 7) + return 3; + if (sizeof(grids()[0]) != 6 * sizeof(int) || + sizeof(grids()[0][1]) != 3 * sizeof(int) || + sizeof(grids()[0][1][2]) != sizeof(int) || sizeof(*grids()[0][1]) != sizeof(int)) + return 4; + if (sizeof((*grids())[1]) != 3 * sizeof(int) || + sizeof((*grids())[1][2]) != sizeof(int) || (*grids())[1][2] != 6) + return 5; + if (sizeof(cubes()[0][1]) != 6 * sizeof(int) || + sizeof(pointers()[0]) != 2 * sizeof(int *) || + sizeof(slots()->a) != 2 * sizeof(int *)) + return 6; + return 0; } EOF -# Union with multiple integer fields -try_ 100 << EOF -typedef union { - int value; - int number; - int data; -} multi_int_union_t; - -int main() { - multi_int_union_t u; - u.value = 100; - return u.number; /* Returns 100 - same memory location */ +# A record element of a call result's array is copied as a record, never loaded +# or stored as one scalar word, whether it is read, passed or assigned. +try_ 0 << 'EOF' +struct triple { char c; int v; short s; }; +typedef struct triple trio[2]; +struct triple store[2] = {{1, 2, 3}, {4, 5, 6}}; +trio *trios(void) { return &store; } +int sum(struct triple t) { return t.c * 100 + t.v * 10 + t.s; } +int main(void) +{ + struct triple copy = trios()[0][1]; + struct triple next = {7, 8, 9}; + if (copy.c != 4 || copy.v != 5 || copy.s != 6) + return 1; + if (sum(trios()[0][0]) != 123 || trios()[0][1].s != 6) + return 2; + copy = trios()[0][0]; + if (copy.c != 1 || copy.s != 3) + return 3; + trios()[0][1] = next; + if (store[1].c != 7 || store[1].v != 8 || store[1].s != 9) + return 4; + if (sizeof(trios()[0][1]) != sizeof(struct triple)) + return 5; + return 0; } EOF +try_compile_error << 'EOF' +struct pair { int x, y; }; +typedef struct pair duo[2]; +duo *duos(void); +int main(void) { duos()[0][1]++; return 0; } +EOF +try_compile_error << 'EOF' +struct pair { int x, y; }; +typedef struct pair duo[2]; +duo *duos(void); +int main(void) { duos()[0][1] = 1; return 0; } +EOF -# Union size calculation - should be size of largest member -try_ 4 << EOF -typedef union { - int i; /* 4 bytes */ - char c; /* 1 byte */ -} size_union_t; +# Normal case +try_output 0 "Hello" << EOF +void func(char *ptr); -int main() { - return sizeof(size_union_t); /* Returns 4 (size of int) */ +void func(char *ptr) +{ + while (*ptr) { + printf("%c", *ptr); + ptr++; + } } -EOF - -try_ 2 << EOF -typedef union { - short s; /* 2 bytes */ - char c; /* 1 byte */ -} size_union_t; -int main() { - return sizeof(size_union_t); /* Returns 2 (size of short) */ +int main() +{ + func("Hello"); + return 0; } EOF -# Union with different data types -try_output 0 "Value as int: 1094795585, as char: 65" << EOF -typedef union { - int i; - char c; -} data_union_t; +# Incorrect function returning type +try_compile_error << EOF +void func(void); -int main() { - data_union_t u; - u.i = 1094795585; /* 0x41414141 in hex - four 'A' characters */ - printf("Value as int: %d, as char: %d", u.i, u.c); +int **func(void) +{ + return 3; +} + +int main() +{ + func(); return 0; } EOF -# Nested union in struct -try_ 50 << EOF -typedef union { - int value; - char byte; -} nested_union_t; +# Incorrect number of parameters +try_compile_error << EOF +void func(void *a); -typedef struct { - int id; - nested_union_t data; -} container_t; +void func(void *a, int x) +{ + return 3; +} -int main() { - container_t c; - c.id = 10; - c.data.value = 40; - return c.id + c.data.value; /* Returns 50 */ +int main() +{ + func(); + return 0; } EOF -# Array of unions -try_ 30 << EOF -typedef union { - int i; - char c; -} array_union_t; +# Conflicting parameter types +try_compile_error << EOF +void func(void *a, char x); -int main() { - array_union_t arr[3]; - arr[0].i = 10; - arr[1].i = 20; - arr[2].i = 0; /* Will be overridden */ - arr[2].c = 0; /* Sets to 0 */ - return arr[0].i + arr[1].i + arr[2].i; /* Returns 30 */ +void func(void *a, int x) +{ + return 3; } -EOF - -# Union with pointer fields -try_ 42 << EOF -typedef union { - int *int_ptr; - char *char_ptr; -} ptr_union_t; -int main() { - int value = 42; - ptr_union_t u; - u.int_ptr = &value; - return *(u.int_ptr); /* Returns 42 */ +int main() +{ + func(); + return 0; } EOF -# Complex union with struct member -try_ 77 << EOF -typedef struct { - int x; - int y; -} point_t; +# Conflicting parameter types (variadic parameters) +try_compile_error << EOF +void func(void *a); -typedef union { - point_t pt; - int values[2]; -} point_union_t; +void func(void *a, ...) +{ + return 3; +} -int main() { - point_union_t u; - u.pt.x = 30; - u.pt.y = 47; - return u.values[0] + u.values[1]; /* Returns 77 (30+47) */ +int main() +{ + func(); + return 0; } EOF -# Union assignment and memory sharing (endianness-neutral) -try_output 0 "Union works: 100" << EOF -typedef union { - int i; - char bytes[4]; -} byte_union_t; +# Incorrect function returning type (const) +try_compile_error << EOF +void *func(int *a, char x); -int main() { - byte_union_t u; - u.i = 100; - printf("Union works: %d", u.i); +const void *func(int *a, char x) +{ + return 3; +} + +int main() +{ + func(); return 0; } EOF -# Union with typedef pointer -try_ 99 << EOF -typedef int *int_ptr_t; +# Conflicting parameter types (const) +try_compile_error << EOF +void func(int *a, char x); -typedef union { - int_ptr_t ptr; - int direct; -} typedef_ptr_union_t; +void func(const int *a, char x) +{ + return 3; +} -int main() { - int value = 99; - typedef_ptr_union_t u; - u.ptr = &value; - return *(u.ptr); /* Returns 99 */ +int main() +{ + func(); + return 0; } EOF -# Union initialization with different members -try_ 25 << EOF -typedef union { - int integer; - char character; -} init_union_t; - -int main() { - init_union_t u1, u2; - u1.integer = 25; - u2.character = 25; - return u1.integer; /* Returns 25 */ -} +# Function-pointer parameter declarations carry a nested function type, not +# merely pointer-sized storage. Redeclarations must reject every incompatible +# part of that nested type. +try_compile_error << EOF +int callback(int (*)(int)); +int callback(int (*)(long)); +EOF +try_compile_error << EOF +int callback(int (*)(int)); +int callback(int (*)(int, int)); +EOF +try_compile_error << EOF +int callback(int (*)(int)); +int callback(long (*)(int)); +EOF +try_compile_error << EOF +int callback(int (*)(int, ...)); +int callback(int (*)(int)); +EOF +try_compile_error << EOF +int callback(int (*)(int)); +int callback(int *); +EOF +try_compile_error << EOF +int nested(int (*)(int (*)(int))); +int nested(int (*)(int (*)(long))); EOF -# Union with function pointers -try_ 15 << EOF -int add_func(int a, int b) { return a + b; } -int mult_func(int a, int b) { return a * b; } +# Parameter names and top-level qualifiers on the callback pointer do not change +# a C99 function type. +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int invoke(int (*)(int)); +int invoke(int (* const callback)(int)) { return callback(4) - 5; } +int main(void) { int (*callback)(int) = plus1; return invoke(callback); } +EOF +try_ 0 << EOF +int plus1(int value) { return value + 1; } +int legacy(int (*)()); +int legacy(int (*callback)(int)) { return callback(3) - 4; } +int main(void) { int (*callback)(int) = plus1; return legacy(callback); } +EOF -typedef union { - int (*add_ptr)(int, int); - int (*mult_ptr)(int, int); -} func_union_t; +begin_category "Bit-fields" -int main() { - func_union_t u; - u.add_ptr = add_func; - return u.add_ptr(7, 8); /* Returns 15 */ +# Prefix ++ and -- update a bit-field inside its allocation unit and yield the +# stored value; postfix -- parses. A _Bool bit-field keeps only 0 or 1. +try_output 0 "7 1 15 7 1 15 1 7 1 15 1 1 0 1 7 1 15 | 2 7 2 15 3 7 3 15 7 1 15" << EOF +struct bits { + unsigned int low : 3; + _Bool flag : 1; + unsigned int high : 4; +}; +struct counters { + unsigned int low : 3; + unsigned int mid : 2; + unsigned int high : 4; +}; +int main(void) +{ + struct bits b; + struct counters c; + int v; + b.low = 7; + b.high = 15; + b.flag = 1; + b.flag++; + printf("%d %d %d ", b.low, b.flag, b.high); + b.flag = 0; + b.flag--; + printf("%d %d %d ", b.low, b.flag, b.high); + b.flag = 1; + v = ++b.flag; + printf("%d %d %d %d ", v, b.low, b.flag, b.high); + b.flag = 0; + v = --b.flag; + printf("%d %d ", v, b.flag); + b.flag = 0; + v = b.flag--; + printf("%d %d ", v, b.flag); + b.flag = 0; + b.flag += 2; + printf("%d %d %d | ", b.low, b.flag, b.high); + c.low = 7; + c.high = 15; + c.mid = 1; + v = ++c.mid; + printf("%d %d %d %d ", v, c.low, c.mid, c.high); + c.mid = 0; + v = --c.mid; + printf("%d %d %d %d ", v, c.low, c.mid, c.high); + c.mid = 2; + c.mid--; + printf("%d %d %d", c.low, c.mid, c.high); + return 0; } EOF - -# Sizeof union with mixed types. The largest member is the pointer, so the union -# is one pointer wide: 4 on the 32-bit targets, 8 on LP64. -try_ $PTR_SZ << EOF -typedef union { - char c; - int i; - char *p; -} mixed_union_t; - -int main() { - return sizeof(mixed_union_t); /* size of the largest member */ +try_ 0 << EOF +typedef _Bool bool_alias; +struct flags { bool_alias value : 1; }; +int main(void) { + struct flags value = {2}; + value.value = 3; + return value.value != 1 || sizeof(struct flags) != 1; } EOF - -# Union field modification -try_ 200 << EOF -typedef union { - int total; - int sum; -} modify_union_t; - -int main() { - modify_union_t u; - u.total = 100; - u.sum += 100; /* Modifies same memory location */ - return u.total; /* Returns 200 */ +try_ 0 << EOF +typedef _Bool bool_alias; +struct flags { bool_alias value : 1; }; +static struct flags value = {2}; +int main(void) { return value.value != 1; } +EOF +try_compile_error << EOF +typedef _Bool bool_alias; +int main(void) { bool_alias values[] = "x"; return 0; } +EOF +try_ 37 << EOF +struct flags { + unsigned int low : 3; + unsigned int high : 5; + int signed_value : 4; + unsigned int : 0; + unsigned int tail : 1; +}; +int main(void) { + struct flags value = {0}; + value.low = 7; + value.high = 31; + value.signed_value = -3; + value.tail = 1; + return value.low + value.high + value.signed_value + value.tail + + (sizeof(struct flags) == 8 ? 1 : 0); } EOF - -# Named union inside struct -try_ 88 << EOF -typedef union { - int value; - char byte; -} inner_union_t; - -typedef struct { - int id; - inner_union_t data; -} named_union_container_t; - -int main() { - named_union_container_t c; - c.id = 8; - c.data.value = 80; - return c.id + c.data.value; /* Returns 88 */ +try_ 7 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +int main(void) { + struct flags value = {0}; + value.left = 2; + value.right = 4; + value.left += 1; + return value.left + value.right; +} +EOF +try_ 6 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +int main(void) { + struct flags value = {2, 4}; + return value.left + value.right; +} +EOF +try_ 117 << EOF +struct flags { + unsigned int low : 3; + unsigned int : 2; + unsigned int high : 3; +}; +static struct flags file_flags = {5, 6}; +int main(void) { + struct flags local = {1, 7}; + return file_flags.low + file_flags.high * 8 + + local.low * 64 + local.high * 512; +} +EOF +try_ 7 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +int main(void) { + struct flags value = {2, 4}; + value.left++; + return value.left + value.right; +} +EOF +try_ 6 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { + struct flags flags = {2}; + return (flags.value += 9) + flags.value; +} +EOF +try_ 10 << EOF +struct flags { unsigned int value : 3; unsigned int adjacent : 3; }; +int main(void) { + struct flags flags = {2, 4}; + struct flags *pointer = &flags; + return (pointer->value += 9) + pointer->value + pointer->adjacent; +} +EOF +try_ 9 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +int main(void) { + struct flags value = {2, 4}; + int old = value.left++; + return old + value.left + value.right; } EOF - -# Union with array members -try_ 15 << EOF -typedef union { - int array[3]; - char bytes[12]; -} array_union_t; - -int main() { - array_union_t u; - u.array[0] = 5; - u.array[1] = 10; - u.array[2] = 0; - return u.array[0] + u.array[1] + u.array[2]; /* Returns 15 */ +try_ 90 << EOF +struct flags { int signed_value : 4; unsigned int adjacent : 4; }; +int main(void) { + struct flags value = {-3, 5}; + struct flags *pointer = &value; + int old = pointer->signed_value++; + pointer->adjacent += 5; + return (old + 4) * 64 + (pointer->signed_value + 4) * 8 + + pointer->adjacent; } EOF - -# Complex union with nested structures -try_ 33 << EOF -typedef struct { - int a; - int b; -} pair_t; - -typedef union { - pair_t pair; - int values[2]; - char bytes[8]; -} complex_union_t; - -int main() { - complex_union_t u; - u.pair.a = 11; - u.pair.b = 22; - return u.values[0] + u.values[1]; /* Returns 33 */ +try_ 0 << EOF +struct flags { int signed_value : 3; unsigned int adjacent : 3; }; +int main(void) { + struct flags value = {0}; + struct flags *pointer = &value; + return (pointer->signed_value = 7) + pointer->signed_value + + (pointer->adjacent = 9) + pointer->adjacent; } EOF - -# Union as function parameter -try_ 60 << EOF -typedef union { - int i; - char c; -} param_union_t; - -int process_union(param_union_t u) { - return u.i; +try_ 2 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { + struct flags flags = {1}; + return (flags.value - 8 < 0) + ((~flags.value) < 0); } - -int main() { - param_union_t u; - u.i = 60; - return process_union(u); /* Returns 60 */ +EOF +try_ 4 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { struct flags flags = {1}; return sizeof(+flags.value); } +EOF +try_ 5 << EOF +union flags { unsigned int value : 3; unsigned int raw; }; +int main(void) { union flags value = {0}; value.value = 5; return value.value; } +EOF +try_ 18 << EOF +union flags { + unsigned int : 2; + unsigned int value : 3; + unsigned int raw; +}; +static union flags first = {5}, second = {6}; +int main(void) { + union flags local = {7}; + return first.value + second.value + local.value; } EOF - -# Union as return type -try_ 45 << EOF -typedef union { - int value; - char byte; -} return_union_t; - -return_union_t create_union(int val) { - return_union_t u; - u.value = val; - return u; +try_ 7 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +int main(void) { + struct flags values[2] = {{1, 2}, {3, 4}}; + return values[1].left + values[1].right; } - -int main() { - return_union_t result = create_union(45); - return result.value; /* Returns 45 */ +EOF +try_ 3 << EOF +struct flags { _Bool left : 1; _Bool right : 1; _Bool third : 1; }; +int main(void) { + struct flags value = {0}; + value.left = 9; + value.right = 1; + return value.left + value.right + (sizeof(struct flags) == 1 ? 1 : 0); } EOF - -# Multiple union declarations -try_ 120 << EOF -typedef union { - int x; - char c; -} union1_t; - -typedef union { - int y; - char d; -} union2_t; - -int main() { - union1_t u1; - union2_t u2; - u1.x = 50; - u2.y = 70; - return u1.x + u2.y; /* Returns 120 */ +try_ 3 << EOF +struct flags { _Bool file : 1; _Bool local : 1; }; +static struct flags file_flags = {2, 0}; +int main(void) { + struct flags local = {0}; + local.local = 2; + return file_flags.file + file_flags.local + local.file + local.local + + (sizeof(struct flags) == 1 ? 1 : 0); } EOF +try_ 6 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +static struct flags value = {2, 4}; +int main(void) { return value.left + value.right; } +EOF +try_ 5 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +static struct flags value = {.left = 7, .right = 3, .left = 2}; +int main(void) { return value.left + value.right; } +EOF +try_ 2 << EOF +struct flags { int signed_value : 4; unsigned int narrow : 3; }; +static struct flags value = {-3, 9}; +int main(void) { return value.signed_value + value.narrow + 4; } +EOF +try_ 7 << EOF +struct flags { unsigned int left : 3; unsigned int right : 3; }; +static struct flags values[2] = {{1, 2}, {.left = 3, .right = 4}}; +int main(void) { return values[1].left + values[1].right; } +EOF +try_ 72 << EOF +struct message { char *text; unsigned int code : 4; }; +static struct message value = {"hello", 7}; +int main(void) { return value.text[0] + value.code - 39; } +EOF +try_ 42 << EOF +int increment(int value) { return value + 1; } +struct callback { int (*call)(int); }; +static struct callback value = {increment}; +int main(void) { return value.call(41); } +EOF +try_ 42 << EOF +static int increment(int value) { return value + 1; } +struct callback { int padding; int (*call)(int); }; +static struct callback value = {0, &increment}; +int main(void) { return value.call(41); } +EOF +try_compile_error << EOF +struct invalid { int value : 33; }; +EOF +try_compile_error << EOF +struct invalid { int value : 0; }; +EOF +try_compile_error << EOF +struct invalid { _Bool value : 2; }; +EOF +try_compile_error << EOF +struct invalid { char value : 1; }; +EOF +try_compile_error << EOF +struct invalid { unsigned int *value : 1; }; +EOF +try_compile_error << EOF +struct invalid { unsigned int value[1] : 1; }; +EOF +try_compile_error << EOF +struct invalid { static unsigned int value : 1; }; +EOF +try_compile_error << EOF +struct invalid { extern unsigned int value : 1; }; +EOF +try_compile_error << EOF +struct flags { unsigned int value : 1; }; +int main(void) { struct flags value; return (int)&value.value; } +EOF +try_compile_error << EOF +struct flags { unsigned int value : 1; }; +int main(void) { struct flags value; return sizeof(value.value); } +EOF +try_compile_error << EOF +struct flags { const unsigned int value : 3; }; +int main(void) { struct flags value = {1}; value.value = 2; return value.value; } +EOF -# Type Casting Tests -echo "Testing type casting functionality..." - -declare -a cast_tests=( - "42 int var; var = (int)42; return var;" - "10 int var; var = (short)10; return var;" - "5 short s; s = (short)5; return s;" - "20 short s; s = (int)20; return s;" - "15 short sa = 10; short sb = (short)5; return sa + sb;" - "30 int ia = 10; int ib = (int)20; return ia + ib;" -) - -run_items_tests cast_tests - -# Basic int to char cast -try_ 65 << EOF -int main() { - int x = 65; - char c = (char)x; - return c; /* Returns 65 ('A') */ +begin_category "Flexible array members" +try_ 16 << EOF +struct packet { int count; int values[]; }; +int main(void) { + int storage[4] = {0}; + struct packet *packet = (struct packet *)storage; + packet->count = 3; + packet->values[0] = 3; + packet->values[1] = 5; + packet->values[2] = 4; + return packet->count + packet->values[0] + packet->values[1] + + packet->values[2] + (sizeof(struct packet) == sizeof(int)); } EOF - -# Char to int cast -try_ 42 << EOF -int main() { - char c = 42; - int x = (int)c; - return x; /* Returns 42 */ +try_compile_error << EOF +union invalid { int values[]; }; +EOF +try_compile_error << EOF +struct invalid { int values[]; int trailing; }; +EOF +try_compile_error << EOF +struct invalid { int values[]; }; +EOF +try_compile_error << EOF +struct flexible { int count; int values[]; }; +struct invalid { struct flexible member; }; +EOF +try_compile_error << EOF +struct flexible { int count; int values[]; }; +struct flexible invalid[2]; +EOF +try_compile_error << EOF +struct flexible { int count; int values[]; }; +int main(void) { + int storage[2]; + struct flexible *value = (struct flexible *)storage; + return sizeof(value->values); } EOF - -# Cast in expressions -try_ 130 << EOF -int main() { - int a = 65; - int b = 65; - return (char)a + (char)b; /* Returns 130 */ +try_compile_error << EOF +struct flexible { int count; int values[]; }; +int main(void) { + int storage[2]; + struct flexible *value = (struct flexible *)storage; + return sizeof((value->values)); } EOF - -# Cast with arithmetic -try_ 42 << EOF -int main() { - int x = 50; - return (int)((char)(x - 8)); /* Returns 42 */ +try_compile_error << EOF +struct flexible { int count; int values[]; }; +int main(void) { + int storage[2]; + struct flexible *value = (struct flexible *)storage; + return sizeof((*value).values); } EOF -# Multiple casts in sequence -try_ 100 << EOF -int main() { - int x = 100; - char c = (char)x; - int y = (int)c; - return y; /* Returns 100 */ -} +begin_category "C99 Conformance Mode" "Testing --std=c99 extension diagnostics" + +try_compile_error_flag --std=c99 << EOF +int main(void) { for (typedef int local_t; ; ) return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int a = 1, b = (int[]){2, 3}; return a + b; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { for (static int value = 0; value; ) return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int hidden(void) { return 0; } +int main(void) { for (extern int hidden(void); 0; ) return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { for (int hidden(void); 0; ) return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { for (int i = 0, hidden(void); i < 1; i++) ; return 0; } +EOF +try_ 1 << EOF +int main(void) { for (auto int value = 1; value; value--) return value; return 0; } +EOF +try_ 1 << EOF +int main(void) { for (register int value = 1; value; value--) return value; return 0; } EOF -# Cast with function parameters -try_ 88 << EOF -int test_func(char c) { - return (int)c; -} +# The default remains intentionally permissive for existing users and suite +# coverage; strict mode makes the C99 boundary explicit. +try_ 2 << EOF +int main(void) { return 0b10; } +EOF +try_ 27 << EOF +int main(void) { return '\e'; } +EOF +try_ 1 << EOF +int main(void) { int value = (int[]){1, 2}; return value; } +EOF -int main() { - int x = 88; - return test_func((char)x); /* Returns 88 */ -} +try_compile_error_flag --std=c99 << EOF +int main(void) { return 0b10; } +EOF +try_compile_error_flag --std=c99 << EOF +struct empty_extension { }; +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +union empty_extension { }; +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +enum empty_extension { }; +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int invalid_static_bound(int values[static 0]) { return values[0]; } +int main(void) { int values[1] = {0}; return invalid_static_bound(values); } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { return '\e'; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int value = (int[]){1, 2}; return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { char value = (char[]){1, 2}; return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { return (int[]){1, 2}; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int values[1] = {}; return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +struct value { int member; }; +int main(void) { struct value item = {}; return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int value = (int){}; return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int *value = (int*){}; return value != 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int *value = (int[]){}; return value != 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int variadic(...) { return 0; } +int main(void) { return variadic(); } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int (*callback)(...) = 0; return callback != 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int identity(int value) { return value; } +int main(void) { return identity(1,); } +EOF +try_compile_error_flag --std=c99 << EOF +int identity(int value) { return value; } +int main(void) { int (*callback)(int) = identity; return callback(1,); } +EOF +try_compile_error_flag --std=c99 << EOF +int prototype(int,); +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int definition(int value,) { return value; } +int main(void) { return definition(0); } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { int (*callback)(int,) = 0; return callback != 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int prototype(void *value,); +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +void value_return(void) { return 1; } +int main(void) { value_return(); return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int bare_return(void) { return; } +int main(void) { return bare_return(); } +EOF +try_compile_error_flag --std=c99 << EOF +void *bare_pointer_return(void) { return; } +int main(void) { return bare_pointer_return() != 0; } EOF - -# Cast in return statement -try_ 123 << EOF -int get_char() { - int x = 123; - return (char)x; +try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (0) { case 1: return 1; case 1: return 2; } + return 0; } - -int main() { - return get_char(); /* Returns 123 */ +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (0) { case 0x100000000LL: return 1; case 0: return 2; } + return 0; } EOF - -# Nested casts -try_ 200 << EOF -int main() { - int x = 200; - return (int)((char)((int)x)); /* Returns 200 */ +try_compile_flag --std=c99 << EOF +int main(void) { + long long x = 0; + switch (x) { case 0x100000000LL: return 1; case 0: return 2; } + return 0; } EOF - -# Cast with assignment -try_ 150 << EOF -int main() { - int x = 150; - char c; - c = (char)x; - return (int)c; /* Returns 150 */ +try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (1) { case 1: int value = 1; return value; } + return 0; } EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { label: int value = 1; return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { label: typedef int value; return 0; } +EOF -# String literal and escape coverage (additional) -try_output 0 "AZ" << 'EOF' -int main() { - printf("%s", "\x41Z"); /* hex escape then normal char */ +# The label lookahead resolves identifiers in the label's own scope: a block +# typedef starts a declaration, and an object hiding a file-scope typedef starts +# an expression statement. +try_compile_error_flag --std=c99 << EOF +int main(void) { typedef int T; goto label; label: T value = 0; return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { + typedef int T; + switch (1) { case 1: T value = 1; return value; } return 0; } EOF - -try_output 0 "AZ" << 'EOF' -int main() { - printf("%s", "A\132"); /* octal escape for 'Z' */ +try_flags 4 --std=c99 << EOF +typedef int T; +int main(void) { int T = 3; goto label; label: T = 4; return T; } +EOF +try_flags 5 --std=c99 << EOF +typedef int T; +int main(void) { + int T = 0; + switch (1) { case 1: T = 5; } + return T; +} +EOF +try_compile_error_flag --std=c99 << EOF +enum { first = 1, second = 1 }; +int main(void) { + switch (0) { case first: return 1; case second: return 2; } return 0; } EOF +try_compile_error_flag --std=c99 << EOF +int invalid(int) { return 0; } +int main(void) { return invalid(0); } +EOF +try_compile_error_flag --std=c99 << EOF +int invalid(int, int value) { return value; } +int main(void) { return invalid(0, 1); } +EOF +try_compile_error_flag --std=c99 << EOF +int invalid(int (*)(int)) { return 0; } +int main(void) { return invalid(0); } +EOF +try_compile_error_flag --std=c99 << EOF +main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +extern legacy(void); +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int invalid(int values[const]) { values = 0; return 0; } +int main(void) { return 0; } +EOF +try_compile_error_flag --std=c99 << EOF +int legacy(); +int legacy(char value) { return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int legacy(); +int legacy(short value) { return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int legacy(); +int legacy(_Bool value) { return value; } +EOF +try_compile_error_flag --std=c99 << EOF +int legacy(); +int legacy(int value, ...) { return value; } +EOF -# Cast zero value +# Ordinary C99 spellings remain accepted in strict mode. +try_flags 0 --std=c99 << EOF +int main(void) { label: ; return 0; } +EOF +try_flags 2 --std=c99 << EOF +int main(void) { return 0x2; } +EOF +try_flags 27 --std=c99 << EOF +int main(void) { return '\033'; } +EOF +try_flags 0 --std=c99 << EOF +int main(void) { int *value = (int[]){1, 2}; return value[0] != 1; } +EOF try_ 0 << EOF -int main() { - int x = 0; - char c = (char)x; - return (int)c; /* Returns 0 */ +struct value { int member; }; +int main(void) { + int values[1] = {}; + struct value item = {}; + int value = (int){}; + int *pointer = (int*){}; + return values[0] || item.member || value || pointer != 0; } EOF - -# Local array initializers - verify compilation and correct values Test 1: -# Implicit size array with single element -try_ 1 << 'EOF' -int main() { - int a[] = {1}; - return a[0]; /* Should return 1 */ +try_compile_flag --std=c99 << EOF +typedef int *int_pointer; +int_pointer values(void) { return (int[]){1, 2}; } +int main(void) { values(); return 0; } +EOF +try_compile_flag --std=c99 << EOF +typedef int *int_pointer; +int second(int_pointer value) { return value[1]; } +int main(void) { + int_pointer value = (int[]){1, 2}; + return second((int[]){1, 2}) != 2 || value[0] != 1; } EOF - -# Test 2: Explicit size array with single element -try_ 42 << 'EOF' -int main() { - int a[1] = {42}; - return a[0]; /* Should return 42 */ +try_flags 7 --std=c99 << EOF +int variadic(int value, ...) { return value; } +int main(void) { return variadic(7, 1, 2); } +EOF +try_flags 2 --std=c99 << EOF +int identity(int value) { return value; } +int main(void) { return identity((1, 2)); } +EOF +try_compile_flag --std=c99 << EOF +int prototype(int value); +int variadic(int value, ...); +int main(void) { return 0; } +EOF +try_flags 4 --std=c99 << EOF +int twice(int); +int twice(int value) { return value * 2; } +int main(void) { + extern int twice(int); + return twice(2); } EOF - -# Test 3: Multiple elements - verify all are initialized -try_ 6 << 'EOF' -int main() { - int a[3] = {1, 2, 3}; - return a[0] + a[1] + a[2]; /* Should return 1+2+3=6 */ +try_flags 5 --std=c99 << EOF +int legacy(); +int legacy(int value) { return value; } +int pointer_legacy(); +int pointer_legacy(int *value) { return *value; } +int main(void) { int value = 3; return legacy(2) + pointer_legacy(&value); } +EOF +try_compile_flag --std=c99 << EOF +int consume(const int *, int [static 2]); +int consume(const int *first, int values[static 2]) { + return *first + values[1]; } +int (*callback)(int); +int main(void) { return 0; } EOF - -# Test 4: Character array initialization -try_ 97 << 'EOF' -int main() { - char s[] = {'a', 'b', 'c'}; - return s[0]; /* Should return ASCII value of 'a' = 97 */ +try_flags 5 --std=c99 << EOF +int first(const int values[const]) { return values[0]; } +int bump(int values[restrict]) { values[0]++; return values[0]; } +int main(void) { + int values[1] = {3}; + return first(values) + bump(values) - 2; } EOF - -# Test 5: Empty initializer (all zeros) -try_ 0 << 'EOF' -int main() { - int a[5] = {}; - return a[0] + a[1] + a[2] + a[3] + a[4]; /* Should return 0 */ +try_flags 0 --std=c99 << EOF +int plus1(int value) { return value + 1; } +int invoke(int (*)(int)); +int invoke(int (*callback)(int)) { return callback(4) - 5; } +int takes(int (*)(int, int)); +int takes_const(int (* const)(int)); +int main(void) { int (*callback)(int) = plus1; return invoke(callback); } +EOF +try_flags 1 --std=c99 << EOF +void no_value(void) { return; } +int with_value(void) { return 1; } +int main(void) { no_value(); return with_value(); } +EOF +try_flags 2 --std=c99 << EOF +int main(void) { + switch (2) { case 1: return 1; case 2: return 2; } + return 0; } EOF - -# Test 6: Partial initialization (remaining should be zero) -try_ 15 << 'EOF' -int main() { - int a[5] = {5, 10}; - return a[0] + a[1] + a[2] + a[3] + a[4]; /* Should return 5+10+0+0+0=15 */ +try_flags 11 --std=c99 << EOF +enum { base = 2 }; +int main(void) { + switch (sizeof(int) + base) { + case -(-((0 ? 1 : 1) << base)) + sizeof(short): return 11; + default: return 0; + } } EOF - -# Test 7: Pass initialized array to function -try_ 30 << 'EOF' -int sum(int *p, int n) { +try_flags 4 --std=c99 << EOF +int select_value(int value) { + switch (value) { default: return 3; case 1: return 1; } +} +int main(void) { return select_value(1) + select_value(2); } +EOF +try_flags 17 --std=c99 << EOF +int score(int value) { int total = 0; - for (int i = 0; i < n; i++) - total += p[i]; + switch (value) { + case 1: total = 1; + default: total += 2; + case 2: total += 4; + } return total; } -int main() { - int a[] = {5, 10, 15}; - return sum(a, 3); /* Should return 5+10+15=30 */ -} +int main(void) { return score(1) + score(2) + score(9); } EOF - -# Test 8: Nested scope with array initialization -try_ 100 << 'EOF' -int main() { +try_flags 7 --std=c99 << EOF +int select_label_statement(int value) { + int total = 0; + switch (value) { + total = 100; + case 1: total += 1; break; { - int values[] = {25, 25, 25, 25}; - return values[0] + values[1] + values[2] + values[3]; + case 2: total += 2; break; + } + default: total += 4; + } + return total; +} +int main(void) { + return select_label_statement(1) + select_label_statement(2) + + select_label_statement(9); +} +EOF +try_flags 2 --std=c99 << EOF +int main(void) { + int value = 0; + switch (3) { + default: + switch (2) { default: value += 1; case 2: value += 2; } + break; + case 1: value += 0; } + return value; +} +EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (0) { default: return 1; default: return 2; } } EOF +try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (0) { case 1 + 1: return 1; case 2: return 2; } +} +EOF +try_ 0 << EOF +int variadic(...) { return 0; } +int main(void) { return variadic(); } +EOF +try_compile_error_message "Expected an argument after ','" << EOF +int identity(int value) { return value; } +int main(void) { return identity(1,); } +EOF +try_compile_error << EOF +int identity(int value) { return value; } +int main(void) { int (*callback)(int) = identity; return callback(1,); } +EOF +try_compile_error << EOF +int add(int left, int right) { return left + right; } +int main(void) { return add(1 2); } +EOF -# Struct Variable Declaration Tests (Bug Fix Validation) -echo "Testing struct variable declaration functionality..." +begin_category "C99 assert.h" "Testing freestanding assertion semantics" + +# A failed assertion in main names its expression and the function, in the words +# of whichever libc reports it: shecc's own for a static build, and glibc's for +# a dynamic one. +function assertion_message() +{ + if [ "$LINK_MODE" = "dynamic" ]; then + echo "main: Assertion \`$1' failed" + else + echo "Assertion failed: $1, function main" + fi +} -# Test 1: Basic struct variable declaration (the original bug case) try_ 0 << EOF -struct point { - int x; - int y; -}; +#include +int main(void) { int calls = 0; assert(++calls == 1); return calls - 1; } +EOF +try_ 0 << EOF +#define NDEBUG +#include +int main(void) { int calls = 0; assert(++calls); return calls; } +EOF +try_ 0 << EOF +#define NDEBUG +#include +int main(void) { int calls = 0; (assert(++calls), calls); return calls; } +EOF +try_failure_output "$(assertion_message "0")" << EOF +#define NDEBUG +#include +#undef NDEBUG +#include +int main(void) { assert(0); return 0; } +EOF +try_ 0 << EOF +#include +#define NDEBUG +#include +int main(void) { int calls = 0; assert(++calls); return calls; } +EOF +try_failure_output "$(assertion_message "2 + 2 == 5")" << EOF +#include +int main(void) { assert(2 + 2 == 5); return 0; } +EOF +try_failure_output "$(assertion_message "SUM(1, 1) == 3")" << EOF +#include +#define SUM(a, b) a + b +int main(void) { assert(SUM(1, 1) == 3); return 0; } +EOF -int main() { - struct point p; - return 0; -} +# The diagnostic goes to standard error, leaving standard output to the program: +# a static build prints only abort()'s own line there, a dynamic one nothing. +if [ "$LINK_MODE" = "dynamic" ]; then + try_output 134 "" << EOF +#include +int main(void) { assert(0); return 0; } EOF +else + try_output 255 "Abnormal program termination" << EOF +#include +int main(void) { assert(0); return 0; } +EOF +fi -# Test 2: Struct variable declaration with initialization -try_ 42 << EOF -struct point { - int x; - int y; -}; +begin_category "C99 stdarg.h" "Testing variadic argument macros" -int main() { - struct point p; - p.x = 20; - p.y = 22; - return p.x + p.y; +try_ 0 << EOF +#include +int sum(int count, ...) { + va_list ap; + int total = 0; + va_start(ap, count); + for (int i = 0; i < count; i++) total += va_arg(ap, int); + va_end(ap); + return total; } +int main(void) { return sum(3, 1, 2, 3) != 6; } EOF -# Test 3: Multiple struct variable declarations -try_ 20 << EOF -struct point { - int x; - int y; -}; - -int main() { - struct point p1; - struct point p2; - p1.x = 10; - p1.y = 15; - p2.x = 3; - p2.y = 2; - return p1.x + p1.y - p2.x - p2.y; +try_ 0 << EOF +#include +int tenth(int count, ...) { + va_list ap; + int value = 0; + va_start(ap, count); + for (int i = 0; i < count; i++) value = va_arg(ap, int); + va_end(ap); + return value; } +int main(void) { return tenth(7, 1, 2, 3, 4, 5, 6, 7) != 7; } EOF -# Test 4: Struct variable declaration in nested scope -try_ 100 << EOF -struct data { - int value; -}; - -int main() { - { - struct data d; - d.value = 100; - return d.value; - } +try_ 0 << EOF +#include +int after_char(char last, ...) { + va_list ap; + va_start(ap, last); + return va_arg(ap, int); +} +int after_pointer(int *last, ...) { + va_list ap; + va_start(ap, last); + return va_arg(ap, int); +} +int main(void) { + int value = 0; + return after_char(0, 2) != 2 || after_pointer(&value, 3) != 3; } EOF +try_ 0 << EOF +#include +int after_wide(long long last, ...) { + va_list ap; + va_start(ap, last); + return va_arg(ap, int); +} +int main(void) { return after_wide(0, 4) != 4; } +EOF -# Test 5: Struct with char fields -try_ 142 << EOF -struct character { - char first; - char second; -}; - -int main() { - struct character ch; - ch.first = 'A'; - ch.second = 'M'; - return ch.first + ch.second; /* 65 + 77 = 142 */ +try_ 0 << EOF +#include +struct named_record { int first; int second; int third; }; +int after_record(struct named_record last, ...) { + va_list ap; + va_start(ap, last); + return va_arg(ap, int); +} +int main(void) { + struct named_record value = {1, 2, 3}; + return after_record(value, 9) != 9; } EOF -# Test 6: Struct with typedef (working pattern) -try_ 55 << EOF -typedef struct { - int data; - void *next; -} node_t; - -int main() { - node_t n; - n.data = 55; - n.next = 0; - return n.data; +try_ 0 << EOF +#include +struct trailing_record { int first; int second; int third; }; +int after_trailing_record(int tag, struct trailing_record last, ...) { + va_list ap; + va_start(ap, last); + return tag != 4 || va_arg(ap, int) != 10; +} +int main(void) { + struct trailing_record value = {1, 2, 3}; + return after_trailing_record(4, value, 10); } EOF -# Test 7: Struct with pointer arithmetic -try_ 25 << EOF -typedef struct { - int width; - int height; -} rect_t; - -int main() { - rect_t rectangle; - rectangle.width = 5; - rectangle.height = 5; - return rectangle.width * rectangle.height; +# A variadic function returning a record receives the hidden result pointer in +# the first argument word. Its named parameter and the unnamed arguments that +# follow must be saved from the words after it. +try_ 0 << EOF +#include +struct varargs_result { int count; int first; int second; }; +struct varargs_result collect(int count, ...) { + struct varargs_result result; + va_list ap; + va_start(ap, count); + result.count = count; + result.first = va_arg(ap, int); + result.second = va_arg(ap, int); + va_end(ap); + return result; +} +int main(void) { + struct varargs_result r = collect(3, 40, 50); + return r.count != 3 || r.first != 40 || r.second != 50; } EOF -# Test 8: Struct variable with multiple fields -try_ 15 << EOF -typedef struct { - int first; - int second; -} container_t; +# A record element selected through a pointer-to-array va_arg type is copied +# whole; a record wider than a register must not be read as one scalar. +try_ 29 << EOF +#include +struct wide_item { int first; int second; int third; }; +int pick_wide_item(int count, ...) { + va_list ap; + va_start(ap, count); + struct wide_item result = va_arg(ap, struct wide_item (*)[2])[0][1]; + return result.second + result.third; +} +int main(void) { + struct wide_item items[2] = {{1, 2, 3}, {4, 9, 20}}; + return pick_wide_item(1, &items); +} +EOF -int main() { - container_t c; - c.first = 10; - c.second = 5; - return c.first + c.second; /* 10 + 5 = 15 */ +try_ 0 << EOF +#include +long long wide_argument(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, long long); } +int main(void) { return wide_argument(1, 1234567890123LL) != 1234567890123LL; } EOF -# Test 9: Simple struct array access -try_ 10 << EOF -typedef struct { - int x; - int y; -} point_t; +try_ 0 << EOF +#include +typedef int *int_pointer; +struct item { int value; }; +int type_names(int count, ...) { + va_list ap; + va_start(ap, count); + unsigned int u = va_arg(ap, unsigned int); + unsigned long l = va_arg(ap, unsigned long); + const int *p = va_arg(ap, const int *); + int_pointer q = va_arg(ap, int_pointer); + struct item *r = va_arg(ap, struct item *); + va_end(ap); + return u != 7U || l != 8UL || *p != 9 || *q != 10 || r->value != 11; +} +int main(void) { + int p = 9, q = 10; + struct item r = { 11 }; + return type_names(5, 7U, 8UL, &p, &q, &r); +} +EOF -int main() { - point_t p; - p.x = 3; - p.y = 7; - return p.x + p.y; /* 3+7 = 10 */ +try_ 0 << EOF +#include +enum colour { red = 3, blue = 7 }; +int enum_argument(int count, ...) { + va_list ap; + va_start(ap, count); + enum colour shade = va_arg(ap, enum colour); + va_end(ap); + return shade != blue; +} +int main(void) { + return enum_argument(1, blue); } EOF -# Test 10: Struct variable declaration mixed with other declarations -try_ 88 << EOF -typedef struct { - int x; - int y; -} coord_t; +try_ 0 << EOF +#include +int direct_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, int (*)[2])[0][1] != 7; +} +int main(void) { int row[2] = { 1, 7 }; return direct_pointer_to_array(1, &row); } +EOF -int main() { - int a = 10; - coord_t pos; - int b = 20; - coord_t vel; - pos.x = 15; - pos.y = 18; - vel.x = 25; - vel.y = 20; - return a + b + pos.x + pos.y + vel.x; /* 10+20+15+18+25 = 88 */ +try_compile_error << EOF +#include +typedef int row[2]; +int unsupported_typedef(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, row); } EOF -# Pointer dereference assignment tests Test Case 1: Simple pointer dereference -# assignment try_ 0 << EOF -void f(int *ap) { - *ap = 0; // Should work now -} -int main() { - return 0; +#include +typedef int row[2]; +int typedef_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, row *)[0][1] != 9; } +int main(void) { row value = { 4, 9 }; return typedef_pointer_to_array(1, &value); } EOF -# Test Case 2: Double pointer assignment try_ 0 << EOF -void f(int **ap) { - *ap = 0; // Should work now +#include +typedef int row[2]; +typedef row *row_pointer; +int typedef_alias_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, row_pointer)[0][1] != 9; } -int main() { - return 0; +int main(void) { row value = { 4, 9 }; return typedef_alias_pointer_to_array(1, &value); } +EOF + +try_ 0 << EOF +#include +typedef int row3[3]; +typedef row3 *row3_pointer; +int typedef_alias_row3(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, row3_pointer)[0][2] != 9; } +int main(void) { row3 value = { 1, 2, 9 }; return typedef_alias_row3(1, &value); } EOF -# Test Case 3: va_list Implementation (Original Context) try_ 0 << EOF -typedef int *va_list; -void va_start(va_list *ap, void *last) { - *ap = (int *)(&last + 1); // Should work now +#include +struct item { int first; int second; }; +typedef struct item item_t; +int aggregate_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + item_t result = va_arg(ap, item_t (*)[2])[0][1]; + return result.second != 9; } -int main() { - return 0; +int main(void) { item_t value[2] = {{1, 2}, {3, 9}}; return aggregate_pointer_to_array(1, &value); } +EOF + +try_ 0 << EOF +#include +struct item { int value; }; +int direct_aggregate_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + struct item result = va_arg(ap, struct item (*)[2])[0][1]; + return result.value != 8; } +int main(void) { struct item value[2] = {{4}, {8}}; return direct_aggregate_pointer_to_array(1, &value); } EOF -# Test Case 4: Compilation test - pointer assignment with local variable try_ 0 << EOF -void modify(int *p) { - *p = 42; // Tests pointer dereference assignment compilation +#include +int pointer_element_array(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, int * (*)[2])[0][1] != 9; } -int main() { - int x = 10; - // Test compilation of pointer assignment - execution may have issues - // but compilation should succeed - return 0; +int main(void) { + int first = 4, second = 9; + int *value[2] = { &first, &second }; + return pointer_element_array(1, &value); } EOF -# Test Case 5: Compilation test - multiple pointer assignments try_ 0 << EOF -void assign_values(int *a, int *b, int *c) { - *a = 5; // Multiple pointer dereference assignments - *b = 4; - *c = 6; +#include +typedef int *pointer; +typedef pointer row[2]; +int typedef_pointer_element_array(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, row *)[0][1] != 8; } -int main() { - // Test compilation success for multiple pointer assignments - return 0; +int main(void) { + int first = 3, second = 8; + row value = { &first, &second }; + return typedef_pointer_element_array(1, &value); } EOF -# Test Case 6: Compilation test - pointer arithmetic assignment +# The element a pointer-to-array va_arg type selects is a pointer to int, and +# dereferencing it reads an int. A typedef carrying the element's star had it +# counted twice, so the dereference read a pointer's width; on an LP64 target +# that took in the neighbouring int as well. try_ 0 << EOF -void fill_array(int *arr, int size) { - int i; - for (i = 0; i < size; i++) { - *(arr + i) = i; // Pointer arithmetic assignment - } +#include +typedef int *pointer; +typedef pointer row[2]; +int typedef_pointer_element_width(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, row *)[0][1] != 8; } -int main() { - // Test compilation of pointer arithmetic assignments - return 0; +int main(void) { + int pair[2] = { 8, 5 }; + row value = { &pair[1], &pair[0] }; + return typedef_pointer_element_width(1, &value); } EOF -# Test Case 7: Compilation test - nested pointer dereference +# An element whose pointer comes from a typedef points at the typedef's scalar, +# so dereferencing it reads that scalar's width rather than a whole pointer. try_ 0 << EOF -void set_nested(int ***ptr) { - ***ptr = 99; // Triple pointer dereference assignment +#include +typedef char *text; +typedef text words[2]; +int typedef_char_pointer_element(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, words *)[0][1] != 'c'; } -int main() { - // Test compilation of nested pointer assignments - return 0; +int main(void) { + words value = { "ab", "cd" }; + return typedef_char_pointer_element(1, &value); } EOF -# Test Case 8: Compilation test - assignment with arithmetic operations try_ 0 << EOF -void complex_assign(int *ptr) { - *ptr = *ptr + 42; // Dereference on both sides - *ptr = (*ptr * 2) + 1; // Complex arithmetic +#include +typedef int *pointer; +int parenthesized_pointer_element_width(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, pointer (*)[2])[0][1] != 8; } -int main() { - // Test compilation of complex pointer assignments - return 0; +int main(void) { + int pair[2] = { 8, 5 }; + pointer value[2] = { &pair[1], &pair[0] }; + return parenthesized_pointer_element_width(1, &value); } EOF - -begin_category "Function parsing" "Forward declaration and implementation" - -# Normal case -try_output 0 "Hello" << EOF -void func(char *ptr); - -void func(char *ptr) -{ - while (*ptr) { - printf("%c", *ptr); - ptr++; - } +try_ 0 << EOF +#include +typedef char *text; +int typedef_pointer_declarator_element(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, text (*)[2])[0][1] != 'c'; } - -int main() -{ - func("Hello"); - return 0; +int main(void) { + text value[2] = { "ab", "cd" }; + return typedef_pointer_declarator_element(1, &value); } EOF -# Incorrect function returning type try_compile_error << EOF -void func(void); - -int **func(void) -{ - return 3; +#include +int unsupported_void_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, void (*)[2]) != 0; } +EOF -int main() -{ - func(); - return 0; +try_ 0 << EOF +#include +int two_dimensional_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, int (*)[2][3])[1][1][2] != 12; +} +int main(void) { + int matrices[2][2][3] = { + { { 1, 2, 3 }, { 4, 5, 6 } }, + { { 7, 8, 9 }, { 10, 11, 12 } } + }; + return two_dimensional_pointer_to_array(1, matrices); } EOF -# Incorrect number of parameters -try_compile_error << EOF -void func(void *a); - -void func(void *a, int x) -{ - return 3; +try_ 0 << EOF +#include +int plus_five(int value) { return value + 5; } +int invoke(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, int (*)(int))(4) != 9; } +int main(void) { int (*callback)(int) = plus_five; return invoke(1, callback); } +EOF -int main() -{ - func(); - return 0; +try_ 0 << EOF +#include +int once(int count, ...) { + va_list cursors[2]; + int i = 0; + va_start(cursors[0], count); + int value = va_arg(cursors[i++], int); + return value != 12 ? 1 : i != 1 ? 2 : 0; } +int main(void) { return once(1, 12); } EOF -# Conflicting parameter types try_compile_error << EOF -void func(void *a, char x); - -void func(void *a, int x) -{ - return 3; -} - -int main() -{ - func(); +#include +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, void); return 0; } EOF -# Conflicting parameter types (variadic parameters) try_compile_error << EOF -void func(void *a); - -void func(void *a, ...) -{ - return 3; +#include +struct incomplete; +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, struct incomplete); + return 0; } +EOF -int main() -{ - func(); +# A va_arg type name reads its specifiers like any other type name: in any +# order, through a block-scope typedef, and without a floating type, which would +# enter the integer-only argument ABI however it is spelled. +try_ 42 << EOF +#include +int pick(int count, ...) { + typedef long long wide; + va_list ap; + va_start(ap, count); + wide first = va_arg(ap, wide); + int second = va_arg(ap, int const unsigned); + va_end(ap); + return (int) first + second; +} +int main(void) { return pick(2, (long long) 2, 40); } +EOF +try_compile_error_message "Floating point types are not yet supported" << EOF +#include +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, double); return 0; } EOF - -# Incorrect function returning type (const) -try_compile_error << EOF -void *func(int *a, char x); - -const void *func(int *a, char x) -{ - return 3; +try_compile_error_message "Floating point types are not yet supported" << EOF +#include +typedef float real; +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, real); + return 0; } - -int main() -{ - func(); +EOF +try_compile_error_message "record type cannot be combined with integer specifiers" << EOF +#include +struct item { int value; }; +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, signed struct item *); return 0; } EOF -# Conflicting parameter types (const) -try_compile_error << EOF -void func(int *a, char x); - -void func(const int *a, char x) -{ - return 3; +try_ 0 << EOF +#include +struct payload { int first; int second; int third; }; +union word { int value; int other; }; +int consume(int count, ...) { + va_list ap; + va_start(ap, count); + struct payload item = va_arg(ap, struct payload); + union word choice = va_arg(ap, union word); + int sum = item.first + item.second + item.third + choice.value; + item.first = 99; + va_end(ap); + return sum; } - -int main() -{ - func(); - return 0; +int main(void) { + struct payload value = { 1, 2, 3 }; + union word choice = { 4 }; + return consume(2, value, choice) != 10 || value.first != 1; } EOF +try_ 0 << EOF +#include +int inspect(int count, ...) { + int *pointer; + va_list ap, copy; + va_start(ap, count); + va_copy(copy, ap); + va_arg(ap, int); + pointer = va_arg(ap, int *); + int value = va_arg(copy, int); + va_end(copy); + va_end(ap); + return value != 4 || *pointer != 9; +} +int main(void) { int marker = 9; return inspect(2, 4, &marker); } +EOF + +try_flags 0 "--no-libc" << EOF +#include +int first(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, int); +} +int main(void) { return first(1, 0); } +EOF + # Test Results Summary echo "" diff --git a/tests/include-angle-child.h b/tests/include-angle-child.h new file mode 100644 index 00000000..ad0ffc8e --- /dev/null +++ b/tests/include-angle-child.h @@ -0,0 +1,2 @@ +#pragma once +enum { ANGLE_INCLUDE_CHILD = 13 }; diff --git a/tests/include-angle.h b/tests/include-angle.h new file mode 100644 index 00000000..85bd1c5e --- /dev/null +++ b/tests/include-angle.h @@ -0,0 +1,2 @@ +#define ANGLE_INCLUDE_BASE 11 +#include diff --git a/tests/include-base.h b/tests/include-base.h new file mode 100644 index 00000000..ff9e09d4 --- /dev/null +++ b/tests/include-base.h @@ -0,0 +1 @@ +#define QUOTED_INCLUDE_BASE 7 diff --git a/tests/include-macro.h b/tests/include-macro.h new file mode 100644 index 00000000..0dba7557 --- /dev/null +++ b/tests/include-macro.h @@ -0,0 +1,2 @@ +#pragma once +#define MACRO_INCLUDE_VALUE 5 diff --git a/tests/include-main.c b/tests/include-main.c new file mode 100644 index 00000000..b74cdd17 --- /dev/null +++ b/tests/include-main.c @@ -0,0 +1,9 @@ +#include "include-values.h" +#define FIRST_MACRO_HEADER SECOND_MACRO_HEADER +#define SECOND_MACRO_HEADER "include-macro.h" +#include FIRST_MACRO_HEADER + +int main(void) +{ + return QUOTED_INCLUDE_BASE + QUOTED_INCLUDE_VALUE + MACRO_INCLUDE_VALUE; +} diff --git a/tests/include-nested/inner/once.h b/tests/include-nested/inner/once.h new file mode 100644 index 00000000..fba5348c --- /dev/null +++ b/tests/include-nested/inner/once.h @@ -0,0 +1,3 @@ +#pragma once +enum { NESTED_ONCE_VALUE = 19 }; +int nested_once_object = 0; diff --git a/tests/include-nested/outer.h b/tests/include-nested/outer.h new file mode 100644 index 00000000..36582e3c --- /dev/null +++ b/tests/include-nested/outer.h @@ -0,0 +1,5 @@ +#pragma once +#include "inner/once.h" + +/* The same header again, spelled with ./ and ../ after its canonical name. */ +#include "./inner/../inner/once.h" diff --git a/tests/include-values.h b/tests/include-values.h new file mode 100644 index 00000000..ba5ccb7a --- /dev/null +++ b/tests/include-values.h @@ -0,0 +1,3 @@ +#pragma once +#include "include-base.h" +#define QUOTED_INCLUDE_VALUE 12 diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index 2ec4b338..34c3aadb 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -293,6 +293,74 @@ int main() { ' "PASS" } +# A named long long takes the next two argument registers, even after an odd +# number of words. The callee reads the words as ints through a pointer of the +# caller's function type. +test_long_long_after_int() +{ + run_abi_test "Long long after one int (a1, a2)" "Parameter Passing" ' +#include +typedef int (*call_t)(int, long long); +int words(int a, int low, int high, int unused) { return low == 3 && high == 2; } +int main() { + call_t call = (call_t) words; + if (call(1, 0x200000003LL)) { + printf("PASS\n"); + return 0; + } + printf("FAIL: long long not in a1 and a2\n"); + return 1; +} +' "PASS" +} + +# A variadic long long takes an even-numbered register pair. +test_variadic_long_long() +{ + run_abi_test "Variadic long long in an aligned pair (a2, a3)" "Parameter Passing" ' +#include +typedef int (*call_t)(int, ...); +int words(int a, int skipped, int low, int high) { return low == 3 && high == 2; } +int main() { + call_t call = (call_t) words; + if (call(1, 0x200000003LL)) { + printf("PASS\n"); + return 0; + } + printf("FAIL: variadic long long not in a2 and a3\n"); + return 1; +} +' "PASS" +} + +# With only a7 left, a named long long puts its low word there and its high word +# in the first stack slot. +test_long_long_split() +{ + run_abi_test "Long long split between a7 and the stack" "Parameter Passing" ' +#include +#include +typedef int (*call_t)(int, int, int, int, int, int, int, long long); +int words(int a, int b, int c, int d, int e, int f, int g, int low, ...) { + va_list ap; + int high; + va_start(ap, low); + high = va_arg(ap, int); + va_end(ap); + return low == 3 && high == 2; +} +int main() { + call_t call = (call_t) words; + if (call(1, 2, 3, 4, 5, 6, 7, 0x200000003LL)) { + printf("PASS\n"); + return 0; + } + printf("FAIL: long long not split between a7 and the stack\n"); + return 1; +} +' "PASS" +} + # Stack Alignment Tests test_stack_alignment_basic() @@ -546,6 +614,9 @@ test_two_args test_four_args test_five_args test_eight_args +test_long_long_after_int +test_variadic_long_long +test_long_long_split echo "" echo -e "${CYAN}Running Stack Alignment Tests...${NC}" diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index 9bcbdbbd..cf91f280 100755 --- a/tests/x64-abi.sh +++ b/tests/x64-abi.sh @@ -286,6 +286,7 @@ test_eight_args() int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { return a + b + c + d + e + f + g + h; } + int main() { int result = sum8(1, 2, 3, 4, 5, 6, 7, 8); if (result == 36) { @@ -298,6 +299,123 @@ int main() { ' "PASS" } +# shecc currently represents long as a 32-bit signed scalar. Exercise that +# spelling through both register and stack argument slots and a return value. +test_long_args_and_return() +{ + run_abi_test "Long arguments and return" "Parameter Passing" ' +#include +long combine(long a, long b, long c, long d, long e, long f, long g) { + return a + b + c + d + e + f + g; +} + +int main() { + long result = combine(1, 2, 3, 4, 5, 6, 21); + if (result == 42) { + printf("PASS\n"); + return 0; + } + printf("FAIL: got %d\n", result); + return 1; +} +' "PASS" +} + +# The first six integer arguments use registers and the final two use the +# overflow area. Each narrow unsigned parameter must arrive zero-extended before +# the callee promotes it for the sum. +test_narrow_unsigned_args() +{ + run_abi_test "Narrow unsigned register and stack arguments" "Parameter Passing" ' +#include +int sum_narrow(unsigned char a, unsigned short b, unsigned char c, + unsigned short d, unsigned char e, unsigned short f, + unsigned char g, unsigned short h) { + return a + b + c + d + e + f + g + h; +} +int main(void) { + int result = sum_narrow(255U, 65535U, 255U, 65535U, + 255U, 65535U, 255U, 65535U); + if (result == 263160) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_mixed_narrow_args() +{ + run_abi_test "Mixed narrow register and stack arguments" "Parameter Passing" ' +#include +int sum_mixed(signed char a, unsigned char b, short c, unsigned short d, + signed char e, unsigned char f, short g, unsigned short h) { + return a + b + c + d + e + f + g + h; +} +int main(void) { + int result = sum_mixed(-128, 255U, -32768, 65535U, + -1, 128U, -2, 32768U); + if (result == 65787) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# System V AMD64 assigns each integer-class long long argument one register +# slot. Exercise all six argument registers, two overflow-area slots, and a +# return value with nonzero upper words so a 32-bit-only path cannot pass. +test_unsigned_long_long_args_and_return() +{ + run_abi_test "Unsigned long long arguments and return" "Parameter Passing" ' +#include +unsigned long long combine8(unsigned long long a, unsigned long long b, + unsigned long long c, unsigned long long d, + unsigned long long e, unsigned long long f, + unsigned long long g, unsigned long long h) { + return a + b + c + d + e + f + g + h; +} +int main() { + unsigned long long result = combine8(0x100000001ULL, 0x100000002ULL, + 0x100000003ULL, 0x100000004ULL, 0x100000005ULL, 0x100000006ULL, + 0x100000007ULL, 0x100000008ULL); + if (result == 0x800000024ULL) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_signed_long_long_args_and_return() +{ + run_abi_test "Signed long long arguments and return" "Parameter Passing" ' +#include +long long combine8(long long a, long long b, long long c, long long d, + long long e, long long f, long long g, long long h) { + return a + b + c + d + e + f + g + h; +} +int main() { + long long result = combine8(-0x100000001LL, -0x100000002LL, + -0x100000003LL, -0x100000004LL, -0x100000005LL, -0x100000006LL, + -0x100000007LL, -0x100000008LL); + if (result == -0x800000024LL) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + # Stack Alignment Tests # # The AMD64 ABI requires RSP to be 16-byte aligned at a call site, which C gives @@ -372,6 +490,61 @@ int main() { ' "PASS" } +# High-bit narrow unsigned returns must remain magnitudes when the caller +# promotes them, rather than being interpreted as signed byte/halfword values. +test_return_narrow_unsigned() +{ + run_abi_test "Return narrow unsigned values" "Return Values" ' +#include +unsigned char get_byte(void) { return 255U; } +unsigned short get_half(void) { return 65535U; } +int main(void) { + if (get_byte() == 255U && get_half() == 65535U) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_return_mixed_narrow() +{ + run_abi_test "Return mixed narrow signedness" "Return Values" ' +#include +signed char get_sbyte(void) { return -128; } +unsigned char get_ubyte(void) { return 255U; } +short get_shalf(void) { return -32768; } +unsigned short get_uhalf(void) { return 65535U; } +int main(void) { + if (get_sbyte() == -128 && get_ubyte() == 255U && + get_shalf() == -32768 && get_uhalf() == 65535U) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_bool_argument_and_return() +{ + run_abi_test "Bool argument and return normalization" "Return Values" ' +#include +_Bool echo_bool(_Bool value) { return value; } +int main(void) { + if (echo_bool(7) == 1 && echo_bool(0) == 0) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + test_return_int() { run_abi_test "Return int value" "Return Values" ' @@ -558,6 +731,11 @@ test_two_args test_four_args test_five_args test_eight_args +test_long_args_and_return +test_narrow_unsigned_args +test_mixed_narrow_args +test_unsigned_long_long_args_and_return +test_signed_long_long_args_and_return echo "" echo -e "${CYAN}Running Stack Alignment Tests...${NC}" @@ -567,6 +745,9 @@ test_stack_alignment_extended echo "" echo -e "${CYAN}Running Return Value Tests...${NC}" test_return_char +test_return_narrow_unsigned +test_return_mixed_narrow +test_bool_argument_and_return test_return_int test_return_pointer diff --git a/tools/inliner.c b/tools/inliner.c index 052160e5..d153ca67 100644 --- a/tools/inliner.c +++ b/tools/inliner.c @@ -129,24 +129,50 @@ void write_line(char *src) write_str("\");\n"); } -void load_from(char *file) +/* Emit @file as the function @name. Each line is a call, and one basic block + * holding them all outgrew what shecc's dead code elimination tracks per block + * once the library passed about a thousand lines, so the lines go into parts of + * LINES_PER_PART, which @name calls in order. + */ +#define LINES_PER_PART 512 + +void load_from(char *file, char *name) { char buffer[MAX_LINE_LEN]; + char header[MAX_LINE_LEN]; + int lines = 0, parts = 0; FILE *f = fopen(file, "rb"); + for (;;) { - if (!fgets(buffer, MAX_LINE_LEN, f)) { - fclose(f); - return; - } + if (!fgets(buffer, MAX_LINE_LEN, f)) + break; if (!strncmp(buffer, "#pragma once", 12)) continue; if (!strncmp(buffer, "#include \"c.h\"", 14)) continue; + if (lines % LINES_PER_PART == 0) { + if (lines) + write_str("}\n"); + snprintf(header, sizeof(header), "void %s_%d(void) {\n", name, + parts++); + write_str(header); + } write_line(buffer); + lines++; } fclose(f); + if (lines) + write_str("}\n"); + + snprintf(header, sizeof(header), "void %s(void) {\n", name); + write_str(header); + for (int i = 0; i < parts; i++) { + snprintf(header, sizeof(header), " %s_%d();\n", name, i); + write_str(header); + } + write_str("}\n"); } void save_to(char *file) @@ -182,13 +208,8 @@ int main(int argc, char *argv[]) write_str(" strbuf_puts(LIBC_SRC, src);\n"); write_str("}\n"); - write_str("void libc_impl(void) {\n"); - load_from(argv[1]); - write_str("}\n"); - - write_str("void libc_decl(void) {\n"); - load_from(argv[2]); - write_str("}\n"); + load_from(argv[1], "libc_impl"); + load_from(argv[2], "libc_decl"); save_to(argv[3]); strbuf_free(SOURCE);