From 4f8844e987ab12180d6762c908bae950e6d87337 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 9 Sep 2026 11:13:21 +0800 Subject: [PATCH 01/40] Add static, const and C99 record initialization Records gain complete file-scope declarators, whole-record copies on assignment, record parameters, and passing by value in direct and indirect calls. Initializers follow C99: nested record and union members, designators at file and block scope, and compound literals of record, array and scalar type in either scope, with static array and record initializers emitted as data at their declared store widths. The static storage class is added, and a function redeclaration inherits its static linkage. const is enforced on writes, conversions, casts and arguments, and is kept on typedefs, trailing qualifiers and continuation declarators. signed and the C99 long spellings are accepted, pointer differences are scaled by the element size, sizeof void and void pointer arithmetic are diagnosed, array decay keeps its metadata, and string literal warnings are available on request. --- lib/c.c | 36 +- lib/c.h | 30 +- src/arm-codegen.c | 30 +- src/defs.h | 33 + src/elf.c | 2 +- src/globals.c | 1 + src/lexer.c | 9 +- src/main.c | 5 +- src/parser.c | 1872 ++++++++++++++++++++++++++++++++------------- src/reg-alloc.c | 45 ++ tests/arm-abi.sh | 23 + tests/driver.sh | 1217 ++++++++++++++++++++++++++++- tests/x64-abi.sh | 23 + 13 files changed, 2734 insertions(+), 592 deletions(-) diff --git a/lib/c.c b/lib/c.c index 6b03909d..df35d93d 100644 --- a/lib/c.c +++ b/lib/c.c @@ -47,7 +47,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 +63,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 +76,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 +91,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 +102,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 +148,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,7 +165,7 @@ char *strncpy(char *dest, char *src, int len) return dest; } -char *memcpy(char *dest, char *src, int count) +char *memcpy(char *dest, const char *src, int count) { int i = 0; @@ -186,9 +186,9 @@ char *memcpy(char *dest, char *src, int count) 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; + const char *p1 = s1, *p2 = s2; for (int i = 0; i < n; i++) { if (p1[i] < p2[i]) @@ -452,7 +452,7 @@ void __format(fmtbuf_t *fmtbuf, __fmtbuf_write_str(fmtbuf, pb + pbi, INT_BUF_LEN - pbi); } -void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) +void __format_to_buf(fmtbuf_t *fmtbuf, const char *format, int *var_args) { int si = 0, pi = 0; @@ -556,7 +556,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) { char buffer[FMT_BUF_LEN]; fmtbuf_t fmtbuf; @@ -591,12 +591,12 @@ 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); } -int sprintf(char *buffer, char *str, ...) +int sprintf(char *buffer, const char *str, ...) { fmtbuf_t fmtbuf; @@ -607,7 +607,7 @@ int sprintf(char *buffer, char *str, ...) return fmtbuf.len; } -int snprintf(char *buffer, int n, char *str, ...) +int snprintf(char *buffer, int n, const char *str, ...) { fmtbuf_t fmtbuf; @@ -620,7 +620,7 @@ int snprintf(char *buffer, int n, char *str, ...) int __free_all(void); -int fprintf(FILE *stream, char *str, ...) +int fprintf(FILE *stream, const char *str, ...) { return __write_fmt(stream, str, &str + 1); } @@ -643,7 +643,7 @@ void abort(void) exit(-1); } -FILE *fopen(char *filename, char *mode) +FILE *fopen(const char *filename, const char *mode) { int fd; @@ -690,7 +690,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. */ diff --git a/lib/c.h b/lib/c.h index d097edcf..1af81348 100644 --- a/lib/c.h +++ b/lib/c.h @@ -114,9 +114,9 @@ typedef int FILE; #define stdout 1 #define stderr 2 -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); @@ -131,23 +131,23 @@ 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); +char *memcpy(char *dest, const char *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 */ diff --git a/src/arm-codegen.c b/src/arm-codegen.c index bf7615f0..8ce95099 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -259,6 +259,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: @@ -341,13 +342,30 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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; + + /* STRH has an 8-bit split immediate while STR/STRB accept 12 bits. + * Materialize larger halfword offsets before selecting the width. + */ + if (store_offset > (ph2_ir->size_bytes == 2 ? 255 : 4095)) { + 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->src1 == 1) diff --git a/src/defs.h b/src/defs.h index 3534f2fe..af343729 100644 --- a/src/defs.h +++ b/src/defs.h @@ -349,6 +349,9 @@ typedef enum { T_continue, T_goto, T_const, /* const qualifier */ + T_static, + T_signed, + T_long, /* C pre-processor directives */ T_cppd_include, T_cppd_define, @@ -553,7 +556,9 @@ struct var { int ptr_level; bool is_func; bool is_global; + bool is_static; /* declaration used the static storage class */ bool is_const_qualified; /* true if variable has const qualifier */ + bool is_const_pointer; /* true for the outermost `* const` qualifier */ 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 @@ -639,6 +644,28 @@ struct var { * array or struct literal temporaries). */ bool is_compound_literal; + + /* 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; + + /* 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; @@ -725,6 +752,8 @@ struct type { var_t *fields; int num_fields; int ptr_level; /* pointer level for typedef pointer types */ + bool is_union; /* preserves union semantics for anonymous typedef unions */ + bool is_const_qualified; /* qualifier carried by a scalar typedef */ }; /* lvalue details */ @@ -733,7 +762,10 @@ typedef struct { int ptr_level; bool is_func; bool is_reference; + bool is_const_qualified; type_t *type; + /* The declaration selected by the lvalue, including a struct member. */ + var_t *decl; } lvalue_t; /* constants for enums */ @@ -946,6 +978,7 @@ struct func { var_t param_defs[MAX_PARAMS]; int num_params; int va_args; + bool is_static; /* internal-linkage declaration */ /* 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 diff --git a/src/elf.c b/src/elf.c index 66ec0e34..ef0b97ec 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1411,6 +1411,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..0810b8cd 100644 --- a/src/globals.c +++ b/src/globals.c @@ -106,6 +106,7 @@ bool expand_only = false; bool dump_ir = false; bool dump_dot = false; bool hard_mul_div = false; +bool warn_string_literals = false; /* Create a new arena block with given capacity. * @capacity: The capacity of the arena block. Must be positive. diff --git a/src/lexer.c b/src/lexer.c index 562c0a48..5021e125 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -12,7 +12,7 @@ /* Hash table constants */ #define NUM_DIRECTIVES 11 -#define NUM_KEYWORDS 18 +#define NUM_KEYWORDS 21 /* Token mapping structure for elegant initialization */ typedef struct { @@ -88,6 +88,9 @@ void lex_init_keywords(void) {"goto", T_goto}, {"union", T_union}, {"const", T_const}, + {"static", T_static}, + {"signed", T_signed}, + {"long", T_long}, }; /* hashmap insertion */ @@ -1002,7 +1005,7 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) kind = T_const; break; - case 6: /* 6-letter keywords: return, struct, switch, sizeof */ + case 6: /* 6-letter keywords: return, struct, switch, sizeof, static */ if (token_buffer[0] == 'r' && !memcmp(token_buffer, "return", 6)) kind = T_return; else if (token_buffer[0] == 's') { @@ -1012,6 +1015,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; diff --git a/src/main.c b/src/main.c index 1ccd5134..2f317dcf 100644 --- a/src/main.c +++ b/src/main.c @@ -109,6 +109,8 @@ 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], "--dot")) dump_dot = true; else if (!strcmp(argv[i], "+m")) @@ -133,7 +135,8 @@ int main(int argc, char *argv[]) if (!in) { printf( - "Usage: shecc [-o output] [+m] [--dot] [--dump-ir] [--no-libc] " + "Usage: shecc [-o output] [+m] [--dot] [--dump-ir] " + "[--warn-string-literals] [--no-libc] " "[--dynlink] [-E] \n"); usage_error("Missing source file"); } diff --git a/src/parser.c b/src/parser.c index 1d710e1f..15d47a68 100644 --- a/src/parser.c +++ b/src/parser.c @@ -48,6 +48,21 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb, bool emit_code); +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 global_compound_literal_starts_here(void) +{ + token_t *next; + + if (!lex_peek(T_open_bracket, NULL)) + return false; + next = cur_token->next->next; + return next && (next->kind == T_identifier || next->kind == T_struct || + next->kind == T_union); +} label_t *find_label(const char *name) { @@ -447,6 +462,43 @@ var_t *resize_to(block_t *block, return resize_var(block, bb, val, &target); } +/* 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) +{ + if (!from || !to || !from->ptr_level || !to->ptr_level) + return false; + + if (from->is_const_qualified && !to->is_const_qualified) + return true; + + return from->ptr_level > 1 && to->ptr_level > 1 && + from->is_const_qualified != to->is_const_qualified; +} + +/* 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 (!incompatible_const_pointer_conversion(from, to)) + return; + + if (from->is_string_literal) { + if (warn_string_literals) + printf("Warning: string literal is read-only\n"); + return; + } + + error_at("discarding const qualifier", cur_token_loc()); +} + void read_parameter_list_decl(func_t *func, bool anon); /* Forward declaration for ternary handling used by initializers */ @@ -494,6 +546,111 @@ var_t *compute_field_address(block_t *parent, 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 && !var->ptr_level && !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)); +} + +void emit_record_copy(block_t *parent, + basic_block_t **bb, + var_t *dest, + var_t *src) +{ + int size = size_var(dest); + var_t *dest_addr = require_ref_var(parent, dest->type, 0); + var_t *src_addr = require_ref_var(parent, src->type, 0); + + dest_addr->var_name = gen_name(); + src_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); + add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); + + 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; + } +} + +/* Copy a record into an address which is already known, such as a nested record + * member. A compound literal is an object, not an integer value, so lowering it + * through one OP_write with the member's aggregate size leaves narrow backends + * with an impossible 8-byte store. + */ +void emit_record_copy_to_address(block_t *parent, + basic_block_t **bb, + var_t *dest_addr, + var_t *src) +{ + int size = size_var(src); + var_t *src_addr = require_ref_var(parent, src->type, 0); + + src_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); + + 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; + } +} + +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); + } +} + var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) { var_t *val = NULL; @@ -552,6 +709,168 @@ void consume_global_constant_syntax(void) } } +bool is_record_type(const type_t *type) +{ + return type && + (type->base_type == TYPE_struct || type->base_type == TYPE_union || + (type->base_type == TYPE_typedef && type->num_fields > 0)); +} + +void parse_struct_field_init(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr, + bool emit_code); + +void parse_array_field_row_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code) +{ + int count = 0; + int elem_size = field->ptr_level ? PTR_SIZE : field->type->size; + + lex_expect(T_open_curly); + while (!lex_peek(T_close_curly, NULL)) { + var_t *value = NULL; + var_t *elem_addr; + + if (count >= field->array_dim2) + error_at("Too many elements in array initializer", + next_token_loc()); + + elem_addr = compute_element_address(parent, bb, target_addr, + start + count, elem_size); + if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { + type_t *record_type = field->type; + 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(parent, bb, record_type, elem_addr, + emit_code); + lex_expect(T_close_curly); + } else if (parent == GLOBAL_BLOCK) { + if (emit_code) + value = parse_global_constant_value(parent, bb); + else + consume_global_constant_syntax(); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + + if (value && emit_code) { + var_t *stored = + 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 (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + if (emit_code) { + 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); + for (; count < field->array_dim2; count++) { + var_t *elem_addr = compute_element_address( + parent, bb, target_addr, start + count, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } +} + +void parse_array_field_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + bool emit_code) +{ + int count = 0; + int elem_size = field->ptr_level ? PTR_SIZE : field->type->size; + + lex_expect(T_open_curly); + while (!lex_peek(T_close_curly, NULL)) { + var_t *value = NULL; + + if (count >= field->array_size) + error_at("Too many elements in array initializer", + next_token_loc()); + + var_t *elem_addr = + compute_element_address(parent, bb, target_addr, count, elem_size); + if (field->array_dim2 && lex_peek(T_open_curly, NULL)) { + parse_array_field_row_init(parent, bb, field, target_addr, count, + emit_code); + count += field->array_dim2; + if (!lex_accept(T_comma)) + break; + continue; + } else if (lex_peek(T_open_curly, NULL) && + is_record_type(field->type)) { + type_t *record_type = field->type; + 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(parent, bb, record_type, elem_addr, + emit_code); + lex_expect(T_close_curly); + } else if (parent == GLOBAL_BLOCK) { + if (emit_code) + value = parse_global_constant_value(parent, bb); + else + consume_global_constant_syntax(); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + + if (value && emit_code) { + var_t *stored = + 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 (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + if (emit_code) { + 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); + + for (; count < field->array_size; count++) { + var_t *elem_addr = compute_element_address(parent, bb, target_addr, + count, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } +} + void parse_struct_field_init(block_t *parent, basic_block_t **bb, type_t *struct_type, @@ -559,12 +878,85 @@ void parse_struct_field_init(block_t *parent, bool emit_code) { int field_idx = 0; + int initializer_count = 0; + + /* 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. + */ + if (emit_code && parent != GLOBAL_BLOCK && + struct_type->base_type != TYPE_union && !struct_type->is_union) { + 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); + + for (int i = 0; i < struct_type->num_fields; i++) { + var_t *field = &struct_type->fields[i]; + var_t *field_addr = + compute_field_address(parent, bb, target_addr, field); + int field_size = size_var(field); + + for (int offset = 0; offset < field_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, field_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } if (!lex_peek(T_close_curly, NULL)) { for (;;) { var_t *field_val_raw = NULL; + var_t *field = NULL; + + if (lex_accept(T_dot)) { + char field_name[MAX_ID_LEN]; + bool found = false; + + lex_ident(T_identifier, field_name); + lex_expect(T_assign); + for (int i = 0; i < struct_type->num_fields; i++) { + if (!strcmp(struct_type->fields[i].var_name, field_name)) { + field_idx = i; + found = true; + break; + } + } + if (!found) + error_at("Unknown field in record initializer", + cur_token_loc()); + } + + if (field_idx >= struct_type->num_fields || + ((struct_type->base_type == TYPE_union || + struct_type->is_union) && + initializer_count > 0)) + error_at("Too many elements in record initializer", + next_token_loc()); + + if (field_idx < struct_type->num_fields) + field = &struct_type->fields[field_idx]; - if (parent == GLOBAL_BLOCK) { + if (field && lex_peek(T_open_curly, NULL) && field->array_size) { + var_t *field_addr = + compute_field_address(parent, bb, target_addr, field); + parse_array_field_init(parent, bb, field, field_addr, + emit_code); + } else if (field && lex_peek(T_open_curly, NULL) && + is_record_type(field->type)) { + type_t *nested_type = field->type; + var_t *field_addr = + compute_field_address(parent, bb, target_addr, field); + + if (nested_type->base_type == TYPE_typedef && + nested_type->base_struct) + nested_type = nested_type->base_struct; + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, nested_type, field_addr, + emit_code); + lex_expect(T_close_curly); + } else if (parent == GLOBAL_BLOCK) { if (emit_code) { field_val_raw = parse_global_constant_value(parent, bb); } else { @@ -577,20 +969,24 @@ void parse_struct_field_init(block_t *parent, } 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); + 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 { + var_t *field_val = resize_to(parent, bb, field_val_raw, + field->type, field->ptr_level); + int field_size = size_var(field); + add_insn(parent, *bb, OP_write, NULL, field_addr, field_val, + field_size, NULL); + } } field_idx++; + initializer_count++; if (!lex_accept(T_comma)) break; if (lex_peek(T_close_curly, NULL)) @@ -819,14 +1215,16 @@ basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) return else_; } +int read_const_expr(void); + void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb, bool emit_code) { int count = 0; + int inferred_size = 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 @@ -842,14 +1240,44 @@ void parse_array_init(var_t *var, if (emit_code) 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 && emit_code && !is_implicit) { + 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); + for (int i = 0; i < var->array_size; i++) { + var_t *elem_addr = + compute_element_address(parent, bb, base_addr, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } + lex_expect(T_open_curly); if (!lex_peek(T_close_curly, NULL)) { for (;;) { var_t *val = NULL; + int prior_count = count; - if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_typedef)) { + if (lex_accept(T_open_square)) { + count = read_const_expr(); + lex_expect(T_close_square); + lex_expect(T_assign); + } + + if (!is_implicit && count >= var->array_size) + error_at("Too many elements in array initializer", + next_token_loc()); + + 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) @@ -899,19 +1327,37 @@ void parse_array_init(var_t *var, 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 (is_implicit && count >= MAX_IMPLICIT_ARRAY) + error_at("Too many elements in array initializer", + next_token_loc()); - if (val && emit_code && !is_implicit && count < var->array_size) { + if (val && emit_code && (is_implicit || count < var->array_size)) { var_t *v = resize_to(parent, bb, val, var->type, 0); + /* A forward designator leaves a gap. Explicit arrays were + * zeroed before parsing; inferred local arrays do not have a + * known bound yet, so zero just the newly skipped elements. + * Global storage begins zeroed. + */ + if (is_implicit && parent != GLOBAL_BLOCK && + count > prior_count) { + 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); + for (int i = prior_count; i < count; i++) { + var_t *gap_addr = compute_element_address( + parent, bb, base_addr, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = compute_element_address( + parent, bb, gap_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, + zero, 1, NULL); + } + } + } + var_t *elem_addr = compute_element_address( parent, bb, base_addr, count, elem_size); @@ -924,6 +1370,8 @@ void parse_array_init(var_t *var, } count++; + if (is_implicit && count > inferred_size) + inferred_size = count; if (!lex_accept(T_comma)) break; if (lex_peek(T_close_curly, NULL)) @@ -931,69 +1379,12 @@ void parse_array_init(var_t *var, } } - 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); - - var_t *elem_addr = compute_element_address( - parent, bb, base_addr, i, elem_size); - - add_insn(parent, *bb, OP_write, NULL, elem_addr, v, elem_size, - NULL); - } - } + var->array_size = inferred_size; } } @@ -1003,22 +1394,110 @@ void parse_array_compound_literal(var_t *var, { int elem_size = var->type->size; int count = 0; - var->array_size = 0; + + /* 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; + + /* A designated element can leave holes before or after it, so initialize + * the declared object before parsing any explicit elements. + */ + if (declared_size) { + 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); + for (int i = 0; i < declared_size; i++) { + var_t *elem_addr = + compute_element_address(parent, bb, var, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } + 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; + if (lex_accept(T_open_square)) { + count = read_const_expr(); + lex_expect(T_close_square); + 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 && count > inferred_size) { + 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); + for (int i = inferred_size; i < count; i++) { + var_t *gap_addr = + compute_element_address(parent, bb, var, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = compute_element_address( + parent, bb, gap_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, + 1, NULL); + } + } + } - 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); + 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 = var->type; + 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(parent, bb, record_type, elem_addr, + true); + lex_expect(T_close_curly); + } 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 (!lex_accept(T_comma)) break; @@ -1028,7 +1507,8 @@ void parse_array_compound_literal(var_t *var, } lex_expect(T_close_curly); - var->array_size = count; + + var->array_size = declared_size ? declared_size : inferred_size; } /* Identify compiler-emitted temporaries that hold array compound literals. They @@ -1233,19 +1713,26 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) * require tracking const-ness of the pointer itself vs the pointed-to * data separately. */ - while (lex_peek(T_const, NULL)) - lex_accept(T_const); + while (lex_accept(T_const)) + vd->is_const_pointer = true; } /* is it function pointer declaration? */ if (lex_accept(T_open_bracket)) { - func_t func; + func_t *func = arena_alloc_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); + + /* 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)); + read_parameter_list_decl(func, true); + vd->func_signature = func; vd->is_func = true; } else { if (!anon) { @@ -1312,12 +1799,49 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) void read_full_var_decl(var_t *vd, bool anon, bool is_param) { 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 is_signed = lex_accept(T_signed); + bool is_const = lex_accept(T_const); + bool is_long = lex_accept(T_long); + if (is_long) { + is_signed |= lex_accept(T_signed); + is_const |= lex_accept(T_const); + } 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); + type_t *type; + + /* `signed` has the existing signed scalar semantics. C permits its `int` + * spelling to be omitted, while `signed char` and `signed short` retain + * their explicit base type. + */ + if (is_long) { + /* C permits long and long int to share int's representation. shecc's + * current ABI is 32-bit for both, which meets C99's minimum range; + * reserve a second long for the later required long-long widening. + */ + if (lex_accept(T_long)) + error_at("long long is not supported yet", cur_token_loc()); + if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = TY_int; + } else if (is_signed && find_type_flag == 1 && + (!lex_peek(T_identifier, type_name) || + (strcmp(type_name, "int") && strcmp(type_name, "char") && + strcmp(type_name, "short")))) { + type = TY_int; + } else { + lex_ident(T_identifier, type_name); + type = find_type(type_name, find_type_flag); + } if (!type) { printf("Could not find type %s%s\n", @@ -1327,6 +1851,21 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) } vd->type = type; + vd->is_const_qualified = type->is_const_qualified; + + /* 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 (lex_accept(T_const)) + is_const = true; + if (is_const || declaration_const) { + if (type->ptr_level) + vd->is_const_pointer = true; + else + vd->is_const_qualified = true; + } read_inner_var_decl(vd, anon, is_param); } @@ -1358,7 +1897,9 @@ void read_parameter_list_decl(func_t *func, bool anon) lex_accept(T_comma); } - while (lex_peek(T_identifier, NULL) || lex_peek(T_const, NULL)) { + while (lex_peek(T_identifier, NULL) || lex_peek(T_const, NULL) || + lex_peek(T_signed, NULL) || lex_peek(T_long, NULL) || + lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { /* Check for const qualifier */ bool is_const = false; if (lex_accept(T_const)) @@ -1367,7 +1908,10 @@ void read_parameter_list_decl(func_t *func, bool anon) 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; + func->param_defs[vn].is_const_qualified |= is_const; + func->param_defs[vn].is_aggregate_param = + is_record_type(func->param_defs[vn].type) && + !func->param_defs[vn].ptr_level; vn++; lex_accept(T_comma); } @@ -1408,6 +1952,8 @@ void read_literal_param(block_t *parent, basic_block_t *bb) 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); @@ -1517,13 +2063,33 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) if (func && param_num < func->num_params) { var_t *target = &func->param_defs[param_num]; - if (!target->ptr_level && !target->array_size) + diagnose_const_pointer_conversion(param, target); + if (is_record_type(target->type) && !target->ptr_level) { + /* 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 (!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. + /* 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 @@ -1539,7 +2105,10 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) * 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]); + 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; @@ -1564,10 +2133,34 @@ void read_func_call(func_t *func, block_t *parent, basic_block_t **bb) func->return_def.var_name); } -void read_indirect_call(block_t *parent, basic_block_t **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; +} + +void read_indirect_call(var_t *callee, block_t *parent, basic_block_t **bb) { - /* Note: Indirect calls use generic parameter handling */ - read_func_parameters(NULL, parent, 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. The callee was evaluated before its argument list. + * Materialize a distinct SSA value now: lowering a by-value record argument + * emits a caller-side copy and can otherwise evict the untracked + * operand-stack value before OP_indirect consumes it. + */ + var_t *target = opstack_pop(); + var_t *saved_target = require_var(parent); + saved_target->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, saved_target, target, NULL, 0, NULL); + opstack_push(saved_target); + + func_t *signature = get_func_signature(callee); + read_func_parameters(signature, parent, bb); add_insn(parent, *bb, OP_indirect, NULL, opstack_pop(), NULL, 0, NULL); } @@ -1598,6 +2191,7 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) rs1 = opstack_pop(); vd = require_ref_var(parent, lvalue.type, lvalue.ptr_level); vd->var_name = gen_name(); + vd->is_const_qualified = lvalue.is_const_qualified; opstack_push(vd); add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); } @@ -1823,16 +2417,39 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) int ptr_cnt = 0; token_t *sizeof_tk = cur_token; type_t *type = NULL; + bool is_function = false; var_t *vd; lex_expect(T_open_bracket); /* Check if this is sizeof(type) or sizeof(expression) */ + bool has_signed_type = lex_accept(T_signed); + bool has_long_type = lex_accept(T_long); 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)) { + if (has_long_type) { + type = TY_int; + if (lex_accept(T_long)) + error_at("long long is not supported yet", cur_token_loc()); + lex_accept(T_signed); + lex_accept(T_const); + if (lex_peek(T_identifier, token) && !strcmp(token, "int")) + lex_expect(T_identifier); + while (lex_accept(T_asterisk)) + ptr_cnt++; + } else if (has_signed_type) { + type = TY_int; + if (lex_peek(T_identifier, token) && + (!strcmp(token, "int") || !strcmp(token, "char") || + !strcmp(token, "short"))) { + lex_expect(T_identifier); + type = find_type(token, true); + } + while (lex_accept(T_asterisk)) + ptr_cnt++; + } else if (lex_peek(T_identifier, token)) { /* Try to parse as a type first */ type = find_type(token, find_type_flag); if (type) { @@ -1850,10 +2467,15 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) var_t *expr_var = opstack_pop(); type = expr_var->type; ptr_cnt = expr_var->ptr_level; + is_function = expr_var->is_func; } 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 && ptr_cnt == 0) + error_at("sizeof(function) is invalid", &sizeof_tk->location); vd = require_var(parent); vd->init_val = ptr_cnt ? PTR_SIZE : type->size; @@ -1939,23 +2561,68 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) bool is_cast = false; type_t *cast_or_literal_type = NULL; int cast_ptr_level = 0; + int cast_array_size = 0; + bool cast_const_qualified = false; + bool cast_const_pointer = false; /* Look ahead to see if we have a typename followed by ) */ - if (lex_peek(T_identifier, lookahead_token)) { + token_t *type_start = cur_token; + bool has_const_type = lex_accept(T_const); + bool has_signed_type = lex_accept(T_signed); + bool has_long_type = lex_accept(T_long); + bool has_type_identifier = lex_peek(T_identifier, lookahead_token); + if (has_const_type || has_signed_type || has_long_type || + has_type_identifier || lex_peek(T_struct, NULL) || + lex_peek(T_union, NULL)) { /* Check if it's a basic type or typedef */ - type_t *type = find_type(lookahead_token, true); + token_t *saved_token = type_start; + bool is_record = lex_accept(T_struct); + if (!is_record) + is_record = lex_accept(T_union); + if (is_record) + lex_ident(T_identifier, lookahead_token); + + type_t *type; + if (has_long_type && !is_record) { + if (lex_accept(T_long)) + error_at("long long is not supported yet", cur_token_loc()); + lex_accept(T_signed); + lex_accept(T_const); + if (lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + type = TY_int; + } else if (has_signed_type && !is_record) { + if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "int") || + !strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short"))) { + lex_expect(T_identifier); + type = find_type(lookahead_token, true); + } else { + type = TY_int; + } + } else { + type = find_type(lookahead_token, is_record ? 2 : true); + } if (type) { - /* Save current position to backtrack if needed */ - token_t *saved_token = cur_token; + /* Save current position to backtrack if needed Try to parse as + * typename + */ + if (!is_record && !has_signed_type && !has_long_type) + lex_expect(T_identifier); - /* Try to parse as typename */ - lex_expect(T_identifier); + /* A qualifier may appear before or after the base type. */ + while (lex_accept(T_const)) + has_const_type = true; /* Check for pointer types: int*, char*, etc. */ int ptr_level = 0; while (lex_accept(T_asterisk)) { ptr_level++; + while (lex_accept(T_const)) + cast_const_pointer = true; } /* Check for array brackets: [size] or [] */ @@ -1963,11 +2630,16 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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); + /* Preserve a declared bound for compound literals. */ + if (lex_peek(T_numeric, NULL)) { + char bound[MAX_TOKEN_LEN]; + lex_ident_n(T_numeric, bound, MAX_TOKEN_LEN); + cast_array_size = parse_numeric_constant(bound); + if (cast_array_size <= 0) + error_at( + "Array compound literal needs a positive bound", + next_token_loc()); + } lex_expect(T_close_square); } @@ -1991,6 +2663,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) is_cast = true; cast_or_literal_type = type; cast_ptr_level = ptr_level; + cast_const_qualified = + has_const_type || type->is_const_qualified; } } else { /* Not a cast or compound literal - backtrack */ @@ -2010,6 +2684,15 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) var_t *cast_var = require_typed_ptr_var( parent, cast_or_literal_type, cast_ptr_level); cast_var->var_name = gen_name(); + cast_var->is_const_qualified = cast_const_qualified; + cast_var->is_const_pointer = cast_const_pointer; + + /* 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"); /* 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 @@ -2044,7 +2727,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) bool consumed_close_brace = false; /* Check if this is a pointer compound literal */ if (is_array_literal) { - compound_var->array_size = 0; + compound_var->array_size = cast_array_size; add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); parse_array_compound_literal(compound_var, parent, bb); @@ -2083,42 +2766,29 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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 */ + /* 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 (is_record_type(cast_or_literal_type)) { + /* Record compound literals use the same aggregate initializer + * path for structs, unions, and their typedef aliases. + */ + type_t *struct_type = cast_or_literal_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, + true); + opstack_push(compound_var); } 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) { @@ -2263,9 +2933,15 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* is it an indirect call with function pointer? */ if (lex_peek(T_open_bracket, NULL)) { - read_indirect_call(parent, bb); + read_indirect_call(lvalue.decl, parent, bb); - vd = require_var(parent); + func_t *signature = get_func_signature(lvalue.decl); + if (signature) + vd = require_typed_ptr_var(parent, + signature->return_def.type, + signature->return_def.ptr_level); + else + vd = require_var(parent); vd->var_name = gen_name(); opstack_push(vd); add_insn(parent, *bb, OP_func_ret, vd, NULL, NULL, 0, NULL); @@ -2360,11 +3036,29 @@ bool accept_compound_assign_op(opcode_t *op) 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) { if (!ptr_var || !ptr_var->type) return PTR_SIZE; /* Default to pointer size */ + /* An array of pointers decays to a pointer-to-pointer. The declaration + * records its element's indirection level, so account for the decay before + * deriving the pointed-to object size. + */ + if (ptr_var->array_size && ptr_var->ptr_level) + return PTR_SIZE; + /* Direct pointer with type info. * * Only a single level of indirection points at the base type. For deeper @@ -2396,6 +3090,24 @@ int get_pointer_element_size(var_t *ptr_var) 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; + 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)); +} + /* Helper function to handle pointer arithmetic (add/sub with scaling) */ void handle_pointer_arithmetic(block_t *parent, basic_block_t **bb, @@ -2407,6 +3119,9 @@ void handle_pointer_arithmetic(block_t *parent, var_t *int_var = NULL; int element_size = 0; + 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. */ @@ -2445,52 +3160,7 @@ void handle_pointer_arithmetic(block_t *parent, */ 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; - } - } - } + element_size = get_pointer_element_size(orig_rs1); /* Perform subtraction first */ var_t *diff = require_var(parent); @@ -2911,6 +3581,7 @@ void read_lvalue(lvalue_t *lvalue, var_t *vd, *rs1, *rs2; bool is_address_got = false; bool is_member = false; + int subscript_depth = 0; /* Callers pass a find_var() result, which is NULL for a name that was never * declared. @@ -2922,11 +3593,21 @@ void read_lvalue(lvalue_t *lvalue, lex_expect(T_identifier); lvalue->type = var->type; + lvalue->decl = var; lvalue->size = get_size(var); lvalue->ptr_level = var->ptr_level; lvalue->is_func = var->is_func; lvalue->is_reference = false; + /* 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_const_pointer + : var->is_const_qualified; + opstack_push(var); if (lex_peek(T_open_square, NULL) || lex_peek(T_arrow, NULL) || @@ -3000,7 +3681,7 @@ void read_lvalue(lvalue_t *lvalue, /* If this is the first index of a 2D array, multiply by dim2 * * element_size */ - if (!is_address_got && var->array_dim2 > 0) + if (subscript_depth == 0 && var->array_dim2 > 0) multiplier = var->array_dim2 * lvalue->size; if (multiplier != 1) { @@ -3022,6 +3703,14 @@ void read_lvalue(lvalue_t *lvalue, 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; + vd->ptr_level = lvalue->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); @@ -3029,7 +3718,13 @@ void read_lvalue(lvalue_t *lvalue, lex_expect(T_close_square); is_address_got = true; is_member = true; + subscript_depth++; lvalue->is_reference = true; + + /* A subscript designates the pointee, whose qualification is the + * declaration's base qualification rather than `* const`. + */ + lvalue->is_const_qualified = var->is_const_qualified; } else { char token[MAX_ID_LEN]; @@ -3067,9 +3762,12 @@ void read_lvalue(lvalue_t *lvalue, if (!var) error_at("Unknown struct or union member", next_token_loc()); lvalue->type = var->type; + lvalue->decl = var; lvalue->ptr_level = var->ptr_level; lvalue->is_func = var->is_func; lvalue->size = get_size(var); + lvalue->is_const_qualified |= var->is_const_qualified; + subscript_depth = 0; /* if it is an array, get the address of first element instead of * its value. @@ -3099,12 +3797,20 @@ void read_lvalue(lvalue_t *lvalue, 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()); + /* 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) { + if (!var->array_size && + is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) + error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); while (lex_peek(T_plus, NULL) && (var->ptr_level || var->array_size)) { lex_expect(T_plus); if (lvalue->is_reference) { @@ -3122,7 +3828,7 @@ void read_lvalue(lvalue_t *lvalue, * 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) + if (var->ptr_level > 1 || (var->array_size && var->ptr_level)) lvalue->size = PTR_SIZE; else lvalue->size = lvalue->type->size; @@ -3147,18 +3853,13 @@ void read_lvalue(lvalue_t *lvalue, 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. + /* A pointer plus an integer is still a pointer. Array expressions + * first decay to a pointer, adding one level to the declaration's + * element indirection. */ - if (var->ptr_level) { + if (var->ptr_level || var->array_size) { vd->type = lvalue->type; - vd->ptr_level = var->ptr_level; + vd->ptr_level = var->ptr_level + !!var->array_size; } vd->var_name = gen_name(); opstack_push(vd); @@ -3182,12 +3883,18 @@ void read_lvalue(lvalue_t *lvalue, add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, NULL); } 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(); /* For pointer arithmetic, increment by the size of pointed-to type */ - if (lvalue->ptr_level) + if (lvalue->ptr_level > 1) + vd->init_val = PTR_SIZE; + else if (lvalue->ptr_level) vd->init_val = lvalue->type->size; else vd->init_val = 1; @@ -3220,6 +3927,10 @@ void read_lvalue(lvalue_t *lvalue, 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()); + /* This arm appends three entries, so check for room once before * writing any of them. */ @@ -3233,7 +3944,9 @@ void read_lvalue(lvalue_t *lvalue, /* Calculate increment size based on pointer type */ int increment_size = 1; - if (lvalue->ptr_level && !lvalue->is_reference) { + if (lvalue->ptr_level > 1 && !lvalue->is_reference) { + increment_size = PTR_SIZE; + } else if (lvalue->ptr_level && !lvalue->is_reference) { increment_size = lvalue->type->size; } else if (!lvalue->is_reference && lvalue->type && lvalue->type->ptr_level > 0) { @@ -3560,6 +4273,11 @@ bool read_body_assignment(char *token, read_lvalue(&lvalue, var, parent, bb, false, OP_generic, true); size = lvalue.size; + 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; @@ -3581,7 +4299,7 @@ bool read_body_assignment(char *token, add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); } - read_indirect_call(parent, bb); + read_indirect_call(lvalue.decl, parent, bb); return true; } else if (prefix_op == OP_generic) { lex_expect(T_assign); @@ -3593,10 +4311,17 @@ bool read_body_assignment(char *token, 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()); + /* 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) + if (lvalue.ptr_level > 1 && !lvalue.is_reference) + increment_size = PTR_SIZE; + else 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 && @@ -3746,8 +4471,14 @@ bool read_body_assignment(char *token, } 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); + if (is_record_object(vd) && is_record_object(rs1)) { + emit_record_copy(parent, bb, vd, rs1); + } else if (incompatible_const_pointer_conversion(rs1, vd)) { + diagnose_const_pointer_conversion(rs1, vd); + } else { + rs1 = resize_var(parent, bb, rs1, vd); + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + } } } return true; @@ -3873,17 +4604,17 @@ int eval_expression_imm(opcode_t op, int op1, int op2) return res; } -bool read_global_assignment(char *token); -void eval_ternary_imm(int cond, char *token) +bool read_global_assignment_var(var_t *var); +void eval_ternary_imm(int cond, var_t *var) { if (cond == 0) { while (!lex_peek(T_colon, NULL)) { lex_next(); } lex_accept(T_colon); - read_global_assignment(token); + read_global_assignment_var(var); } else { - read_global_assignment(token); + read_global_assignment_var(var); lex_expect(T_colon); while (!lex_peek(T_semicolon, NULL)) { lex_next(); @@ -3891,15 +4622,14 @@ void eval_ternary_imm(int cond, char *token) } } -bool read_global_assignment(char *token) +bool read_global_assignment_var(var_t *var) { - var_t *vd, *rs1, *var; + var_t *vd, *rs1; 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 @@ -3909,6 +4639,7 @@ bool read_global_assignment(char *token) read_literal_param(parent, bb); rs1 = opstack_pop(); vd = var; + diagnose_const_pointer_conversion(rs1, vd); add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); return true; } @@ -3934,7 +4665,7 @@ bool read_global_assignment(char *token) } if (op == OP_ternary) { lex_expect(T_question); - eval_ternary_imm(operand1, token); + eval_ternary_imm(operand1, var); return true; } operand2 = read_primary_constant(); @@ -4006,7 +4737,7 @@ bool read_global_assignment(char *token) if (op_stack_index == 1) { if (op == OP_ternary) { lex_expect(T_question); - eval_ternary_imm(val_stack[0], token); + eval_ternary_imm(val_stack[0], var); } else { vd = require_var(parent); vd->var_name = gen_name(); @@ -4026,7 +4757,7 @@ bool read_global_assignment(char *token) } if (op == OP_ternary) { lex_expect(T_question); - eval_ternary_imm(val_stack[0], token); + eval_ternary_imm(val_stack[0], var); } else { vd = require_var(parent); vd->var_name = gen_name(); @@ -4044,6 +4775,12 @@ bool read_global_assignment(char *token) return false; } +bool read_global_assignment(char *token) +{ + var_t *var = find_global_var(token); + return var && read_global_assignment_var(var); +} + void perform_side_effect(block_t *parent, basic_block_t *bb) { for (int i = 0; i < se_idx; i++) { @@ -4380,7 +5117,6 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) { char token[MAX_ID_LEN]; type_t *type; - var_t *rs1; var_t *var; bool is_const = false; @@ -4402,68 +5138,20 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) 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_union || 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; + 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); - 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; - } - } + parse_struct_field_init(parent, &bb, struct_type, struct_addr, + true); lex_expect(T_close_curly); } else { read_expr(parent, &bb); @@ -4474,8 +5162,7 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) 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); + emit_object_assignment(parent, &bb, var, rhs); } } while (lex_accept(T_comma)) { @@ -4495,69 +5182,20 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) 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_union || 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; + 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); 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; - } - } + parse_struct_field_init(parent, &bb, struct_type, + struct_addr, true); lex_expect(T_close_curly); } else { read_expr(parent, &bb); @@ -4567,8 +5205,7 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) 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); + emit_object_assignment(parent, &bb, nv, rhs); } } } @@ -4586,16 +5223,18 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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; + bool is_static = 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()); + while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL)) { + if (lex_accept(T_static)) + is_static = true; + else { + lex_expect(T_const); + is_const = true; + } } /* statement with prefix */ @@ -4605,7 +5244,13 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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) + 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_long_keyword = lex_peek(T_long, NULL); + if (!is_const && !has_identifier && !has_asterisk && !has_record_keyword && + !has_signed_keyword && !has_long_keyword) error_at("Unexpected token", next_token_loc()); /* is it a variable declaration? Special handling when statement starts with @@ -4637,87 +5282,64 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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); + token_t *type_token = cur_token; + if (lex_peek(T_signed, NULL) || lex_peek(T_long, NULL)) { + type = TY_int; + } else { + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) + find_type_flag = 2; + if (find_type_flag == 2) + lex_peek(T_identifier, token); + type = find_type(token, find_type_flag); + if (find_type_flag == 2) + cur_token = type_token; + } } if (type) { var = require_typed_var(parent, type); + var->is_static = is_static; + var->is_global = is_static; 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_insn(is_static ? GLOBAL_BLOCK : parent, + is_static ? GLOBAL_FUNC->bbs : 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)) { + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || 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, + true); + } else if (lex_peek(T_open_curly, NULL)) { + parse_global_record_init(var, GLOBAL_BLOCK); + } else { + read_global_assignment_var(var); + } + } else 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 */ + is_record_type(var->type)) { 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; + 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); - 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; - } - } + parse_struct_field_init(parent, &bb, struct_type, struct_addr, + true); lex_expect(T_close_curly); } else { read_expr(parent, &bb); @@ -4744,8 +5366,8 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) expr_result = first_elem; } - rs1 = resize_var(parent, &bb, expr_result, var); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + diagnose_const_pointer_conversion(expr_result, var); + emit_object_assignment(parent, &bb, var, expr_result); } } while (lex_accept(T_comma)) { @@ -4756,85 +5378,48 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) /* multiple (partial) declarations */ nv = require_typed_var(parent, type); + nv->is_static = is_static; + nv->is_global = is_static; + nv->is_const_qualified = var->is_const_qualified; read_partial_var_decl(nv, var); /* partial */ - add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); + add_insn(is_static ? GLOBAL_BLOCK : parent, + is_static ? GLOBAL_FUNC->bbs : 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)) { + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->ptr_level > 0)) { + parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, + true); + } else if (lex_peek(T_open_curly, NULL)) { + parse_global_record_init(nv, GLOBAL_BLOCK); + } else { + read_global_assignment_var(nv); + } + } else 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 */ + is_record_type(nv->type)) { 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; + 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); 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; - } - } + parse_struct_field_init(parent, &bb, struct_type, + struct_addr, true); 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); + emit_object_assignment(parent, &bb, nv, opstack_pop()); } } } @@ -4870,6 +5455,9 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) read_ternary_operation(parent, &bb); var_t *addr = opstack_pop(); + if (addr->is_const_qualified) + error_at("assignment of read-only location", next_token_loc()); + /* The width of the store is the pointee's, not the address's. */ int store_sz = get_pointer_element_size(addr); @@ -5034,6 +5622,9 @@ void read_func_body(func_t *func) 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; 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); @@ -5112,7 +5703,7 @@ void print_func_decl(func_t *func, const char *prefix, bool newline) * written with a brace list go through the array initializer; everything else * is a scalar constant. */ -void read_global_init(var_t *var, block_t *block) +void read_global_init_var(var_t *var, block_t *block) { if (!lex_accept(T_assign)) return; @@ -5120,27 +5711,208 @@ void read_global_init(var_t *var, block_t *block) 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 (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_name); + read_global_assignment_var(var); } /* Read one declarator after the first in a global declaration. Each shares the * declaration's base type: "int a = 1, b, c = 3;". */ -void read_global_declarator(block_t *block, type_t *decl_type, bool is_const) +void read_global_declarator(block_t *block, + type_t *decl_type, + bool is_const, + bool is_static) { var_t *nv = require_typed_var(block, decl_type); nv->is_global = true; + nv->is_static = is_static; 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); + read_global_init_var(nv, block); +} + +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; + 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, true); + lex_expect(T_close_curly); +} + +/* 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]; + int find_type_flag = 1; + type_t *compound_type, *target_type; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { + find_type_flag = 2; + lex_ident(T_identifier, type_name); + } else { + lex_ident(T_identifier, type_name); + } + lex_expect(T_close_bracket); + + compound_type = find_type(type_name, find_type_flag); + 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) +{ + char type_name[MAX_ID_LEN]; + type_t *compound_type; + + UNUSED(block); + + lex_expect(T_open_bracket); + lex_ident(T_identifier, type_name); + compound_type = find_type(type_name, true); + 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) +{ + char type_name[MAX_ID_LEN]; + type_t *element_type; + var_t *array; + int find_type_flag = 1; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { + find_type_flag = 2; + lex_ident(T_identifier, type_name); + } else { + lex_ident(T_identifier, type_name); + } + element_type = find_type(type_name, find_type_flag); + lex_expect(T_open_square); + + array = require_typed_var(GLOBAL_BLOCK, element_type); + array->var_name = gen_name(); + array->is_global = true; + if (lex_peek(T_numeric, NULL)) { + char bound[MAX_TOKEN_LEN]; + lex_ident_n(T_numeric, bound, MAX_TOKEN_LEN); + array->array_size = parse_numeric_constant(bound); + if (array->array_size <= 0) + error_at("Array compound literal needs a positive bound", + cur_token_loc()); + } + lex_expect(T_close_square); + lex_expect(T_close_bracket); + + if (!element_type || element_type != var->type) + 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, true); + 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. + */ +void read_global_record_declarator(block_t *block, + type_t *decl_type, + bool is_const, + bool is_static) +{ + var_t *var = require_typed_var(block, decl_type); + var->is_global = true; + var->is_static = is_static; + var->is_const_qualified = is_const; + read_inner_var_decl(var, false, false); + add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); + + if (!lex_accept(T_assign)) + 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 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); + } } -void read_global_decl(block_t *block, bool is_const) +void read_global_decl(block_t *block, bool is_const, bool is_static) { var_t *var = require_var(block); var->is_global = true; + var->is_static = is_static; var->is_const_qualified = is_const; /* new function, or variables under parent */ @@ -5159,6 +5931,16 @@ void read_global_decl(block_t *block, bool is_const) func = add_func(var->var_name, false); memcpy(&func->return_def, var, sizeof(var_t)); + + /* A declaration without a storage-class specifier inherits a prior + * visible function's linkage. Thus `static int f(void); int f(void)` + * remains internal, whereas a first external declaration cannot later + * be made static in the same translation unit. + */ + 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; var_reset_subscripts(&func->return_def); block->locals.size--; read_parameter_list_decl(func, 0); @@ -5228,7 +6010,7 @@ void read_global_decl(block_t *block, bool is_const) /* is a variable */ if (lex_peek(T_assign, NULL)) { - read_global_init(var, block); + read_global_init_var(var, block); } else if (lex_peek(T_semicolon, NULL)) { opstack_pop(); } else if (!lex_peek(T_comma, NULL)) { @@ -5240,7 +6022,8 @@ void read_global_decl(block_t *block, bool is_const) * first, mirroring what the struct-tagged global path already does. */ while (lex_accept(T_comma)) - read_global_declarator(block, var->type, is_const); + read_global_declarator(block, var->type, var->is_const_qualified, + is_static); lex_expect(T_semicolon); return; @@ -5299,10 +6082,17 @@ 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; - /* Handle const qualifier */ - if (lex_accept(T_const)) - is_const = true; + /* These specifiers may appear in either order. */ + while (lex_peek(T_const, NULL) || lex_peek(T_static, NULL)) { + if (lex_accept(T_const)) + is_const = true; + else { + lex_expect(T_static); + is_static = true; + } + } if (lex_accept(T_struct)) { int i = 0, size = 0; @@ -5316,52 +6106,11 @@ void read_global_statement(void) 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); - } - } - } + read_global_record_declarator(block, decl_type, is_const, + is_static); + while (lex_accept(T_comma)) + read_global_record_declarator(block, decl_type, is_const, + is_static); lex_expect(T_semicolon); return; } @@ -5395,6 +6144,15 @@ void read_global_statement(void) type->size = size; type->num_fields = i; + + /* A record definition may be followed by its declarators, as in "struct + * pair { int x, y; } first, *second;". + */ + if (!lex_peek(T_semicolon, NULL)) { + read_global_record_declarator(block, type, is_const, is_static); + while (lex_accept(T_comma)) + read_global_record_declarator(block, type, is_const, is_static); + } lex_expect(T_semicolon); } else if (lex_accept(T_union)) { int i = 0, max_size = 0; @@ -5434,6 +6192,12 @@ void read_global_statement(void) type->size = max_size; type->num_fields = i; + + if (!lex_peek(T_semicolon, NULL)) { + read_global_record_declarator(block, type, is_const, is_static); + while (lex_accept(T_comma)) + read_global_record_declarator(block, type, is_const, is_static); + } lex_expect(T_semicolon); } else if (lex_accept(T_typedef)) { if (lex_accept(T_enum)) { @@ -5565,6 +6329,7 @@ void read_global_statement(void) type->size = max_size; type->num_fields = i; type->base_type = TYPE_typedef; + type->is_union = true; if (tag && has_union_def == 1) { strcpy(token, tag->type_name); @@ -5584,14 +6349,38 @@ void read_global_statement(void) 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); + bool typedef_const = lex_accept(T_const); + bool is_signed = lex_accept(T_signed); + bool is_long = lex_accept(T_long); + if (is_long) { + is_signed |= lex_accept(T_signed); + typedef_const |= lex_accept(T_const); + } + + if (is_long) { + if (lex_accept(T_long)) + error_at("long long is not supported yet", cur_token_loc()); + if (lex_peek(T_identifier, base_type) && + !strcmp(base_type, "int")) + lex_expect(T_identifier); + base = TY_int; + } else if (is_signed && (!lex_peek(T_identifier, base_type) || + (strcmp(base_type, "int") && + strcmp(base_type, "char") && + strcmp(base_type, "short")))) { + base = TY_int; + } else { + 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; + type->is_const_qualified = + typedef_const || base->is_const_qualified; /* Handle pointer types in typedef: typedef char *string; */ while (lex_accept(T_asterisk)) { @@ -5602,8 +6391,9 @@ void read_global_statement(void) 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 if (lex_peek(T_identifier, NULL) || lex_peek(T_signed, NULL) || + lex_peek(T_long, NULL)) { + read_global_decl(block, is_const, is_static); } else error_at("Syntax error in global statement", next_token_loc()); } diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 3e38c15c..3fc848cb 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -2152,6 +2152,13 @@ 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; break; } /* Fallback generic write */ @@ -2325,6 +2332,44 @@ 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); + 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 (param_idx < MAX_ARGS_IN_REG) { + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = param_idx; + ir->dest = dest; + } else { + ir = bb_add_ph2_ir(bb, OP_load); + ir->src0 = (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. diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index fd1b2771..539e2df0 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,27 @@ 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" +} + # Stack Alignment Tests test_stack_alignment_basic() @@ -544,6 +566,7 @@ test_two_args test_four_args test_five_args test_eight_args +test_long_args_and_return echo "" echo -e "${CYAN}Running Stack Alignment Tests...${NC}" diff --git a/tests/driver.sh b/tests/driver.sh index 0f3afd66..71cc25e6 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -347,6 +347,35 @@ function try_compile_error() 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 +} + function items() { local expected="$1" @@ -747,6 +776,27 @@ EOF # Category: Compound Literals begin_category "Compound Literals" "Testing C99 compound literal features" +# 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 + +try_compile_error << EOF +long long value; +int main(void) { return 0; } +EOF + # Compound literal support - C90/C99 compliant implementation Basic struct # compound literals (verified working) try_ 42 << EOF @@ -757,6 +807,25 @@ int main() { } 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() { @@ -782,6 +851,276 @@ int main() { 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 + +# 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 + +# 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() { @@ -798,6 +1137,46 @@ int main() { } 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_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 @@ -840,6 +1219,14 @@ int main() { } 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; }; @@ -855,6 +1242,83 @@ int main() { 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 + +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} }; @@ -1176,13 +1640,68 @@ EOF # Category: Functions begin_category "Functions" "Testing function definitions, calls, and recursion" -# functions +# `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 +# 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 + try_ 55 << EOF int sum(int m, int n) { int acc; @@ -1661,6 +2180,22 @@ int main() { } 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 + # Pointer arithmetic tests # Basic integer pointer difference @@ -1702,13 +2237,73 @@ int main() { } EOF -# Test with void* cast (treated as char*) -try_ 8 << 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; - void *vp2 = array + 8; - return (char*)vp2 - (char*)vp1; + 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 + +# A pointer-to-void-pointer advances over pointer objects, so it remains valid. +try_ 1 << EOF +int main() { + void *values[2]; + void **p = values; + p += 1; + return (char *)p - (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 @@ -1938,6 +2533,33 @@ int main() { } 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 @@ -1976,6 +2598,34 @@ EOF # Category: Arrays begin_category "Arrays" "Testing array declarations, indexing, and operations" +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 @@ -2322,9 +2972,545 @@ int main() } 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 + +# 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 + +# 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 + +# 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 + +# 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 << EOF +int helper(void); +static int helper(void) { return 42; } +int main(void) { return helper(); } +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 + +try_ 42 << EOF +int main(void) { + int value = 42; + const int *read_only = (const int *)&value; + return *read_only; +} +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 +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 + +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 + +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() { @@ -2369,11 +3555,11 @@ int main() { } EOF -# Test 6: Non-const pointer to const data +# Test 6: Pointer to const data try_ 35 << EOF int main() { const int value = 35; - int *ptr = &value; + const int *ptr = &value; return *ptr; } EOF @@ -2541,7 +3727,22 @@ items 24 "short s; s = 6; s *= 4; return s;" begin_category "Sizeof Operator" "Testing sizeof operator on various types" # sizeof -expr 0 "sizeof(void)" +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)" diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index 9bcbdbbd..180477e0 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,27 @@ 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" +} + # Stack Alignment Tests # # The AMD64 ABI requires RSP to be 16-byte aligned at a call site, which C gives @@ -558,6 +580,7 @@ test_two_args test_four_args test_five_args test_eight_args +test_long_args_and_return echo "" echo -e "${CYAN}Running Stack Alignment Tests...${NC}" From d6beb312e3f595523a8185c885bc17c2123488ac Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 9 Sep 2026 23:52:47 +0800 Subject: [PATCH 02/40] Add unsigned, long long and enum types Unsigned types work end to end, through the parser, the SSA folds and every backend, including high-bit software division on Arm, unary and conditional operators, narrow updates and reads, the integer promotions and conversions to bool. Integer literals take the C99 suffixes with checked ranges, repeated or contradictory type specifiers are diagnosed, and long keeps a rank of its own. long long is added on x86-64 and AArch64: typedefs, literals, wide global initializers and their operations, comparisons and division at the operand width, and ABI spill slots; 32-bit targets permit sizeof of it and pointers to it. Enumerations are declared in every scope, block static storage is complete, global pointers are initialized with addresses, unsized character arrays take their extent from a string, logical negation yields int, and a global object may not reuse a function's name. Arm sign-extends byte array loads, 32-bit targets keep narrow widths, and the driver gates wide cases on the target width. --- src/arm-codegen.c | 124 ++- src/arm.c | 58 +- src/arm64-codegen.c | 124 ++- src/defs.h | 50 +- src/globals.c | 81 +- src/lexer.c | 28 +- src/parser.c | 2228 +++++++++++++++++++++++++++++++++++++------ src/peephole.c | 42 +- src/reg-alloc.c | 159 ++- src/riscv-codegen.c | 125 ++- src/riscv.c | 26 +- src/ssa.c | 38 +- src/x64-codegen.c | 234 +++-- src/x64.c | 30 +- tests/arm-abi.sh | 105 ++ tests/arm64-abi.sh | 21 +- tests/driver.sh | 1376 +++++++++++++++++++++++++- tests/x64-abi.sh | 158 +++ 18 files changed, 4525 insertions(+), 482 deletions(-) diff --git a/src/arm-codegen.c b/src/arm-codegen.c index 8ce95099..3841687e 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -46,10 +46,12 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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 uses eight bits. A larger + * signed-byte offset is materialized with MOVW/MOVT/ADD before the + * load, exactly as an offset beyond the general load range is. */ - if (ph2_ir->src0 > 4095) + if (ph2_ir->src0 > 4095 || (ph2_ir->size_bytes == 1 && + !ph2_ir->is_unsigned && ph2_ir->src0 > 255)) elf_offset += 16; else if (ph2_ir->src0 >= 0) elf_offset += 4; @@ -115,7 +117,7 @@ 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: @@ -141,11 +143,19 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 24; 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->src0_is_unsigned) + elf_offset += 8; + else + elf_offset += 4; return; case OP_cast: elf_offset += 4; @@ -331,12 +341,31 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_load: case OP_global_load: interm = ph2_ir->op == OP_load ? __sp : __r12; - if (ph2_ir->src0 > 4095) { + + /* LDRSB's immediate field is only eight bits, unlike LDRB's 12-bit + * field. Materialize a larger signed-byte offset before loading. + */ + if (ph2_ir->src0 > 4095 || + (ph2_ir->size_bytes == 1 && !ph2_ir->is_unsigned && + ph2_ir->src0 > 255)) { 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: @@ -369,9 +398,11 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_read: 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 @@ -498,9 +529,13 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_mod: 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)); } @@ -509,13 +544,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 { @@ -534,9 +581,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)); @@ -563,7 +618,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__sll(__AL, rd, rn, rm)); return; case OP_rshift: - emit(__sra(__AL, rd, rn, rm)); + emit(ph2_ir->src0_is_unsigned ? __srl(__AL, rd, rn, rm) + : __sra(__AL, rd, rn, rm)); return; case OP_eq: case OP_neq: @@ -573,7 +629,9 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_leq: emit(__cmp_r(__AL, rn, rm)); 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)); @@ -596,8 +654,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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)); @@ -610,6 +672,12 @@ 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->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 { diff --git a/src/arm.c b/src/arm.c index 6bc83555..ed43dce1 100644 --- a/src/arm.c +++ b/src/arm.c @@ -52,6 +52,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 +88,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 +96,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"); } @@ -133,19 +134,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) @@ -317,6 +324,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 +360,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 +425,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..323beae7 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -54,6 +54,56 @@ void a64_mov_imm(int d, int v) emit(0xd2800000 | ((v & 0xffff) << 5) | d); emit(0xf2800000 | (((v >> 16) & 0xffff) << 5) | d | (1 << 21)); } + +/* 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(0xd2800000 | ((value & 0xffff) << 5) | d); + emit(0xf2800000 | (((value >> 16) & 0xffff) << 5) | d | (1 << 21)); +} + +/* 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(0xd2800000 | ((low & 0xffff) << 5) | d); + emit(0xf2800000 | (((low >> 16) & 0xffff) << 5) | d | (1 << 21)); + emit(0xf2800000 | ((high & 0xffff) << 5) | d | (2 << 21)); + emit(0xf2800000 | (((high >> 16) & 0xffff) << 5) | d | (3 << 21)); +} + +int a64_div_opcode(ph2_ir_t *p) +{ + if (p->size_bytes == 8) { + if (p->src0_is_unsigned || p->src1_is_unsigned) + return 0x9ac00800; /* UDIV Xd, Xn, Xm */ + return 0x9ac00c00; /* SDIV Xd, Xn, Xm */ + } + if (p->src0_is_unsigned || p->src1_is_unsigned) + return 0x1ac00800; /* UDIV Wd, Wn, Wm */ + return 0x1ac00c00; /* SDIV Wd, Wn, Wm */ +} + +int a64_rshift_opcode(ph2_ir_t *p) +{ + if (p->size_bytes == 8) { + if (p->src0_is_unsigned) + return 0x9ac02400; /* LSRV Xd, Xn, Xm */ + return 0x9ac02800; /* ASRV Xd, Xn, Xm */ + } + if (p->src0_is_unsigned) + return 0x1ac02400; /* LSRV Wd, Wn, Wm */ + return 0x1ac02800; /* ASRV Wd, Wn, Wm */ +} void a64_add(int d, int n, int m) { int op = (d == A64_SP || n == A64_SP) ? 0x8b206000 : 0x8b000000; @@ -85,15 +135,18 @@ void a64_sub_sxtw(int d, int n, int m) /* 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 ? 0xd3401c00 : 0x93401c00) | (n << 5) | d); else if (size == 2) - emit(0x93403c00 | (n << 5) | d); - else if (size == 4) - a64_sxtw(d, n); - else + emit((is_unsigned ? 0xd3403c00 : 0x93403c00) | (n << 5) | d); + else if (size == 4) { + if (is_unsigned) + emit(0xd3407c00 | (n << 5) | d); + else + a64_sxtw(d, n); + } else a64_mov(d, n); } @@ -214,10 +267,10 @@ 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) +int a64_cond(opcode_t op, bool is_unsigned) { switch (op) { case OP_eq: @@ -225,13 +278,13 @@ int a64_cond(opcode_t op) case OP_neq: return 1; case OP_geq: - return 10; + return is_unsigned ? 2 : 10; /* HS / GE */ case OP_lt: - return 11; + return is_unsigned ? 3 : 11; /* LO / LT */ case OP_gt: - return 12; + return is_unsigned ? 8 : 12; /* HI / GT */ default: - return 13; + return is_unsigned ? 9 : 13; /* LS / LE */ } } @@ -251,7 +304,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: @@ -403,7 +456,12 @@ void emit_ph2_ir(ph2_ir_t *p) 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) @@ -463,38 +521,44 @@ 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((p->size_bytes == 8 ? 0x9b007c00 : 0x1b007c00) | (m << 16) | + (n << 5) | d); return; case OP_div: - emit(0x1ac00c00 | (m << 16) | (n << 5) | d); + emit(a64_div_opcode(p) | (m << 16) | (n << 5) | d); 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_opcode(p) | (m << 16) | (n << 5) | A64_IP0); + emit((p->size_bytes == 8 ? 0x9b008000 : 0x1b008000) | (m << 16) | + (n << 10) | (A64_IP0 << 5) | d); return; case OP_lshift: - emit(0x1ac02000 | (m << 16) | (n << 5) | d); + emit((p->size_bytes == 8 ? 0x9ac02000 : 0x1ac02000) | (m << 16) | + (n << 5) | d); return; case OP_rshift: - emit(0x1ac02800 | (m << 16) | (n << 5) | d); + emit(a64_rshift_opcode(p) | (m << 16) | (n << 5) | d); return; case OP_bit_and: - emit(0x0a000000 | (m << 16) | (n << 5) | d); + emit((p->size_bytes == 8 ? 0x8a000000 : 0x0a000000) | (m << 16) | + (n << 5) | d); return; case OP_bit_or: - emit(0x2a000000 | (m << 16) | (n << 5) | d); + emit((p->size_bytes == 8 ? 0xaa000000 : 0x2a000000) | (m << 16) | + (n << 5) | d); return; case OP_bit_xor: - emit(0x4a000000 | (m << 16) | (n << 5) | d); + emit((p->size_bytes == 8 ? 0xca000000 : 0x4a000000) | (m << 16) | + (n << 5) | d); return; case OP_negate: - emit(0x4b0003e0 | (n << 16) | d); + emit((p->size_bytes == 8 ? 0xcb0003e0 : 0x4b0003e0) | (n << 16) | d); return; case OP_bit_not: - emit(0x2a2003e0 | (n << 16) | d); + emit((p->size_bytes == 8 ? 0xaa2003e0 : 0x2a2003e0) | (n << 16) | d); return; case OP_eq: case OP_neq: @@ -505,7 +569,9 @@ void emit_ph2_ir(ph2_ir_t *p) 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); + ((a64_cond(p->op, p->src0_is_unsigned || p->src1_is_unsigned) ^ 1) + << 12) | + d); return; /* Width follows the operand, exactly as the comparisons above do. A pointer @@ -524,7 +590,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,10 +598,10 @@ 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: diff --git a/src/defs.h b/src/defs.h index af343729..b0c63fb8 100644 --- a/src/defs.h +++ b/src/defs.h @@ -351,6 +351,7 @@ typedef enum { T_const, /* const qualifier */ T_static, T_signed, + T_unsigned, T_long, /* C pre-processor directives */ T_cppd_include, @@ -417,6 +418,8 @@ typedef enum { TYPE_int, TYPE_char, TYPE_short, + TYPE_long, + TYPE_long_long, TYPE_struct, TYPE_union, TYPE_typedef @@ -544,6 +547,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 @@ -556,7 +561,8 @@ struct var { int ptr_level; bool is_func; bool is_global; - bool is_static; /* declaration used the static storage class */ + bool is_static; /* declaration used the static storage class */ + bool has_initializer; /* a file-scope definition supplied an initializer */ bool is_const_qualified; /* true if variable has const qualifier */ bool is_const_pointer; /* true for the outermost `* const` qualifier */ bool address_taken; /* true if variable address was taken (&var) */ @@ -591,9 +597,11 @@ 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_unsized_array; /* `T name[]`: bound is supplied by initializer */ + 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 */ + 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. */ @@ -673,6 +681,12 @@ typedef struct func func_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; struct block *parent; func_t *func; struct block *next; @@ -728,11 +742,19 @@ 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; }; typedef struct ph2_ir ph2_ir_t; @@ -754,12 +776,23 @@ struct type { int ptr_level; /* pointer level for typedef pointer types */ bool is_union; /* preserves union semantics for anonymous typedef unions */ bool is_const_qualified; /* qualifier carried by a scalar typedef */ + /* 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; }; /* 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; @@ -769,11 +802,18 @@ typedef struct { } 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; diff --git a/src/globals.c b/src/globals.c index 0810b8cd..a18ee22d 100644 --- a/src/globals.c +++ b/src/globals.c @@ -37,9 +37,16 @@ int types_idx = 0; type_t *TY_void; type_t *TY_char; +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; /* Arenas */ @@ -591,7 +598,8 @@ type_t *find_type(const char *type_name, int flag) /* If it is a forwardly declared alias of a structure, return * the base structure type. */ - if (TYPES[i].base_type == TYPE_typedef && TYPES[i].size == 0) + if (TYPES[i].base_type == TYPE_typedef && TYPES[i].size == 0 && + TYPES[i].ptr_level == 0) return TYPES[i].base_struct; return &TYPES[i]; } @@ -652,6 +660,8 @@ 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->parent = parent; blk->func = func; blk->next = NULL; @@ -834,10 +844,16 @@ int parse_numeric_constant(const char *buffer) int i = 0; 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)) @@ -851,7 +867,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'); @@ -939,6 +956,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); } @@ -947,6 +965,63 @@ 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; +} + +constant_t *find_scoped_constant(char alias[], block_t *block) +{ + for (; block; block = block->parent) { + for (constant_t *constant = block->constants; constant; + constant = constant->next) { + if (!strcmp(constant->alias, alias)) + return constant; + } + } + return find_constant(alias); +} + +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_type_tag(char name[], block_t *block) +{ + for (; block; block = block->parent) { + for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) + if (!strcmp(tag->name, name)) + return tag->type; + } + return find_type(name, 1); +} + +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; +} + var_t *find_member(const char token[], type_t *type) { /* If it is a forwardly declared alias of a structure, switch to the base diff --git a/src/lexer.c b/src/lexer.c index 5021e125..59cc43bf 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -12,7 +12,7 @@ /* Hash table constants */ #define NUM_DIRECTIVES 11 -#define NUM_KEYWORDS 21 +#define NUM_KEYWORDS 22 /* Token mapping structure for elegant initialization */ typedef struct { @@ -90,6 +90,7 @@ void lex_init_keywords(void) {"const", T_const}, {"static", T_static}, {"signed", T_signed}, + {"unsigned", T_unsigned}, {"long", T_long}, }; @@ -496,6 +497,31 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) } } + /* 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); diff --git a/src/parser.c b/src/parser.c index 15d47a68..eb4fea06 100644 --- a/src/parser.c +++ b/src/parser.c @@ -52,6 +52,7 @@ 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); bool global_compound_literal_starts_here(void) { @@ -138,6 +139,7 @@ var_t *require_var(block_t *blk) 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; @@ -202,6 +204,59 @@ var_t *require_deref_var(block_t *blk, type_t *type, int ptr) return var; } +/* 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) +{ + if (!type || !type->ptr_level) + return type; + + if (type->base_type == TYPE_typedef && type->base_struct) + return type->base_struct; + + switch (type->base_type) { + case TYPE_void: + return TY_void; + case TYPE_char: + return type->is_unsigned ? TY_uchar : 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; + default: + return type; + } +} + +/* 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. + */ +int pointer_typedef_pointee_size(type_t *type, type_t *pointee) +{ + if (!type || !type->ptr_level || type->base_type != TYPE_typedef || + !type->num_fields) + return pointee == TY_void ? 1 : pointee->size; + + 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; + } + return size; +} + /* The next free field slot of @type. * * Struct and union bodies fill these in from two places -- one field per @@ -249,6 +304,16 @@ var_t *opstack_pop(void) return operand_stack[--operand_stack_idx]; } +/* 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) +{ + if (operand_stack_idx && operand_stack[operand_stack_idx - 1] == var) + opstack_pop(); +} + void read_expr(block_t *parent, basic_block_t **bb); int write_symbol(const char *data) @@ -404,6 +469,34 @@ var_t *truncate_unchecked(block_t *block, return rd; } +var_t *normalize_bool(block_t *block, basic_block_t **bb, var_t *var) +{ + var_t *zero; + var_t *rd; + + if (var->type == TY_bool && !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 != 0; + 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; +} + 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, @@ -413,6 +506,9 @@ var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) if (is_from_ptr && is_to_ptr) return from; + if (!is_to_ptr && to->type == TY_bool) + return normalize_bool(block, bb, from); + int from_size = get_size(from), to_size = get_size(to); if (from_size > to_size) { @@ -437,6 +533,16 @@ var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) return promote_unchecked(block, bb, from, to->type, to->ptr_level); } + /* 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. + */ + if (!is_from_ptr && !is_to_ptr && to->type && to->type->is_unsigned && + to_size < PTR_SIZE) + return truncate_unchecked(block, bb, from, to->type, to->ptr_level); + return from; } @@ -500,6 +606,7 @@ void diagnose_const_pointer_conversion(const var_t *from, const var_t *to) } void read_parameter_list_decl(func_t *func, bool anon); +var_t *integer_promote_operand(block_t *parent, basic_block_t **bb, var_t *var); /* Forward declaration for ternary handling used by initializers */ void read_ternary_operation(block_t *parent, basic_block_t **bb); @@ -1077,6 +1184,14 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) } perform_side_effect(parent, bb); + /* 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. + */ + 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; @@ -1215,7 +1330,7 @@ basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) return else_; } -int read_const_expr(void); +int read_const_expr(block_t *scope); void parse_array_init(var_t *var, block_t *parent, @@ -1226,6 +1341,10 @@ void parse_array_init(var_t *var, int inferred_size = 0; var_t *base_addr = NULL; bool is_implicit = (var->array_size == 0); + block_t *initializer_scope = parent; + + if (parent == GLOBAL_BLOCK && var->scope && var->scope != GLOBAL_BLOCK) + initializer_scope = var->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 @@ -1268,7 +1387,7 @@ void parse_array_init(var_t *var, int prior_count = count; if (lex_accept(T_open_square)) { - count = read_const_expr(); + count = read_const_expr(initializer_scope); lex_expect(T_close_square); lex_expect(T_assign); } @@ -1315,16 +1434,41 @@ void parse_array_init(var_t *var, * 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()); + if (parent == GLOBAL_BLOCK && + initializer_scope != GLOBAL_BLOCK && + !lex_peek(T_string, NULL)) { + /* 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. + */ + 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); + + if (!lex_peek(T_numeric, NULL) && + !lex_peek(T_minus, NULL) && + !lex_peek(T_string, NULL) && + !lex_peek(T_char, NULL) && !enum_constant) + error_at( + "Global array initialization requires constant " + "values", + next_token_loc()); + } - read_expr(parent, bb); - read_ternary_operation(parent, bb); - val = opstack_pop(); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + val = opstack_pop(); + } } if (is_implicit && count >= MAX_IMPLICIT_ARRAY) @@ -1385,6 +1529,7 @@ void parse_array_init(var_t *var, if (var->ptr_level > 0) var->ptr_level = 0; var->array_size = inferred_size; + var->has_unsized_array = false; } } @@ -1428,7 +1573,7 @@ void parse_array_compound_literal(var_t *var, if (!lex_peek(T_close_curly, NULL)) { for (;;) { if (lex_accept(T_open_square)) { - count = read_const_expr(); + count = read_const_expr(parent); lex_expect(T_close_square); lex_expect(T_assign); } @@ -1596,23 +1741,23 @@ var_t *scalarize_array_literal_if_needed(block_t *parent, #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(block_t *scope); -int read_const_expr_operand(void) +int read_const_expr_operand(block_t *scope) { char buffer[MAX_TOKEN_LEN]; if (lex_accept(T_minus)) - return -read_const_expr_operand(); + return -read_const_expr_operand(scope); if (lex_accept(T_plus)) - return read_const_expr_operand(); + return read_const_expr_operand(scope); if (lex_accept(T_bit_not)) - return ~read_const_expr_operand(); + return ~read_const_expr_operand(scope); if (lex_accept(T_log_not)) - return !read_const_expr_operand(); + return !read_const_expr_operand(scope); if (lex_accept(T_open_bracket)) { - int res = read_const_expr(); + int res = read_const_expr(scope); lex_expect(T_close_bracket); return res; } @@ -1629,7 +1774,7 @@ int read_const_expr_operand(void) } if (lex_peek(T_identifier, buffer)) { lex_expect(T_identifier); - constant_t *con = find_constant(buffer); + constant_t *con = find_scoped_constant(buffer, scope); if (con) return con->value; error_at("Identifier is not an integer constant", next_token_loc()); @@ -1638,13 +1783,13 @@ int read_const_expr_operand(void) return 0; } -int read_const_expr(void) +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; - val_stack[val_n++] = read_const_expr_operand(); + val_stack[val_n++] = read_const_expr_operand(scope); while (true) { opcode_t op = get_operator(); @@ -1663,9 +1808,9 @@ int read_const_expr(void) op_stack[op_n], val_stack[val_n - 1], val_stack[val_n]); } lex_expect(T_question); - int then_val = read_const_expr(); + int then_val = read_const_expr(scope); lex_expect(T_colon); - int else_val = read_const_expr(); + int else_val = read_const_expr(scope); return val_stack[0] ? then_val : else_val; } @@ -1682,7 +1827,7 @@ int read_const_expr(void) 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(); + val_stack[val_n++] = read_const_expr_operand(scope); } while (op_n > 0) { @@ -1773,7 +1918,7 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) else vd->ptr_level++; } else { - int next_dim = read_const_expr(); + int next_dim = read_const_expr(vd->scope); if (dim == 0) { vd->array_size = next_dim; @@ -1789,8 +1934,17 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) lex_expect(T_close_square); dims++; } - if (first_dim_empty && dims == 1) - vd->ptr_level++; + if (first_dim_empty && dims == 1) { + /* An array parameter adjusts to a pointer, but an object declarator + * such as `char text[] = "hi"` has an inferred array bound. Keeping + * those cases distinct lets its initializer create writable array + * storage instead of a pointer to string data. + */ + if (is_param) + vd->ptr_level++; + else + vd->has_unsized_array = true; + } vd->is_func = false; } } @@ -1806,13 +1960,67 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) * unqualified `const_int_pointer` only qualifies the pointed-to object. */ bool declaration_const = vd->is_const_qualified; - bool is_signed = lex_accept(T_signed); - bool is_const = lex_accept(T_const); - bool is_long = lex_accept(T_long); - if (is_long) { - is_signed |= lex_accept(T_signed); - is_const |= lex_accept(T_const); + bool is_signed = false; + bool is_unsigned = false; + bool is_const = false; + bool is_long = false; + bool is_long_long = false; + type_t *leading_scalar_type = NULL; + + /* The declaration dispatcher normally leaves the base type for this routine + * to consume. C99 also permits the modifier after that base, e.g. `short + * unsigned value`; retain the base while consuming its following scalar + * modifiers instead of mistaking `unsigned` for the declarator name. + */ + if (lex_peek(T_identifier, type_name) && + (!strcmp(type_name, "char") || !strcmp(type_name, "short") || + !strcmp(type_name, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && + (after_base->kind == T_signed || after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(type_name, true); + } + } + + /* C permits these declaration specifiers in either order. Consume the + * scalar set as a group so `const unsigned int` and `unsigned const int` + * follow the same path. + */ + while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_const, NULL) || lex_peek(T_long, NULL)) { + 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_const)) + is_const = true; + else { + lex_expect(T_long); + if (is_long_long) + error_at("too many long type specifiers", cur_token_loc()); + if (is_long) + is_long_long = true; + else + is_long = true; + } } + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && lex_peek(T_identifier, type_name) && + !strcmp(type_name, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + bool is_enum_type = lex_accept(T_enum); + if (is_enum_type && (is_signed || is_unsigned || is_long)) + error_at("enum type cannot be combined with integer specifiers", + cur_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; @@ -1823,21 +2031,53 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) * spelling to be omitted, while `signed char` and `signed short` retain * their explicit base type. */ - if (is_long) { - /* C permits long and long int to share int's representation. shecc's - * current ABI is 32-bit for both, which meets C99's minimum range; - * reserve a second long for the later required long-long widening. + if (is_enum_type) { + lex_ident(T_identifier, type_name); + type = find_type_tag(type_name, vd->scope); + } else if (is_unsigned) { + if (is_long) { + if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = is_long_long ? TY_ulong_long : TY_ulong; + } else if (leading_scalar_type == TY_char) { + type = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = TY_ushort; + } else if (lex_peek(T_identifier, type_name) && + (!strcmp(type_name, "char") || !strcmp(type_name, "short") || + !strcmp(type_name, "int"))) { + lex_expect(T_identifier); + if (!strcmp(type_name, "char")) + type = TY_uchar; + else if (!strcmp(type_name, "short")) + type = TY_ushort; + else + type = TY_uint; + } else + type = TY_uint; /* `unsigned` is `unsigned int` */ + } else if (is_long) { + /* The current ABI gives long the same 32-bit representation as int, + * while retaining its distinct C type and rank. */ - if (lex_accept(T_long)) - error_at("long long is not supported yet", cur_token_loc()); if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) lex_expect(T_identifier); - type = TY_int; + type = is_long_long ? TY_long_long : TY_long; + } else if (is_signed && leading_scalar_type && + (leading_scalar_type == TY_char || + leading_scalar_type == TY_short)) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = leading_scalar_type; } else if (is_signed && find_type_flag == 1 && (!lex_peek(T_identifier, type_name) || (strcmp(type_name, "int") && strcmp(type_name, "char") && strcmp(type_name, "short")))) { type = TY_int; + } else if (leading_scalar_type) { + type = leading_scalar_type; } else { lex_ident(T_identifier, type_name); type = find_type(type_name, find_type_flag); @@ -1868,6 +2108,17 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) } read_inner_var_decl(vd, anon, is_param); + + /* A 32-bit target can carry a pointer to an eight-byte object without a + * paired-value register representation. Keep direct wide objects, arrays, + * parameters, returns, and function-pointer returns rejected until that + * lowering exists, but admit declarations such as `long long *p` and + * typedef aliases used exclusively behind a pointer. + */ + if (PTR_SIZE < 8 && vd->type && vd->type->base_type == TYPE_long_long && + (!(vd->ptr_level || vd->type->ptr_level) || vd->is_func)) + error_at("long long value needs 64-bit target lowering", + cur_token_loc()); } /* starting next_token, need to check the type */ @@ -1898,8 +2149,9 @@ void read_parameter_list_decl(func_t *func, bool anon) } while (lex_peek(T_identifier, NULL) || lex_peek(T_const, NULL) || - lex_peek(T_signed, NULL) || lex_peek(T_long, NULL) || - lex_peek(T_struct, NULL) || lex_peek(T_union, 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; if (lex_accept(T_const)) @@ -1959,14 +2211,201 @@ void read_literal_param(block_t *parent, basic_block_t *bb) 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 literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN], + combined[MAX_LINE_LEN]; + int len; + + lex_ident(T_string, literal); + unescape_string(literal, combined, MAX_LINE_LEN); + while (lex_peek(T_string, NULL)) { + int used = strlen(combined); + + lex_ident(T_string, literal); + unescape_string(literal, unescaped, MAX_LINE_LEN - used); + if (used + (int) strlen(unescaped) >= MAX_LINE_LEN - 1) + error_at("Concatenated string literal too long", cur_token_loc()); + strcpy(combined + used, unescaped); + } + + len = strlen(combined) + 1; + if (var->has_unsized_array) { + var->array_size = len; + var->has_unsized_array = false; + } else if (len > var->array_size) + error_at("String initializer is too long for character array", + cur_token_loc()); + + for (int i = 0; i < len; i++) { + var_t *value = require_var(parent); + var_t *addr; + + value->var_name = gen_name(); + value->init_val = (unsigned char) combined[i]; + 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); + } +} + +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) +{ + if (value->type->size >= 8) + return; + if (PTR_SIZE < 8) + error_at("long long literal needs 64-bit target lowering", + cur_token_loc()); + 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; +} + void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) { char token[MAX_TOKEN_LEN]; - int value = 0; + 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; @@ -1975,6 +2414,8 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) if (token[0] == '0') { if ((token[1] | 32) == 'x') { /* hexdecimal */ i = 2; + base = 16; + is_decimal = false; do { c = token[i++]; if (isdigit(c)) @@ -1987,39 +2428,89 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) error_at("Invalid numeric constant", cur_token_loc()); } - value = (value * 16) + c; + 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'; - value = (value * 2) + c; + 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'; - value = (value * 8) + c; + 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'; - value = (value * 10) + c; + 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 (is_neg) - value = -value; + 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()); var_t *vd = require_var(parent); vd->var_name = gen_name(); + if (is_long_long || value_hi || + (is_decimal && !has_unsigned_suffix && !is_neg && + numeric_literal_needs_wide_path(token)) || + (is_decimal && !has_unsigned_suffix && + (value > 0x80000000U || (value == 0x80000000U && !is_neg)))) { + if (PTR_SIZE < 8) + error_at("long long literal needs 64-bit target lowering", + cur_token_loc()); + 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; + + /* Keep the exact 2^31 magnitude as an int bit pattern: C spells INT_MIN as + * unary minus plus that token, and the unary operator is parsed after the + * literal. Larger decimal values really need long long here. + */ + if (is_neg) { + value = 0 - value; + value_hi = ~value_hi + (value == 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); } @@ -2034,6 +2525,7 @@ void read_char_param(block_t *parent, basic_block_t *bb) var_t *vd = require_typed_var(parent, TY_char); vd->var_name = gen_name(); vd->init_val = unescaped[0]; + vd->is_const = true; opstack_push(vd); add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); } @@ -2422,31 +2914,140 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) lex_expect(T_open_bracket); + /* 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); + vd = require_var(parent); + vd->init_val = array->array_size * array->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); + return; + } + } + /* Check if this is sizeof(type) or sizeof(expression) */ - bool has_signed_type = lex_accept(T_signed); - bool has_long_type = lex_accept(T_long); + bool has_signed_type = false; + bool has_unsigned_type = false; + int long_type_count = 0; + type_t *leading_scalar_type = NULL; + + if (lex_peek(T_identifier, token) && + (!strcmp(token, "char") || !strcmp(token, "short") || + !strcmp(token, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && + (after_base->kind == T_signed || after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(token, true); + } + } + while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL) || lex_peek(T_const, NULL)) { + if (lex_accept(T_signed)) { + if (has_signed_type) + error_at("duplicate signed type specifier", cur_token_loc()); + has_signed_type = true; + } else if (lex_accept(T_unsigned)) { + if (has_unsigned_type) + error_at("duplicate unsigned type specifier", cur_token_loc()); + has_unsigned_type = true; + } else if (lex_accept(T_long)) + long_type_count++; + else + lex_expect(T_const); + } + if (long_type_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + if (has_signed_type && has_unsigned_type) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && lex_peek(T_identifier, token) && + !strcmp(token, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + bool has_long_type = long_type_count > 0; + bool has_enum_type = lex_accept(T_enum); + if (has_enum_type && + (has_signed_type || has_unsigned_type || has_long_type)) + error_at("enum type cannot be combined with integer specifiers", + cur_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; - if (has_long_type) { - type = TY_int; - if (lex_accept(T_long)) - error_at("long long is not supported yet", cur_token_loc()); + if (has_enum_type) { + lex_ident(T_identifier, token); + type = find_type_tag(token, parent); + if (!type) + error_at("Unknown enum type", cur_token_loc()); + while (lex_accept(T_asterisk)) + ptr_cnt++; + } else if (has_long_type) { + type = has_unsigned_type ? TY_ulong : TY_long; + if (long_type_count > 1) { + /* sizeof only consumes type metadata. It does not materialize a + * long-long value, so 32-bit targets can correctly report the + * required eight-byte object size before their paired-register + * value ABI is implemented. + */ + if (has_unsigned_type) + type = TY_ulong_long; + else + type = TY_long_long; + } lex_accept(T_signed); lex_accept(T_const); if (lex_peek(T_identifier, token) && !strcmp(token, "int")) lex_expect(T_identifier); while (lex_accept(T_asterisk)) ptr_cnt++; + } else if (has_unsigned_type) { + if (leading_scalar_type == TY_char) { + type = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, token) && !strcmp(token, "int")) + lex_expect(T_identifier); + type = TY_ushort; + } else if (lex_peek(T_identifier, token) && + (!strcmp(token, "int") || !strcmp(token, "char") || + !strcmp(token, "short"))) { + lex_expect(T_identifier); + if (!strcmp(token, "char")) + type = TY_uchar; + else if (!strcmp(token, "short")) + type = TY_ushort; + } else + type = TY_uint; + while (lex_accept(T_asterisk)) + ptr_cnt++; } else if (has_signed_type) { - type = TY_int; - if (lex_peek(T_identifier, token) && - (!strcmp(token, "int") || !strcmp(token, "char") || - !strcmp(token, "short"))) { + if (leading_scalar_type == TY_char || leading_scalar_type == TY_short) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, token) && !strcmp(token, "int")) + lex_expect(T_identifier); + type = leading_scalar_type; + } else if (lex_peek(T_identifier, token) && + (!strcmp(token, "int") || !strcmp(token, "char") || + !strcmp(token, "short"))) { lex_expect(T_identifier); type = find_type(token, true); - } + } else + type = TY_int; + while (lex_accept(T_asterisk)) + ptr_cnt++; + } else if (leading_scalar_type) { + type = leading_scalar_type; while (lex_accept(T_asterisk)) ptr_cnt++; } else if (lex_peek(T_identifier, token)) { @@ -2521,6 +3122,14 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } 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); } @@ -2528,11 +3137,13 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr_operand(parent, bb); rs1 = opstack_pop(); + 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; opstack_push(vd); @@ -2540,6 +3151,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } 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); } @@ -2567,13 +3179,63 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* Look ahead to see if we have a typename followed by ) */ token_t *type_start = cur_token; - bool has_const_type = lex_accept(T_const); - bool has_signed_type = lex_accept(T_signed); - bool has_long_type = lex_accept(T_long); + bool has_const_type = false; + bool has_signed_type = false; + bool has_unsigned_type = false; + int long_type_count = 0; + type_t *leading_scalar_type = NULL; + + if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short") || + !strcmp(lookahead_token, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && (after_base->kind == T_signed || + after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(lookahead_token, true); + } + } + while (lex_peek(T_const, NULL) || lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { + if (lex_accept(T_const)) + has_const_type = true; + else if (lex_accept(T_signed)) { + if (has_signed_type) + error_at("duplicate signed type specifier", + cur_token_loc()); + has_signed_type = true; + } else if (lex_accept(T_unsigned)) { + if (has_unsigned_type) + error_at("duplicate unsigned type specifier", + cur_token_loc()); + has_unsigned_type = true; + } else { + lex_expect(T_long); + long_type_count++; + } + } + if (long_type_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + if (has_signed_type && has_unsigned_type) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && + lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + bool has_long_type = long_type_count > 0; + bool has_enum_type = lex_accept(T_enum); + if (has_enum_type && + (has_signed_type || has_unsigned_type || has_long_type)) + error_at("enum type cannot be combined with integer specifiers", + cur_token_loc()); bool has_type_identifier = lex_peek(T_identifier, lookahead_token); - if (has_const_type || has_signed_type || has_long_type || - has_type_identifier || lex_peek(T_struct, NULL) || - lex_peek(T_union, NULL)) { + if (has_const_type || has_signed_type || has_unsigned_type || + has_long_type || has_enum_type || has_type_identifier || + lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { /* Check if it's a basic type or typedef */ token_t *saved_token = type_start; bool is_record = lex_accept(T_struct); @@ -2583,25 +3245,67 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_ident(T_identifier, lookahead_token); type_t *type; - if (has_long_type && !is_record) { - if (lex_accept(T_long)) - error_at("long long is not supported yet", cur_token_loc()); + if (has_enum_type) { + lex_ident(T_identifier, lookahead_token); + type = find_type_tag(lookahead_token, parent); + } else if (has_unsigned_type && !is_record) { + if (has_long_type) { + type = TY_ulong; + if (long_type_count > 1) { + type = TY_ulong_long; + } + if (lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + } else if (leading_scalar_type == TY_char) { + type = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + type = TY_ushort; + } else if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "int") || + !strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short"))) { + lex_expect(T_identifier); + if (!strcmp(lookahead_token, "char")) + type = TY_uchar; + else if (!strcmp(lookahead_token, "short")) + type = TY_ushort; + else + type = TY_uint; + } else + type = TY_uint; + } else if (has_long_type && !is_record) { + type = TY_long; + if (long_type_count > 1) { + type = TY_long_long; + } lex_accept(T_signed); lex_accept(T_const); if (lex_peek(T_identifier, lookahead_token) && !strcmp(lookahead_token, "int")) lex_expect(T_identifier); - type = TY_int; } else if (has_signed_type && !is_record) { - if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "int") || - !strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short"))) { + if (leading_scalar_type == TY_char || + leading_scalar_type == TY_short) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + type = leading_scalar_type; + } else if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "int") || + !strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short"))) { lex_expect(T_identifier); type = find_type(lookahead_token, true); } else { type = TY_int; } + } else if (leading_scalar_type) { + type = leading_scalar_type; } else { type = find_type(lookahead_token, is_record ? 2 : true); } @@ -2610,7 +3314,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* Save current position to backtrack if needed Try to parse as * typename */ - if (!is_record && !has_signed_type && !has_long_type) + if (!is_record && !has_enum_type && !has_signed_type && + !has_unsigned_type && !has_long_type) lex_expect(T_identifier); /* A qualifier may appear before or after the base type. */ @@ -2625,6 +3330,11 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) cast_const_pointer = true; } + if (PTR_SIZE < 8 && type->base_type == TYPE_long_long && + !(ptr_level || type->ptr_level)) + error_at("long long value needs 64-bit target lowering", + cur_token_loc()); + /* Check for array brackets: [size] or [] */ bool is_array = false; if (lex_accept(T_open_square)) { @@ -2915,7 +3625,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_peek(T_identifier, token); /* is a constant or variable? */ - const constant_t *con = find_constant(token); + const constant_t *con = find_scoped_constant(token, parent); var_t *var = find_var(token, parent); func_t *func = find_func(token); @@ -2973,11 +3683,13 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (is_neg) { rs1 = opstack_pop(); + rs1 = integer_promote_operand(parent, bb, rs1); /* 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->type = rs1->type; vd->is_const = true; vd->init_val = -rs1->init_val; opstack_push(vd); @@ -2986,6 +3698,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } else { vd = require_var(parent); vd->var_name = gen_name(); + vd->type = rs1->type; opstack_push(vd); add_insn(parent, *bb, OP_negate, vd, rs1, NULL, 0, NULL); } @@ -3241,42 +3954,163 @@ bool is_pointer_operation(opcode_t op, var_t *rs1, var_t *rs2) return is_pointer_like_value(rs1) || is_pointer_like_value(rs2); } -void read_expr_body(block_t *parent, basic_block_t **bb) +/* 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) { - var_t *vd, *rs1, *rs2; - opcode_t oper_stack[MAX_OPERATOR_STACK_SIZE]; - int oper_stack_idx = 0; + return var && !var->ptr_level && var->type && var->type->is_unsigned && + var->type->size >= TY_int->size; +} - /* 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); +/* 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) + var->is_const = false; +} - read_expr_operand(parent, bb); +/* 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; + } - 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; + 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; } - read_expr_operand(parent, bb); + + 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) +{ + if (!var || var->ptr_level || !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); +} + +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) { @@ -3298,8 +4132,13 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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); @@ -3439,9 +4278,16 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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); + 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->init_val && !rs1->ptr_level && !rs1->is_global && - rs2->init_val && !rs2->ptr_level && !rs2->is_global) { + 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; @@ -3510,6 +4356,8 @@ void read_expr_body(block_t *parent, basic_block_t **bb) /* Create constant result */ vd = require_var(parent); vd->var_name = gen_name(); + vd->type = result_type; + vd->is_const = true; vd->init_val = result; opstack_push(vd); add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, @@ -3518,6 +4366,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) /* 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); } @@ -3525,6 +4374,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) /* 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); } @@ -3596,6 +4446,7 @@ void read_lvalue(lvalue_t *lvalue, 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; @@ -3617,6 +4468,8 @@ void read_lvalue(lvalue_t *lvalue, while (lex_peek(T_open_square, NULL) || lex_peek(T_arrow, NULL) || lex_peek(T_dot, NULL)) { if (lex_accept(T_open_square)) { + int indexed_ptr_level; + /* 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. @@ -3638,6 +4491,19 @@ void read_lvalue(lvalue_t *lvalue, 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 + !!var->array_size; + lvalue->value_ptr_level = + indexed_ptr_level ? indexed_ptr_level - 1 : 0; + /* if nested pointer, still pointer Also handle typedef pointers * which have ptr_level == 0 */ @@ -3647,25 +4513,17 @@ void read_lvalue(lvalue_t *lvalue, * 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; - } + 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; } @@ -3764,6 +4622,7 @@ void read_lvalue(lvalue_t *lvalue, lvalue->type = var->type; lvalue->decl = 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->size = get_size(var); lvalue->is_const_qualified |= var->is_const_qualified; @@ -3879,6 +4738,8 @@ void read_lvalue(lvalue_t *lvalue, rs1 = operand_stack[operand_stack_idx - 1]; t = require_var(parent); t->var_name = gen_name(); + t->type = pointee_type_from_pointer_typedef(lvalue->type); + t->ptr_level = lvalue->value_ptr_level; opstack_push(t); add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, NULL); } @@ -3924,6 +4785,7 @@ void read_lvalue(lvalue_t *lvalue, } else { rs1 = vd; vd = operand_stack[operand_stack_idx - 1]; + 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)) { @@ -4234,6 +5096,14 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) vd = require_var(parent); vd->var_name = gen_name(); + 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); + } add_insn(parent, then_, OP_assign, vd, true_val, NULL, 0, NULL); add_insn(parent, else_, OP_assign, vd, false_val, NULL, 0, NULL); @@ -4357,6 +5227,8 @@ bool read_body_assignment(char *token, 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); add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, NULL); @@ -4379,6 +5251,7 @@ bool read_body_assignment(char *token, 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); } } else { @@ -4386,6 +5259,8 @@ bool read_body_assignment(char *token, 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); add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, NULL); @@ -4410,13 +5285,29 @@ bool read_body_assignment(char *token, 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) { @@ -4477,6 +5368,7 @@ bool read_body_assignment(char *token, diagnose_const_pointer_conversion(rs1, vd); } else { rs1 = resize_var(parent, bb, rs1, vd); + mark_var_mutated(vd); add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); } } @@ -4486,7 +5378,7 @@ bool read_body_assignment(char *token, return false; } -int read_primary_constant(void) +int read_primary_constant(block_t *scope) { /* return signed constant */ int isneg = 0, res; @@ -4494,7 +5386,7 @@ int read_primary_constant(void) if (lex_accept(T_minus)) isneg = 1; if (lex_accept(T_open_bracket)) { - res = read_primary_constant(); + res = read_primary_constant(scope); lex_expect(T_close_bracket); } else if (lex_peek(T_numeric, buffer)) { res = parse_numeric_constant(buffer); @@ -4504,6 +5396,14 @@ int read_primary_constant(void) unescape_string(buffer, unescaped, MAX_TOKEN_LEN); res = unescaped[0]; lex_expect(T_char); + } else if (lex_peek(T_identifier, buffer)) { + constant_t *con; + + lex_expect(T_identifier); + con = find_scoped_constant(buffer, scope); + if (!con) + error_at("Identifier is not an integer constant", next_token_loc()); + res = con->value; } else error_at("Invalid value after assignment", next_token_loc()); if (isneg) @@ -4605,6 +5505,174 @@ int eval_expression_imm(opcode_t op, int op1, int op2) } bool read_global_assignment_var(var_t *var); + +void emit_global_scalar_assignment(block_t *parent, + basic_block_t *bb, + var_t *dest, + var_t *src) +{ + if (!dest->ptr_level && dest->type == TY_bool) + src->init_val = src->init_val != 0; + add_insn(parent, bb, OP_assign, dest, src, NULL, 0, NULL); +} + +/* 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 wide_global_literal_appears_before_initializer_end(token_t *token) +{ + int bracket_depth = 0; + + for (; 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)) + return false; + else if (token->kind == T_numeric && + numeric_literal_needs_wide_path(token->literal)) + return true; + } + return false; +} + +var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb); + +/* 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) +{ + 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); + 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 = read_wide_global_literal_primary(parent, bb); + 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; + add_insn(parent, bb, OP_sub, result, zero, value, 0, NULL); + return result; + } + if (lex_accept(T_bit_not)) { + var_t *result; + + value = read_wide_global_literal_primary(parent, bb); + result = require_var(parent); + result->var_name = gen_name(); + result->type = value->type; + add_insn(parent, bb, OP_bit_not, result, value, NULL, 0, NULL); + return result; + } + if (lex_accept(T_log_not)) { + var_t *result; + + value = read_wide_global_literal_primary(parent, bb); + result = require_typed_var(parent, TY_int); + result->var_name = gen_name(); + add_insn(parent, bb, OP_log_not, result, value, NULL, 0, NULL); + return result; + } + if (lex_accept(T_open_bracket)) { + value = read_wide_global_literal_expression(parent, bb); + lex_expect(T_close_bracket); + 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) +{ + opcode_t op_stack[MAX_OPERATOR_STACK_SIZE]; + 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++] = read_wide_global_literal_primary(parent, bb); + op = get_operator(); + while (op != OP_generic) { + if (op == OP_ternary || is_logical(op)) + error_at("Wide global initializer needs arithmetic literals", + cur_token_loc()); + 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); + + result->var_name = gen_name(); + result->type = integer_binary_result_type( + 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"); + op_stack[op_stack_index++] = op; + val_stack[val_stack_index++] = + read_wide_global_literal_primary(parent, bb); + 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); + + result->var_name = gen_name(); + result->type = + integer_binary_result_type(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]; +} + void eval_ternary_imm(int cond, var_t *var) { if (cond == 0) { @@ -4625,11 +5693,146 @@ void eval_ternary_imm(int cond, var_t *var) 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; + if ((var->array_size > 0 || var->has_unsized_array) && !var->ptr_level && + var->type == TY_char && lex_peek(T_string, NULL)) { + parse_string_array_init(var, parent, &bb); + return true; + } + /* global initialization must be constant */ { + /* 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 explicit_address = lex_accept(T_ampersand); + char token[MAX_ID_LEN]; + if (lex_peek(T_identifier, token)) { + func_t *func = find_func(token); + 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); + lex_expect(T_identifier); + 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)) { + var_t *object_addr = + require_ref_var(parent, object->type, object->ptr_level); + + object_addr->var_name = gen_name(); + lex_expect(T_identifier); + add_insn(parent, bb, OP_address_of, object_addr, object, NULL, + 0, NULL); + if (!explicit_address && object->array_size && + lex_accept(T_plus)) { + int index = read_primary_constant(scope); + var_t *byte_offset = require_var(parent); + var_t *offset_addr = require_ref_var(parent, object->type, + object->ptr_level); + + byte_offset->var_name = gen_name(); + byte_offset->init_val = index * object->type->size; + add_insn(parent, bb, OP_load_constant, byte_offset, NULL, + NULL, 0, NULL); + offset_addr->var_name = gen_name(); + add_insn(parent, bb, OP_add, offset_addr, object_addr, + byte_offset, 0, NULL); + object_addr = offset_addr; + } + while (explicit_address) { + 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, object->type); + if (!field) + error_at("Unknown struct or union member", + cur_token_loc()); + object_addr = compute_field_address(parent, &bb, + object_addr, field); + object = field; + } else if (object->array_size && + lex_accept(T_open_square)) { + int index = read_primary_constant(scope); + + lex_expect(T_close_square); + object_addr = + compute_element_address(parent, &bb, object_addr, + index, object->type->size); + } else + break; + } + + /* An address constant may be offset after an explicitly + * addressed member or element too. Array decay had this + * handling above, but forms such as "&record.items[0] + 1" + * stopped at the plus token. Keep the offset byte-scaled here + * so global setup receives a literal byte offset. + */ + if (explicit_address && + (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL))) { + /* read_const_expr accepts the leading binary sign as a + * unary sign here and consumes the entire integer constant + * expression, including grouping and enum constants. + */ + int index = read_const_expr(scope); + int elem_size = + object->ptr_level ? PTR_SIZE : object->type->size; + + object_addr = compute_element_address( + parent, &bb, object_addr, index, elem_size); + } + add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, + NULL); + return true; + } + } + if (explicit_address) + error_at("Expected a global object or function after '&'", + cur_token_loc()); + + /* 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 (wide_global_literal_appears_before_initializer_end( + cur_token->next)) { + rs1 = read_wide_global_literal_expression(parent, bb); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + return true; + } if (lex_peek(T_string, NULL)) { /* String literal global initialization: String literals are now * stored in .rodata section. TODO: Implement compile-time address @@ -4640,7 +5843,7 @@ bool read_global_assignment_var(var_t *var) rs1 = opstack_pop(); vd = var; diagnose_const_pointer_conversion(rs1, vd); - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); + emit_global_scalar_assignment(parent, bb, vd, rs1); return true; } @@ -4649,7 +5852,7 @@ bool read_global_assignment_var(var_t *var) int val_stack[MAX_OPERATOR_STACK_SIZE]; int op_stack_index = 0, val_stack_index = 0; int operand1, operand2; - operand1 = read_primary_constant(); + operand1 = read_primary_constant(scope); op = get_operator(); /* only one value after assignment */ if (op == OP_generic) { @@ -4660,7 +5863,7 @@ bool read_global_assignment_var(var_t *var) rs1 = vd; vd = opstack_pop(); - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); + emit_global_scalar_assignment(parent, bb, vd, rs1); return true; } if (op == OP_ternary) { @@ -4668,7 +5871,7 @@ bool read_global_assignment_var(var_t *var) eval_ternary_imm(operand1, var); return true; } - operand2 = read_primary_constant(); + operand2 = read_primary_constant(scope); next_op = get_operator(); if (next_op == OP_generic) { /* only two operands, apply and return */ @@ -4716,7 +5919,7 @@ bool read_global_assignment_var(var_t *var) 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(); + val_stack[val_stack_index++] = read_primary_constant(scope); /* push operator on stack */ op_stack[op_stack_index++] = op; op = get_operator(); @@ -4747,7 +5950,7 @@ bool read_global_assignment_var(var_t *var) rs1 = vd; vd = opstack_pop(); - add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); + emit_global_scalar_assignment(parent, bb, vd, rs1); } return true; } @@ -4767,8 +5970,7 @@ bool read_global_assignment_var(var_t *var) rs1 = vd; vd = opstack_pop(); - add_insn(parent, GLOBAL_FUNC->bbs, OP_assign, vd, rs1, NULL, 0, - NULL); + emit_global_scalar_assignment(parent, GLOBAL_FUNC->bbs, vd, rs1); } return true; } @@ -4838,7 +6040,7 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) case_val = unescaped[0]; lex_expect(T_char); } else if (lex_peek(T_identifier, token)) { - const constant_t *cd = find_constant(token); + const constant_t *cd = find_scoped_constant(token, parent); if (!cd) error_at("Unknown constant in case label", cur_token_loc()); case_val = cd->value; @@ -4938,6 +6140,8 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) var_t *rs1; var_t *var; opcode_t prefix_op = OP_generic; + bool is_const = false; + bool is_static = false; lex_expect(T_open_bracket); @@ -4949,7 +6153,23 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) bb_connect(bb, setup, NEXT); if (!lex_accept(T_semicolon)) { - if (!lex_peek(T_identifier, token)) + while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL)) { + if (lex_accept(T_static)) { + if (is_static) + error_at("duplicate static storage class specifier", + cur_token_loc()); + is_static = true; + } else { + lex_expect(T_const); + is_const = true; + } + } + + bool has_builtin_type = lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL); + if (!has_builtin_type && !lex_peek(T_identifier, token) && + !lex_peek(T_struct, NULL) && !lex_peek(T_union, NULL)) error_at("Unexpected token when parsing for loop", next_token_loc()); @@ -4957,18 +6177,42 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) if (find_type_flag == 1 && lex_accept(T_union)) { find_type_flag = 2; } - type = find_type(token, find_type_flag); + type = has_builtin_type ? TY_int : find_type(token, find_type_flag); if (type) { var = require_typed_var(blk, type); + var->is_static = is_static; + var->is_global = is_static; + var->is_const_qualified = is_const; read_full_var_decl(var, false, false); - add_insn(blk, setup, OP_allocat, var, NULL, NULL, 0, NULL); + add_insn(is_static ? GLOBAL_BLOCK : blk, + is_static ? GLOBAL_FUNC->bbs : 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); + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, + true); + 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->ptr_level && + var->type == TY_char && lex_peek(T_string, NULL)) { + parse_string_array_init(var, blk, &setup); + } else if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, blk, &setup, true); + } else { + 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); + 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; @@ -4978,14 +6222,39 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) /* multiple (partial) declarations */ nv = require_typed_var(blk, type); + nv->is_static = is_static; + nv->is_global = is_static; + nv->is_const_qualified = is_const; read_partial_var_decl(nv, var); /* partial */ - add_insn(blk, setup, OP_allocat, nv, NULL, NULL, 0, NULL); + add_insn(is_static ? GLOBAL_BLOCK : blk, + is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, nv, + NULL, NULL, 0, NULL); add_symbol(setup, nv); if (lex_accept(T_assign)) { - read_expr(blk, &setup); + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) + parse_array_init(nv, GLOBAL_BLOCK, + &GLOBAL_FUNC->bbs, true); + else if (lex_peek(T_open_curly, NULL)) + parse_global_record_init(nv, GLOBAL_BLOCK); + else + read_global_assignment_var(nv); + } else if (nv->has_unsized_array && !nv->ptr_level && + nv->type == TY_char && + lex_peek(T_string, NULL)) { + parse_string_array_init(nv, blk, &setup); + } else if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, blk, &setup, true); + } else { + read_expr(blk, &setup); - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); + rs1 = resize_var(parent, &bb, opstack_pop(), nv); + add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); + } } } } else { @@ -5134,7 +6403,8 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) add_symbol(bb, var); if (lex_accept(T_assign)) { if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { + (var->array_size > 0 || var->has_unsized_array || + 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 || @@ -5178,7 +6448,8 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) add_symbol(bb, nv); if (lex_accept(T_assign)) { if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { + (nv->array_size > 0 || nv->has_unsized_array || + 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 || @@ -5215,6 +6486,115 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) error_at("Unknown struct/union type", next_token_loc()); } +basic_block_t *handle_enum_declarators(block_t *parent, + basic_block_t *bb, + type_t *type, + bool is_const, + bool is_static) +{ + for (;;) { + var_t *var = require_typed_var(parent, type); + + var->is_const_qualified = is_const; + var->is_static = is_static; + var->is_global = is_static; + read_partial_var_decl(var, NULL); + add_insn(is_static ? GLOBAL_BLOCK : parent, + is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, var, NULL, NULL, + 0, NULL); + add_symbol(bb, var); + + if (lex_accept(T_assign)) { + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, + true); + } else { + read_global_assignment_var(var); + } + } else if (var->has_unsized_array && !var->ptr_level && + var->type == TY_char && lex_peek(T_string, NULL)) { + parse_string_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)) { + parse_array_init(var, parent, &bb, true); + } 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); + emit_object_assignment(parent, &bb, var, rhs); + } + } + if (is_static) + discard_global_declarator_operand(var); + + if (!lex_accept(T_comma)) + break; + perform_side_effect(parent, bb); + } + lex_expect(T_semicolon); + return bb; +} + +/* A block-scope enum definition contributes integer constants to the current + * expression parser just as a file-scope definition does. Like a record + * definition, it may introduce declarators after the closing brace. + */ +basic_block_t *handle_enum_statement(block_t *parent, + basic_block_t *bb, + bool is_const, + bool is_static) +{ + char token[MAX_ID_LEN]; + int val = 0; + type_t *type = NULL; + bool has_tag = false; + + lex_expect(T_enum); + if (lex_peek(T_identifier, token)) { + lex_expect(T_identifier); + type = find_local_type_tag(token, parent); + has_tag = true; + } + if (!lex_peek(T_open_curly, NULL)) { + if (!has_tag) + error_at("Unknown enum type", next_token_loc()); + if (!type) + type = find_type_tag(token, parent); + if (!type) + error_at("Unknown enum type", next_token_loc()); + return handle_enum_declarators(parent, bb, type, is_const, is_static); + } + if (!type) + type = add_type(); + type->base_type = TYPE_int; + type->size = TY_int->size; + if (has_tag) { + set_type_name(type, token); + if (!find_local_type_tag(token, parent)) + add_type_tag(parent, token, type); + } + lex_expect(T_open_curly); + do { + lex_ident(T_identifier, token); + if (lex_accept(T_assign)) + val = read_const_expr(parent); + add_scoped_constant(parent, token, val++); + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); + lex_expect(T_close_curly); + + if (lex_accept(T_semicolon)) + return bb; + return handle_enum_declarators(parent, bb, type, is_const, is_static); +} + /* Everything a statement can still be: a declaration, an assignment, a call, or * an expression evaluated for its effect. */ @@ -5229,18 +6609,24 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) bool is_static = false; while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL)) { - if (lex_accept(T_static)) + if (lex_accept(T_static)) { + if (is_static) + error_at("duplicate static storage class specifier", + cur_token_loc()); is_static = true; - else { + } else { lex_expect(T_const); is_const = true; } } + if (lex_peek(T_enum, NULL)) + return handle_enum_statement(parent, bb, is_const, is_static); + /* statement with prefix */ - if (!is_const && lex_accept(T_increment)) + if (!is_const && !is_static && lex_accept(T_increment)) prefix_op = OP_add; - else if (!is_const && lex_accept(T_decrement)) + else if (!is_const && !is_static && 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); @@ -5248,9 +6634,10 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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 (!is_const && !has_identifier && !has_asterisk && !has_record_keyword && - !has_signed_keyword && !has_long_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 @@ -5283,7 +6670,8 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } else { /* Normal type checking without asterisk */ token_t *type_token = cur_token; - if (lex_peek(T_signed, NULL) || lex_peek(T_long, NULL)) { + if (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL)) { type = TY_int; } else { int find_type_flag = lex_accept(T_struct) ? 2 : 1; @@ -5297,6 +6685,9 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } } + if (is_static && !type) + error_at("Expected declaration after static", next_token_loc()); + if (type) { var = require_typed_var(parent, type); var->is_static = is_static; @@ -5310,20 +6701,34 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) if (lex_accept(T_assign)) { if (is_static) { if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { + (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, true); + } 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->ptr_level && + var->type == TY_char && lex_peek(T_string, NULL)) { + parse_string_array_init(var, parent, &bb); } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { + (var->array_size > 0 || var->has_unsized_array || + 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) && @@ -5370,6 +6775,8 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) emit_object_assignment(parent, &bb, var, expr_result); } } + if (is_static) + discard_global_declarator_operand(var); while (lex_accept(T_comma)) { var_t *nv; @@ -5389,16 +6796,30 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) if (lex_accept(T_assign)) { if (is_static) { if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (global_compound_literal_starts_here() && + !(nv->ptr_level || nv->type->ptr_level) && + is_record_type(nv->type)) { + parse_global_compound_record_init(nv, GLOBAL_BLOCK); + } else if (global_compound_literal_starts_here() && + (nv->ptr_level || nv->type->ptr_level)) { + parse_global_compound_array_init(nv, GLOBAL_BLOCK); + } else if (global_compound_literal_starts_here()) { + parse_global_compound_scalar_init(nv, GLOBAL_BLOCK); } else if (lex_peek(T_open_curly, NULL)) { parse_global_record_init(nv, GLOBAL_BLOCK); } else { read_global_assignment_var(nv); } + } else if (nv->has_unsized_array && !nv->ptr_level && + nv->type == TY_char && lex_peek(T_string, NULL)) { + parse_string_array_init(nv, parent, &bb); } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { /* Emit code for locals */ parse_array_init(nv, parent, &bb, 1); } else if (lex_peek(T_open_curly, NULL) && @@ -5422,6 +6843,8 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) emit_object_assignment(parent, &bb, nv, opstack_pop()); } } + if (is_static) + discard_global_declarator_operand(nv); } lex_expect(T_semicolon); return bb; @@ -5583,6 +7006,9 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) return handle_record_statement(parent, bb); + if (lex_peek(T_enum, NULL)) + return handle_enum_statement(parent, bb, false, false); + /* Handle const qualifier for local variable declarations */ return handle_declaration(parent, bb); } @@ -5708,8 +7134,10 @@ 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->ptr_level > 0)) + (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) parse_array_init(var, block, &GLOBAL_FUNC->bbs, true); else if (global_compound_literal_starts_here() && !(var->ptr_level || var->type->ptr_level) && @@ -5726,21 +7154,157 @@ void read_global_init_var(var_t *var, block_t *block) read_global_assignment_var(var); } +/* 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. + */ +void read_global_function_declarator(block_t *block, var_t *var, bool is_static) +{ + /* 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; + + 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)); + 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; + var_reset_subscripts(&func->return_def); + block->locals.size--; + read_parameter_list_decl(func, 0); + + if (check_decl) { + 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) + error_at("conflicting types for function declaration", + next_token_loc()); + 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 (now->type != before->type || + now->ptr_level != before->ptr_level || + now->is_const_qualified != before->is_const_qualified) + error_at("conflicting types for function declaration", + next_token_loc()); + } + } + + if (lex_peek(T_open_curly, NULL)) { + if (check_decl && func_tmp.bbs) + error_at("redefinition of function", next_token_loc()); + read_func_body(func); + return; + } + if (!lex_accept(T_semicolon)) + error_at("Syntax error in global declaration", next_token_loc()); +} + +/* 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; + + if (previous->type != var->type || previous->ptr_level != var->ptr_level || + previous->array_size != var->array_size || + previous->array_dim2 != var->array_dim2 || + previous->is_const_qualified != var->is_const_qualified) + 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()); + + /* 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;". */ -void read_global_declarator(block_t *block, +bool read_global_declarator(block_t *block, type_t *decl_type, bool is_const, bool is_static) { + 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; read_inner_var_decl(nv, false, false); - add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, nv, NULL, NULL, 0, NULL); + if (lex_peek(T_open_bracket, NULL)) { + read_global_function_declarator(block, nv, is_static); + return true; + } + nv = resolve_global_declarator(block, nv, is_static, &is_redeclaration); + if (!is_redeclaration) + 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); @@ -5883,18 +7447,25 @@ void read_global_record_declarator(block_t *block, bool is_const, bool is_static) { + 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; read_inner_var_decl(var, false, false); - add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); + var = resolve_global_declarator(block, var, is_static, &is_redeclaration); + if (!is_redeclaration) + add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); - if (!lex_accept(T_assign)) + if (!lex_accept(T_assign)) { + discard_global_declarator_operand(var); return; + } + + var->has_initializer = true; if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { + (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) { parse_array_init(var, block, &GLOBAL_FUNC->bbs, true); } else if (global_compound_literal_starts_here() && (var->ptr_level || var->type->ptr_level)) { @@ -5906,10 +7477,12 @@ void read_global_record_declarator(block_t *block, } else { read_global_assignment_var(var); } + discard_global_declarator_operand(var); } void read_global_decl(block_t *block, bool is_const, bool is_static) { + bool is_redeclaration; var_t *var = require_var(block); var->is_global = true; var->is_static = is_static; @@ -5919,103 +7492,24 @@ void read_global_decl(block_t *block, bool is_const, bool is_static) 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)); - - /* A declaration without a storage-class specifier inherits a prior - * visible function's linkage. Thus `static int f(void); int f(void)` - * remains internal, whereas a first external declaration cannot later - * be made static in the same translation unit. - */ - 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; - 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); + read_global_function_declarator(block, var, is_static); + return; + } else { + var = + resolve_global_declarator(block, var, is_static, &is_redeclaration); + if (!is_redeclaration) + 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)) { - opstack_pop(); } 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;". Every declarator after the first * shares this declaration's base type and is handled exactly like the @@ -6090,6 +7584,9 @@ void read_global_statement(void) is_const = true; else { lex_expect(T_static); + if (is_static) + error_at("duplicate static storage class specifier", + cur_token_loc()); is_static = true; } } @@ -6199,6 +7696,56 @@ void read_global_statement(void) read_global_record_declarator(block, type, is_const, is_static); } lex_expect(T_semicolon); + } else if (lex_accept(T_enum)) { + /* An enum definition is a declaration in its own right; it need not + * introduce a typedef. Its enumerators are integer constants and may + * use the same integer constant expressions accepted for array bounds + * and case labels. + */ + int val = 0; + bool has_tag = false; + type_t *type; + + if (lex_peek(T_identifier, token)) { + lex_expect(T_identifier); + has_tag = true; + } + if (!lex_peek(T_open_curly, NULL)) { + if (!has_tag) + error_at("Expected enum tag or definition", cur_token_loc()); + type = find_type(token, true); + if (!type) + error_at("Unknown enum type", cur_token_loc()); + if (read_global_declarator(block, type, is_const, is_static)) + return; + while (lex_accept(T_comma)) + read_global_declarator(block, type, is_const, is_static); + lex_expect(T_semicolon); + return; + } + type = has_tag ? find_type(token, true) : NULL; + if (!type) + type = add_type(); + + type->base_type = TYPE_int; + type->size = 4; + if (has_tag) + set_type_name(type, token); + lex_expect(T_open_curly); + do { + lex_ident(T_identifier, token); + if (lex_accept(T_assign)) + val = read_const_expr(block); + add_constant(token, val++); + } while (lex_accept(T_comma)); + lex_expect(T_close_curly); + if (!lex_peek(T_semicolon, NULL)) { + if (read_global_declarator(block, type, is_const, is_static)) + return; + while (lex_accept(T_comma)) + read_global_declarator(block, type, is_const, is_static); + } + lex_expect(T_semicolon); } else if (lex_accept(T_typedef)) { if (lex_accept(T_enum)) { int val = 0; @@ -6209,11 +7756,8 @@ void read_global_statement(void) 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); - } + if (lex_accept(T_assign)) + val = read_const_expr(block); add_constant(token, val++); } while (lex_accept(T_comma)); lex_expect(T_close_curly); @@ -6261,8 +7805,12 @@ void read_global_statement(void) } while (!lex_accept(T_close_curly)); } + while (lex_accept(T_asterisk)) { + type->ptr_level++; + type->size = PTR_SIZE; + } lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->size = size; + type->size = type->ptr_level ? PTR_SIZE : size; type->num_fields = i; type->base_type = TYPE_typedef; @@ -6325,8 +7873,12 @@ void read_global_statement(void) } while (!lex_accept(T_close_curly)); } + while (lex_accept(T_asterisk)) { + type->ptr_level++; + type->size = PTR_SIZE; + } lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->size = max_size; + type->size = type->ptr_level ? PTR_SIZE : max_size; type->num_fields = i; type->base_type = TYPE_typedef; type->is_union = true; @@ -6349,26 +7901,109 @@ void read_global_statement(void) char base_type[MAX_ID_LEN]; const type_t *base; type_t *type = add_type(); - bool typedef_const = lex_accept(T_const); - bool is_signed = lex_accept(T_signed); - bool is_long = lex_accept(T_long); - if (is_long) { - is_signed |= lex_accept(T_signed); - typedef_const |= lex_accept(T_const); + bool typedef_const = false; + bool is_signed = false; + bool is_unsigned = false; + bool is_long = false; + bool is_long_long = false; + type_t *leading_scalar_type = NULL; + + if (lex_peek(T_identifier, base_type) && + (!strcmp(base_type, "char") || !strcmp(base_type, "short") || + !strcmp(base_type, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && (after_base->kind == T_signed || + after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(base_type, true); + } } + /* Typedef declarations use the same freely ordered scalar specifier + * set as object declarations. Keeping this in a loop admits C99 + * spellings such as `long unsigned long` rather than treating the + * second specifier as the typedef name. + */ + while (lex_peek(T_const, NULL) || lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { + if (lex_accept(T_const)) + typedef_const = true; + else 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 { + lex_expect(T_long); + if (is_long_long) + error_at("too many long type specifiers", + cur_token_loc()); + if (is_long) + is_long_long = true; + else + is_long = true; + } + } + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && + lex_peek(T_identifier, base_type) && !strcmp(base_type, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + if (is_long) { - if (lex_accept(T_long)) - error_at("long long is not supported yet", cur_token_loc()); if (lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) lex_expect(T_identifier); - base = TY_int; + if (is_long_long) + base = is_unsigned ? TY_ulong_long : TY_long_long; + else + base = is_unsigned ? TY_ulong : TY_long; + } else if (is_unsigned) { + if (leading_scalar_type == TY_char) { + base = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, base_type) && + !strcmp(base_type, "int")) + lex_expect(T_identifier); + base = TY_ushort; + } else { + if (lex_peek(T_identifier, base_type) && + (!strcmp(base_type, "int") || + !strcmp(base_type, "char") || + !strcmp(base_type, "short"))) { + lex_expect(T_identifier); + if (!strcmp(base_type, "char")) + base = TY_uchar; + else if (!strcmp(base_type, "short")) + base = TY_ushort; + else + base = TY_uint; + } else { + base = TY_uint; + } + } + } else if (is_signed && (leading_scalar_type == TY_char || + leading_scalar_type == TY_short)) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, base_type) && + !strcmp(base_type, "int")) + lex_expect(T_identifier); + base = leading_scalar_type; } else if (is_signed && (!lex_peek(T_identifier, base_type) || (strcmp(base_type, "int") && strcmp(base_type, "char") && strcmp(base_type, "short")))) { base = TY_int; + } else if (leading_scalar_type) { + base = leading_scalar_type; } else { lex_ident(T_identifier, base_type); base = find_type(base_type, true); @@ -6381,6 +8016,7 @@ void read_global_statement(void) type->ptr_level = 0; type->is_const_qualified = typedef_const || base->is_const_qualified; + type->is_unsigned = base->is_unsigned; /* Handle pointer types in typedef: typedef char *string; */ while (lex_accept(T_asterisk)) { @@ -6392,7 +8028,7 @@ void read_global_statement(void) lex_expect(T_semicolon); } } else if (lex_peek(T_identifier, NULL) || lex_peek(T_signed, NULL) || - lex_peek(T_long, NULL)) { + lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { read_global_decl(block, is_const, is_static); } else error_at("Syntax error in global statement", next_token_loc()); @@ -6420,14 +8056,56 @@ void parse_internal(void) TY_char->base_type = TYPE_char; TY_char->size = 1; + 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; + + /* 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; + /* 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'). diff --git a/src/peephole.c b/src/peephole.c index 06fb273d..71cb82f4 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -79,7 +79,8 @@ 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)) { /* Pattern: {li 0; add x, 0} → {mov x} (additive identity: x+0 = x) @@ -135,7 +136,8 @@ 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)) { /* Pattern: {li 1; mul x, 1} → {mov x} (multiplicative identity: x * @@ -153,7 +155,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) /* Bitwise identity operations */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == -1 && - next->op == OP_bit_and && ph2_ir->dest == next->src1) { + ph2_ir->src1 == 0 && next->op == OP_bit_and && + ph2_ir->dest == next->src1) { /* 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,7 +169,7 @@ 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->src1 == 0 && (next->op == OP_lshift || next->op == OP_rshift) && ph2_ir->dest == next->src1) { /* 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} @@ -180,7 +183,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) { /* 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,7 +200,7 @@ 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) { /* Pattern: {li 2^n; mul x, 2^n} → {li n; shl x, n} Example: {li t1, @@ -211,7 +215,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) { /* 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 +232,7 @@ 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) { /* 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 @@ -515,7 +520,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 +529,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; @@ -578,7 +585,8 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * 32-bit) */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == -1 && - next->op == OP_bit_and && next->src1 == ph2_ir->dest) { + ph2_ir->src1 == 0 && next->op == OP_bit_and && + next->src1 == ph2_ir->dest) { /* Replace AND with assignment */ next->op = OP_assign; next->src1 = 0; @@ -588,7 +596,8 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) /* 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) { + ph2_ir->src1 == 0 && next->op == OP_bit_or && + next->src1 == ph2_ir->dest) { /* Replace OR with assignment */ next->op = OP_assign; next->src1 = 0; @@ -598,7 +607,8 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) /* 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) { + ph2_ir->src1 == 0 && next->op == OP_bit_xor && + next->src1 == ph2_ir->dest) { /* Replace XOR with assignment */ next->op = OP_assign; next->src1 = 0; @@ -608,7 +618,7 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) /* 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 && + ph2_ir->src1 == 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; @@ -620,7 +630,7 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) /* 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 && + ph2_ir->src1 == 0 && 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; @@ -634,7 +644,7 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * x << 0 = x, x >> 0 = x */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - (next->op == OP_lshift || next->op == OP_rshift) && + ph2_ir->src1 == 0 && (next->op == OP_lshift || next->op == OP_rshift) && next->src1 == ph2_ir->dest) { /* Replace shift with assignment */ next->op = OP_assign; diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 3fc848cb..00c65977 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -44,12 +44,23 @@ 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); } /* Width of the value a local's frame slot actually holds. @@ -216,8 +227,11 @@ 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; if (!bb->ph2_ir_list.head) bb->ph2_ir_list.head = n; @@ -701,6 +715,7 @@ void store_var(basic_block_t *bb, var_t *var, int idx) 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); REGS[idx].polluted = 0; } @@ -935,6 +950,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 @@ -953,7 +969,17 @@ void load_var(basic_block_t *bb, var_t *var, int idx) ir->dest = idx; 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); @@ -2094,7 +2120,11 @@ 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->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); @@ -2108,6 +2138,24 @@ void reg_alloc_global(insn_t *global_insn) 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; + 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 @@ -2125,13 +2173,50 @@ 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_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); + ir->src0 = src0; + ir->dest = dest; + set_ptr_flags(ir, global_insn); + 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 = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); ir->src0 = src0; ir->src1 = src1; ir->dest = dest; @@ -2139,6 +2224,18 @@ void reg_alloc_global(insn_t *global_insn) break; } case OP_write: { + if (global_insn->rs2 && global_insn->rs2->is_func) { + 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); + 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); @@ -2159,6 +2256,7 @@ void reg_alloc_global(insn_t *global_insn) */ ir->size_bytes = global_insn->sz; ir->is_pointer = global_insn->rs2->ptr_level > 0; + ir->is_unsigned = is_unsigned_scalar(global_insn->rs2); break; } /* Fallback generic write */ @@ -2169,6 +2267,7 @@ void reg_alloc_global(insn_t *global_insn) ir->src0 = src0; ir->src1 = src1; ir->dest = global_insn->sz; + set_ptr_flags(ir, global_insn); break; } case OP_trunc: @@ -2315,7 +2414,11 @@ 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->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) { @@ -2515,12 +2618,18 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = src0; ir->dest = dest; + 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; /* store global variable immediately after assignment */ if (insn->rd->is_global) { ir = bb_add_ph2_ir(bb, OP_global_store); ir->src0 = dest; ir->src1 = insn->rd->offset; + ir->is_unsigned = is_unsigned_scalar(insn->rd); + ir->size_bytes = var_slot_size(insn->rd); REGS[dest].polluted = 0; } @@ -2529,6 +2638,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); @@ -2537,6 +2651,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->src0 = src0; ir->src1 = insn->sz; ir->dest = dest; + set_ptr_flags(ir, insn); break; case OP_write: if (insn->rs2->is_func) { @@ -2561,6 +2676,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->src0 = src0; ir->src1 = src1; ir->dest = insn->sz; + set_ptr_flags(ir, insn); } break; case OP_branch: @@ -2583,6 +2699,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) * over its low word only. */ ir->src0_is_pointer = is_address_like(insn->rs1); + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); ir->then_bb = bb->then_; ir->else_bb = bb->else_; break; @@ -2605,6 +2722,8 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = src0; 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; break; @@ -2634,6 +2753,7 @@ 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_load_func); ir->src0 = src0; + ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); bb_add_ph2_ir(bb, OP_indirect); @@ -2648,6 +2768,9 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = 0; ir->dest = dest; + 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) @@ -2657,6 +2780,10 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir = bb_add_ph2_ir(bb, OP_return); ir->src0 = src0; + 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: @@ -2690,6 +2817,24 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) * 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; + if (right_size > ir->size_bytes) + ir->size_bytes = right_size; break; case OP_negate: case OP_bit_not: @@ -2704,6 +2849,10 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) * 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; break; case OP_trunc: case OP_sign_ext: @@ -2714,6 +2863,10 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->src1 = insn->sz; ir->src0 = src0; ir->dest = dest; + 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; break; default: printf("Unknown opcode\n"); diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index d394d10e..63e57180 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -143,7 +143,7 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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: @@ -341,8 +341,21 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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: @@ -352,15 +365,24 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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->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 @@ -471,9 +493,13 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_mod: 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 +512,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)); @@ -521,7 +569,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__sll(rd, rs1, rs2)); return; case OP_rshift: - emit(__sra(rd, rs1, rs2)); + emit(ph2_ir->src0_is_unsigned ? __srl(rd, rs1, rs2) + : __sra(rd, rs1, rs2)); return; case OP_eq: emit(__sub(rd, rs1, rs2)); @@ -533,17 +582,25 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__sltu(rd, __zero, rd)); return; case OP_gt: - emit(__slt(rd, rs2, rs1)); + emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + ? __sltu(rd, rs2, rs1) + : __slt(rd, rs2, rs1)); return; case OP_geq: - emit(__slt(rd, rs1, rs2)); + emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + ? __sltu(rd, rs1, rs2) + : __slt(rd, rs1, rs2)); emit(__xori(rd, rd, 1)); return; case OP_lt: - emit(__slt(rd, rs1, rs2)); + emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + ? __sltu(rd, rs1, rs2) + : __slt(rd, rs1, rs2)); return; case OP_leq: - emit(__slt(rd, rs2, rs1)); + emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) + ? __sltu(rd, rs2, rs1) + : __slt(rd, rs2, rs1)); emit(__xori(rd, rd, 1)); return; case OP_negate: @@ -566,8 +623,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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) { @@ -596,12 +657,14 @@ 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; } diff --git a/src/riscv.c b/src/riscv.c index 0baf159f..da1e2aa7 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) */, @@ -49,7 +51,9 @@ typedef enum { /* m */ rv_mul = 33554483 /* 0b0110011 + (1 << 25) */, 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 */ @@ -271,6 +275,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 +356,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..563bf17c 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -850,6 +850,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); @@ -3402,6 +3403,8 @@ bool mark_const(insn_t *insn) if (insn->rs1->address_taken) 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 +3416,41 @@ 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; +} + 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; @@ -3508,6 +3532,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; @@ -3961,7 +3990,8 @@ 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; /* x + 0 = x, x - 0 = x, x | 0 = x, x ^ 0 = x */ @@ -4140,12 +4170,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; } diff --git a/src/x64-codegen.c b/src/x64-codegen.c index d1560077..794e26c2 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -261,7 +261,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 +269,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 +547,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. @@ -1289,7 +1288,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 +1379,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,48 +1767,64 @@ 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. + */ + 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. */ - emit_byte(REX_W | REX_B); /* MOV r10, rax (save) */ + emit_rex(wide, 0, 10); /* MOV r10{d}, rax{d} (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(wide, 10, 0); /* MOV rax{d}, r10{d} (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))); return; @@ -1878,7 +1900,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 +1937,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 +1985,24 @@ 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) { + if (ph2_ir->is_unsigned) + emit_zero_extend(rd, rs1, ph2_ir->size_bytes); + else + emit_mov_reg(rd, rs1); + emit_shift_imm(rd, + ph2_ir->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; } @@ -1995,9 +2024,9 @@ void emit_bitwise(ph2_ir_t *ph2_ir, 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->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 +2054,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: { @@ -2154,7 +2186,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 +2252,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. @@ -2370,15 +2411,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 +2488,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 +2500,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 +2508,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); @@ -2672,19 +2714,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] */ } @@ -2847,6 +2894,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 +2932,25 @@ 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); + /* Equal-width signed-to-unsigned casts still need to discard the source + * register's sign extension on LP64. + */ + 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; @@ -2946,7 +3005,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 +3019,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; } } @@ -3086,10 +3152,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; @@ -3121,6 +3188,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 +3209,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 +3229,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; } 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 539e2df0..1fa990a6 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -315,6 +315,51 @@ int main() { ' "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" +} + # Stack Alignment Tests test_stack_alignment_basic() @@ -382,6 +427,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" ' @@ -567,6 +667,8 @@ test_four_args test_five_args test_eight_args test_long_args_and_return +test_narrow_unsigned_args +test_mixed_narrow_args echo "" echo -e "${CYAN}Running Stack Alignment Tests...${NC}" @@ -576,6 +678,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 71cc25e6..ae8b5fa7 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -512,6 +512,42 @@ begin_category "Literals and Constants" "Testing integer, character, and string # just a number expr 0 0 + +# 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_ 2 << EOF +_Bool global_true = 2; +int main(void) { + static _Bool local_true = -3; + return (global_true == 1) + (local_true == 1); +} +EOF + +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 expr 42 42 # octal constant (satisfying re(0[0-7]+)) @@ -521,6 +557,768 @@ expr 65 0101 # Category: Arithmetic Operations begin_category "Arithmetic Operations" "Testing +, -, *, /, % operators" +# 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 + +try_ 1 << EOF +int main(void) { + unsigned int bits = 2147483647; + bits = bits + bits + 1; + return bits >> 31; +} +EOF + +try_ 1 << EOF +int main(void) { + unsigned int bits = 2147483647; + bits = bits + bits + 1; + return bits / 2 == 2147483647; +} +EOF +try_ 2 << EOF +int main(void) { + int negative = -1; + unsigned int divisor = 2U; + return (negative / divisor == 2147483647U) + + (negative % divisor == 1U); +} +EOF +try_ 1 << EOF +int main(void) { + unsigned int all_bits = 4294967295U; + return all_bits >> 31; +} +EOF +try_ 2 << EOF +int main(void) { + unsigned int value = 1UL; + return (value + 1L == 2U) + (sizeof(long) == 4); +} +EOF +try_ 2 << EOF +int main(void) { + return (0xffffffff >> 31) + (-2147483648 < 0); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned int one = 1U; + return ((~one) >> 31) + ((1 ? one - 2 : -1) >> 31); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned char byte = 1; + unsigned short half = 1; + return (-byte == -1) + (-half == -1); +} +EOF +try_ 2 << EOF +int main(void) { + unsigned char byte = 255U; + unsigned short half = 65535U; + byte++; + half++; + return (byte == 0U) + (half == 0U); +} +EOF +if [ "$PTR_SZ" -ge 8 ]; then + 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); +} +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 + 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 + 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 +fi +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 +if [ "$PTR_SZ" -ge 8 ]; then + try_ 1 << EOF +int main(void) { return (unsigned long long) 1 == 1ULL; } +EOF +fi +if [ "$PTR_SZ" -ge 8 ]; then + 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_ 4 << EOF +int main(void) { + return (sizeof(2147483647) == 4) + + (sizeof(2147483648) == 8) + + (sizeof(0xffffffff) == 4) + + (sizeof(0x100000000) == 8); +} +EOF +fi +if [ "$PTR_SZ" -lt 8 ]; then + try_compile_error << EOF +int main(void) { return 4294967296U; } +EOF +else + try_ 1 << EOF +int main(void) { return 4294967296U >> 32; } +EOF +fi +if [ "$PTR_SZ" -lt 8 ]; then + try_compile_error << EOF +int main(void) { return 1LL; } +EOF + try_compile_error << EOF +long long unsupported_value; +int main(void) { return 0; } +EOF + try_compile_error << EOF +long long unsupported_return(void) { return 0; } +int main(void) { return 0; } +EOF + try_compile_error << EOF +long long (*unsupported_callback)(void); +int main(void) { return 0; } +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 +else + 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 + try_ 2 << EOF +unsigned long long global_sum = 0x100000000ULL + 7ULL; +int main(void) { + return ((global_sum >> 32) == 1ULL) + + ((global_sum - 7ULL) == 0x100000000ULL); +} +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 + + 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_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 + 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 +fi +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_ 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_ 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 +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_ 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_ 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 +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" @@ -631,6 +1429,19 @@ declare -a logical_tests=( 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" @@ -792,10 +1603,12 @@ int main(void) { } EOF -try_compile_error << EOF +if [ "$PTR_SZ" -lt 8 ]; then + try_compile_error << EOF long long value; int main(void) { return 0; } EOF +fi # Compound literal support - C90/C99 compliant implementation Basic struct # compound literals (verified working) @@ -2598,6 +3411,27 @@ EOF # Category: Arrays begin_category "Arrays" "Testing array declarations, indexing, and operations" +# 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) { @@ -2999,6 +3833,20 @@ int main(void) } 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(). @@ -3016,6 +3864,151 @@ int main(void) } 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 + +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 + +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 @@ -3027,6 +4020,36 @@ int main(void) } 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 + +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 @@ -3210,6 +4233,69 @@ int main(void) } 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 + +# A C99 for-init declaration has block scope, so its static object persists +# across calls but remains visible only to the loop's clauses and 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 @@ -3217,6 +4303,130 @@ static int helper(void); int helper(void) { return 42; } int main(void) { return helper(); } EOF +try_compile_error << 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 + +# 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); @@ -3224,6 +4434,24 @@ 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 + # Category: Const Qualifiers begin_category "Const Qualifiers" "Testing const qualifier support for variables and parameters" @@ -3722,6 +4950,35 @@ items 20 "int *p; int a[3]; a[0] = 10; a[1] = 20; a[2] = 30; p = a; p+=1; return 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_ 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 +if [ "$PTR_SZ" -ge 8 ]; then + 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 +fi # Category: Sizeof Operator begin_category "Sizeof Operator" "Testing sizeof operator on various types" @@ -3788,6 +5045,14 @@ EOF # sizeof with expressions items 4 "int x = 42; return sizeof(x);" +items 12 "int values[3]; return sizeof(values);" +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 4 "int x = 10; int *ptr = &x; return sizeof(*ptr);" items 1 "char c = 'A'; return sizeof(c);" @@ -3853,6 +5118,115 @@ typedef enum { enum1 = 5, enum2 } enum_t; int main() { enum_t v = enum2; return v; } 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 + # Category: Memory Management begin_category "Memory Management" "Testing malloc, free, and dynamic memory allocation" diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index 180477e0..cf91f280 100755 --- a/tests/x64-abi.sh +++ b/tests/x64-abi.sh @@ -308,6 +308,7 @@ test_long_args_and_return() 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) { @@ -320,6 +321,101 @@ int main() { ' "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 @@ -394,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" ' @@ -581,6 +732,10 @@ 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}" @@ -590,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 From 29712e5fd3a53a1479180e4370b571c9c5985342 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Thu, 10 Sep 2026 16:54:31 +0800 Subject: [PATCH 03/40] Complete the preprocessor, qualifiers and sizeof The preprocessor defines the C99 implementation macros, evaluates every conditional directive, expands header names and #error operands, includes nested quoted headers, and concatenates adjacent string literals. extern, register, restrict, inline and volatile are accepted, const survives pointer levels and pointer typedefs, and signed char is a distinct type. __func__ is provided, multi-character and hexadecimal escape constants decode, sizeof gains complete expression semantics and type names in constant expressions, flexible array members work in one or more dimensions, nested designators and string-initialized char members are supported, and extern declarations are allowed in blocks and for initializers. Pointer subtraction and arithmetic are constrained and excess scalar initializers rejected. 64-bit integers are lowered to register pairs on 32-bit targets, though not yet admitted there. Paired global arithmetic folds, the ELF32 data segment is aligned, token streams are freed, and CFG traversal marks its epochs correctly. --- src/arm-codegen.c | 441 ++++++++++- src/arm.c | 42 + src/defs.h | 54 +- src/elf.c | 21 +- src/globals.c | 40 +- src/lexer.c | 49 +- src/parser.c | 1692 +++++++++++++++++++++++++++++++++++----- src/peephole.c | 4 +- src/preprocessor.c | 504 ++++++++---- src/reg-alloc.c | 437 ++++++++++- src/riscv-codegen.c | 456 ++++++++++- src/riscv.c | 6 + src/ssa.c | 18 +- tests/driver.sh | 917 +++++++++++++++++++++- tests/include-base.h | 1 + tests/include-macro.h | 2 + tests/include-main.c | 9 + tests/include-values.h | 3 + 18 files changed, 4225 insertions(+), 471 deletions(-) create mode 100644 tests/include-base.h create mode 100644 tests/include-macro.h create mode 100644 tests/include-main.c create mode 100644 tests/include-values.h diff --git a/src/arm-codegen.c b/src/arm-codegen.c index 3841687e..1dd72a2c 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -26,6 +26,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,6 +51,9 @@ 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; return; case OP_load: case OP_global_load: @@ -52,9 +63,15 @@ void update_elf_offset(ph2_ir_t *ph2_ir) */ if (ph2_ir->src0 > 4095 || (ph2_ir->size_bytes == 1 && !ph2_ir->is_unsigned && ph2_ir->src0 > 255)) - elf_offset += 16; + 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; @@ -64,9 +81,15 @@ void update_elf_offset(ph2_ir_t *ph2_ir) * (no rotation). */ if (ph2_ir->src1 > 4095) - elf_offset += 16; + 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; @@ -75,17 +98,40 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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); @@ -109,6 +155,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; @@ -124,14 +182,20 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 8; return; case OP_address_of_func: - case OP_eq: - case OP_neq: + case OP_log_not: + elf_offset += 12; + return; case OP_gt: case OP_lt: + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 24 : 12; + return; 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 ? 28 : 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) @@ -141,6 +205,8 @@ void update_elf_offset(ph2_ir_t *ph2_ir) return; case OP_return: elf_offset += 24; + if (ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_trunc: /* Unsigned byte/half truncation uses a logical shift pair; signed @@ -152,13 +218,24 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 4; return; case OP_sign_ext: - if (ph2_ir->src0_is_unsigned) + 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"); @@ -318,6 +395,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: @@ -334,13 +422,28 @@ 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) - 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->dest_hi >= 0) { + if (ph2_ir->src0 > 4095) { + 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's immediate field is only eight bits, unlike LDRB's 12-bit * field. Materialize a larger signed-byte offset before loading. @@ -372,6 +475,19 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_global_store: interm = ph2_ir->op == OP_store ? __sp : __r12; store_offset = ph2_ir->src1; + if (ph2_ir->src0_hi >= 0) { + if (store_offset > 4095) { + 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. @@ -499,6 +615,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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. @@ -517,16 +635,167 @@ 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) { + emit(__umull(__AL, rd, ph2_ir->dest_hi, rn, rm)); + emit(__mul(__AL, __r8, rn, ph2_ir->src1_hi)); + emit(__mul(__AL, __r9, ph2_ir->src0_hi, rm)); + emit(__add_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r8)); + emit(__add_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r9)); + } 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(ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned @@ -615,9 +884,50 @@ 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: + 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)); + emit(shift_kind == logic_rs ? __srl(__AL, rd, rn, rm) + : __sra(__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; @@ -627,26 +937,80 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_lt: case OP_geq: case OP_leq: + if (ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { + bool unsigned_cmp = + ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned; + arm_cond_t true_cond = arm_get_cond(ph2_ir->op, unsigned_cmp); + arm_cond_t false_cond; + + switch (ph2_ir->op) { + case OP_lt: + false_cond = unsigned_cmp ? __CS : __GE; + emit(__zero(rd)); + break; + case OP_gt: + false_cond = unsigned_cmp ? __LS : __LE; + emit(__zero(rd)); + break; + case OP_geq: + false_cond = unsigned_cmp ? __CC : __LT; + emit(__mov_i(__AL, rd, 1)); + break; + default: /* OP_leq */ + false_cond = unsigned_cmp ? __HI : __GT; + emit(__mov_i(__AL, rd, 1)); + break; + } + emit(__cmp_r(__AL, ph2_ir->src0_hi, ph2_ir->src1_hi)); + emit(__mov_i(true_cond, rd, 1)); + emit(__mov_i(false_cond, rd, 0)); + emit(__cmp_r(__EQ, rn, rm)); + if (ph2_ir->op == OP_geq || ph2_ir->op == OP_leq) + emit(__mov_i(__EQ, rd, 0)); + emit(__mov_i(true_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, 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)); @@ -672,6 +1036,27 @@ 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)); @@ -687,8 +1072,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"); diff --git a/src/arm.c b/src/arm.c index ed43dce1..cdcd43d7 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, @@ -126,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); @@ -255,11 +267,41 @@ 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 __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); diff --git a/src/defs.h b/src/defs.h index b0c63fb8..da61d7d2 100644 --- a/src/defs.h +++ b/src/defs.h @@ -349,7 +349,12 @@ typedef enum { T_continue, T_goto, T_const, /* const qualifier */ + T_volatile, T_static, + T_extern, + T_register, + T_restrict, + T_inline, T_signed, T_unsigned, T_long, @@ -560,12 +565,25 @@ struct var { char *var_name; int ptr_level; bool is_func; + + /* 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; bool is_global; bool is_static; /* declaration used the static storage class */ + 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 is_volatile; /* declaration used the volatile qualifier */ bool is_const_pointer; /* true for the outermost `* const` qualifier */ - bool address_taken; /* true if variable address was taken (&var) */ + /* 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 @@ -598,7 +616,11 @@ struct var { bool in_select_arm; int array_size; bool has_unsized_array; /* `T name[]`: bound is supplied by initializer */ - int array_dim2; /* second dimension size for 2D arrays */ + /* `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 2D arrays */ int offset; /* offset from stack or frame, index 0 is reserved */ int init_val; /* for global initialization */ int init_val_hi; /* upper word of an 8-byte integer constant */ @@ -636,7 +658,11 @@ 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) */ + 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 */ @@ -727,6 +753,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 */ @@ -774,13 +807,25 @@ struct type { var_t *fields; int num_fields; int ptr_level; /* pointer level for typedef pointer types */ + /* 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 is_const_qualified; /* qualifier carried by a scalar typedef */ + bool + has_flexible_array_member; /* cannot be embedded by value in a record */ + bool is_const_qualified; /* qualifier carried by a scalar typedef */ /* 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; + + /* Plain char and signed char share this target's representation but are + * distinct C types. Scalar typedefs preserve this fact. + */ + bool is_signed_char; }; /* lvalue details */ @@ -796,6 +841,7 @@ typedef struct { bool is_func; bool is_reference; bool is_const_qualified; + unsigned int pointer_const_mask; type_t *type; /* The declaration selected by the lvalue, including a struct member. */ var_t *decl; diff --git a/src/elf.c b/src/elf.c index ef0b97ec..76ed9f69 100644 --- a/src/elf.c +++ b/src/elf.c @@ -512,7 +512,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 +805,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; @@ -1263,18 +1268,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 +1360,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 +1386,6 @@ void elf_generate(const char *outfile) left -= n; } } -#endif /* Readable and writable sections */ elf_write_all(fp, elf_data->elements, elf_data->size); diff --git a/src/globals.c b/src/globals.c index a18ee22d..0081160d 100644 --- a/src/globals.c +++ b/src/globals.c @@ -37,6 +37,7 @@ 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; @@ -779,7 +780,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])) { @@ -791,12 +794,9 @@ int unescape_string(const char *input, char *output, int output_size) } int value = 0; - int count = 0; - - while (isxdigit(input[i]) && count < 2) { + while (isxdigit(input[i])) { value = (value << 4) + hex_digit_value(input[i]); i++; - count++; } output[j++] = (char) value; @@ -839,6 +839,22 @@ 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; +} + int parse_numeric_constant(const char *buffer) { int i = 0; @@ -1106,6 +1122,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. @@ -1808,6 +1826,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); diff --git a/src/lexer.c b/src/lexer.c index 59cc43bf..55816100 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -12,7 +12,7 @@ /* Hash table constants */ #define NUM_DIRECTIVES 11 -#define NUM_KEYWORDS 22 +#define NUM_KEYWORDS 27 /* Token mapping structure for elegant initialization */ typedef struct { @@ -88,7 +88,12 @@ 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}, + {"restrict", T_restrict}, + {"inline", T_inline}, {"signed", T_signed}, {"unsigned", T_unsigned}, {"long", T_long}, @@ -572,30 +577,28 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) if (ch == '\'') { int sz = 0; - bool escaped = false; ch = read_char(buf); - if (ch == '\\') { + while (ch && ch != '\'') { + 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) + 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); @@ -1031,9 +1034,17 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) kind = T_const; break; - case 6: /* 6-letter keywords: return, struct, switch, sizeof, static */ + 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] == 's') { if (!memcmp(token_buffer, "struct", 6)) kind = T_struct; @@ -1053,9 +1064,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: diff --git a/src/parser.c b/src/parser.c index eb4fea06..03db04fe 100644 --- a/src/parser.c +++ b/src/parser.c @@ -42,7 +42,10 @@ 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); +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, @@ -134,6 +137,7 @@ 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; @@ -175,6 +179,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) @@ -190,20 +196,54 @@ 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; } +/* 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. + */ +int effective_pointer_depth(const var_t *var) +{ + return var && var->type ? var->ptr_level + var->type->ptr_level : 0; +} + +unsigned int effective_pointer_const_mask(const var_t *var) +{ + if (!var || !var->type) + return 0; + if (var->type->ptr_level >= 32) + return var->type->pointer_const_mask; + return var->type->pointer_const_mask | + (var->pointer_const_mask << var->type->ptr_level); +} + +unsigned int dereferenced_pointer_const_mask(const var_t *var) +{ + 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; +} + /* 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 @@ -222,7 +262,8 @@ type_t *pointee_type_from_pointer_typedef(type_t *type) case TYPE_void: return TY_void; case TYPE_char: - return type->is_unsigned ? TY_uchar : TY_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: @@ -236,6 +277,26 @@ type_t *pointee_type_from_pointer_typedef(type_t *type) } } +/* Scalar typedefs have their own descriptor, but C declaration compatibility is + * based on the represented type. Records retain nominal identity. + */ +bool compatible_decl_type(const type_t *left, const type_t *right) +{ + if (left == right) + return true; + 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) + 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; +} + /* 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. @@ -274,6 +335,45 @@ var_t *type_add_field(type_t *type, int *idx) return &type->fields[i]; } +/* 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 (!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; +} + +bool is_array_declarator(const var_t *var) +{ + return var->array_size > 0 || var->is_flexible_array_member; +} + +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)); +} + +bool is_flexible_array_member_container(const var_t *field) +{ + return !field->ptr_level && type_has_flexible_array_member(field->type); +} + +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()); +} + void opstack_push(var_t *var) { if (operand_stack_idx >= MAX_OPERAND_STACK_SIZE) @@ -576,13 +676,37 @@ var_t *resize_to(block_t *block, */ bool incompatible_const_pointer_conversion(const var_t *from, const var_t *to) { - if (!from || !to || !from->ptr_level || !to->ptr_level) + unsigned int from_mask, to_mask; + int from_depth, to_depth; + + if (!from || !to) + return false; + + 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; if (from->is_const_qualified && !to->is_const_qualified) return true; - return from->ptr_level > 1 && to->ptr_level > 1 && + /* 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; } @@ -605,8 +729,37 @@ void diagnose_const_pointer_conversion(const var_t *from, const var_t *to) error_at("discarding const qualifier", cur_token_loc()); } +/* 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) +{ + 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_parameter_list_decl(func_t *func, bool anon); 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); +void read_global_function_declarator(block_t *block, + var_t *var, + bool is_static); +var_t *bind_block_extern_object(block_t *parent, var_t *var); +int read_const_expr(block_t *scope); /* Forward declaration for ternary handling used by initializers */ void read_ternary_operation(block_t *parent, basic_block_t **bb); @@ -781,9 +934,9 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) lex_ident(T_char, chtok); unescape_string(chtok, unescaped, MAX_TOKEN_LEN); - val = require_typed_var(parent, TY_char); + val = require_typed_var(parent, TY_int); val->var_name = gen_name(); - val->init_val = unescaped[0]; + val->init_val = parse_character_constant(chtok); add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); } else if (lex_peek(T_string, NULL)) { lex_accept(T_string); @@ -900,6 +1053,52 @@ void parse_array_field_row_init(block_t *parent, } } +/* 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, + bool emit_code) +{ + char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN], + combined[MAX_LINE_LEN]; + int len; + + lex_ident(T_string, literal); + unescape_string(literal, combined, MAX_LINE_LEN); + while (lex_peek(T_string, NULL)) { + int used = strlen(combined); + + lex_ident(T_string, literal); + unescape_string(literal, unescaped, MAX_LINE_LEN - used); + if (used + (int) strlen(unescaped) >= MAX_LINE_LEN - 1) + error_at("Concatenated string literal too long", cur_token_loc()); + strcpy(combined + used, unescaped); + } + + len = strlen(combined) + 1; + if (len > field->array_size) + error_at("String initializer is too long for character array", + cur_token_loc()); + if (!emit_code) + return; + + 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); + } +} + void parse_array_field_init(block_t *parent, basic_block_t **bb, const var_t *field, @@ -987,6 +1186,18 @@ void parse_struct_field_init(block_t *parent, int field_idx = 0; int initializer_count = 0; + /* 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; + /* 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. @@ -1016,23 +1227,114 @@ void parse_struct_field_init(block_t *parent, for (;;) { var_t *field_val_raw = NULL; var_t *field = NULL; + var_t *designated_field_addr = NULL; + bool consumed_pending_array_element = false; if (lex_accept(T_dot)) { char field_name[MAX_ID_LEN]; - bool found = false; + type_t *designator_type = struct_type; + var_t *designator_base = target_addr; + bool first = true; + + for (;;) { + bool found = false; + + lex_ident(T_identifier, field_name); + for (int i = 0; i < designator_type->num_fields; i++) { + if (!strcmp(designator_type->fields[i].var_name, + field_name)) { + if (first) + field_idx = i; + field = &designator_type->fields[i]; + found = true; + break; + } + } + if (!found) + error_at("Unknown field in record initializer", + cur_token_loc()); - lex_ident(T_identifier, field_name); - lex_expect(T_assign); - for (int i = 0; i < struct_type->num_fields; i++) { - if (!strcmp(struct_type->fields[i].var_name, field_name)) { - field_idx = i; - found = true; - break; + designated_field_addr = compute_field_address( + parent, bb, designator_base, field); + if (lex_accept(T_open_square)) { + int index, element_count, linear_index; + int total_element_count; + int elem_size; + bool direct_array_member = first; + var_t *array_base_addr = designated_field_addr; + + if (!field->array_size) + error_at( + "Array designator requires an array member", + cur_token_loc()); + index = read_const_expr(parent); + lex_expect(T_close_square); + total_element_count = field->array_size; + element_count = total_element_count; + if (field->array_dim2) + element_count /= field->array_dim2; + if (index < 0 || index >= element_count) + error_at("Array designator index is out of bounds", + cur_token_loc()); + elem_size = + field->ptr_level ? PTR_SIZE : field->type->size; + if (field->array_dim2) + index *= field->array_dim2; + linear_index = index; + designated_field_addr = compute_element_address( + parent, bb, designated_field_addr, index, + elem_size); + memcpy(&array_element, field, sizeof(var_t)); + array_element.array_size = field->array_dim2; + array_element.array_dim2 = 0; + field = &array_element; + + /* A first subscript of a two-dimensional member names a + * row; a second selects its scalar element. Leaving it + * as an array supports `.cells[row] = {...}`. + */ + if (field->array_size && lex_accept(T_open_square)) { + index = read_const_expr(parent); + lex_expect(T_close_square); + if (index < 0 || index >= field->array_size) + error_at( + "Array designator index is out of bounds", + cur_token_loc()); + designated_field_addr = compute_element_address( + parent, bb, designated_field_addr, index, + elem_size); + linear_index += index; + array_element.array_size = 0; + } + if (direct_array_member && !field->array_size) { + memcpy(&pending_array_element, field, + sizeof(var_t)); + pending_array_base = array_base_addr; + pending_array_index = linear_index + 1; + pending_array_count = total_element_count; + pending_array_elem_size = elem_size; + has_pending_array_element = true; + } } + if (!lex_accept(T_dot)) + break; + if (!is_record_type(field->type)) + error_at("Nested designator requires a record member", + cur_token_loc()); + designator_type = field->type; + if (designator_type->base_type == TYPE_typedef && + designator_type->base_struct) + designator_type = designator_type->base_struct; + designator_base = designated_field_addr; + first = false; } - if (!found) - error_at("Unknown field in record initializer", - cur_token_loc()); + lex_expect(T_assign); + } else if (has_pending_array_element) { + field = &pending_array_element; + designated_field_addr = compute_element_address( + parent, bb, pending_array_base, pending_array_index, + pending_array_elem_size); + consumed_pending_array_element = true; } if (field_idx >= struct_type->num_fields || @@ -1042,19 +1344,32 @@ void parse_struct_field_init(block_t *parent, error_at("Too many elements in record initializer", next_token_loc()); - if (field_idx < struct_type->num_fields) + if (!field && field_idx < struct_type->num_fields) field = &struct_type->fields[field_idx]; - if (field && lex_peek(T_open_curly, NULL) && field->array_size) { + if (field && field->array_size && !field->ptr_level && + field->type == TY_char && lex_peek(T_string, NULL)) { + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + parse_string_field_init(parent, bb, field, field_addr, + emit_code); + } else if (field && lex_peek(T_open_curly, NULL) && + field->array_size) { var_t *field_addr = - compute_field_address(parent, bb, target_addr, field); + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); parse_array_field_init(parent, bb, field, field_addr, emit_code); } else if (field && lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { type_t *nested_type = field->type; var_t *field_addr = - compute_field_address(parent, bb, target_addr, field); + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); if (nested_type->base_type == TYPE_typedef && nested_type->base_struct) @@ -1077,7 +1392,9 @@ void parse_struct_field_init(block_t *parent, if (field_val_raw && field_idx < struct_type->num_fields) { var_t *field_addr = - compute_field_address(parent, bb, target_addr, field); + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); if (is_record_type(field->type) && is_record_object(field_val_raw)) { @@ -1092,7 +1409,16 @@ void parse_struct_field_init(block_t *parent, } } - field_idx++; + 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++; if (!lex_accept(T_comma)) break; @@ -1743,6 +2069,95 @@ var_t *scalarize_array_literal_if_needed(block_t *parent, int eval_expression_imm(opcode_t op, int op1, int op2); int read_const_expr(block_t *scope); +/* 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) +{ + char token[MAX_ID_LEN]; + type_t *type = NULL; + int ptr_level = 0; + bool is_unsigned = false; + bool is_signed = false; + int long_count = 0; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { + lex_ident(T_identifier, token); + type = find_type(token, 2); + } else if (lex_accept(T_enum)) { + lex_ident(T_identifier, token); + type = find_type_tag(token, scope); + } else { + /* Declaration specifiers may appear in any order: all of "unsigned long + * int", "long unsigned int", and "int unsigned" name the same type. + */ + while (true) { + 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_signed)) { + if (is_signed) + error_at("duplicate signed type specifier", + cur_token_loc()); + is_signed = true; + } else if (lex_accept(T_long)) { + long_count++; + } else if (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) { + ; + } else if (lex_peek(T_identifier, token)) { + type_t *candidate = find_type(token, true); + + if (!candidate) + break; + lex_expect(T_identifier); + type = candidate; + } else { + break; + } + } + if (is_unsigned && is_signed) + error_at("both signed and unsigned specified", cur_token_loc()); + if (long_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + if (long_count) { + type = is_unsigned ? TY_ulong : TY_long; + if (long_count == 2) + type = is_unsigned ? TY_ulong_long : TY_long_long; + } else if (is_unsigned) { + if (type == TY_char) + type = TY_uchar; + else if (type == TY_short) + type = TY_ushort; + else + type = TY_uint; + } else if (is_signed) { + type = type == TY_char ? TY_schar : (type ? type : TY_int); + } + } + if (!type) + error_at( + "sizeof in an integer constant expression requires a type name", + cur_token_loc()); + while (lex_accept(T_asterisk)) { + ptr_level++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + lex_expect(T_close_bracket); + if (ptr_level) + return PTR_SIZE; + if (type->size) + return type->size; + return type->base_struct->size; +} + int read_const_expr_operand(block_t *scope) { char buffer[MAX_TOKEN_LEN]; @@ -1755,6 +2170,8 @@ int read_const_expr_operand(block_t *scope) 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_const_sizeof_type(scope); if (lex_accept(T_open_bracket)) { int res = read_const_expr(scope); @@ -1770,7 +2187,7 @@ int read_const_expr_operand(block_t *scope) lex_expect(T_char); if (unescape_string(buffer, unescaped, MAX_TOKEN_LEN) < 0) error_at("Invalid escape sequence", cur_token_loc()); - return unescaped[0]; + return parse_character_constant(buffer); } if (lex_peek(T_identifier, buffer)) { lex_expect(T_identifier); @@ -1839,7 +2256,10 @@ int read_const_expr(block_t *scope) return val_stack[0]; } -void read_inner_var_decl(var_t *vd, bool anon, bool is_param) +void read_inner_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member) { /* Preserve typedef pointer level - don't reset if already inherited */ vd->init_val = 0; @@ -1858,8 +2278,18 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) * require tracking const-ness of the pointer itself vs the pointed-to * data separately. */ - while (lex_accept(T_const)) - vd->is_const_pointer = true; + 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; + } } /* is it function pointer declaration? */ @@ -1867,6 +2297,16 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) func_t *func = arena_alloc_func(); char temp_name[MAX_VAR_LEN]; lex_expect(T_asterisk); + while (true) { + if (lex_accept(T_const)) + vd->is_const_pointer = true; + 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; + } lex_ident(T_identifier, temp_name); vd->var_name = intern_string(temp_name); lex_expect(T_close_bracket); @@ -1934,7 +2374,13 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) lex_expect(T_close_square); dims++; } - if (first_dim_empty && dims == 1) { + 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 && dims == 1) { /* An array parameter adjusts to a pointer, but an object declarator * such as `char text[] = "hi"` has an inferred array bound. Keeping * those cases distinct lets its initializer create writable array @@ -1945,12 +2391,33 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) else vd->has_unsized_array = true; } + + /* 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()); vd->is_func = false; } + + /* 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)); } /* starting next_token, need to check the type */ -void read_full_var_decl(var_t *vd, bool anon, bool is_param) +void read_full_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member) { char type_name[MAX_ID_LEN]; @@ -1960,9 +2427,13 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) * 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_signed = false; bool is_unsigned = false; bool is_const = false; + bool is_inline = false; + bool is_volatile = false; bool is_long = false; bool is_long_long = false; type_t *leading_scalar_type = NULL; @@ -1991,7 +2462,8 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) * follow the same path. */ while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_const, NULL) || lex_peek(T_long, NULL)) { + lex_peek(T_const, NULL) || lex_peek(T_inline, NULL) || + lex_peek(T_volatile, NULL) || lex_peek(T_long, NULL)) { if (lex_accept(T_signed)) { if (is_signed) error_at("duplicate signed type specifier", cur_token_loc()); @@ -2002,7 +2474,14 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) is_unsigned = true; } else if (lex_accept(T_const)) is_const = true; - else { + else if (lex_accept(T_volatile)) + is_volatile = true; + else if (lex_accept(T_inline)) { + if (is_inline || declaration_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + is_inline = true; + } else { lex_expect(T_long); if (is_long_long) error_at("too many long type specifiers", cur_token_loc()); @@ -2070,7 +2549,14 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) if (leading_scalar_type == TY_short && lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) lex_expect(T_identifier); - type = leading_scalar_type; + type = leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; + } else if (is_signed && lex_peek(T_identifier, type_name) && + (!strcmp(type_name, "char") || !strcmp(type_name, "short") || + !strcmp(type_name, "int"))) { + lex_expect(T_identifier); + type = !strcmp(type_name, "char") + ? TY_schar + : (!strcmp(type_name, "short") ? TY_short : TY_int); } else if (is_signed && find_type_flag == 1 && (!lex_peek(T_identifier, type_name) || (strcmp(type_name, "int") && strcmp(type_name, "char") && @@ -2092,14 +2578,30 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) vd->type = type; vd->is_const_qualified = type->is_const_qualified; + 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 (lex_accept(T_const)) - is_const = true; + while (true) { + if (lex_accept(T_const)) + is_const = true; + else if (lex_accept(T_volatile)) + is_volatile = true; + else if (lex_accept(T_inline)) { + if (is_inline || declaration_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + is_inline = true; + } else + break; + } + vd->is_inline = declaration_inline || is_inline; + vd->is_volatile = declaration_volatile || is_volatile; if (is_const || declaration_const) { if (type->ptr_level) vd->is_const_pointer = true; @@ -2107,7 +2609,7 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) vd->is_const_qualified = true; } - read_inner_var_decl(vd, anon, is_param); + read_inner_var_decl(vd, anon, is_param, is_record_member); /* A 32-bit target can carry a pointer to an eight-byte object without a * paired-value register representation. Keep direct wide objects, arrays, @@ -2125,7 +2627,7 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) void read_partial_var_decl(var_t *vd, var_t *template) { UNUSED(template); - read_inner_var_decl(vd, false, false); + read_inner_var_decl(vd, false, false, false); } void read_parameter_list_decl(func_t *func, bool anon) @@ -2139,7 +2641,7 @@ void read_parameter_list_decl(func_t *func, bool anon) if (lex_accept(T_close_bracket)) return; func->param_defs[vn].type = TY_void; - read_inner_var_decl(&func->param_defs[vn], anon, true); + 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", @@ -2149,18 +2651,26 @@ void read_parameter_list_decl(func_t *func, bool anon) } while (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; 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()); - read_full_var_decl(&func->param_defs[vn], anon, true); + read_full_var_decl(&func->param_defs[vn], anon, true, 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; @@ -2522,9 +3032,9 @@ void read_char_param(block_t *parent, basic_block_t *bb) lex_ident(T_char, literal); unescape_string(literal, unescaped, MAX_TOKEN_LEN); - var_t *vd = require_typed_var(parent, TY_char); + var_t *vd = require_typed_var(parent, TY_int); vd->var_name = gen_name(); - vd->init_val = unescaped[0]; + 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); @@ -2555,6 +3065,11 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) 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()); diagnose_const_pointer_conversion(param, target); if (is_record_type(target->type) && !target->ptr_level) { /* A record parameter is a by-value object. Keep that promise @@ -2677,13 +3192,21 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) if (!lex_peek(T_identifier, token)) error_at("Expected an identifier", next_token_loc()); var_t *var = find_var(token, parent); + 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, true); if (!lvalue.is_reference) { rs1 = opstack_pop(); vd = require_ref_var(parent, lvalue.type, lvalue.ptr_level); vd->var_name = gen_name(); - vd->is_const_qualified = lvalue.is_const_qualified; + + /* 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; opstack_push(vd); add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); } @@ -2707,7 +3230,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) * defaults */ type_t *deref_type = rs1->type ? rs1->type : TY_int; - int deref_ptr = rs1->ptr_level > 0 ? rs1->ptr_level - 1 : 0; + 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 @@ -2721,6 +3244,11 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) else sz = 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))); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } else { @@ -2735,7 +3263,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) rs1 = opstack_pop(); vd = require_deref_var(parent, var->type, var->ptr_level); - if (lvalue.ptr_level > 1) + if (var->ptr_level + var->type->ptr_level > 1) sz = PTR_SIZE; else { /* For typedef pointers, get the size of the pointed-to type */ @@ -2763,6 +3291,11 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) } } vd->var_name = gen_name(); + vd->is_const_qualified = var->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))); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } @@ -2849,6 +3382,11 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) else sz = 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))); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } @@ -2897,22 +3435,156 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) } } 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))); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } } } +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_TOKEN_LEN]; + char unescaped[MAX_TOKEN_LEN]; + int size = 1; + + do { + lex_ident(T_string, literal); + unescape_string(literal, unescaped, MAX_TOKEN_LEN); + size += strlen(unescaped); + } while (lex_peek(T_string, NULL)); + + return size; +} + +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; +} + 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; var_t *vd; - lex_expect(T_open_bracket); + 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 is the + * complete operand. + */ + token_t *after_string = cur_token->next; + while (after_string && after_string->kind == T_string) + after_string = after_string->next; + if (lex_peek(T_string, NULL) && + (!parenthesized || + (after_string && after_string->kind == T_close_bracket))) { + vd = require_var(parent); + vd->init_val = read_sizeof_string_literal(); + vd->var_name = gen_name(); + if (parenthesized) + lex_expect(T_close_bracket); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + 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); + vd = require_var(parent); + vd->init_val = strlen(parent->func->return_def.var_name) + 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + return; + } + + if (lex_peek(T_identifier, token)) { + var_t *array = find_var(token, parent); + + if (array && array->array_size > 0) { + lex_expect(T_identifier); + vd = require_var(parent); + vd->init_val = sizeof_array_object(array); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + return; + } + } + + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + read_expr_operand(parent, &unevaluated_bb); + se_idx = saved_side_effects; + var_t *expr_var = opstack_pop(); + 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); + + vd = require_var(parent); + vd->init_val = type->size; + if (array_size > 0) + vd->init_val = array_size * type->size; + if (ptr_cnt) + vd->init_val = PTR_SIZE; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + 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); + vd = require_var(parent); + vd->init_val = strlen(parent->func->return_def.var_name) + 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + return; + } /* A bare array identifier is the one expression form that must retain its * declared extent for sizeof; ordinary expression parsing intentionally @@ -2926,7 +3598,7 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (array && array->array_size > 0) { lex_expect(T_identifier); vd = require_var(parent); - vd->init_val = array->array_size * array->type->size; + vd->init_val = sizeof_array_object(array); vd->var_name = gen_name(); opstack_push(vd); lex_expect(T_close_bracket); @@ -2955,7 +3627,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) } } while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_long, NULL) || lex_peek(T_const, NULL)) { + lex_peek(T_long, NULL) || lex_peek(T_const, NULL) || + lex_peek(T_volatile, NULL)) { if (lex_accept(T_signed)) { if (has_signed_type) error_at("duplicate signed type specifier", cur_token_loc()); @@ -2966,8 +3639,10 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) has_unsigned_type = true; } else if (lex_accept(T_long)) long_type_count++; + else if (lex_accept(T_const)) + ; else - lex_expect(T_const); + lex_expect(T_volatile); } if (long_type_count > 2) error_at("too many long type specifiers", cur_token_loc()); @@ -2991,8 +3666,12 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = find_type_tag(token, parent); if (!type) error_at("Unknown enum type", cur_token_loc()); - while (lex_accept(T_asterisk)) + while (lex_accept(T_asterisk)) { ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } } else if (has_long_type) { type = has_unsigned_type ? TY_ulong : TY_long; if (long_type_count > 1) { @@ -3010,8 +3689,12 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) lex_accept(T_const); if (lex_peek(T_identifier, token) && !strcmp(token, "int")) lex_expect(T_identifier); - while (lex_accept(T_asterisk)) + while (lex_accept(T_asterisk)) { ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } } else if (has_unsigned_type) { if (leading_scalar_type == TY_char) { type = TY_uchar; @@ -3029,45 +3712,66 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = TY_ushort; } else type = TY_uint; - while (lex_accept(T_asterisk)) + while (lex_accept(T_asterisk)) { ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } } else if (has_signed_type) { if (leading_scalar_type == TY_char || leading_scalar_type == TY_short) { if (leading_scalar_type == TY_short && lex_peek(T_identifier, token) && !strcmp(token, "int")) lex_expect(T_identifier); - type = leading_scalar_type; + type = + leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; } else if (lex_peek(T_identifier, token) && (!strcmp(token, "int") || !strcmp(token, "char") || !strcmp(token, "short"))) { lex_expect(T_identifier); - type = find_type(token, true); + type = !strcmp(token, "char") ? TY_schar : find_type(token, true); } else type = TY_int; - while (lex_accept(T_asterisk)) + while (lex_accept(T_asterisk)) { ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } } else if (leading_scalar_type) { type = leading_scalar_type; - while (lex_accept(T_asterisk)) + while (lex_accept(T_asterisk)) { ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } } else 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)) + while (lex_accept(T_asterisk)) { ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } } } if (!type) { /* sizeof(expression) - parse the expression and get its type */ - read_expr(parent, bb); - read_ternary_operation(parent, bb); + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + se_idx = saved_side_effects; var_t *expr_var = opstack_pop(); type = expr_var->type; ptr_cnt = expr_var->ptr_level; + array_size = expr_var->array_size; is_function = expr_var->is_func; } @@ -3079,7 +3783,11 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) error_at("sizeof(function) is invalid", &sizeof_tk->location); vd = require_var(parent); - vd->init_val = ptr_cnt ? PTR_SIZE : type->size; + vd->init_val = type->size; + if (array_size > 0) + vd->init_val = array_size * type->size; + if (ptr_cnt) + vd->init_val = PTR_SIZE; vd->var_name = gen_name(); opstack_push(vd); lex_expect(T_close_bracket); @@ -3091,6 +3799,17 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) var_t *vd, *rs1; bool is_neg = false; + if (lex_accept(T_plus)) { + read_expr_operand(parent, bb); + rs1 = opstack_pop(); + 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); + opstack_push(rs1); + return; + } + if (lex_accept(T_minus)) { is_neg = true; if (!lex_peek(T_numeric, NULL) && !lex_peek(T_identifier, NULL) && @@ -3115,8 +3834,9 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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; + 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 { @@ -3137,6 +3857,9 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr_operand(parent, bb); rs1 = opstack_pop(); + 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 */ @@ -3146,6 +3869,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) vd->type = rs1->type; vd->is_const = true; vd->init_val = ~rs1->init_val; + vd->init_val_hi = ~rs1->init_val_hi; opstack_push(vd); add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); } else { @@ -3176,6 +3900,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) int cast_array_size = 0; bool cast_const_qualified = false; bool cast_const_pointer = false; + unsigned int cast_pointer_const_mask = 0; + bool cast_volatile_qualified = false; /* Look ahead to see if we have a typename followed by ) */ token_t *type_start = cur_token; @@ -3199,10 +3925,13 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) leading_scalar_type = find_type(lookahead_token, true); } } - while (lex_peek(T_const, NULL) || lex_peek(T_signed, NULL) || - lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { + while (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)) { if (lex_accept(T_const)) has_const_type = true; + else if (lex_accept(T_volatile)) + cast_volatile_qualified = true; else if (lex_accept(T_signed)) { if (has_signed_type) error_at("duplicate signed type specifier", @@ -3294,13 +4023,16 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_peek(T_identifier, lookahead_token) && !strcmp(lookahead_token, "int")) lex_expect(T_identifier); - type = leading_scalar_type; + type = leading_scalar_type == TY_char ? TY_schar + : leading_scalar_type; } else if (lex_peek(T_identifier, lookahead_token) && (!strcmp(lookahead_token, "int") || !strcmp(lookahead_token, "char") || !strcmp(lookahead_token, "short"))) { lex_expect(T_identifier); - type = find_type(lookahead_token, true); + type = !strcmp(lookahead_token, "char") + ? TY_schar + : find_type(lookahead_token, true); } else { type = TY_int; } @@ -3319,15 +4051,30 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_expect(T_identifier); /* A qualifier may appear before or after the base type. */ - while (lex_accept(T_const)) - has_const_type = true; + while (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL)) { + if (lex_accept(T_const)) + has_const_type = true; + else + cast_volatile_qualified = true; + } /* Check for pointer types: int*, char*, etc. */ int ptr_level = 0; while (lex_accept(T_asterisk)) { ptr_level++; - while (lex_accept(T_const)) - cast_const_pointer = true; + while (lex_peek(T_const, NULL) || + lex_peek(T_volatile, NULL) || + lex_peek(T_restrict, NULL)) { + if (lex_accept(T_const)) { + cast_const_pointer = true; + if (ptr_level <= 32) + cast_pointer_const_mask |= 1U + << (ptr_level - 1); + } else if (lex_accept(T_volatile)) + cast_volatile_qualified = true; + else + lex_expect(T_restrict); + } } if (PTR_SIZE < 8 && type->base_type == TYPE_long_long && @@ -3396,6 +4143,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) cast_var->var_name = gen_name(); cast_var->is_const_qualified = cast_const_qualified; cast_var->is_const_pointer = cast_const_pointer; + cast_var->pointer_const_mask = cast_pointer_const_mask; + cast_var->is_volatile = cast_volatile_qualified; /* An explicit C cast is permitted to remove qualifiers, but it is * almost always a bug. Keep compiling it while making that loss @@ -3462,15 +4211,9 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* 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(); - } + if (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in scalar compound literal", + cur_token_loc()); } else { /* Empty pointer compound literal: (int*){} */ compound_var->init_val = 0; /* NULL pointer */ @@ -3517,7 +4260,18 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr(parent, bb); read_ternary_operation(parent, bb); - /* Check if there are more elements (comma-separated) */ + /* 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(); @@ -3629,7 +4383,43 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) var_t *var = find_var(token, parent); func_t *func = find_func(token); - if (con) { + /* 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, "__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) { vd = require_var(parent); vd->init_val = con->value; vd->var_name = gen_name(); @@ -3683,6 +4473,9 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (is_neg) { rs1 = opstack_pop(); + 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); /* Constant folding for negation */ @@ -3692,6 +4485,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) vd->type = rs1->type; vd->is_const = true; vd->init_val = -rs1->init_val; + vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); opstack_push(vd); add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); @@ -3832,6 +4626,14 @@ void handle_pointer_arithmetic(block_t *parent, var_t *int_var = NULL; int element_size = 0; + /* 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()); @@ -3846,31 +4648,49 @@ void handle_pointer_arithmetic(block_t *parent, /* If they have names, they might be variable references - look them up */ - if (rs1->var_name[0] && !rs1->init_val) { + if (rs1->var_name[0]) { var_t *found = find_var(rs1->var_name, parent); if (found) orig_rs1 = found; } - if (rs2->var_name[0] && !rs2->init_val) { + 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); - bool rs2_is_ptr = is_pointer_like_value(orig_rs2); + 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_ptr = is_pointer_like_value(rs1) || rs1->is_func; if (!rs2_is_ptr) - rs2_is_ptr = is_pointer_like_value(rs2); + 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 + * 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); + int left_depth = orig_rs1->ptr_level + orig_rs1->type->ptr_level; + int right_depth = orig_rs2->ptr_level + orig_rs2->type->ptr_level; + + 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 = get_pointer_element_size(orig_rs1); @@ -3951,7 +4771,8 @@ 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); + 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 @@ -4255,8 +5076,10 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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 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); @@ -4266,6 +5089,12 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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); @@ -4458,6 +5287,7 @@ void read_lvalue(lvalue_t *lvalue, (var->ptr_level || (var->type && var->type->ptr_level)) ? var->is_const_pointer : var->is_const_qualified; + lvalue->pointer_const_mask = var->pointer_const_mask; opstack_push(var); @@ -4486,7 +5316,7 @@ void read_lvalue(lvalue_t *lvalue, * 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 && + if (var->ptr_level == 0 && !is_array_declarator(var) && !is_typedef_pointer) error_at("Cannot apply square operator to non-pointer", cur_token_loc()); @@ -4499,8 +5329,8 @@ void read_lvalue(lvalue_t *lvalue, if (subscript_depth) indexed_ptr_level = lvalue->value_ptr_level; else - indexed_ptr_level = - var->ptr_level + var->type->ptr_level + !!var->array_size; + indexed_ptr_level = var->ptr_level + var->type->ptr_level + + !!is_array_declarator(var); lvalue->value_ptr_level = indexed_ptr_level ? indexed_ptr_level - 1 : 0; @@ -4508,7 +5338,7 @@ void read_lvalue(lvalue_t *lvalue, * which have ptr_level == 0 */ if ((var->ptr_level <= 1 || is_typedef_pointer) && - var->array_size == 0) { + !is_array_declarator(var)) { /* For typedef pointers, get the size of the base type that the * pointer points to */ @@ -4631,7 +5461,7 @@ void read_lvalue(lvalue_t *lvalue, /* if it is an array, get the address of first element instead of * its value. */ - if (var->array_size > 0) + if (is_array_declarator(var)) lvalue->is_reference = false; /* move pointer to offset of structure */ @@ -4666,11 +5496,12 @@ void read_lvalue(lvalue_t *lvalue, * a pointer. */ if (allow_ptr_arith && lex_peek(T_plus, NULL) && - (var->ptr_level || var->array_size) && !lvalue->is_reference) { - if (!var->array_size && + (var->ptr_level || is_array_declarator(var)) && !lvalue->is_reference) { + if (!is_array_declarator(var) && is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); - while (lex_peek(T_plus, NULL) && (var->ptr_level || var->array_size)) { + while (lex_peek(T_plus, NULL) && + (var->ptr_level || is_array_declarator(var))) { lex_expect(T_plus); if (lvalue->is_reference) { rs1 = opstack_pop(); @@ -4687,7 +5518,8 @@ void read_lvalue(lvalue_t *lvalue, * 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 || (var->array_size && var->ptr_level)) + if (var->ptr_level > 1 || + (is_array_declarator(var) && var->ptr_level)) lvalue->size = PTR_SIZE; else lvalue->size = lvalue->type->size; @@ -4716,11 +5548,16 @@ void read_lvalue(lvalue_t *lvalue, * first decay to a pointer, adding one level to the declaration's * element indirection. */ - if (var->ptr_level || var->array_size) { + if (var->ptr_level || is_array_declarator(var)) { vd->type = lvalue->type; - vd->ptr_level = var->ptr_level + !!var->array_size; + vd->ptr_level = var->ptr_level + !!is_array_declarator(var); } vd->var_name = gen_name(); + vd->is_const_qualified = var->is_const_qualified; + vd->pointer_const_mask = var->pointer_const_mask; + vd->is_const_pointer = + vd->ptr_level > 0 && vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); opstack_push(vd); add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); } @@ -5269,6 +6106,11 @@ bool read_body_assignment(char *token, 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(); rhs_val = scalarize_array_literal_if_needed( parent, bb, rhs_val, lvalue.type, @@ -5364,6 +6206,10 @@ bool read_body_assignment(char *token, vd = opstack_pop(); if (is_record_object(vd) && is_record_object(rs1)) { emit_record_copy(parent, bb, vd, rs1); + } else if (incompatible_character_pointer_conversion(rs1, vd)) { + error_at( + "incompatible character pointer types in assignment", + cur_token_loc()); } else if (incompatible_const_pointer_conversion(rs1, vd)) { diagnose_const_pointer_conversion(rs1, vd); } else { @@ -5394,7 +6240,7 @@ int read_primary_constant(block_t *scope) } else if (lex_peek(T_char, buffer)) { char unescaped[MAX_TOKEN_LEN]; unescape_string(buffer, unescaped, MAX_TOKEN_LEN); - res = unescaped[0]; + res = parse_character_constant(buffer); lex_expect(T_char); } else if (lex_peek(T_identifier, buffer)) { constant_t *con; @@ -5545,6 +6391,159 @@ bool wide_global_literal_appears_before_initializer_end(token_t *token) var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb); +/* 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 (op == OP_div || op == OP_mod) { + unsigned int rem_lo = 0, rem_hi = 0, quo_lo = 0, quo_hi = 0; + bool is_unsigned = left->type->is_unsigned || right->type->is_unsigned; + bool neg_left = !is_unsigned && (hi >> 31); + bool neg_right = !is_unsigned && (rhs_hi >> 31); + + if (rhs_lo == 0 && rhs_hi == 0) + return false; + 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); + } + } + result->init_val = out_lo; + result->init_val_hi = out_hi; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + 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; + } + result->init_val = out_lo; + result->init_val_hi = out_hi; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; +} + /* 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. @@ -5583,7 +6582,8 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) result = require_var(parent); result->var_name = gen_name(); result->type = value->type; - add_insn(parent, bb, OP_sub, result, zero, value, 0, NULL); + emit_wide_global_word_arithmetic(parent, bb, result, OP_sub, zero, + value); return result; } if (lex_accept(T_bit_not)) { @@ -5593,7 +6593,8 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) result = require_var(parent); result->var_name = gen_name(); result->type = value->type; - add_insn(parent, bb, OP_bit_not, result, value, NULL, 0, NULL); + emit_wide_global_word_arithmetic(parent, bb, result, OP_bit_not, value, + NULL); return result; } if (lex_accept(T_log_not)) { @@ -5646,8 +6647,10 @@ var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb) result->var_name = gen_name(); result->type = integer_binary_result_type( op_stack[--op_stack_index], left, right); - add_insn(parent, bb, op_stack[op_stack_index], result, left, right, - 0, NULL); + 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 || @@ -5666,8 +6669,10 @@ var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb) result->var_name = gen_name(); result->type = integer_binary_result_type(op_stack[--op_stack_index], left, right); - add_insn(parent, bb, op_stack[op_stack_index], result, left, right, 0, - NULL); + 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]; @@ -6037,7 +7042,7 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) 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]; + case_val = parse_character_constant(literal); lex_expect(T_char); } else if (lex_peek(T_identifier, token)) { const constant_t *cd = find_scoped_constant(token, parent); @@ -6142,6 +7147,10 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) opcode_t prefix_op = OP_generic; bool is_const = false; bool is_static = false; + bool is_extern = false; + bool is_register = false; + bool is_volatile = false; + bool for_decl_semicolon_consumed = false; lex_expect(T_open_bracket); @@ -6153,17 +7162,34 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) bb_connect(bb, setup, NEXT); if (!lex_accept(T_semicolon)) { - while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL)) { + 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)) { 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_volatile)) { + is_volatile = true; } else { lex_expect(T_const); is_const = true; } } + if ((is_static && is_register) || (is_static && is_extern) || + (is_register && is_extern)) + error_at("incompatible storage class specifiers", cur_token_loc()); bool has_builtin_type = lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || @@ -6181,79 +7207,115 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) if (type) { var = require_typed_var(blk, type); var->is_static = is_static; + var->is_register = is_register; var->is_global = is_static; var->is_const_qualified = is_const; - read_full_var_decl(var, false, false); - add_insn(is_static ? GLOBAL_BLOCK : blk, - is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, var, - NULL, NULL, 0, NULL); - add_symbol(setup, var); - if (lex_accept(T_assign)) { - if (is_static) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, - true); - 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->ptr_level && - var->type == TY_char && lex_peek(T_string, NULL)) { - parse_string_array_init(var, blk, &setup); - } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, blk, &setup, true); + var->is_volatile = is_volatile; + read_full_var_decl(var, false, false, false); + if (is_extern) { + if (var->is_func || lex_peek(T_open_bracket, NULL)) { + /* This helper consumes the declaration's semicolon. */ + blk->locals.size--; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = + var; + read_global_function_declarator(GLOBAL_BLOCK, var, false); + var_t *alias = require_var(blk); + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + for_decl_semicolon_consumed = true; } else { - 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); + for (;;) { + var = bind_block_extern_object(blk, var); + if (lex_peek(T_assign, NULL)) + error_at( + "extern declaration cannot have an initializer", + next_token_loc()); + if (!lex_accept(T_comma)) + break; + var = require_typed_var(blk, type); + var->is_const_qualified = is_const; + var->is_volatile = is_volatile; + read_partial_var_decl(var, 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); - nv->is_static = is_static; - nv->is_global = is_static; - nv->is_const_qualified = is_const; - read_partial_var_decl(nv, var); /* partial */ + } else { add_insn(is_static ? GLOBAL_BLOCK : blk, - is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, nv, + is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(setup, nv); + add_symbol(setup, var); if (lex_accept(T_assign)) { if (is_static) { if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) - parse_array_init(nv, GLOBAL_BLOCK, + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); else if (lex_peek(T_open_curly, NULL)) - parse_global_record_init(nv, GLOBAL_BLOCK); + parse_global_record_init(var, GLOBAL_BLOCK); else - read_global_assignment_var(nv); - } else if (nv->has_unsized_array && !nv->ptr_level && - nv->type == TY_char && + read_global_assignment_var(var); + } else if (var->has_unsized_array && !var->ptr_level && + var->type == TY_char && lex_peek(T_string, NULL)) { - parse_string_array_init(nv, blk, &setup); + parse_string_array_init(var, blk, &setup); } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, blk, &setup, true); + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, blk, &setup, true); } else { 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); + nv->is_static = is_static; + nv->is_register = is_register; + nv->is_global = is_static; + nv->is_const_qualified = is_const; + nv->is_volatile = is_volatile; + read_partial_var_decl(nv, var); /* partial */ + add_insn(is_static ? GLOBAL_BLOCK : blk, + is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, + nv, NULL, NULL, 0, NULL); + add_symbol(setup, nv); + if (lex_accept(T_assign)) { + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) + parse_array_init(nv, GLOBAL_BLOCK, + &GLOBAL_FUNC->bbs, true); + else if (lex_peek(T_open_curly, NULL)) + parse_global_record_init(nv, GLOBAL_BLOCK); + else + read_global_assignment_var(nv); + } else if (nv->has_unsized_array && !nv->ptr_level && + nv->type == TY_char && + lex_peek(T_string, NULL)) { + parse_string_array_init(nv, blk, &setup); + } else if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || + nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, blk, &setup, true); + } else { + read_expr(blk, &setup); - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); + rs1 = resize_var(parent, &bb, opstack_pop(), nv); + add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, + NULL); + } } } } @@ -6261,7 +7323,8 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) read_body_assignment(token, blk, OP_generic, &setup); } - lex_expect(T_semicolon); + if (!for_decl_semicolon_consumed) + lex_expect(T_semicolon); } basic_block_t *cond_ = bb_create(blk); @@ -6443,7 +7506,7 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) /* multiple (partial) declarations */ nv = require_typed_var(parent, type); - read_inner_var_decl(nv, false, false); + read_inner_var_decl(nv, false, false, false); add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); add_symbol(bb, nv); if (lex_accept(T_assign)) { @@ -6595,6 +7658,33 @@ basic_block_t *handle_enum_statement(block_t *parent, return handle_enum_declarators(parent, bb, type, is_const, is_static); } +/* 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; + + 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; +} + /* Everything a statement can still be: a declaration, an assignment, a call, or * an expression evaluated for its effect. */ @@ -6607,18 +7697,38 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) opcode_t prefix_op = OP_generic; bool is_const = false; bool is_static = false; + bool is_extern = false; + bool is_register = false; + bool is_volatile = false; - while (lex_peek(T_static, NULL) || lex_peek(T_const, NULL)) { + 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)) { 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_volatile)) { + is_volatile = true; } else { lex_expect(T_const); is_const = true; } } + if ((is_static && is_register) || (is_static && is_extern) || + (is_register && is_extern)) + error_at("incompatible storage class specifiers", cur_token_loc()); if (lex_peek(T_enum, NULL)) return handle_enum_statement(parent, bb, is_const, is_static); @@ -6685,15 +7795,55 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } } - if (is_static && !type) - error_at("Expected declaration after static", next_token_loc()); + if ((is_static || is_extern) && !type) + error_at("Expected declaration after storage class specifier", + next_token_loc()); if (type) { var = require_typed_var(parent, type); var->is_static = is_static; + var->is_register = is_register; var->is_global = is_static; var->is_const_qualified = is_const; - read_full_var_decl(var, false, false); + var->is_volatile = is_volatile; + read_full_var_decl(var, false, false, false); + if (var->is_inline) + error_at("inline specifier requires a function declarator", + next_token_loc()); + if (is_extern) { + if (var->is_func || lex_peek(T_open_bracket, NULL)) { + /* The global helper owns function redeclaration compatibility + * and parameter parsing. Unlike an object declaration it + * consumes the trailing semicolon itself. + */ + parent->locals.size--; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = + var; + read_global_function_declarator(GLOBAL_BLOCK, var, false); + + /* Keep a lexical marker so this declaration hides an outer + * automatic object of the same name. + */ + var_t *alias = require_var(parent); + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + return bb; + } + for (;;) { + var = bind_block_extern_object(parent, var); + if (lex_peek(T_assign, NULL)) + error_at("extern declaration cannot have an initializer", + next_token_loc()); + if (!lex_accept(T_comma)) + break; + var = require_typed_var(parent, type); + var->is_const_qualified = is_const; + var->is_volatile = is_volatile; + read_partial_var_decl(var, NULL); + } + lex_expect(T_semicolon); + return bb; + } add_insn(is_static ? GLOBAL_BLOCK : parent, is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, var, NULL, NULL, 0, NULL); @@ -6786,8 +7936,10 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) /* multiple (partial) declarations */ nv = require_typed_var(parent, type); nv->is_static = is_static; + nv->is_register = is_register; nv->is_global = is_static; nv->is_const_qualified = var->is_const_qualified; + nv->is_volatile = var->is_volatile; read_partial_var_decl(nv, var); /* partial */ add_insn(is_static ? GLOBAL_BLOCK : parent, is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, @@ -6851,7 +8003,8 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } /* is a function call? Skip function call check when has_asterisk is true */ - if (!has_asterisk && !find_local_var(token, parent)) { + var_t *local = find_local_var(token, parent); + if (!has_asterisk && (!local || local->is_extern_function_alias)) { func = find_func(token); if (func) { lex_expect(T_identifier); @@ -6878,7 +8031,11 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) read_ternary_operation(parent, &bb); var_t *addr = opstack_pop(); - if (addr->is_const_qualified) + int addr_depth = effective_pointer_depth(addr); + unsigned int addr_mask = effective_pointer_const_mask(addr); + if (addr->is_const_qualified || + (addr_depth > 1 && addr_depth <= 32 && + (addr_mask & (1U << (addr_depth - 2))))) error_at("assignment of read-only location", next_token_loc()); /* The width of the store is the pointee's, not the address's. */ @@ -7193,7 +8350,8 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) read_parameter_list_decl(func, 0); if (check_decl) { - if (func->return_def.type != func_tmp.return_def.type || + 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) @@ -7207,7 +8365,7 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) const var_t *now = &func->param_defs[i]; const var_t *before = &func_tmp.param_defs[i]; - if (now->type != before->type || + if (!compatible_decl_type(now->type, before->type) || now->ptr_level != before->ptr_level || now->is_const_qualified != before->is_const_qualified) error_at("conflicting types for function declaration", @@ -7260,7 +8418,8 @@ var_t *resolve_global_declarator(block_t *block, *is_redeclaration = true; - if (previous->type != var->type || previous->ptr_level != var->ptr_level || + if (!compatible_decl_type(previous->type, var->type) || + previous->ptr_level != var->ptr_level || previous->array_size != var->array_size || previous->array_dim2 != var->array_dim2 || previous->is_const_qualified != var->is_const_qualified) @@ -7287,14 +8446,16 @@ var_t *resolve_global_declarator(block_t *block, bool read_global_declarator(block_t *block, type_t *decl_type, bool is_const, - bool is_static) + bool is_static, + bool is_volatile) { 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; - read_inner_var_decl(nv, false, false); + nv->is_volatile = is_volatile; + read_inner_var_decl(nv, false, false, false); if (lex_peek(T_open_bracket, NULL)) { read_global_function_declarator(block, nv, is_static); return true; @@ -7452,7 +8613,7 @@ void read_global_record_declarator(block_t *block, var->is_global = true; var->is_static = is_static; var->is_const_qualified = is_const; - read_inner_var_decl(var, false, false); + read_inner_var_decl(var, false, false, false); var = resolve_global_declarator(block, var, is_static, &is_redeclaration); if (!is_redeclaration) add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); @@ -7480,21 +8641,30 @@ void read_global_record_declarator(block_t *block, discard_global_declarator_operand(var); } -void read_global_decl(block_t *block, bool is_const, bool is_static) +void read_global_decl(block_t *block, + bool is_const, + bool is_static, + 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); + read_full_var_decl(var, false, false, false); if (lex_peek(T_open_bracket, NULL)) { read_global_function_declarator(block, var, is_static); return; } else { + if (var->is_inline) + error_at("inline specifier requires a function declarator", + next_token_loc()); var = resolve_global_declarator(block, var, is_static, &is_redeclaration); if (!is_redeclaration) @@ -7517,7 +8687,7 @@ void read_global_decl(block_t *block, bool is_const, bool is_static) */ while (lex_accept(T_comma)) read_global_declarator(block, var->type, var->is_const_qualified, - is_static); + is_static, var->is_volatile); lex_expect(T_semicolon); return; @@ -7577,22 +8747,43 @@ void read_global_statement(void) 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. */ - while (lex_peek(T_const, NULL) || lex_peek(T_static, NULL)) { + 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 { - lex_expect(T_static); + 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()); if (lex_accept(T_struct)) { int i = 0, size = 0; + bool has_flexible_array_member = false; lex_ident(T_identifier, token); token_t *id_tk = cur_token; @@ -7623,24 +8814,47 @@ void read_global_statement(void) lex_expect(T_open_curly); do { var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); + var_t *last = v; + read_full_var_decl(v, false, false, true); + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, false); v->offset = size; size += size_var(v); /* Handle multiple variable declarations with same base type */ while (lex_accept(T_comma)) { + if (last->is_flexible_array_member) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); var_t *nv = type_add_field(type, &i); initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, true); + read_inner_var_decl(nv, false, false, true); + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, false); + last = nv; nv->offset = size; size += size_var(nv); } lex_expect(T_semicolon); + if (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->size = size; type->num_fields = i; + type->has_flexible_array_member = has_flexible_array_member; /* A record definition may be followed by its declarators, as in "struct * pair { int x, y; } first, *second;". @@ -7653,6 +8867,7 @@ void read_global_statement(void) lex_expect(T_semicolon); } else if (lex_accept(T_union)) { int i = 0, max_size = 0; + bool has_flexible_array_member = false; lex_ident(T_identifier, token); @@ -7667,7 +8882,9 @@ void read_global_statement(void) lex_expect(T_open_curly); do { var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); + read_full_var_decl(v, false, false, true); + has_flexible_array_member |= is_flexible_array_member_container(v); + mark_flexible_array_member(v, true); v->offset = 0; /* All union fields start at offset 0 */ int field_size = size_var(v); if (field_size > max_size) @@ -7678,7 +8895,10 @@ void read_global_statement(void) 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); + read_inner_var_decl(nv, false, false, true); + has_flexible_array_member |= + is_flexible_array_member_container(nv); + mark_flexible_array_member(nv, true); field_size = size_var(nv); if (field_size > max_size) max_size = field_size; @@ -7689,6 +8909,7 @@ void read_global_statement(void) type->size = max_size; type->num_fields = i; + type->has_flexible_array_member = has_flexible_array_member; if (!lex_peek(T_semicolon, NULL)) { read_global_record_declarator(block, type, is_const, is_static); @@ -7716,10 +8937,12 @@ void read_global_statement(void) type = find_type(token, true); if (!type) error_at("Unknown enum type", cur_token_loc()); - if (read_global_declarator(block, type, is_const, is_static)) + if (read_global_declarator(block, type, is_const, is_static, + is_volatile)) return; while (lex_accept(T_comma)) - read_global_declarator(block, type, is_const, is_static); + read_global_declarator(block, type, is_const, is_static, + is_volatile); lex_expect(T_semicolon); return; } @@ -7740,10 +8963,12 @@ void read_global_statement(void) } while (lex_accept(T_comma)); lex_expect(T_close_curly); if (!lex_peek(T_semicolon, NULL)) { - if (read_global_declarator(block, type, is_const, is_static)) + if (read_global_declarator(block, type, is_const, is_static, + is_volatile)) return; while (lex_accept(T_comma)) - read_global_declarator(block, type, is_const, is_static); + read_global_declarator(block, type, is_const, is_static, + is_volatile); } lex_expect(T_semicolon); } else if (lex_accept(T_typedef)) { @@ -7767,6 +8992,7 @@ void read_global_statement(void) } else if (lex_accept(T_struct)) { int i = 0, size = 0; bool has_struct_def = false; + bool has_flexible_array_member = false; type_t *tag = NULL, *type = add_type(); /* is struct definition? */ @@ -7787,21 +9013,45 @@ void read_global_statement(void) has_struct_def = true; do { var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); + var_t *last = v; + read_full_var_decl(v, false, false, true); + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, false); v->offset = size; size += size_var(v); /* Handle multiple variable declarations with same base type */ while (lex_accept(T_comma)) { + if (last->is_flexible_array_member) + error_at( + "Flexible array member must be the final " + "struct member", + cur_token_loc()); var_t *nv = type_add_field(type, &i); initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, true); + read_inner_var_decl(nv, false, false, true); + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, false); + last = nv; nv->offset = size; size += size_var(nv); } lex_expect(T_semicolon); + if (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)); } @@ -7813,6 +9063,7 @@ void read_global_statement(void) type->size = type->ptr_level ? PTR_SIZE : size; type->num_fields = i; type->base_type = TYPE_typedef; + type->has_flexible_array_member = has_flexible_array_member; if (tag && has_struct_def == 1) { strcpy(token, tag->type_name); @@ -7831,6 +9082,7 @@ void read_global_statement(void) } else if (lex_accept(T_union)) { int i = 0, max_size = 0; bool has_union_def = false; + bool has_flexible_array_member = false; type_t *tag = NULL, *type = add_type(); /* is union definition? */ @@ -7851,7 +9103,10 @@ void read_global_statement(void) has_union_def = true; do { var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, true); + read_full_var_decl(v, false, false, true); + has_flexible_array_member |= + is_flexible_array_member_container(v); + mark_flexible_array_member(v, true); v->offset = 0; /* All union fields start at offset 0 */ int field_size = size_var(v); if (field_size > max_size) @@ -7863,7 +9118,10 @@ void read_global_statement(void) 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); + read_inner_var_decl(nv, false, false, true); + has_flexible_array_member |= + is_flexible_array_member_container(nv); + mark_flexible_array_member(nv, true); field_size = size_var(nv); if (field_size > max_size) max_size = field_size; @@ -7882,6 +9140,7 @@ void read_global_statement(void) type->num_fields = i; type->base_type = TYPE_typedef; type->is_union = true; + type->has_flexible_array_member = has_flexible_array_member; if (tag && has_union_def == 1) { strcpy(token, tag->type_name); @@ -7990,13 +9249,22 @@ void read_global_statement(void) base = TY_uint; } } + } else if (is_signed && lex_peek(T_identifier, base_type) && + (!strcmp(base_type, "char") || + !strcmp(base_type, "short") || + !strcmp(base_type, "int"))) { + lex_expect(T_identifier); + base = !strcmp(base_type, "char") + ? TY_schar + : (!strcmp(base_type, "short") ? TY_short : TY_int); } else if (is_signed && (leading_scalar_type == TY_char || leading_scalar_type == TY_short)) { if (leading_scalar_type == TY_short && lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) lex_expect(T_identifier); - base = leading_scalar_type; + base = leading_scalar_type == TY_char ? TY_schar + : leading_scalar_type; } else if (is_signed && (!lex_peek(T_identifier, base_type) || (strcmp(base_type, "int") && strcmp(base_type, "char") && @@ -8013,15 +9281,28 @@ void read_global_statement(void) type->base_type = base->base_type; type->size = base->size; type->num_fields = 0; - type->ptr_level = 0; + 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_unsigned = base->is_unsigned; + type->is_signed_char = base->is_signed_char; /* Handle pointer types in typedef: typedef char *string; */ 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); + } else if (lex_accept(T_volatile) || + lex_accept(T_restrict)) { + ; + } else + break; + } } lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); @@ -8029,7 +9310,7 @@ void read_global_statement(void) } } 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); + read_global_decl(block, is_const, is_static, is_inline, is_volatile); } else error_at("Syntax error in global statement", next_token_loc()); } @@ -8056,6 +9337,11 @@ void parse_internal(void) 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; diff --git a/src/peephole.c b/src/peephole.c index 71cb82f4..f450dbed 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -73,6 +73,7 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) * Example: {add t1, a, b; mv result, t1} → {add result, a, b} */ ph2_ir->dest = next->dest; + ph2_ir->dest_hi = next->dest_hi; ph2_ir_drop_after(bb, ph2_ir, next); return true; } @@ -762,7 +763,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; } diff --git a/src/preprocessor.c b/src/preprocessor.c index 422a9c7e..0f231821 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; @@ -227,6 +228,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 +256,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 +275,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,11 +337,64 @@ 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; } -token_t *pp_read_constant_expr_operand(token_t *tk, int *val) +token_t *pp_read_constant_infix_expr(int precedence, + token_t *tk, + int *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.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, int *val, bool evaluate) { if (pp_lex_peek_token(tk, T_numeric, true)) { tk = pp_lex_next_token(tk, true); @@ -335,27 +402,49 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val) return tk; } + if (pp_lex_peek_token(tk, T_char, 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); + val[0] = parse_character_constant(tk->literal); + 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); + 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); + 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); /* 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; @@ -368,7 +457,7 @@ 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; @@ -385,115 +474,147 @@ 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, + int *val, + bool evaluate) { - int lhs, rhs; + int lhs = 0, rhs = 0; /* 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: + 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; - 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); - 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_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; - break; - default: - error_at("Unexpected infix token while evaluating constant", - &tk->location); + if (op == OP_ternary) { + int if_true, if_false; + + tk = pp_read_constant_infix_expr(0, tk, &if_true, evaluate && lhs); + 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 && !lhs); + if (evaluate) + lhs = lhs ? if_true : if_false; + continue; + } + + bool rhs_evaluate = evaluate; + if (op == OP_log_and && !lhs) + rhs_evaluate = false; + if (op == OP_log_or && lhs) + rhs_evaluate = false; + tk = pp_read_constant_infix_expr(current_precedence, tk, &rhs, + rhs_evaluate); + + if (evaluate) { + 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: + lhs %= rhs; + break; + 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; + break; + default: + error_at("Unexpected infix token while evaluating constant", + &tk->location); + } } - tk = pp_get_operator(tk, &op); } - val[0] = lhs; + val[0] = evaluate ? lhs : 0; return tk; } token_t *pp_read_constant_expr(token_t *tk, int *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; @@ -597,6 +718,43 @@ 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); +} + /* Join two tokens into one, as '##' requires. * * Pasting is textual, so the result has to be scanned again: "a" and "1" give @@ -873,7 +1031,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; @@ -952,9 +1111,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); @@ -1056,6 +1226,7 @@ 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; preprocess_ctx_t inclusion_ctx; inclusion_ctx.hide_set = ctx->hide_set; inclusion_ctx.expanded_from = NULL; @@ -1066,29 +1237,36 @@ 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 */ - char path[MAX_LINE_LEN]; - const char *file = tk->location.filename; - int c = strlen(file) - 1; - - while (c > 0 && file[c] != '/') - c--; - - if (c) { - if (c >= MAX_LINE_LEN - 1) - c = MAX_LINE_LEN - 2; + /* 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; - memcpy(path, file, c); - path[c] = '\0'; - } else { - path[0] = '.'; - path[1] = '\0'; - c = 1; + tk = pp_lex_next_token(tk, true); + macro = hashmap_get(MACROS, tk->literal); + while (macro && !macro->is_disabled && !macro->param_num && + 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); } - - 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'; + if (!macro || macro->is_disabled || macro->param_num || + !macro->replacement || macro->replacement->next || + macro->replacement->kind != T_string) + error_at("#include macro must expand to a header name", + &tk->location); + strcpy(inclusion_path, macro->replacement->literal); } else { tk = pp_lex_expect_token(tk, T_lt, true); @@ -1113,6 +1291,30 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) continue; } + /* normalize path */ + char path[MAX_LINE_LEN]; + const char *file = include_tk->location.filename; + int c = strlen(file) - 1; + + while (c > 0 && file[c] != '/') + c--; + + if (c) { + if (c >= MAX_LINE_LEN - 1) + c = MAX_LINE_LEN - 2; + + memcpy(path, file, c); + path[c] = '\0'; + } else { + path[0] = '.'; + path[1] = '\0'; + c = 1; + } + + 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'; + tk = pp_lex_expect_token(tk, T_newline, true); tk = pp_lex_next_token(tk, false); @@ -1142,8 +1344,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); @@ -1290,17 +1501,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) continue; } case T_cppd_error: { - if (pp_lex_peek_token(tk, T_string, true)) { - tk = pp_lex_next_token(tk, true); - - error_at(tk->literal, &tk->location); - } else { - error_at( - "Internal error, #error does not support non-string error " - "message", - &tk->location); - } - break; + pp_error_directive(tk); } case T_backslash: { /* This branch is designed to be failed since backslash should be @@ -1358,6 +1559,25 @@ 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. + */ + if (cur != &head && cur->kind == T_string && tk->kind == T_string) { + char combined[MAX_TOKEN_LEN]; + int left_len = strlen(cur->literal); + int right_len = strlen(tk->literal); + + if (left_len + right_len >= MAX_TOKEN_LEN) + error_at("Concatenated string literal too long", &tk->location); + memcpy(combined, cur->literal, left_len); + memcpy(combined + left_len, tk->literal, right_len + 1); + cur->literal = intern_string(combined); + cur->location.len += tk->location.len; + continue; + } cur->next = tk; cur = tk; } @@ -1383,25 +1603,47 @@ 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); + /* 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 +1654,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"; diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 00c65977..f1d3deb3 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -85,11 +85,17 @@ 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 (v->type->base_type != TYPE_struct && v->type->size == 8) + return 8; + if (!v->address_taken) + 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 @@ -102,12 +108,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) @@ -115,10 +145,60 @@ 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; +} + +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->base_type != TYPE_struct && var->type->size == 8; +} + +/* 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; +} + +int abi_arg_start(int cursor, const var_t *var) +{ + if (abi_arg_words(var) == 2) + cursor = ALIGN_UP(cursor, 2); + return cursor; +} + +int abi_arg_next(int cursor, const var_t *var) +{ + return abi_arg_start(cursor, var) + abi_arg_words(var); +} + +int abi_param_start(const func_t *func, int param_idx) +{ + int cursor = 0; + + for (int i = 0; i < param_idx; i++) + cursor = abi_arg_next(cursor, &func->param_defs[i]); + return abi_arg_start(cursor, &func->param_defs[param_idx]); +} + +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 @@ -220,6 +300,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; @@ -407,7 +490,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) return false; if (var->is_global || var->ofs_based_on_stack_top) return false; @@ -654,7 +738,7 @@ void collapse_slot_roundtrip(func_t *func) /* 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); } @@ -696,11 +780,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); } @@ -712,6 +805,7 @@ 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); @@ -737,7 +831,7 @@ 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]; } /* Return the index of register for given variable. Otherwise, return -1. */ @@ -757,6 +851,8 @@ bool var_is_pinnable(var_t *var) { if (!var || var->is_const || !var->base) return false; + if (var->is_volatile) + return false; /* slot_is_private() rules out everything reachable other than by name: * globals, address-taken variables, arrays and backing storage. @@ -842,7 +938,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; @@ -931,10 +1029,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 = 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]); + } + + if (reg < arg_words_in_reg) return; pinned_base[reg] = best; @@ -942,6 +1046,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; @@ -968,6 +1077,7 @@ void load_var(basic_block_t *bb, var_t *var, int idx) } ir->dest = idx; + ir->dest_hi = vreg_get_phys_hi(var); ir->is_pointer = is_pointer_like(var); ir->is_unsigned = is_unsigned_scalar(var); @@ -1005,6 +1115,55 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) if (phys_reg >= 0 && phys_reg < REG_CNT && REGS[phys_reg].var == var) return phys_reg; + if (var_needs_register_pair(var)) { + int 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; + break; + } + } + + /* Loading a spilled pair needs the same two-register recovery as + * producing one. Without this, several live wide locals could be stored + * safely in stack slots yet a later reload aborted before the ordinary + * spill chooser got a chance to free a complete pair. + */ + if (high < 0 && !is_pushing_args) { + for (int r = 0; r < REG_CNT; r++) { + var_t *owner = REGS[r].var; + + if (!owner || r == operand_0 || pinned_base[r] || + !var_needs_register_pair(owner)) + continue; + spill_var(bb, owner, r); + low = -1; + high = -1; + for (int q = 0; q < REG_CNT; q++) { + if (!reg_is_free(q)) + continue; + if (low < 0) + low = q; + else { + high = q; + break; + } + } + if (high >= 0) + break; + } + } + if (high < 0) + 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) */ int i = find_in_regs(var); if (i > -1 && !var->address_taken) { @@ -1080,7 +1239,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; } @@ -1151,6 +1313,97 @@ 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 = -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; + break; + } + } + + /* Unlike a word destination, a pair cannot use the ordinary single + * register spill chooser. Before argument staging begins, spill whole + * non-source pairs and retry; their slot preserves both words. Once + * OP_push has installed ABI arguments, those registers are live until + * the call and must never be reclaimed here. + */ + if (high < 0 && !is_pushing_args) { + 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] || !var_needs_register_pair(owner)) + continue; + spill_var(bb, owner, r); + low = -1; + high = -1; + for (int q = 0; q < REG_CNT; q++) { + if (!reg_is_free(q)) + continue; + if (low < 0) + low = q; + else { + high = q; + break; + } + } + if (high >= 0) + break; + } + } + + /* A pair result may need two registers while otherwise-dead scalar + * values still occupy the file. Whole-pair spilling above cannot help + * in that case, but scalar values already have an ordinary stack-backed + * spill path. Do not touch either source, pinned state, or a reserved + * high half. + */ + if (high < 0 && !is_pushing_args) { + 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] || pair_high_owner[r] || reg_is_locked(r) || + var_needs_register_pair(owner)) + continue; + spill_var(bb, owner, r); + low = -1; + high = -1; + for (int q = 0; q < REG_CNT; q++) { + if (!reg_is_free(q)) + continue; + if (low < 0) + low = q; + else { + high = q; + break; + } + } + if (high >= 0) + break; + } + } + if (high < 0) + 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; @@ -1396,22 +1649,83 @@ 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); +} + /* Return whether extra arguments are pushed onto stack. */ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) { + insn_t *pushes[MAX_PARAMS]; + int starts[MAX_PARAMS]; int num_of_args = 0; - int stack_args = 0; + int cursor = 0; + while (insn && insn->opcode == OP_push) { - num_of_args += 1; + if (num_of_args >= MAX_PARAMS) + fatal("Too many call arguments"); + starts[num_of_args] = abi_arg_start(cursor, insn->rs1); + pushes[num_of_args++] = insn; + cursor = abi_arg_next(cursor, insn->rs1); 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]; + + 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. @@ -1427,10 +1741,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; @@ -2122,6 +2433,7 @@ void reg_alloc_global(insn_t *global_insn) 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; @@ -2131,7 +2443,9 @@ void reg_alloc_global(insn_t *global_insn) dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_assign); ir->src0 = src0; + ir->src0_hi = vreg_get_phys_hi(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; @@ -2416,6 +2730,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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; @@ -2458,13 +2773,15 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->dest = dest; ir->ofs_based_on_stack_top = insn->rs1->ofs_based_on_stack_top; - } else if (param_idx < MAX_ARGS_IN_REG) { + } else if (abi_param_start(func, param_idx) < MAX_ARGS_IN_REG) { ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = param_idx; + ir->src0 = abi_param_start(func, param_idx); ir->dest = dest; } else { ir = bb_add_ph2_ir(bb, OP_load); - ir->src0 = (param_idx - MAX_ARGS_IN_REG) * PTR_SIZE; + ir->src0 = + (abi_param_start(func, param_idx) - MAX_ARGS_IN_REG) * + PTR_SIZE; ir->dest = dest; ir->ofs_based_on_stack_top = true; } @@ -2617,9 +2934,12 @@ 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; @@ -2627,6 +2947,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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); @@ -2715,17 +3036,29 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) handle_abi = true; } - if (args_on_stack && args >= MAX_ARGS_IN_REG) + args = abi_arg_start(args, insn->rs1); + if (args_on_stack && args >= MAX_ARGS_IN_REG) { + args = abi_arg_next(args, insn->rs1); + break; + } + + if (abi_arg_words(insn->rs1) == 2) { + if (args + 2 > MAX_ARGS_IN_REG) + fatal("Wide argument crosses ABI register boundary"); + prepare_pair_argument(bb, insn->rs1, args); + args = abi_arg_next(args, insn->rs1); 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; + args = abi_arg_next(args, insn->rs1); break; case OP_call: callee_func = find_func(insn->str); @@ -2767,7 +3100,10 @@ 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; @@ -2780,6 +3116,7 @@ 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 = @@ -2808,8 +3145,11 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) dest = prepare_dest(bb, insn, insn->rd, src0, src1); ir = bb_add_ph2_ir(bb, insn->opcode); 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, @@ -2843,7 +3183,9 @@ 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, insn->opcode); 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. @@ -2862,7 +3204,9 @@ 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 = @@ -2922,19 +3266,29 @@ void reg_alloc(void) 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; 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; + 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 @@ -2945,17 +3299,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 */ @@ -3024,9 +3379,13 @@ 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 (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; } diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 63e57180..c868ac29 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,18 +104,31 @@ 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; 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; @@ -119,49 +138,95 @@ void update_elf_offset(ph2_ir_t *ph2_ir) case OP_call: 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 += 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; 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; 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 +238,14 @@ 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) { + elf_offset += + (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) && + (source_size == 1 || source_size == 2) + ? 12 + : 8; + return; + } if (source_size == 2) elf_offset += 8; /* short extension: 2 instructions */ else @@ -180,7 +253,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"); @@ -295,6 +372,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 @@ -320,6 +400,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: @@ -333,10 +420,26 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_assign: 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))); @@ -361,6 +464,19 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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))); @@ -457,6 +573,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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))); @@ -466,14 +584,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)); @@ -491,6 +664,109 @@ 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(ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned @@ -566,57 +842,138 @@ 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)); + emit(__addi(__t1, __zero, 32)); + emit(__addi(__t2, rs1, 0)); + 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: + 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)); + emit(__addi(__t1, __zero, 32)); + emit(__addi(__t2, rs1_hi, 0)); + emit(is_unsigned ? __srl(rd, rs1, rs2) : __sra(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((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) - ? __sltu(rd, rs2, rs1) - : __slt(rd, rs2, rs1)); - return; - case OP_geq: - emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) - ? __sltu(rd, rs1, rs2) - : __slt(rd, rs1, rs2)); - emit(__xori(rd, rd, 1)); - return; case OP_lt: - emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) - ? __sltu(rd, rs1, rs2) - : __slt(rd, rs1, rs2)); - return; + case OP_geq: case OP_leq: - emit((ph2_ir->src0_is_unsigned || ph2_ir->src1_is_unsigned) - ? __sltu(rd, rs2, rs1) - : __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: emit(__sltu(rd, __zero, rs1)); @@ -648,6 +1005,28 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) int target_size = ph2_ir->src1 & 0xFFFF; int source_size = (ph2_ir->src1 >> 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(__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; + } + /* Calculate shift amount based on target and source sizes */ int shift_amount = (target_size - source_size) * 8; @@ -669,8 +1048,19 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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"); diff --git a/src/riscv.c b/src/riscv.c index da1e2aa7..82cd9d1c 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -50,6 +50,7 @@ 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_divu = 33574963 /* 0b0110011 + (1 << 25) + (5 << 12) */, rv_mod = 33579059 /* 0b0110011 + (1 << 25) + (6 << 12) */, @@ -195,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); diff --git a/src/ssa.c b/src/ssa.c index 563bf17c..bca01494 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -89,7 +89,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 +125,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); @@ -888,6 +888,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); @@ -1015,6 +1021,8 @@ void solve_phi_params(void) var_t *base = &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; @@ -3381,7 +3389,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) { @@ -3400,7 +3408,7 @@ 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) @@ -3802,7 +3810,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; diff --git a/tests/driver.sh b/tests/driver.sh index ae8b5fa7..e004a224 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -306,7 +306,11 @@ function try_output() # 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: @@ -347,6 +351,31 @@ function try_compile_error() 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 ] || ! 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. @@ -823,6 +852,12 @@ int main(void) { ((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) { @@ -831,6 +866,18 @@ int main(void) { (sizeof(0xffffffff) == 4) + (sizeof(0x100000000) == 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 fi if [ "$PTR_SZ" -lt 8 ]; then @@ -1098,6 +1145,13 @@ int main(void) { 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; } @@ -1223,6 +1277,73 @@ int main(void) { (((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; @@ -1261,6 +1382,46 @@ int main(void) { 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 '\x123' == 0x23; } +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) { @@ -1287,6 +1448,92 @@ int main(void) { 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) { @@ -1488,6 +1735,108 @@ declare -a variable_tests=( 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_ 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 + +try_compile_error << EOF +inline int invalid_inline_object; +int main(void) { return 0; } +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 + +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 @@ -1612,6 +1961,21 @@ fi # 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() { @@ -2335,6 +2699,22 @@ int main() { } 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 + # Test: Empty array compound literal (edge case) try_ 0 << EOF int main() { @@ -2412,6 +2792,7 @@ items 1 "int i = 0; for (;;) { i++; if (i < 4) { continue; } break; } return i = 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;" @@ -3009,6 +3390,60 @@ int main(void) { } 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 @@ -3806,6 +4241,164 @@ int main() } 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 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 + +# A declaration in a C99 for initializer has block scope too. Its extern object +# and function forms must 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 + +# 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 + +# 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 + +# 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 @@ -3864,6 +4457,15 @@ int main(void) } 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) { @@ -3875,6 +4477,21 @@ int static_address_constants(void) 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) { @@ -4494,6 +5111,50 @@ int main(void) { } 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; }; @@ -4547,6 +5208,57 @@ int main(void) { 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; @@ -4631,6 +5343,32 @@ int main(void) { } 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; @@ -4950,6 +5688,20 @@ items 20 "int *p; int a[3]; a[0] = 10; a[1] = 20; a[2] = 30; p = a; p+=1; return 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; @@ -5058,6 +5810,24 @@ 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 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 @@ -5261,6 +6031,24 @@ 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" +try_file 24 "$TESTS_DIR/include-main.c" +try_compile_error << EOF +#define FIRST_HEADER SECOND_HEADER +#define SECOND_HEADER FIRST_HEADER +#include FIRST_HEADER +int main(void) { return 0; } +EOF +try_compile_error_message "unsupported platform configuration" << EOF +#error unsupported platform configuration +int main(void) { return 0; } +EOF + # #ifdef...#else...#endif try_ 0 << EOF #define A 0 @@ -5348,6 +6136,118 @@ int main() } 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 + +# 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_ 1 << EOF +#if '\\x123' == 0x23 +#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_ 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 @@ -5754,6 +6654,12 @@ 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 + # stringification: '#' spells the argument as it was written try_output 0 "hello world" << EOF #define STR(x) #x @@ -7114,6 +8020,15 @@ int main() { } EOF +# C99 hexadecimal escapes consume every following hex digit; assigning to a char +# produces the low byte. +try_ 1 << EOF +int main() { + char *s = "\\x123"; + return s[0] == 0x23; +} +EOF + # Test octal escape sequences try_ 65 << EOF int main() { 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-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 From 313044566b6f6fa62d59950d07f61724f9dd2fd8 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Fri, 11 Sep 2026 02:57:51 +0800 Subject: [PATCH 04/40] Add bit-fields, include paths and deeper arrays Calls go through function pointer expressions and arrays of function pointers. Bit-fields and the C99 record layout rules are implemented, record tags may be forward declared or declared in a block, and typedef aliases keep record metadata. Arrays reach four dimensions with three-dimensional initializers, and sizeof keeps member arrays and rows. The preprocessor gains reproducible __DATE__ and __TIME__, canonical quoted include paths, angle headers resolved through include paths, #line, _Pragma and the stdbool and iso646 headers, and the lexer reads universal character names. Parenthesized pointer expressions can be subscripted, __func__ arrays are addressable, global aggregate pointer members are initialized, and COMPLIANCE.md is reconciled with the implementation. --- COMPLIANCE.md | 52 +- Makefile | 12 + src/defs.h | 41 +- src/globals.c | 65 ++ src/lexer.c | 189 ++- src/main.c | 14 +- src/parser.c | 1795 ++++++++++++++++++++++++++--- src/preprocessor.c | 333 +++++- src/reg-alloc.c | 63 +- src/riscv-codegen.c | 4 +- tests/driver.sh | 915 ++++++++++++++- tests/include-angle-child.h | 2 + tests/include-angle.h | 2 + tests/include-nested/inner/once.h | 3 + tests/include-nested/outer.h | 5 + 15 files changed, 3269 insertions(+), 226 deletions(-) create mode 100644 tests/include-angle-child.h create mode 100644 tests/include-angle.h create mode 100644 tests/include-nested/inner/once.h create mode 100644 tests/include-nested/outer.h diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 0762ac80..101eb76f 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -48,72 +48,66 @@ 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 | +| `static` | Partial | File-scope internal linkage and persistent block-scope objects work, including C99 `for` initializers; cross-translation-unit linkage remains incomplete. | +| `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` | 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 | +| `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 | Parsed and retained as an aliasing qualifier; does not yet drive 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 x64/AArch64; 32-bit target admission remains gated. | +| `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` | +| Integer suffixes (`u`, `l`, `ll`) | Partial | Common suffix spellings and x64 wide literals are parsed; full candidate-type selection remains incomplete. | | 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 | +| Multi-character constants | Supported | Implementation-defined left-to-right packing of up to four bytes. | +| Universal characters (`\u`, `\U`) | Partial | Narrow literals and identifiers are validated and decoded to UTF-8; wide literals remain unsupported. | +| Hex escapes (`\x...`) | Supported | The full following hexadecimal run is consumed before narrowing. | ### 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 | | 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 ### Implementation-Specific diff --git a/Makefile b/Makefile index 6661e623..9f23ec5e 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,17 @@ 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. +SOURCE_DATE_EPOCH ?= $(shell date -u +%s) +TRANSLATION_DATE := $(shell LC_ALL=C TZ=UTC date -u -d "@$(SOURCE_DATE_EPOCH)" '+%b %e %Y') +TRANSLATION_TIME := $(shell LC_ALL=C TZ=UTC date -u -d "@$(SOURCE_DATE_EPOCH)" '+%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. @@ -132,6 +143,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) diff --git a/src/defs.h b/src/defs.h index da61d7d2..7561bf7f 100644 --- a/src/defs.h +++ b/src/defs.h @@ -35,6 +35,7 @@ #define MAX_TOKEN_LEN 256 #define MAX_ID_LEN 64 #define MAX_LINE_LEN 256 +#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 @@ -57,7 +58,12 @@ #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. Field tables + * are allocated lazily, so this does not inflate scalar type storage. + */ +#define MAX_FIELDS 96 #define MAX_TYPES 256 #define MAX_LABELS 256 /* Pending postfix ++/-- effects in one statement; each one appends 3. */ @@ -370,6 +376,7 @@ typedef enum { T_cppd_ifdef, T_cppd_ifndef, T_cppd_pragma, + T_cppd_line, /* C pre-processor specific, these kinds will be removed after * pre-processing is done. @@ -392,6 +399,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; @@ -620,8 +632,18 @@ struct var { * contributes no bytes to the record's fixed layout. */ bool is_flexible_array_member; - int array_dim2; /* second dimension size for 2D arrays */ - int offset; /* offset from stack or frame, index 0 is reserved */ + 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 */ + 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 @@ -685,6 +707,13 @@ struct var { */ bool is_string_literal; + /* `&__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) @@ -799,6 +828,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 diff --git a/src/globals.c b/src/globals.c index 0081160d..b0b84a86 100644 --- a/src/globals.c +++ b/src/globals.c @@ -115,6 +115,8 @@ bool dump_ir = false; bool dump_dot = false; bool hard_mul_div = false; bool warn_string_literals = 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. @@ -714,6 +716,38 @@ 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; +} + int unescape_string(const char *input, char *output, int output_size) { if (!input || !output || output_size == 0) @@ -802,6 +836,37 @@ int unescape_string(const char *input, char *output, int output_size) 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': diff --git a/src/lexer.c b/src/lexer.c index 55816100..c2647f16 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -11,7 +11,7 @@ #include "globals.c" /* Hash table constants */ -#define NUM_DIRECTIVES 11 +#define NUM_DIRECTIVES 12 #define NUM_KEYWORDS 27 /* Token mapping structure for elegant initialization */ @@ -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 */ @@ -307,7 +307,10 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) 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++; @@ -568,6 +571,14 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) } 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); @@ -604,6 +615,10 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) error_at("Unenclosed character literal", 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); token->literal = intern_string(token_buffer); @@ -960,6 +975,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 @@ -970,8 +1050,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() @@ -983,9 +1064,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 */ @@ -1088,9 +1187,9 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) 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; } @@ -1132,11 +1231,72 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return NULL; } -token_stream_t *gen_file_token_stream(char *filename) +/* 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) { - /* FIXME: We should normalize filename first to make cache works as expected - */ + 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; @@ -1145,7 +1305,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); @@ -1192,7 +1353,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); diff --git a/src/main.c b/src/main.c index 2f317dcf..0bececd4 100644 --- a/src/main.c +++ b/src/main.c @@ -121,7 +121,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++; @@ -135,7 +145,7 @@ int main(int argc, char *argv[]) if (!in) { printf( - "Usage: shecc [-o output] [+m] [--dot] [--dump-ir] " + "Usage: shecc [-I directory] [-o output] [+m] [--dot] [--dump-ir] " "[--warn-string-literals] [--no-libc] " "[--dynlink] [-E] \n"); usage_error("Missing source file"); diff --git a/src/parser.c b/src/parser.c index 03db04fe..41c7a3a6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -56,6 +56,7 @@ 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 global_compound_literal_starts_here(void) { @@ -335,6 +336,143 @@ var_t *type_add_field(type_t *type, int *idx) return &type->fields[i]; } +/* 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. + */ +int alignment_type(type_t *type) +{ + 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; +} + +int alignment_var(var_t *var) +{ + if (var->ptr_level || var->is_func || var->type->ptr_level) + return PTR_SIZE; + return alignment_type(var->type); +} + +int layout_struct_field(int size, var_t *field, int *record_alignment) +{ + int alignment = alignment_var(field); + + if (alignment > *record_alignment) + *record_alignment = alignment; + field->offset = ALIGN_UP(size, alignment); + return field->offset + size_var(field); +} + +/* 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; + +int read_const_expr(block_t *scope); +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; +} + +/* 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) +{ + int width; + + if (!lex_accept(T_colon)) + return; + if (field->ptr_level || field->is_func || field->array_size || + field->has_unsized_array || + !(field->type == TY_bool || + (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()); + + width = read_const_expr(scope); + int storage_size = field->type == TY_bool ? 1 : 4; + int max_width = field->type == TY_bool ? 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()); + + field->is_bitfield = true; + field->bit_width = width; + field->bit_offset = 0; + field->bit_storage_size = storage_size; +} + +int flush_bitfield_layout(int size, bitfield_layout_t *bits) +{ + 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; +} + +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; + + 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; + } + field->offset = bits->unit_offset; + field->bit_offset = bits->used_bits; + bits->used_bits += field->bit_width; + return size; +} + /* 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. @@ -915,7 +1053,26 @@ 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)) { + if (lex_peek(T_ampersand, NULL) || lex_peek(T_identifier, NULL)) { + bool explicit_address = lex_accept(T_ampersand); + char name[MAX_ID_LEN]; + + if (lex_peek(T_identifier, name)) { + func_t *func = find_func(name); + if (func) { + lex_expect(T_identifier); + val = require_func_symbol_var(parent); + val->var_name = intern_string(name); + val->is_func = true; + return val; + } + } + if (explicit_address) + error_at("Expected function after '&' in global initializer", + cur_token_loc()); + error_at("Global aggregate initializer requires a constant value", + cur_token_loc()); + } else if (lex_peek(T_numeric, NULL) || lex_peek(T_minus, NULL)) { bool is_neg = false; if (lex_accept(T_minus)) is_neg = true; @@ -939,11 +1096,12 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) val->init_val = parse_character_constant(chtok); 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 + /* 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()); @@ -990,7 +1148,8 @@ void parse_array_field_row_init(block_t *parent, bool emit_code) { int count = 0; - int elem_size = field->ptr_level ? PTR_SIZE : field->type->size; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; lex_expect(T_open_curly); while (!lex_peek(T_close_curly, NULL)) { @@ -1024,8 +1183,10 @@ void parse_array_field_row_init(block_t *parent, } if (value && emit_code) { - var_t *stored = - resize_to(parent, bb, value, field->type, field->ptr_level); + 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); } @@ -1053,6 +1214,119 @@ void parse_array_field_row_init(block_t *parent, } } +/* 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_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code) +{ + var_t row; + int rows = 0; + int row_width = field->array_dim3; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + + memcpy(&row, field, sizeof(row)); + row.array_dim2 = row_width; + row.array_dim3 = 0; + lex_expect(T_open_curly); + while (!lex_peek(T_close_curly, NULL)) { + if (rows >= field->array_dim2) + error_at("Too many rows in array initializer", next_token_loc()); + if (!lex_peek(T_open_curly, NULL)) + error_at("Three-dimensional array plane requires braced rows", + next_token_loc()); + parse_array_field_row_init(parent, bb, &row, target_addr, + start + rows * row_width, emit_code); + rows++; + if (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + if (emit_code) { + 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); + for (; rows < field->array_dim2; rows++) { + for (int col = 0; col < row_width; col++) { + var_t *addr = compute_element_address( + parent, bb, target_addr, start + rows * row_width + col, + elem_size); + for (int byte = 0; byte < elem_size; byte++) { + var_t *byte_addr = + compute_element_address(parent, bb, addr, byte, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, + NULL); + } + } + } + } +} + +/* 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, + bool emit_code) +{ + var_t plane; + int planes = 0; + int plane_size = field->array_dim3 * field->array_dim4; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + + 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); + while (!lex_peek(T_close_curly, NULL)) { + if (planes >= field->array_dim2) + error_at("Too many planes in array initializer", next_token_loc()); + if (!lex_peek(T_open_curly, NULL)) + error_at("Four-dimensional array slice requires braced planes", + next_token_loc()); + parse_array_field_plane_init(parent, bb, &plane, target_addr, + start + planes * plane_size, emit_code); + planes++; + if (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + if (emit_code) { + 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); + for (; planes < field->array_dim2; planes++) { + for (int element = 0; element < plane_size; element++) { + var_t *addr = compute_element_address( + parent, bb, target_addr, + start + planes * plane_size + element, elem_size); + for (int byte = 0; byte < elem_size; byte++) { + var_t *byte_addr = + compute_element_address(parent, bb, addr, byte, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, + NULL); + } + } + } + } +} + /* 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 @@ -1106,7 +1380,8 @@ void parse_array_field_init(block_t *parent, bool emit_code) { int count = 0; - int elem_size = field->ptr_level ? PTR_SIZE : field->type->size; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; lex_expect(T_open_curly); while (!lex_peek(T_close_curly, NULL)) { @@ -1118,7 +1393,21 @@ void parse_array_field_init(block_t *parent, var_t *elem_addr = compute_element_address(parent, bb, target_addr, count, elem_size); - if (field->array_dim2 && lex_peek(T_open_curly, NULL)) { + if (field->array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, field, target_addr, + count, emit_code); + count += field->array_dim2 * field->array_dim3 * field->array_dim4; + if (!lex_accept(T_comma)) + break; + continue; + } else if (field->array_dim3 && lex_peek(T_open_curly, NULL)) { + parse_array_field_plane_init(parent, bb, field, target_addr, count, + emit_code); + count += field->array_dim2 * field->array_dim3; + if (!lex_accept(T_comma)) + break; + continue; + } else if (field->array_dim2 && lex_peek(T_open_curly, NULL)) { parse_array_field_row_init(parent, bb, field, target_addr, count, emit_code); count += field->array_dim2; @@ -1147,8 +1436,10 @@ void parse_array_field_init(block_t *parent, } if (value && emit_code) { - var_t *stored = - resize_to(parent, bb, value, field->type, field->ptr_level); + 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); } @@ -1198,6 +1489,16 @@ void parse_struct_field_init(block_t *parent, 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. + */ + var_t *global_bitfield_unit[MAX_FIELDS]; + unsigned global_bitfield_value[MAX_FIELDS]; + int global_bitfield_count = 0; + /* 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. @@ -1257,10 +1558,9 @@ void parse_struct_field_init(block_t *parent, designated_field_addr = compute_field_address( parent, bb, designator_base, field); if (lex_accept(T_open_square)) { - int index, element_count, linear_index; + int index, element_count, linear_index, stride; int total_element_count; int elem_size; - bool direct_array_member = first; var_t *array_base_addr = designated_field_addr; if (!field->array_size) @@ -1271,42 +1571,79 @@ void parse_struct_field_init(block_t *parent, lex_expect(T_close_square); total_element_count = field->array_size; element_count = total_element_count; - if (field->array_dim2) - element_count /= field->array_dim2; + stride = 1; + if (field->array_dim2) { + stride = field->array_dim2; + if (field->array_dim3) { + stride *= field->array_dim3; + if (field->array_dim4) + stride *= field->array_dim4; + } + element_count /= stride; + } if (index < 0 || index >= element_count) error_at("Array designator index is out of bounds", cur_token_loc()); - elem_size = - field->ptr_level ? PTR_SIZE : field->type->size; - if (field->array_dim2) - index *= field->array_dim2; - linear_index = index; + elem_size = (field->ptr_level || field->is_func) + ? PTR_SIZE + : field->type->size; + linear_index = index * stride; designated_field_addr = compute_element_address( - parent, bb, designated_field_addr, index, + parent, bb, designated_field_addr, linear_index, elem_size); memcpy(&array_element, field, sizeof(var_t)); - array_element.array_size = field->array_dim2; - array_element.array_dim2 = 0; + if (field->array_dim4) { + array_element.array_size = field->array_dim2 * + field->array_dim3 * + field->array_dim4; + array_element.array_dim2 = field->array_dim3; + array_element.array_dim3 = field->array_dim4; + array_element.array_dim4 = 0; + } else if (field->array_dim3) { + array_element.array_size = + field->array_dim2 * field->array_dim3; + array_element.array_dim2 = field->array_dim3; + array_element.array_dim3 = 0; + } else if (field->array_dim2) { + array_element.array_size = field->array_dim2; + array_element.array_dim2 = 0; + } else { + array_element.array_size = 0; + } field = &array_element; - /* A first subscript of a two-dimensional member names a - * row; a second selects its scalar element. Leaving it - * as an array supports `.cells[row] = {...}`. + /* Each subscript leaves the remaining inner array in + * `field`, so rows, planes, and a final scalar can all + * share the same bounds and continuation path. */ - if (field->array_size && lex_accept(T_open_square)) { + while (field->array_size && lex_accept(T_open_square)) { index = read_const_expr(parent); lex_expect(T_close_square); - if (index < 0 || index >= field->array_size) + stride = field->array_dim2 ? field->array_dim2 : 1; + if (field->array_dim3) + stride *= field->array_dim3; + element_count = field->array_size / stride; + if (index < 0 || index >= element_count) error_at( "Array designator index is out of bounds", cur_token_loc()); designated_field_addr = compute_element_address( - parent, bb, designated_field_addr, index, - elem_size); - linear_index += index; - array_element.array_size = 0; + parent, bb, designated_field_addr, + index * stride, elem_size); + linear_index += index * stride; + if (field->array_dim3) { + array_element.array_size = + field->array_dim2 * field->array_dim3; + array_element.array_dim2 = field->array_dim3; + array_element.array_dim3 = 0; + } else if (field->array_dim2) { + array_element.array_size = field->array_dim2; + array_element.array_dim2 = 0; + } else { + array_element.array_size = 0; + } } - if (direct_array_member && !field->array_size) { + if (!field->array_size) { memcpy(&pending_array_element, field, sizeof(var_t)); pending_array_base = array_base_addr; @@ -1337,6 +1674,17 @@ void parse_struct_field_init(block_t *parent, consumed_pending_array_element = true; } + /* An unnamed bit-field is padding, not an aggregate member. It + * participates in layout but consumes no positional initializer; + * otherwise `{ low, high }` would incorrectly assign `high` to the + * padding field and leave the named field zero-initialized. + */ + if (!field && !has_pending_array_element) + while (field_idx < struct_type->num_fields && + is_bitfield(&struct_type->fields[field_idx]) && + !struct_type->fields[field_idx].var_name[0]) + field_idx++; + if (field_idx >= struct_type->num_fields || ((struct_type->base_type == TYPE_union || struct_type->is_union) && @@ -1400,12 +1748,48 @@ void parse_struct_field_init(block_t *parent, is_record_object(field_val_raw)) { emit_record_copy_to_address(parent, bb, field_addr, field_val_raw); + } else if (parent == GLOBAL_BLOCK && 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. + */ + 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); - add_insn(parent, *bb, OP_write, NULL, field_addr, field_val, - field_size, NULL); + if (is_bitfield(field) && parent == GLOBAL_BLOCK) { + int unit = -1; + for (int i = 0; i < global_bitfield_count; i++) + if (global_bitfield_unit[i]->offset == + field->offset && + global_bitfield_unit[i]->bit_storage_size == + field->bit_storage_size) { + unit = i; + break; + } + if (unit < 0) { + unit = global_bitfield_count++; + global_bitfield_unit[unit] = field; + global_bitfield_value[unit] = 0; + } + unsigned mask = bitfield_mask(field) + << field->bit_offset; + unsigned init_val = + field->type == TY_bool + ? field_val_raw->init_val != 0 + : (unsigned) field_val_raw->init_val; + global_bitfield_value[unit] = + (global_bitfield_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); } } @@ -1426,6 +1810,18 @@ void parse_struct_field_init(block_t *parent, break; } } + + if (emit_code && parent == GLOBAL_BLOCK) { + for (int i = 0; i < global_bitfield_count; i++) { + var_t *unit = global_bitfield_unit[i]; + var_t *unit_addr = + compute_field_address(parent, bb, target_addr, unit); + var_t *value = + bitfield_constant(parent, bb, global_bitfield_value[i]); + add_insn(parent, *bb, OP_write, NULL, unit_addr, value, + unit->bit_storage_size, NULL); + } + } } void parse_array_literal_expr(block_t *parent, basic_block_t **bb) @@ -1666,7 +2062,12 @@ void parse_array_init(var_t *var, int count = 0; int inferred_size = 0; var_t *base_addr = NULL; - bool is_implicit = (var->array_size == 0); + + /* 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) @@ -1679,7 +2080,7 @@ void parse_array_init(var_t *var, * explicitly sized array is treated this way. */ int elem_size = var->type->size; - if (!is_implicit && var->ptr_level > 0) + if (!is_implicit && (var->ptr_level > 0 || var->is_func)) elem_size = PTR_SIZE; if (emit_code) @@ -1710,11 +2111,83 @@ void parse_array_init(var_t *var, if (!lex_peek(T_close_curly, NULL)) { for (;;) { var_t *val = NULL; + var_t designated_array; + var_t *initializer_var = var; int prior_count = count; if (lex_accept(T_open_square)) { - count = read_const_expr(initializer_scope); + int index; + int stride = 1; + int elements; + int remaining_dims = 0; + + index = read_const_expr(initializer_scope); lex_expect(T_close_square); + if (var->array_dim2) { + stride = var->array_dim2; + remaining_dims = 1; + if (var->array_dim3) { + stride *= var->array_dim3; + remaining_dims = 2; + if (var->array_dim4) { + stride *= var->array_dim4; + remaining_dims = 3; + } + } + } + elements = var->array_size / stride; + if (index < 0 || (!is_implicit && index >= elements)) + error_at("Array designator index is out of bounds", + cur_token_loc()); + count = index * stride; + + /* A complete multidimensional designator names a scalar + * element. Keep the outer-only form on the existing braced + * slice path, but turn every complete path into the row-major + * flat offset used by the ordinary store path. + */ + while (remaining_dims && lex_accept(T_open_square)) { + index = read_const_expr(initializer_scope); + lex_expect(T_close_square); + if (remaining_dims == 3) { + elements = var->array_dim2; + stride = var->array_dim3 * var->array_dim4; + } else if (remaining_dims == 2) { + elements = var->array_dim2; + stride = var->array_dim3; + if (var->array_dim4) { + elements = var->array_dim3; + stride = var->array_dim4; + } + } else { + elements = var->array_dim2; + if (var->array_dim3) + elements = var->array_dim3; + if (var->array_dim4) + elements = var->array_dim4; + stride = 1; + } + if (index < 0 || index >= elements) + error_at("Array designator index is out of bounds", + cur_token_loc()); + count += index * stride; + remaining_dims--; + } + if (remaining_dims == 1 && + (var->array_dim3 || var->array_dim4)) { + /* A path ending one dimension short of a scalar names a + * row. Present it to the braced-row helper rather than + * asking a scalar initializer to consume `{ ... }`. + */ + int row_width = + var->array_dim4 ? var->array_dim4 : var->array_dim3; + memcpy(&designated_array, var, sizeof(designated_array)); + designated_array.array_size = row_width; + designated_array.array_dim2 = row_width; + designated_array.array_dim3 = 0; + designated_array.array_dim4 = 0; + initializer_var = &designated_array; + } lex_expect(T_assign); } @@ -1722,7 +2195,40 @@ void parse_array_init(var_t *var, error_at("Too many elements in array initializer", next_token_loc()); - if (lex_peek(T_open_curly, NULL) && is_record_type(var->type)) { + if (initializer_var->array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, initializer_var, + base_addr, count, emit_code); + count += initializer_var->array_dim2 * + initializer_var->array_dim3 * + initializer_var->array_dim4; + 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, emit_code); + count += + initializer_var->array_dim2 * initializer_var->array_dim3; + 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, emit_code); + count += initializer_var->array_dim2; + if (is_implicit && count > inferred_size) + inferred_size = count; + if (!lex_accept(T_comma)) + break; + continue; + } 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) @@ -1760,9 +2266,15 @@ void parse_array_init(var_t *var, * the value left every global array zero-filled, while the same * initializer on a local worked. */ + char initializer_name[MAX_ID_LEN]; + bool function_initializer = + lex_peek(T_identifier, initializer_name) && + find_func(initializer_name); + if (parent == GLOBAL_BLOCK && initializer_scope != GLOBAL_BLOCK && - !lex_peek(T_string, NULL)) { + !lex_peek(T_string, NULL) && !function_initializer && + !lex_peek(T_ampersand, NULL)) { /* Storage for a block-scope static lives globally, while * its initializer is an integer constant expression in the * surrounding block. Resolve local enumerators before @@ -1780,20 +2292,43 @@ void parse_array_init(var_t *var, bool enum_constant = lex_peek(T_identifier, token) && find_scoped_constant(token, parent); + bool function_constant = + lex_peek(T_identifier, token) && find_func(token); if (!lex_peek(T_numeric, NULL) && !lex_peek(T_minus, NULL) && !lex_peek(T_string, NULL) && - !lex_peek(T_char, NULL) && !enum_constant) + !lex_peek(T_char, NULL) && !enum_constant && + !function_constant && !lex_peek(T_ampersand, NULL)) error_at( "Global array initialization requires constant " "values", next_token_loc()); } - read_expr(parent, bb); - read_ternary_operation(parent, bb); - val = opstack_pop(); + /* `&function` is an address constant, but the ordinary + * expression reader only treats a bare function designator + * as a value. Preserve the symbol through global lowering + * so its final code address can be relocated. + */ + if (parent == GLOBAL_BLOCK && lex_accept(T_ampersand)) { + char function_name[MAX_ID_LEN]; + + if (!lex_peek(T_identifier, function_name) || + !find_func(function_name)) + error_at( + "Global array address initializer requires " + "a declared function", + next_token_loc()); + lex_ident(T_identifier, function_name); + val = require_func_symbol_var(parent); + val->is_func = true; + val->var_name = intern_string(function_name); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + val = opstack_pop(); + } } } @@ -1802,7 +2337,13 @@ void parse_array_init(var_t *var, next_token_loc()); if (val && emit_code && (is_implicit || count < var->array_size)) { - var_t *v = resize_to(parent, bb, val, var->type, 0); + /* 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 = val->is_func + ? val + : resize_to(parent, bb, val, var->type, 0); /* A forward designator leaves a gap. Explicit arrays were * zeroed before parsing; inferred local arrays do not have a @@ -2263,6 +2804,11 @@ void read_inner_var_decl(var_t *vd, { /* Preserve typedef pointer level - don't reset if already inherited */ vd->init_val = 0; + + /* Declarator continuations inherit base metadata. A new suffix must not + * retain a fourth bound from an earlier declarator. + */ + vd->array_dim4 = 0; if (is_param) { /* However, if the parsed variable is a function parameter, reset its * pointer level to zero. @@ -2309,6 +2855,43 @@ void read_inner_var_decl(var_t *vd, } lex_ident(T_identifier, temp_name); vd->var_name = intern_string(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++) { + if (dim >= 4) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); + if (lex_peek(T_close_square, NULL)) { + 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 (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; + } + } + lex_expect(T_close_square); + } lex_expect(T_close_bracket); /* The return declaration was parsed before the parenthesized @@ -2320,7 +2903,7 @@ void read_inner_var_decl(var_t *vd, vd->func_signature = func; vd->is_func = true; } else { - if (!anon) { + if (!anon && !lex_peek(T_colon, NULL)) { char temp_name[MAX_VAR_LEN]; lex_ident(T_identifier, temp_name); vd->var_name = intern_string(temp_name); @@ -2333,6 +2916,8 @@ void read_inner_var_decl(var_t *vd, if (!lex_peek(T_open_square, NULL)) { vd->array_size = 0; vd->array_dim2 = 0; + vd->array_dim3 = 0; + vd->array_dim4 = 0; } /* Every dimension multiplies into array_size, so "int matrix[3][4]" @@ -2345,6 +2930,9 @@ void read_inner_var_decl(var_t *vd, int dims = 0; for (int dim = 0; lex_accept(T_open_square); dim++) { + if (dim >= 4) + error_at("Array declarators support at most four dimensions", + cur_token_loc()); 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 @@ -2365,6 +2953,10 @@ void read_inner_var_decl(var_t *vd, } 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 @@ -2380,16 +2972,17 @@ void read_inner_var_decl(var_t *vd, * 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) { - /* An array parameter adjusts to a pointer, but an object declarator - * such as `char text[] = "hi"` has an inferred array bound. Keeping - * those cases distinct lets its initializer create writable array - * storage instead of a pointer to string data. + /* Only a one-dimensional parameter needs the pointer adjustment; a + * multidimensional parameter retains its inner row stride. */ - if (is_param) - vd->ptr_level++; - else - vd->has_unsized_array = true; + vd->ptr_level++; } /* C99 permits an object of a flexible-record type, but not an array @@ -3150,26 +3743,156 @@ func_t *get_func_signature(var_t *var) 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) +{ + var_t *address = require_ref_var(parent, object->type, object->ptr_level); + var_t *target = require_typed_ptr_var(parent, 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; +} + void read_indirect_call(var_t *callee, 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. The callee was evaluated before its argument list. - * Materialize a distinct SSA value now: lowering a by-value record argument - * emits a caller-side copy and can otherwise evict the untracked - * operand-stack value before OP_indirect consumes it. + * 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(); - var_t *saved_target = require_var(parent); - saved_target->var_name = gen_name(); - add_insn(parent, *bb, OP_assign, saved_target, target, NULL, 0, NULL); - opstack_push(saved_target); func_t *signature = get_func_signature(callee); read_func_parameters(signature, parent, bb); - add_insn(parent, *bb, OP_indirect, NULL, opstack_pop(), NULL, 0, NULL); + add_insn(parent, *bb, OP_indirect, NULL, target, NULL, 0, NULL); +} + +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 (field->type != TY_bool && !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 (field->type == TY_bool) + 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; +} + +void write_bitfield_value(block_t *parent, + basic_block_t **bb, + var_t *address, + var_t *value, + const var_t *field) +{ + if (field->type == TY_bool) + 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, @@ -3191,11 +3914,42 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) if (!lex_peek(T_identifier, token)) error_at("Expected an identifier", next_token_loc()); + 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. + */ + if (find_func(token)) { + lex_expect(T_identifier); + vd = require_func_symbol_var(parent); + vd->is_func = true; + vd->var_name = intern_string(token); + opstack_push(vd); + return; + } var_t *var = find_var(token, parent); 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, true); + if (is_bitfield(lvalue.decl)) + error_at("cannot take address of bit-field", next_token_loc()); + if (!lvalue.is_reference) { rs1 = opstack_pop(); vd = require_ref_var(parent, lvalue.type, lvalue.ptr_level); @@ -3225,6 +3979,19 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) rs1 = opstack_pop(); + 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; + } + /* For pointer dereference, we need to determine the target type and * size. Since we do not have full type tracking in expressions, use * defaults @@ -3262,6 +4029,14 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) read_lvalue(&lvalue, var, parent, bb, true, OP_generic, false); rs1 = opstack_pop(); + if (var->func_signature) { + /* C99 6.3.2.1 makes a function-pointer dereference a function + * designator. The pointer object itself therefore needs one load, + * not a dereference of its return type. + */ + opstack_push(load_function_pointer_object(parent, bb, rs1)); + return; + } vd = require_deref_var(parent, var->type, var->ptr_level); if (var->ptr_level + var->type->ptr_level > 1) sz = PTR_SIZE; @@ -3444,37 +4219,393 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } } -} +} + +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_TOKEN_LEN]; + char unescaped[MAX_TOKEN_LEN]; + int size = 1; + + do { + lex_ident(T_string, literal); + unescape_string(literal, unescaped, MAX_TOKEN_LEN); + size += strlen(unescaped); + } while (lex_peek(T_string, NULL)); + + return size; +} + +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; +} + +/* `&array` points to the complete array object, and dereferencing that pointer + * restores an array lvalue. Ordinary expression lowering deliberately decays + * arrays before it can represent the unary pair, so recognize the complete + * `*&identifier` operand here while sizeof still has access to its declaration. + */ +bool read_sizeof_address_dereference_array(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + token_t *star = cur_token->next; + token_t *ampersand; + token_t *identifier; + var_t *array; + var_t *result; + + if (!star || star->kind != T_asterisk || !star->next || + star->next->kind != T_ampersand || !star->next->next || + star->next->next->kind != T_identifier) + return false; + ampersand = star->next; + identifier = ampersand->next; + if (parenthesized && + (!identifier->next || identifier->next->kind != T_close_bracket)) + return false; + + array = find_var(identifier->literal, parent); + if (!array || array->array_size <= 0) + return false; + + lex_expect(T_asterisk); + lex_expect(T_ampersand); + lex_expect(T_identifier); + if (parenthesized) + lex_expect(T_close_bracket); + + result = require_var(parent); + result->init_val = sizeof_array_object(array); + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; +} + +/* A member access is also an array lvalue before the usual array-to-pointer + * conversion. Resolve a complete dot-member path against its declarations so + * sizeof can observe the last member's extent without changing ordinary member + * expression lowering. + */ +bool read_sizeof_member_array(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + token_t *object_token = cur_token->next; + token_t *member_token; + token_t *tail; + var_t *object; + var_t *field = NULL; + int object_ptr_level; + + if (!object_token || object_token->kind != T_identifier || + !object_token->next || + (object_token->next->kind != T_dot && + object_token->next->kind != T_arrow) || + !object_token->next->next || + object_token->next->next->kind != T_identifier) + return false; + object = find_var(object_token->literal, parent); + object_ptr_level = object ? object->ptr_level + object->type->ptr_level : 0; + if (!object || + ((object_token->next->kind == T_dot && object_ptr_level != 0) || + (object_token->next->kind == T_arrow && object_ptr_level != 1))) + return false; + + member_token = object_token->next->next; + for (;;) { + field = find_member(member_token->literal, + field ? field->type : object->type); + if (!field || field->ptr_level) + return false; + tail = member_token; + if (!tail->next || tail->next->kind != T_dot) + break; + if (!tail->next->next || tail->next->next->kind != T_identifier) + return false; + member_token = tail->next->next; + } + if (field->array_size <= 0 || + (tail->next && tail->next->kind == T_open_square) || + (parenthesized && (!tail->next || tail->next->kind != T_close_bracket))) + return false; + + lex_expect(T_identifier); + if (object_token->next->kind == T_arrow) + lex_expect(T_arrow); + else + lex_expect(T_dot); + lex_expect(T_identifier); + while (lex_peek(T_dot, NULL)) { + lex_expect(T_dot); + lex_expect(T_identifier); + } + if (parenthesized) + lex_expect(T_close_bracket); + + var_t *result = require_var(parent); + result->init_val = sizeof_array_object(field); + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; +} + +/* Preserve the remaining row type for a direct multidimensional array member. + * The index expressions are parsed in detached IR, just like direct arrays, so + * sizeof does not evaluate them. + */ +bool read_sizeof_member_array_row(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + token_t *object_token = cur_token->next; + token_t *member_token; + token_t *tail; + var_t *object; + var_t *field; + int object_ptr_level; + int subscript_count = 0; + int dimensions; + int row_elements; + + if (!object_token || object_token->kind != T_identifier || + !object_token->next || + (object_token->next->kind != T_dot && + object_token->next->kind != T_arrow) || + !object_token->next->next || + object_token->next->next->kind != T_identifier || + !object_token->next->next->next || + object_token->next->next->next->kind != T_open_square) + return false; + object = find_var(object_token->literal, parent); + object_ptr_level = object ? object->ptr_level + object->type->ptr_level : 0; + if (!object || + ((object_token->next->kind == T_dot && object_ptr_level != 0) || + (object_token->next->kind == T_arrow && object_ptr_level != 1))) + return false; + member_token = object_token->next->next; + field = find_member(member_token->literal, object->type); + if (!field || field->ptr_level || field->array_dim2 <= 0) + return false; + + tail = member_token->next; + while (tail && tail->kind == T_open_square) { + int depth = 0; + for (; tail; tail = tail->next) { + if (tail->kind == T_open_square) + depth++; + else if (tail->kind == T_close_square && --depth == 0) { + tail = tail->next; + break; + } + } + if (depth) + return false; + subscript_count++; + } + if (!tail || (parenthesized && tail->kind != T_close_bracket)) + return false; + dimensions = 2; + if (field->array_dim3) + dimensions = 3; + if (field->array_dim4) + dimensions = 4; + if (subscript_count > dimensions) + return false; -void read_expr_operand(block_t *parent, basic_block_t **bb); + lex_expect(T_identifier); + if (object_token->next->kind == T_arrow) + lex_expect(T_arrow); + else + lex_expect(T_dot); + lex_expect(T_identifier); + for (int i = 0; i < subscript_count; i++) { + lex_expect(T_open_square); + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + opstack_pop(); + se_idx = saved_side_effects; + lex_expect(T_close_square); + } + if (parenthesized) + lex_expect(T_close_bracket); -/* 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. + row_elements = 1; + if (subscript_count < 2) + row_elements *= field->array_dim2; + if (subscript_count < 3 && field->array_dim3) + row_elements *= field->array_dim3; + if (subscript_count < 4 && field->array_dim4) + row_elements *= field->array_dim4; + var_t *result = require_var(parent); + result->init_val = row_elements * field->type->size; + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; +} + +/* Parenthesized dereference is another spelling of a direct record member + * lvalue. Preserve a trailing array member for sizeof without teaching normal + * expression lowering to suppress its usual array conversion. */ -int read_sizeof_string_literal(void) -{ - char literal[MAX_TOKEN_LEN]; - char unescaped[MAX_TOKEN_LEN]; - int size = 1; +bool read_sizeof_dereference_member_array(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + bool sizeof_open_is_deref = + parenthesized && cur_token->next && cur_token->next->kind == T_asterisk; + token_t *open = sizeof_open_is_deref ? cur_token : cur_token->next; + token_t *star = open ? open->next : NULL; + token_t *pointer_token; + token_t *member_token; + token_t *tail; + var_t *pointer; + var_t *field = NULL; + int pointer_level; + + if (!open || open->kind != T_open_bracket || !star || + star->kind != T_asterisk || !star->next || + star->next->kind != T_identifier || !star->next->next || + star->next->next->kind != T_close_bracket || !star->next->next->next || + star->next->next->next->kind != T_dot || + !star->next->next->next->next || + star->next->next->next->next->kind != T_identifier) + return false; + pointer_token = star->next; + pointer = find_var(pointer_token->literal, parent); + pointer_level = pointer ? pointer->ptr_level + pointer->type->ptr_level : 0; + if (!pointer || pointer_level != 1) + return false; + member_token = star->next->next->next->next; + for (;;) { + field = find_member(member_token->literal, + field ? field->type : pointer->type); + if (!field || field->ptr_level) + return false; + tail = member_token; + if (!tail->next || tail->next->kind != T_dot) + break; + if (!tail->next->next || tail->next->next->kind != T_identifier) + return false; + member_token = tail->next->next; + } + if (field->array_size <= 0 || + (parenthesized && !sizeof_open_is_deref && + (!tail->next || tail->next->kind != T_close_bracket))) + return false; + + if (!sizeof_open_is_deref) + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_expect(T_identifier); + lex_expect(T_close_bracket); do { - lex_ident(T_string, literal); - unescape_string(literal, unescaped, MAX_TOKEN_LEN); - size += strlen(unescaped); - } while (lex_peek(T_string, NULL)); + lex_expect(T_dot); + lex_expect(T_identifier); + } while (lex_peek(T_dot, NULL)); + if (parenthesized && !sizeof_open_is_deref) + lex_expect(T_close_bracket); - return size; + var_t *result = require_var(parent); + result->init_val = sizeof_array_object(field); + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; } -int sizeof_array_object(const var_t *array) +/* Subscripted multidimensional arrays retain their remaining inner array type + * under sizeof. Parse the index in detached IR for the ordinary expression + * constraints, but never emit its side effects. + */ +bool read_sizeof_array_row(block_t *parent, + basic_block_t **bb, + bool parenthesized) { - int element_size = array->type->size; + token_t *identifier = cur_token->next; + token_t *tail; + var_t *array; + int subscript_count = 0; + int dimensions; + int row_elements; - if (array->ptr_level || array->type->ptr_level || array->is_func) - element_size = PTR_SIZE; - return array->array_size * element_size; + if (!identifier || identifier->kind != T_identifier || !identifier->next || + identifier->next->kind != T_open_square) + return false; + tail = identifier->next; + while (tail && tail->kind == T_open_square) { + int depth = 0; + for (; tail; tail = tail->next) { + if (tail->kind == T_open_square) + depth++; + else if (tail->kind == T_close_square && --depth == 0) { + tail = tail->next; + break; + } + } + if (depth) + return false; + subscript_count++; + } + if (!tail || (parenthesized && tail->kind != T_close_bracket)) + return false; + + array = find_var(identifier->literal, parent); + if (!array || array->array_dim2 <= 0) + return false; + dimensions = 2; + if (array->array_dim3) + dimensions = 3; + if (array->array_dim4) + dimensions = 4; + if (subscript_count > dimensions) + return false; + + lex_expect(T_identifier); + for (int i = 0; i < subscript_count; i++) { + lex_expect(T_open_square); + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + opstack_pop(); + se_idx = saved_side_effects; + lex_expect(T_close_square); + } + if (parenthesized) + lex_expect(T_close_bracket); + + row_elements = 1; + if (subscript_count < 2) + row_elements *= array->array_dim2; + if (subscript_count < 3 && array->array_dim3) + row_elements *= array->array_dim3; + if (subscript_count < 4 && array->array_dim4) + row_elements *= array->array_dim4; + var_t *result = require_var(parent); + result->init_val = row_elements * array->type->size; + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; } void handle_sizeof_operator(block_t *parent, basic_block_t **bb) @@ -3510,6 +4641,17 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) return; } + if (read_sizeof_address_dereference_array(parent, bb, parenthesized)) + return; + if (read_sizeof_member_array(parent, bb, parenthesized)) + return; + if (read_sizeof_member_array_row(parent, bb, parenthesized)) + return; + if (read_sizeof_dereference_member_array(parent, bb, parenthesized)) + return; + if (read_sizeof_array_row(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. @@ -3546,6 +4688,9 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) read_expr_operand(parent, &unevaluated_bb); se_idx = saved_side_effects; 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; @@ -3769,6 +4914,9 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) read_ternary_operation(parent, &unevaluated_bb); se_idx = saved_side_effects; 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; @@ -3806,6 +4954,11 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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; } @@ -4363,6 +5516,82 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr(parent, bb); read_ternary_operation(parent, bb); lex_expect(T_close_bracket); + + /* A parenthesized pointer/string expression is still a postfix + * operand. Unlike identifier lvalues it has no declaration to feed + * read_lvalue(), so lower its first subscript directly while + * retaining the same explicit element-size scaling. + */ + if (lex_accept(T_open_square)) { + var_t *base = opstack_pop(); + var_t *index; + var_t *address; + int element_size; + + if (!base->ptr_level && !base->type->ptr_level) + error_at("Cannot apply square operator to non-pointer", + cur_token_loc()); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + index = opstack_pop(); + lex_expect(T_close_square); + element_size = base->type->size; + if (element_size != 1) { + var_t *scale = require_var(parent); + scale->var_name = gen_name(); + scale->init_val = element_size; + 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, base->type, 1); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); + vd = require_typed_var(parent, base->type); + vd->ptr_level = base->ptr_level > 1 ? base->ptr_level - 1 : 0; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, + vd->ptr_level ? PTR_SIZE : element_size, NULL); + } + + /* Function calls are postfix expressions, so a parenthesized + * function designator remains callable: `(fn)(...)` and + * `(*fp)(...)` have the same meaning as `fn(...)` and `fp(...)`. + */ + if (lex_peek(T_open_bracket, NULL)) { + var_t *callee = operand_stack[operand_stack_idx - 1]; + func_t *signature = get_func_signature(callee); + + if (signature) { + if (!callee->ptr_level) { + opstack_pop(); + opstack_push( + load_function_pointer_object(parent, bb, callee)); + } + read_indirect_call(callee, parent, bb); + } else if (callee->is_func) { + signature = find_func(callee->var_name); + if (!signature) + error_at("Called object is not a function", + cur_token_loc()); + opstack_pop(); + read_func_call(signature, parent, bb); + } else { + error_at("Called object is not a function pointer", + cur_token_loc()); + } + + vd = require_typed_ptr_var(parent, signature->return_def.type, + signature->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 if (lex_accept(T_sizeof)) { handle_sizeof_operator(parent, bb); @@ -4433,6 +5662,17 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* is it an indirect call with function pointer? */ if (lex_peek(T_open_bracket, NULL)) { + /* 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)); + } read_indirect_call(lvalue.decl, parent, bb); func_t *signature = get_func_signature(lvalue.decl); @@ -5361,16 +6601,22 @@ void read_lvalue(lvalue_t *lvalue, read_expr(parent, bb); - /* multiply by element size For 2D arrays, check if this is the - * first or second dimension - */ + /* Multiply by the remaining element extent. */ int multiplier = lvalue->size; - /* If this is the first index of a 2D array, multiply by dim2 * - * element_size - */ - if (subscript_depth == 0 && var->array_dim2 > 0) + if (subscript_depth == 0 && var->array_dim2 > 0) { multiplier = var->array_dim2 * lvalue->size; + if (var->array_dim3 > 0) + multiplier *= var->array_dim3; + if (var->array_dim4 > 0) + multiplier *= var->array_dim4; + } else if (subscript_depth == 1 && var->array_dim3 > 0) { + multiplier = var->array_dim3 * lvalue->size; + if (var->array_dim4 > 0) + multiplier *= var->array_dim4; + } else if (subscript_depth == 2 && var->array_dim4 > 0) { + multiplier = var->array_dim4 * lvalue->size; + } if (multiplier != 1) { vd = require_var(parent); @@ -5398,7 +6644,13 @@ void read_lvalue(lvalue_t *lvalue, * still know whether this is an int or struct element. */ vd->type = lvalue->type; - vd->ptr_level = lvalue->ptr_level ? lvalue->ptr_level : 1; + + /* 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); @@ -5578,7 +6830,17 @@ void read_lvalue(lvalue_t *lvalue, t->type = pointee_type_from_pointer_typedef(lvalue->type); t->ptr_level = lvalue->value_ptr_level; opstack_push(t); - add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, NULL); + if (is_bitfield(lvalue->decl)) { + /* Bit-fields are values, never independently addressable + * storage: extract their slice from the containing unit. + */ + opstack_pop(); + t = read_bitfield_value(parent, bb, rs1, lvalue->decl); + opstack_push(t); + } else { + add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, + NULL); + } } if (prefix_op != OP_generic) { if ((prefix_op == OP_add || prefix_op == OP_sub) && @@ -6067,8 +7329,14 @@ bool read_body_assignment(char *token, vd->type = pointee_type_from_pointer_typedef(lvalue.type); vd->ptr_level = lvalue.value_ptr_level; opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, - NULL); + 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]; @@ -6085,7 +7353,11 @@ bool read_body_assignment(char *token, 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); + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, t, vd, lvalue.decl); + else + add_insn(parent, *bb, OP_write, NULL, t, vd, size, + NULL); } else { vd = resize_var(parent, bb, vd, t); mark_var_mutated(t); @@ -6099,8 +7371,14 @@ bool read_body_assignment(char *token, vd->type = pointee_type_from_pointer_typedef(lvalue.type); vd->ptr_level = lvalue.value_ptr_level; opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, t, NULL, lvalue.size, - NULL); + 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]; @@ -6153,8 +7431,11 @@ bool read_body_assignment(char *token, 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); + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, t, vd, lvalue.decl); + else + 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); @@ -6200,7 +7481,10 @@ bool read_body_assignment(char *token, } else if (lvalue.is_reference) { rs2 = opstack_pop(); rs1 = opstack_pop(); - add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); + else + add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); } else { rs1 = opstack_pop(); vd = opstack_pop(); @@ -7445,29 +8729,155 @@ basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) } /* A local struct or union declaration. */ -basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) +basic_block_t *handle_record_statement(block_t *parent, + basic_block_t *bb, + bool is_const, + bool is_static) { char token[MAX_ID_LEN]; type_t *type; var_t *var; - bool is_const = false; + bool is_union = false; int find_type_flag = lex_accept(T_struct) ? 2 : 1; if (find_type_flag == 1 && lex_accept(T_union)) { find_type_flag = 2; + is_union = true; } lex_ident(T_identifier, token); type = find_type(token, find_type_flag); + if (lex_peek(T_open_curly, NULL)) { + int i = 0; + int size = 0; + int alignment = 1; + int max_size = 0; + bitfield_layout_t bits = {0}; + bool has_flexible_array_member = false; + + if (!type) + type = add_type(); + set_type_name(type, token); + type->base_type = is_union ? TYPE_union : TYPE_struct; + + lex_expect(T_open_curly); + do { + var_t *v = type_add_field(type, &i); + var_t *last = v; + + read_full_var_decl(v, false, false, true); + read_bitfield_width(v, parent); + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, is_union); + if (is_union) { + v->offset = 0; + int field_size = is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); + } else { + size = is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), v, + &alignment); + } + + while (lex_accept(T_comma)) { + if (!is_union && last->is_flexible_array_member) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); + var_t *nv = type_add_field(type, &i); + initialize_struct_field(nv, v, 0); + read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, parent); + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, is_union); + last = nv; + if (is_union) { + nv->offset = 0; + int field_size = + is_bitfield(nv) + ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); + } else { + size = + is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); + } + } + + 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->has_flexible_array_member = has_flexible_array_member; + + if (lex_peek(T_semicolon, NULL)) { + lex_expect(T_semicolon); + return bb; + } + } + if (!type && lex_peek(T_semicolon, NULL)) { + /* A block-scope `struct tag;` or `union tag;` introduces an incomplete + * tag. Its pointer declarators become valid immediately; the later + * complete definition reuses this type record. + */ + type = add_type(); + type->base_type = is_union ? TYPE_union : TYPE_struct; + set_type_name(type, token); + lex_expect(T_semicolon); + return bb; + } if (type) { var = require_typed_var(parent, type); var->is_const_qualified = is_const; + var->is_static = is_static; + var->is_global = is_static; read_partial_var_decl(var, NULL); - add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); + if (!type->size && !var->ptr_level) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : parent, + is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, var, NULL, NULL, + 0, NULL); add_symbol(bb, var); if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && + if (is_static && lex_peek(T_open_curly, NULL) && (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) { + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (is_static && lex_peek(T_open_curly, NULL)) { + parse_global_record_init(var, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + 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 || @@ -7506,13 +8916,27 @@ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) /* multiple (partial) declarations */ nv = require_typed_var(parent, type); + nv->is_static = is_static; + nv->is_global = is_static; + nv->is_const_qualified = is_const; read_inner_var_decl(nv, false, false, false); - add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); + if (!type->size && !nv->ptr_level) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : parent, + is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, + NULL, 0, NULL); add_symbol(bb, nv); if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && + if (is_static && lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || nv->ptr_level > 0)) { + parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (is_static && lex_peek(T_open_curly, NULL)) { + parse_global_record_init(nv, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + 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 || @@ -7733,6 +9157,13 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) if (lex_peek(T_enum, NULL)) return handle_enum_statement(parent, bb, is_const, is_static); + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { + if (is_extern || is_register) + error_at("Unsupported storage class on record definition", + next_token_loc()); + return handle_record_statement(parent, bb, is_const, is_static); + } + /* statement with prefix */ if (!is_const && !is_static && lex_accept(T_increment)) prefix_op = OP_add; @@ -8161,7 +9592,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) /* struct/union variable declaration */ if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) - return handle_record_statement(parent, bb); + return handle_record_statement(parent, bb, false, false); if (lex_peek(T_enum, NULL)) return handle_enum_statement(parent, bb, false, false); @@ -8422,6 +9853,8 @@ var_t *resolve_global_declarator(block_t *block, previous->ptr_level != var->ptr_level || 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->is_const_qualified != var->is_const_qualified) error_at("conflicting types for global declaration", next_token_loc()); if (!previous->is_static && is_static) @@ -8614,6 +10047,9 @@ void read_global_record_declarator(block_t *block, var->is_static = is_static; var->is_const_qualified = is_const; read_inner_var_decl(var, false, false, false); + if (!decl_type->size && !var->ptr_level) + 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_redeclaration) add_insn(block, GLOBAL_FUNC->bbs, OP_allocat, var, NULL, NULL, 0, NULL); @@ -8734,6 +10170,10 @@ void initialize_struct_field(var_t *nv, var_t *v, int offset) 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; @@ -8782,7 +10222,8 @@ void read_global_statement(void) cur_token_loc()); if (lex_accept(T_struct)) { - int i = 0, size = 0; + int i = 0, size = 0, alignment = 1; + bitfield_layout_t bits = {0}; bool has_flexible_array_member = false; lex_ident(T_identifier, token); @@ -8791,6 +10232,13 @@ void read_global_statement(void) /* variable declaration using existing struct tag? */ if (!lex_peek(T_open_curly, NULL)) { type_t *decl_type = find_type(token, 2); + if (!decl_type && lex_peek(T_semicolon, NULL)) { + decl_type = add_type(); + decl_type->base_type = TYPE_struct; + set_type_name(decl_type, token); + lex_expect(T_semicolon); + return; + } if (!decl_type) error_at("Unknown struct type", &id_tk->location); @@ -8816,10 +10264,13 @@ void read_global_statement(void) var_t *v = type_add_field(type, &i); var_t *last = v; read_full_var_decl(v, false, false, true); + read_bitfield_width(v, block); reject_flexible_array_member_container(v); mark_flexible_array_member(v, false); - v->offset = size; - size += size_var(v); + size = is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field(flush_bitfield_layout(size, &bits), + v, &alignment); /* Handle multiple variable declarations with same base type */ while (lex_accept(T_comma)) { @@ -8830,11 +10281,15 @@ void read_global_statement(void) var_t *nv = type_add_field(type, &i); initialize_struct_field(nv, v, 0); read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, block); reject_flexible_array_member_container(nv); mark_flexible_array_member(nv, false); last = nv; - nv->offset = size; - size += size_var(nv); + size = is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); } lex_expect(T_semicolon); @@ -8852,7 +10307,9 @@ void read_global_statement(void) } } while (!lex_accept(T_close_curly)); - type->size = size; + size = flush_bitfield_layout(size, &bits); + type->alignment = alignment; + type->size = ALIGN_UP(size, alignment); type->num_fields = i; type->has_flexible_array_member = has_flexible_array_member; @@ -8866,10 +10323,36 @@ void read_global_statement(void) } lex_expect(T_semicolon); } else if (lex_accept(T_union)) { - int i = 0, max_size = 0; + int i = 0, max_size = 0, alignment = 1; bool has_flexible_array_member = false; lex_ident(T_identifier, token); + token_t *id_tk = cur_token; + + /* A tagged union declaration may name an already-complete tag, just + * like `struct tag object;`. Do not require a second definition body + * before routing its declarators through the shared record path. + */ + if (!lex_peek(T_open_curly, NULL)) { + type_t *decl_type = find_type(token, 2); + if (!decl_type && lex_peek(T_semicolon, NULL)) { + decl_type = add_type(); + decl_type->base_type = TYPE_union; + set_type_name(decl_type, token); + lex_expect(T_semicolon); + return; + } + if (!decl_type) + error_at("Unknown union type", &id_tk->location); + + read_global_record_declarator(block, decl_type, is_const, + is_static); + while (lex_accept(T_comma)) + read_global_record_declarator(block, decl_type, is_const, + is_static); + lex_expect(T_semicolon); + return; + } /* has forward declaration? */ type_t *type = find_type(token, 2); @@ -8883,12 +10366,17 @@ void read_global_statement(void) do { var_t *v = type_add_field(type, &i); read_full_var_decl(v, false, false, true); + read_bitfield_width(v, block); has_flexible_array_member |= is_flexible_array_member_container(v); mark_flexible_array_member(v, true); v->offset = 0; /* All union fields start at offset 0 */ - int field_size = size_var(v); + int field_size = is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); if (field_size > max_size) max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); /* Handle multiple variable declarations with same base type */ while (lex_accept(T_comma)) { @@ -8896,18 +10384,24 @@ void read_global_statement(void) /* All union fields start at offset 0 */ initialize_struct_field(nv, v, 0); read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, block); has_flexible_array_member |= is_flexible_array_member_container(nv); mark_flexible_array_member(nv, true); - field_size = size_var(nv); + field_size = is_bitfield(nv) + ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); if (field_size > max_size) max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); } lex_expect(T_semicolon); } while (!lex_accept(T_close_curly)); - type->size = max_size; + type->alignment = alignment; + type->size = ALIGN_UP(max_size, alignment); type->num_fields = i; type->has_flexible_array_member = has_flexible_array_member; @@ -8990,7 +10484,8 @@ void read_global_statement(void) set_type_name(type, token); lex_expect(T_semicolon); } else if (lex_accept(T_struct)) { - int i = 0, size = 0; + int i = 0, size = 0, alignment = 1; + bitfield_layout_t bits = {0}; bool has_struct_def = false; bool has_flexible_array_member = false; type_t *tag = NULL, *type = add_type(); @@ -9015,10 +10510,15 @@ void read_global_statement(void) var_t *v = type_add_field(type, &i); var_t *last = v; read_full_var_decl(v, false, false, true); + read_bitfield_width(v, block); reject_flexible_array_member_container(v); mark_flexible_array_member(v, false); - v->offset = size; - size += size_var(v); + size = + is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), v, + &alignment); /* Handle multiple variable declarations with same base type */ @@ -9031,11 +10531,16 @@ void read_global_statement(void) var_t *nv = type_add_field(type, &i); initialize_struct_field(nv, v, 0); read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, block); reject_flexible_array_member_container(nv); mark_flexible_array_member(nv, false); last = nv; - nv->offset = size; - size += size_var(nv); + size = is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, + &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); } lex_expect(T_semicolon); @@ -9060,7 +10565,9 @@ void read_global_statement(void) type->size = PTR_SIZE; } lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->size = type->ptr_level ? PTR_SIZE : size; + size = flush_bitfield_layout(size, &bits); + type->alignment = type->ptr_level ? PTR_SIZE : alignment; + type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(size, alignment); type->num_fields = i; type->base_type = TYPE_typedef; type->has_flexible_array_member = has_flexible_array_member; @@ -9080,7 +10587,7 @@ void read_global_statement(void) lex_expect(T_semicolon); } else if (lex_accept(T_union)) { - int i = 0, max_size = 0; + int i = 0, max_size = 0, alignment = 1; bool has_union_def = false; bool has_flexible_array_member = false; type_t *tag = NULL, *type = add_type(); @@ -9104,13 +10611,19 @@ void read_global_statement(void) do { var_t *v = type_add_field(type, &i); read_full_var_decl(v, false, false, true); + read_bitfield_width(v, block); has_flexible_array_member |= is_flexible_array_member_container(v); mark_flexible_array_member(v, true); v->offset = 0; /* All union fields start at offset 0 */ - int field_size = size_var(v); + int field_size = + is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); if (field_size > max_size) max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); /* Handle multiple variable declarations with same base type */ @@ -9119,12 +10632,18 @@ void read_global_statement(void) /* All union fields start at offset 0 */ initialize_struct_field(nv, v, 0); read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, block); has_flexible_array_member |= is_flexible_array_member_container(nv); mark_flexible_array_member(nv, true); - field_size = size_var(nv); + field_size = + is_bitfield(nv) + ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); if (field_size > max_size) max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); } lex_expect(T_semicolon); @@ -9136,7 +10655,9 @@ void read_global_statement(void) type->size = PTR_SIZE; } lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->size = type->ptr_level ? PTR_SIZE : max_size; + type->alignment = type->ptr_level ? PTR_SIZE : alignment; + type->size = + type->ptr_level ? PTR_SIZE : ALIGN_UP(max_size, alignment); type->num_fields = i; type->base_type = TYPE_typedef; type->is_union = true; @@ -9280,7 +10801,21 @@ void read_global_statement(void) error_at("Unable to find base type", cur_token_loc()); type->base_type = base->base_type; type->size = base->size; - type->num_fields = 0; + + /* 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 = diff --git a/src/preprocessor.c b/src/preprocessor.c index 0f231821..b136ae78 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -96,6 +96,110 @@ 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, "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); +} + +/* 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, "^="); +} + +/* An angle header is lexed as ordinary preprocessing tokens. Recover its raw + * spelling from the immutable source buffer so dotted and slash-separated names + * do not need special punctuation reconstruction here. + */ +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 start = open->location.pos + open->location.len; + int len = close->location.pos - start; + char name[MAX_LINE_LEN]; + + if (len <= 0 || len >= MAX_LINE_LEN) + error_at("Invalid #include <...> header name", &open->location); + memcpy(name, source->elements + start, len); + 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; + snprintf(resolved, resolved_size, "%s/%s", include_dirs[i], name); + 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 (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 */ @@ -123,6 +227,39 @@ 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. + */ +token_t *translation_timestamp_macro_handler(token_t *tk) +{ + token_t *new_tk = copy_token(tk); + + new_tk->kind = T_string; + new_tk->literal = !strcmp(tk->literal, "__DATE__") ? SHECC_TRANSLATION_DATE + : SHECC_TRANSLATION_TIME; + memcpy(&new_tk->location, &tk->location, sizeof(source_location_t)); + return new_tk; +} + +/* 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. @@ -204,6 +341,59 @@ 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.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); + *requested_line = parse_numeric_constant(cursor->literal); + 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 */ @@ -755,6 +945,18 @@ __noreturn void pp_error_directive(token_t *directive) 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; consume the operator syntax so no tokens reach the + * parser. + */ +token_t *pp_ignore_pragma_operator(token_t *tk) +{ + tk = pp_lex_expect_token(tk, T_open_bracket, true); + tk = pp_lex_expect_token(tk, T_string, true); + 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 @@ -1009,6 +1211,12 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) continue; } + if (!strcmp(tk->literal, "_Pragma")) { + tk = pp_ignore_pragma_operator(tk); + 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; @@ -1227,6 +1435,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) 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.expanded_from = NULL; @@ -1262,18 +1472,34 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) macro = hashmap_get(MACROS, macro->replacement->literal); } if (!macro || macro->is_disabled || macro->param_num || - !macro->replacement || macro->replacement->next || - macro->replacement->kind != T_string) + !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); - strcpy(inclusion_path, macro->replacement->literal); + } } 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 - * at this moment, since all - * libc functions are included done by inlining. - */ + if (angle_form && !angle_header) { tk = pp_lex_expect_token(tk, T_newline, true); tk = pp_lex_next_token(tk, false); continue; } - /* normalize path */ - char path[MAX_LINE_LEN]; - const char *file = include_tk->location.filename; - int c = strlen(file) - 1; + if (!angle_header) { + /* Quoted headers are relative to the physical source path. */ + char path[MAX_LINE_LEN]; + const char *file = include_tk->location.physical_filename; + int c = strlen(file) - 1; - while (c > 0 && file[c] != '/') - c--; + while (c > 0 && file[c] != '/') + c--; - if (c) { - if (c >= MAX_LINE_LEN - 1) - c = MAX_LINE_LEN - 2; + if (c) { + if (c >= MAX_LINE_LEN - 1) + c = MAX_LINE_LEN - 2; + memcpy(path, file, c); + path[c] = '\0'; + } else { + path[0] = '.'; + path[1] = '\0'; + c = 1; + } - memcpy(path, file, c); - path[c] = '\0'; - } else { - path[0] = '.'; - path[1] = '\0'; - c = 1; + 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'; } - 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'; - 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) { @@ -1491,7 +1727,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)) @@ -1500,6 +1737,23 @@ 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_line: { + char *original_filename = tk->location.physical_filename; + char *logical_filename = NULL; + int requested_line; + + 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); } @@ -1613,6 +1867,16 @@ token_t *preprocess(token_t *tk) macro->handler = line_macro_handler; hashmap_put(MACROS, "__LINE__", macro); + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__DATE__"; + macro->handler = translation_timestamp_macro_handler; + hashmap_put(MACROS, "__DATE__", macro); + + macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); + macro->name = "__TIME__"; + macro->handler = translation_timestamp_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. @@ -1847,6 +2111,7 @@ 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: error_at( "Internal error, preprocessor directives should be ommited " "after preprocessing", diff --git a/src/reg-alloc.c b/src/reg-alloc.c index f1d3deb3..74c28a3e 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -2393,7 +2393,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 { @@ -2539,10 +2539,37 @@ void reg_alloc_global(insn_t *global_insn) } case OP_write: { if (global_insn->rs2 && global_insn->rs2->is_func) { - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + /* 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) @@ -2611,6 +2638,7 @@ void reg_alloc_global(insn_t *global_insn) 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; int args = 0; @@ -2696,7 +2724,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; @@ -3028,6 +3056,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; } @@ -3083,16 +3129,19 @@ 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; - ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); + 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; args = 0; handle_abi = false; + riscv_indirect_target_staged = false; clobber_caller_saved(); break; diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index c868ac29..449b1973 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -563,10 +563,10 @@ 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) diff --git a/tests/driver.sh b/tests/driver.sh index e004a224..69a68126 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -302,6 +302,40 @@ 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() @@ -577,12 +611,124 @@ int main(void) { 16 * (bool_identity(null_value) == 0); } EOF + +# 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 + +# 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 # 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 + +# 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 + +# 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 + +# 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 + +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 + # Category: Arithmetic Operations begin_category "Arithmetic Operations" "Testing +, -, *, /, % operators" @@ -1796,6 +1942,23 @@ 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) { @@ -1933,6 +2096,40 @@ int main() { } 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 + # Category: Compound Literals begin_category "Compound Literals" "Testing C99 compound literal features" @@ -2386,13 +2583,13 @@ int main() { } EOF -# Test: Multi-element array of structs -try_ 1 << 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() { - /* 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) */ + return pts[0].x + pts[0].y + pts[1].x + pts[1].y; } EOF @@ -3390,6 +3587,32 @@ int main(void) { } 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) { @@ -3729,6 +3952,117 @@ int main() { } 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 + +# The LP64 ABIs return an eight-byte callback result in the normal integer +# return register pair/value. Keep this separate from the still-gated 32-bit +# long-long ABI work. +if [ "$PTR_SZ" -eq 8 ]; then + 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 +fi + # 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 @@ -3846,6 +4180,84 @@ EOF # Category: Arrays begin_category "Arrays" "Testing array declarations, indexing, and operations" +# 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 +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 @@ -4388,6 +4800,135 @@ int main(void) { } 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 + +# 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 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; +static struct node *head; +static union payload *slot; +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 + +# 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 + # Translation phase 6 also concatenates literals made adjacent by macro # expansion, before the expression parser sees them. try_ 3 << EOF @@ -5777,7 +6318,7 @@ typedef struct { int main() { return sizeof(struct_t*); } EOF -try_ 6 << EOF +try_ 8 << EOF typedef struct { int x; short y; @@ -5786,6 +6327,38 @@ typedef struct { 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 { @@ -5798,6 +6371,44 @@ 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[2][3]; }; struct holder value; return sizeof(value.values[0]);" +items 12 "struct holder { int values[2][3]; }; struct holder value; return sizeof value.values[0];" +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) { @@ -6037,7 +6648,57 @@ begin_category "Preprocessor Directives" "Testing #define, #ifdef, #ifndef, #if, 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 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 +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_compile_error_message "Angle header not found in -I search paths" << EOF +#include +EOF try_compile_error << EOF #define FIRST_HEADER SECOND_HEADER #define SECOND_HEADER FIRST_HEADER @@ -6048,6 +6709,19 @@ 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 # #ifdef...#else...#endif try_ 0 << EOF @@ -6651,6 +7325,46 @@ int main() } 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 + +# 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 + # Category: Function-like Macros begin_category "Function-like Macros" "Testing function-like macros and variadic macros" @@ -6660,6 +7374,13 @@ try_ 6 << EOF 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 + # stringification: '#' spells the argument as it was written try_output 0 "hello world" << EOF #define STR(x) #x @@ -8048,6 +8769,12 @@ int main() { } EOF +# 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 + # Test hex escapes in strings try_output 0 "Hello World" << EOF int main() { @@ -9110,6 +9837,16 @@ int main() { } EOF +# 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 + # Union with different data types try_output 0 "Value as int: 1094795585, as char: 65" << EOF typedef union { @@ -9974,6 +10711,174 @@ int main() } EOF +begin_category "Bit-fields" +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 +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_ 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 +try_ 2 << EOF +struct flags { unsigned int value : 3; }; +int main(void) { + struct flags flags = {1}; + return (flags.value - 8 < 0) + ((~flags.value) < 0); +} +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 +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; +} +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 +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_ 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 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 + # 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-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" From b6c4203fea1d3eb163b5789d9bfe8d9ae51a4071 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Fri, 11 Sep 2026 20:19:55 +0800 Subject: [PATCH 05/40] Add C99 literals, standard headers and strict mode auto is accepted, comma expressions sequence in every context, assignments lower in nested contexts, compound literals are lvalues whose members and elements can be updated, and conditionals lower inside controlling expressions. Function prototypes are checked for compatibility, qualified array parameters are supported, inline is rejected on objects, implicit enum overflow is diagnosed, and bit-field updates and compound values are preserved. Typedef array bounds are retained and composed, and array compound literals may have several dimensions. The lexer recognizes floating literals and keywords, wide character constants and strings, digraphs and trigraphs. The limits, stddef, stdint, assert and stdarg headers are provided, #if evaluates in intmax_t, and a strict mode diagnoses extensions and rejects forms outside C99. Global constants fold sizeof operands, and sizeof of a flexible array member is rejected. --- COMPLIANCE.md | 4 +- lib/c.c | 6 + lib/c.h | 1 + src/defs.h | 43 +- src/globals.c | 5 + src/lexer.c | 481 ++++-- src/main.c | 4 +- src/parser.c | 3477 +++++++++++++++++++++++++++++++++++++++++--- src/preprocessor.c | 924 +++++++++++- tests/driver.sh | 2140 ++++++++++++++++++++++++++- 10 files changed, 6687 insertions(+), 398 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 101eb76f..7632c2de 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -51,7 +51,7 @@ This document tracks compliance gaps and non-standard behaviors. | `static` | Partial | File-scope internal linkage and persistent block-scope objects work, including C99 `for` initializers; cross-translation-unit linkage remains incomplete. | | `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` | Not implemented | Default storage class (implicit) | +| `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 | Parsed and retained as an aliasing qualifier; does not yet drive optimization. | @@ -98,7 +98,7 @@ This document tracks compliance gaps and non-standard behaviors. |---------|--------|-------------| | 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 | +| 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 | diff --git a/lib/c.c b/lib/c.c index df35d93d..d2653662 100644 --- a/lib/c.c +++ b/lib/c.c @@ -643,6 +643,12 @@ void abort(void) exit(-1); } +void __assert_fail(const char *expr, const char *file, int line) +{ + printf("Assertion failed: %s, file %s, line %d\n", expr, file, line); + abort(); +} + FILE *fopen(const char *filename, const char *mode) { int fd; diff --git a/lib/c.h b/lib/c.h index 1af81348..1f0fa0c3 100644 --- a/lib/c.h +++ b/lib/c.h @@ -153,6 +153,7 @@ int fflush(FILE *stream); /* Terminating program */ void exit(int exit_code); void abort(void); +void __assert_fail(const char *expr, const char *file, int line); /* Dynamic memory allocation/deallocation functions */ void *malloc(int size); diff --git a/src/defs.h b/src/defs.h index 7561bf7f..3ab6fe72 100644 --- a/src/defs.h +++ b/src/defs.h @@ -288,10 +288,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, /* { */ @@ -359,11 +362,16 @@ typedef enum { 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, @@ -701,6 +709,18 @@ struct var { */ bool is_compound_literal; + /* A scalar value read from a compound-literal aggregate. This points at its + * element/member address so a following prefix update can write back + * through the automatic aggregate rather than assign the temporary. + */ + bool is_compound_literal_reference; + struct var *compound_literal_address; + + /* 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. @@ -842,6 +862,15 @@ struct type { var_t *fields; int num_fields; int ptr_level; /* pointer level for typedef pointer types */ + /* 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; + /* 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. @@ -861,6 +890,11 @@ struct type { * 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 */ @@ -1099,6 +1133,11 @@ struct func { 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; bool is_static; /* internal-linkage declaration */ /* inline_calls()'s verdict on this body and the return that ends it, diff --git a/src/globals.c b/src/globals.c index b0b84a86..ee993b80 100644 --- a/src/globals.c +++ b/src/globals.c @@ -115,6 +115,7 @@ 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; @@ -778,6 +779,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; diff --git a/src/lexer.c b/src/lexer.c index c2647f16..04f66ed0 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -12,7 +12,7 @@ /* Hash table constants */ #define NUM_DIRECTIVES 12 -#define NUM_KEYWORDS 27 +#define NUM_KEYWORDS 32 /* Token mapping structure for elegant initialization */ typedef struct { @@ -92,11 +92,16 @@ void lex_init_keywords(void) {"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 */ @@ -154,19 +159,95 @@ 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 (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; + + 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 = skip_splices(buf, buf->size); + + 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 = skip_splices(buf, buf->size); + + 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); } /* Fill @dst with @len bytes of @f, returning how many arrived. @@ -253,6 +334,31 @@ token_t *new_token(token_kind_t kind, const source_location_t *loc, int len) * 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; \ + return tk; \ + } while (0) /* Preprocessor directives, comments, and the whitespace between tokens. * @@ -264,36 +370,55 @@ 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++; + 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); + + 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'; token_kind_t directive_kind = lookup_directive(token_buffer); @@ -302,8 +427,8 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) error_at("Unsupported directive", loc); } - token = new_token(directive_kind, loc, sz); - loc->column += sz; + token = new_token(directive_kind, loc, source_len); + loc->column += source_len; return token; } @@ -330,33 +455,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; @@ -364,13 +481,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); } @@ -425,13 +542,31 @@ 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); + 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)) { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz; + error_at("Token too long", loc); + } + token_buffer[sz++] = ch; + ch = read_char(buf); + } + } else { + token_buffer[sz++] = ch; + ch = read_char(buf); + } - if (token_buffer[0] == '0' && ((ch | 32) == 'x')) { + if (!is_floating && token_buffer[0] == '0' && ((ch | 32) == 'x')) { /* Hexadecimal: starts with 0x or 0X */ + is_hex = true; if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz; error_at("Token too long", loc); @@ -439,23 +574,26 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) token_buffer[sz++] = ch; ch = read_char(buf); - if (!isxdigit(ch)) { - loc->len = 3; - error_at("Invalid hex literal: expected hex digit after 0x", - loc); - } - do { + /* 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)) { if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz; error_at("Token too long", loc); } token_buffer[sz++] = ch; + 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 (strict_c99) + error_at("binary literals are a GNU extension in C99", loc); if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz; error_at("Token too long", loc); @@ -477,14 +615,9 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) 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); @@ -493,7 +626,7 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) ch = read_char(buf); } - } else { + } else if (!is_floating) { /* Decimal */ while (isdigit(ch)) { if (sz >= MAX_TOKEN_LEN - 1) { @@ -505,6 +638,85 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) } } + /* 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; + token_buffer[sz++] = ch; + ch = read_char(buf); + if (is_hex) { + while (isxdigit(ch)) { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz; + error_at("Token too long", loc); + } + token_buffer[sz++] = ch; + has_hex_significand = true; + ch = read_char(buf); + } + } else { + while (isdigit(ch)) { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz; + error_at("Token too long", loc); + } + token_buffer[sz++] = ch; + 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; + token_buffer[sz++] = ch; + ch = read_char(buf); + if (ch == '+' || ch == '-') { + token_buffer[sz++] = ch; + ch = read_char(buf); + } + if (!isdigit(ch)) + error_at("Floating literal needs an exponent", loc); + do { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz; + error_at("Token too long", loc); + } + token_buffer[sz++] = ch; + 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') { + token_buffer[sz++] = ch; + 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; + } + 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 @@ -556,6 +768,14 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) ch = read_char(buf); while (ch != '"' || special) { + if (ch == '\\' && peek_char(buf, 1) == '\n') { + read_char(buf); + ch = read_char(buf); + loc->line++; + loc->column = 1; + special = false; + continue; + } if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; error_at("String literal too long", loc); @@ -591,6 +811,13 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) ch = read_char(buf); while (ch && ch != '\'') { + if (ch == '\\' && peek_char(buf, 1) == '\n') { + read_char(buf); + ch = read_char(buf); + loc->line++; + loc->column = 1; + continue; + } if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; error_at("Character literal too long", loc); @@ -659,6 +886,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); @@ -666,6 +901,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); @@ -673,6 +916,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); @@ -680,6 +931,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); @@ -1119,7 +1378,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' && @@ -1131,6 +1390,9 @@ 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, static, @@ -1144,6 +1406,9 @@ token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) 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; @@ -1184,7 +1449,9 @@ 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, source_len); @@ -1204,33 +1471,85 @@ 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 phase-6 literal so it cannot concatenate with bytes. + */ + 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; + 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; } +#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) +{ + 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 diff --git a/src/main.c b/src/main.c index 0bececd4..ddf4646d 100644 --- a/src/main.c +++ b/src/main.c @@ -111,6 +111,8 @@ int main(int argc, char *argv[]) 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")) @@ -146,7 +148,7 @@ int main(int argc, char *argv[]) if (!in) { printf( "Usage: shecc [-I directory] [-o output] [+m] [--dot] [--dump-ir] " - "[--warn-string-literals] [--no-libc] " + "[--warn-string-literals] [--std=c99] [--no-libc] " "[--dynlink] [-E] \n"); usage_error("Missing source file"); } diff --git a/src/parser.c b/src/parser.c index 41c7a3a6..958b5f74 100644 --- a/src/parser.c +++ b/src/parser.c @@ -36,12 +36,37 @@ 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; + /* 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); +bool read_assignment_expression(block_t *parent, basic_block_t **bb); +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, @@ -57,6 +82,28 @@ 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); + +/* 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)); +} + +/* 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); +} bool global_compound_literal_starts_here(void) { @@ -288,7 +335,8 @@ bool compatible_decl_type(const type_t *left, const type_t *right) 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_signed_char != right->is_signed_char || + left->is_bool != right->is_bool) return false; if (left->base_type == TYPE_struct || left->base_type == TYPE_union) return false; @@ -388,6 +436,15 @@ typedef struct { 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 { + int value; + struct switch_case_value *next; +} switch_case_value_t; + int read_const_expr(block_t *scope); void read_literal_param(block_t *parent, basic_block_t *bb); var_t *bitfield_constant(block_t *parent, basic_block_t **bb, unsigned value); @@ -403,6 +460,22 @@ bool is_bitfield(const var_t *field) return field && field->is_bitfield; } +bool is_bool_type(const type_t *type) +{ + return type && type->is_bool; +} + +/* 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. + */ +void reject_empty_initializer_in_strict_c99(void) +{ + if (strict_c99 && lex_peek(T_close_curly, NULL)) + error_at("empty initializer list is not permitted in C99", + cur_token_loc()); +} + /* 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. @@ -415,14 +488,14 @@ void read_bitfield_width(var_t *field, block_t *scope) return; if (field->ptr_level || field->is_func || field->array_size || field->has_unsized_array || - !(field->type == TY_bool || + !(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()); width = read_const_expr(scope); - int storage_size = field->type == TY_bool ? 1 : 4; - int max_width = field->type == TY_bool ? 1 : storage_size * 8; + 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]) @@ -563,6 +636,152 @@ int write_symbol(const char *data) return start_len; } +int write_wide_symbol(const int *data, int length) +{ + int start_len = elf_rodata->size; + + for (int i = 0; i < length; i++) + elf_write_int(elf_rodata, data[i]); + elf_write_int(elf_rodata, 0); + return start_len; +} + +/* 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; +} + +int read_wstring_units(int *units, int capacity) +{ + char literal[MAX_TOKEN_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 get_size(var_t *var) { if (var->ptr_level || var->is_func) @@ -888,6 +1107,7 @@ bool incompatible_character_pointer_conversion(const var_t *from, } 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, @@ -1152,6 +1372,7 @@ void parse_array_field_row_init(block_t *parent, (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); while (!lex_peek(T_close_curly, NULL)) { var_t *value = NULL; var_t *elem_addr; @@ -1234,6 +1455,7 @@ void parse_array_field_plane_init(block_t *parent, row.array_dim2 = row_width; row.array_dim3 = 0; lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); while (!lex_peek(T_close_curly, NULL)) { if (rows >= field->array_dim2) error_at("Too many rows in array initializer", next_token_loc()); @@ -1292,6 +1514,7 @@ void parse_array_field_hyperplane_init(block_t *parent, 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)) { if (planes >= field->array_dim2) error_at("Too many planes in array initializer", next_token_loc()); @@ -1373,6 +1596,70 @@ void parse_string_field_init(block_t *parent, } } +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, + bool emit_code) +{ + int values[MAX_LINE_LEN]; + int length; + int units; + + length = read_wstring_units(values, MAX_LINE_LEN); + + units = length + 1; + if (units > field->array_size) + error_at("Wide string initializer is too long for array", + cur_token_loc()); + if (!emit_code) + return; + + 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); + } +} + void parse_array_field_init(block_t *parent, basic_block_t **bb, const var_t *field, @@ -1384,6 +1671,7 @@ void parse_array_field_init(block_t *parent, (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); while (!lex_peek(T_close_curly, NULL)) { var_t *value = NULL; @@ -1477,6 +1765,8 @@ void parse_struct_field_init(block_t *parent, int field_idx = 0; int initializer_count = 0; + 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. @@ -1696,13 +1986,31 @@ void parse_struct_field_init(block_t *parent, field = &struct_type->fields[field_idx]; if (field && field->array_size && !field->ptr_level && - field->type == TY_char && lex_peek(T_string, NULL)) { + (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 (field && field->array_size && is_char_array(field) && + lex_peek(T_string, NULL)) { var_t *field_addr = designated_field_addr ? designated_field_addr : compute_field_address(parent, bb, target_addr, field); parse_string_field_init(parent, bb, field, field_addr, emit_code); + } else if (field && field->array_size && !field->ptr_level && + compatible_decl_type(field->type, + find_type("wchar_t", true)) && + lex_peek(T_wstring, NULL)) { + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + parse_wstring_field_init(parent, bb, field, field_addr, + emit_code); } else if (field && lex_peek(T_open_curly, NULL) && field->array_size) { var_t *field_addr = @@ -1777,7 +2085,7 @@ void parse_struct_field_init(block_t *parent, unsigned mask = bitfield_mask(field) << field->bit_offset; unsigned init_val = - field->type == TY_bool + is_bool_type(field->type) ? field_val_raw->init_val != 0 : (unsigned) field_val_raw->init_val; global_bitfield_value[unit] = @@ -1868,13 +2176,34 @@ void parse_array_literal_expr(block_t *parent, basic_block_t **bb) 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; } - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); + 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)) { + opstack_pop(); + perform_side_effect(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(); @@ -1882,7 +2211,12 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) /* 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] == '.') { + 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; @@ -1926,7 +2260,7 @@ basic_block_t *handle_if_statement(block_t *parent, basic_block_t *bb) bb = n; lex_expect(T_open_bracket); - read_expr(parent, &bb); + read_control_expression(parent, &bb); lex_expect(T_close_bracket); var_t *vd = opstack_pop(); @@ -1979,7 +2313,7 @@ basic_block_t *handle_while_statement(block_t *parent, basic_block_t *bb) basic_block_t *cond = bb; lex_expect(T_open_bracket); - read_expr(parent, &bb); + read_control_expression(parent, &bb); lex_expect(T_close_bracket); var_t *vd = opstack_pop(); @@ -2108,6 +2442,7 @@ void parse_array_init(var_t *var, } lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); if (!lex_peek(T_close_curly, NULL)) { for (;;) { var_t *val = NULL; @@ -2407,6 +2742,8 @@ void parse_array_compound_literal(var_t *var, 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 @@ -2552,6 +2889,10 @@ var_t *scalarize_array_literal(block_t *parent, 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. */ @@ -2609,6 +2950,7 @@ var_t *scalarize_array_literal_if_needed(block_t *parent, int eval_expression_imm(opcode_t op, int op1, int op2); int read_const_expr(block_t *scope); +static int read_const_wstring_size(void); /* Integer constant expressions may contain sizeof(type-name). This parser only * needs type metadata, so keep it separate from expression lowering and avoid @@ -2711,8 +3053,11 @@ int read_const_expr_operand(block_t *scope) return ~read_const_expr_operand(scope); if (lex_accept(T_log_not)) return !read_const_expr_operand(scope); - if (lex_accept(T_sizeof)) + if (lex_accept(T_sizeof)) { + if (lex_peek(T_wstring, NULL)) + return read_const_wstring_size(); return read_const_sizeof_type(scope); + } if (lex_accept(T_open_bracket)) { int res = read_const_expr(scope); @@ -2723,14 +3068,52 @@ int read_const_expr_operand(block_t *scope) lex_expect(T_numeric); return parse_numeric_constant(buffer); } - if (lex_peek(T_char, buffer)) { + if (lex_peek(T_char, buffer) || lex_peek(T_wchar, buffer)) { char unescaped[MAX_TOKEN_LEN]; - lex_expect(T_char); + 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 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); + if (lex_accept(T_struct) || lex_accept(T_union)) { + lex_ident(T_identifier, type_name); + record = find_type(type_name, 2); + } 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; + 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) @@ -2863,10 +3246,33 @@ void read_inner_var_decl(var_t *vd, * 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; + 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 " @@ -2876,6 +3282,9 @@ void read_inner_var_decl(var_t *vd, } 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) @@ -2890,6 +3299,10 @@ void read_inner_var_decl(var_t *vd, 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); @@ -2913,7 +3326,8 @@ void read_inner_var_decl(var_t *vd, } } } - if (!lex_peek(T_open_square, NULL)) { + 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; @@ -2930,10 +3344,36 @@ void read_inner_var_decl(var_t *vd, int dims = 0; for (int dim = 0; lex_accept(T_open_square); dim++) { + 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]". @@ -2948,6 +3388,10 @@ void read_inner_var_decl(var_t *vd, } 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 { @@ -2963,6 +3407,10 @@ void read_inner_var_decl(var_t *vd, 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++; } @@ -3171,6 +3619,12 @@ void read_full_var_decl(var_t *vd, vd->type = type; vd->is_const_qualified = type->is_const_qualified; + if (type->array_size) { + vd->array_size = type->array_size; + vd->array_dim2 = type->array_dim2; + vd->array_dim3 = type->array_dim3; + vd->array_dim4 = type->array_dim4; + } if (type->ptr_level && type->ptr_level <= 32) vd->is_const_pointer = type->pointer_const_mask & (1U << (type->ptr_level - 1)); @@ -3185,7 +3639,10 @@ void read_full_var_decl(var_t *vd, is_const = true; else if (lex_accept(T_volatile)) is_volatile = true; - else if (lex_accept(T_inline)) { + else if (lex_accept(T_restrict)) { + if (!type->ptr_level) + error_at("restrict requires a pointer type", cur_token_loc()); + } else if (lex_accept(T_inline)) { if (is_inline || declaration_inline) error_at("duplicate inline function specifier", cur_token_loc()); @@ -3229,10 +3686,15 @@ void read_parameter_list_decl(func_t *func, bool anon) 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; if (lex_peek(T_identifier, token) && !strncmp(token, "void", 4)) { lex_next(); - if (lex_accept(T_close_bracket)) + if (lex_accept(T_close_bracket)) { + func->has_prototype = true; return; + } func->param_defs[vn].type = TY_void; read_inner_var_decl(&func->param_defs[vn], anon, true, false); if (!func->param_defs[vn].ptr_level && !func->param_defs[vn].is_func && @@ -3240,9 +3702,15 @@ void read_parameter_list_decl(func_t *func, bool anon) error_at("'void' must be the only parameter and unnamed", cur_token_loc()); vn++; - lex_accept(T_comma); + if (lex_accept(T_comma) && strict_c99 && + lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in parameter list is not permitted in C99", + cur_token_loc()); } + if (floating_type_starts_here()) + error_at("Floating point types are not yet supported", cur_token_loc()); + while (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) || @@ -3268,14 +3736,22 @@ void read_parameter_list_decl(func_t *func, bool anon) is_record_type(func->param_defs[vn].type) && !func->param_defs[vn].ptr_level; vn++; - lex_accept(T_comma); + if (lex_accept(T_comma) && strict_c99 && + lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in parameter list is not permitted in C99", + cur_token_loc()); } func->num_params = vn; /* Up to 'MAX_PARAMS' parameters are accepted for the variadic function. */ - if (lex_accept(T_elipsis)) + if (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); } @@ -3358,6 +3834,36 @@ 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) +{ + int values[MAX_LINE_LEN]; + int length; + int units; + + length = read_wstring_units(values, MAX_LINE_LEN); + + units = length + 1; + if (var->has_unsized_array) { + var->array_size = units; + var->has_unsized_array = false; + } else if (units > 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++) @@ -3587,10 +4093,9 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) var_t *vd = require_var(parent); vd->var_name = gen_name(); if (is_long_long || value_hi || - (is_decimal && !has_unsigned_suffix && !is_neg && - numeric_literal_needs_wide_path(token)) || (is_decimal && !has_unsigned_suffix && - (value > 0x80000000U || (value == 0x80000000U && !is_neg)))) { + numeric_literal_needs_wide_path(token)) || + (is_decimal && !has_unsigned_suffix && value > 0x80000000U)) { if (PTR_SIZE < 8) error_at("long long literal needs 64-bit target lowering", cur_token_loc()); @@ -3603,9 +4108,9 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) else if (long_suffix_count) vd->type = TY_long; - /* Keep the exact 2^31 magnitude as an int bit pattern: C spells INT_MIN as - * unary minus plus that token, and the unary operator is parsed after the - * literal. Larger decimal values really need long long here. + /* 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; @@ -3633,6 +4138,39 @@ void read_char_param(block_t *parent, basic_block_t *bb) 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_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_LINE_LEN]; + int length = read_wstring_units(values, MAX_LINE_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); +} + 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) { @@ -3641,8 +4179,10 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) lex_expect(T_open_bracket); while (!lex_accept(T_close_bracket)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } param = opstack_pop(); @@ -3664,7 +4204,8 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) "argument", cur_token_loc()); diagnose_const_pointer_conversion(param, target); - if (is_record_type(target->type) && !target->ptr_level) { + if (is_record_type(target->type) && + !has_effective_pointer(target)) { /* 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 @@ -3682,7 +4223,7 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) param->var_name = gen_name(); add_insn(parent, *bb, OP_address_of, param, copy, NULL, 0, NULL); - } else if (!target->ptr_level && !target->array_size) { + } else if (!has_effective_pointer(target) && !target->array_size) { param = scalarize_array_literal(parent, bb, param, target->type); } @@ -3696,7 +4237,19 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) * 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)) + 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 = 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 @@ -3712,9 +4265,20 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) } params[param_num++] = param; - lex_accept(T_comma); + if (lex_accept(T_comma) && strict_c99 && + lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in function call is not permitted in C99", + 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()); + 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. @@ -3733,28 +4297,547 @@ void read_func_call(func_t *func, block_t *parent, basic_block_t **bb) func->return_def.var_name); } -/* Function-pointer prototypes are stored as opaque data in var_t so defs.h - * remains parseable before func_t is complete. +/* The freestanding maps offsetof(type, member-designator) here. Keep + * it unevaluated: no null pointer or temporary object is materialized. */ -func_t *get_func_signature(var_t *var) +void read_builtin_offsetof(block_t *parent, basic_block_t **bb) { - if (!var) - return NULL; - return var->func_signature; + 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); + if (lex_accept(T_struct) || lex_accept(T_union)) { + lex_ident(T_identifier, name); + record = find_type(name, 2); + } 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; + 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); } -/* 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. +/* 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. */ -var_t *load_function_pointer_object(block_t *parent, - basic_block_t **bb, - var_t *object) -{ - var_t *address = require_ref_var(parent, object->type, object->ptr_level); - var_t *target = require_typed_ptr_var(parent, object->type, 1); +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; +} va_arg_type_t; + +void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) +{ + char name[MAX_ID_LEN]; + type_t *type; + bool is_signed = false; + bool is_unsigned = false; + int long_count = 0; + bool is_short = false; + bool is_char = false; + bool saw_base = false; + + (void) parent; + 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; + while (true) { + if (lex_accept(T_const) || lex_accept(T_volatile)) + continue; + if (lex_accept(T_signed)) { + if (is_signed) + error_at("duplicate signed type specifier", cur_token_loc()); + is_signed = true; + continue; + } + if (lex_accept(T_unsigned)) { + if (is_unsigned) + error_at("duplicate unsigned type specifier", cur_token_loc()); + is_unsigned = true; + continue; + } + if (lex_accept(T_long)) { + long_count++; + continue; + } + if (lex_peek(T_identifier, name) && !strcmp(name, "short")) { + if (is_short) + error_at("duplicate short type specifier", cur_token_loc()); + lex_expect(T_identifier); + is_short = true; + continue; + } + if (lex_peek(T_identifier, name) && + (!strcmp(name, "int") || !strcmp(name, "char"))) { + if (saw_base) + error_at("duplicate type specifier", cur_token_loc()); + is_char = !strcmp(name, "char"); + saw_base = true; + lex_expect(T_identifier); + continue; + } + break; + } + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (long_count > 2 || (is_short && long_count)) + error_at("invalid va_arg integer type", cur_token_loc()); + + if (lex_accept(T_struct) || lex_accept(T_union)) { + if (is_signed || is_unsigned || long_count || is_short) + error_at("record type cannot have integer specifiers", + cur_token_loc()); + lex_ident(T_identifier, name); + type = find_type(name, 2); + } else if (lex_accept(T_enum)) { + if (is_signed || is_unsigned || long_count || is_short || saw_base) + error_at("enum type cannot have integer specifiers", + cur_token_loc()); + lex_ident(T_identifier, name); + type = find_type_tag(name, parent); + } else { + if (!saw_base && !is_signed && !is_unsigned && !long_count && + !is_short) { + lex_ident(T_identifier, name); + type = find_type(name, true); + goto parsed_base_type; + } + + if (long_count) + type = is_unsigned ? (long_count == 2 ? TY_ulong_long : TY_ulong) + : (long_count == 2 ? TY_long_long : TY_long); + else if (is_short) + type = is_unsigned ? TY_ushort : TY_short; + else if (is_char) + type = is_unsigned ? TY_uchar : (is_signed ? TY_schar : TY_char); + else + type = is_unsigned ? TY_uint : TY_int; + } +parsed_base_type: + 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()); + + /* The temporary-expression path below can materialize scalar array + * elements directly. A record element needs its complete derived + * descriptor so a subsequent member designator can retain its object + * identity; do not silently use the pointer typedef's ABI width. + */ + if (is_record_type(type)) + error_at( + "va_arg aggregate pointer-to-array type is not yet supported", + cur_token_loc()); + + if (result->ptr_level + type->ptr_level > 1) + error_at("va_arg pointer-element array type is not yet supported", + 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; + } + + /* 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 (is_record_type(type)) + error_at( + "va_arg aggregate pointer-to-array type is not yet " + "supported", + cur_token_loc()); + + if (type->base_type == TYPE_void) + error_at( + "va_arg pointer-to-array cannot have void elements", + cur_token_loc()); + + if (return_ptr_level || 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; + } 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; + 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); + } + } + result->type = type; +} + +void emit_record_copy_from_address(block_t *parent, + basic_block_t **bb, + var_t *dest, + var_t *src_addr) +{ + int size = size_var(dest); + 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); + for (int offset = 0; offset < size;) { + int width = size - offset >= 4 ? 4 : size - offset >= 2 ? 2 : 1; + 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; + } +} + +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(); + + /* 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; + 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(); + + while (lex_accept(T_open_square)) { + var_t *index; + var_t *address; + int element_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, requested.type, 1); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); + depth++; + if (depth == dimension_count + 1) { + var_t *value = require_typed_var(parent, requested.type); + 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)) { + read_indirect_call(result, parent, bb); + result = require_typed_ptr_var( + parent, requested.func_signature->return_def.type, + requested.func_signature->return_def.ptr_level); + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_func_ret, result, NULL, NULL, 0, NULL); + } + } +} + +/* 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) +{ + var_t *address = require_ref_var(parent, object->type, object->ptr_level); + var_t *target = require_typed_ptr_var(parent, object->type, 1); address->var_name = gen_name(); add_insn(parent, *bb, OP_address_of, address, object, NULL, 0, NULL); @@ -3829,7 +4912,7 @@ var_t *read_bitfield_value(block_t *parent, value = bitfield_binary( parent, bb, OP_bit_and, value, bitfield_constant(parent, bb, bitfield_mask(field)), TY_uint); - if (field->type != TY_bool && !field->type->is_unsigned && + if (!is_bool_type(field->type) && !field->type->is_unsigned && field->bit_width != 32) { unsigned sign_bit = 1U << (field->bit_width - 1); @@ -3844,7 +4927,7 @@ var_t *read_bitfield_value(block_t *parent, bitfield_binary(parent, bb, OP_sub, value, bitfield_constant(parent, bb, sign_bit), TY_int); } - if (field->type == TY_bool) + 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 @@ -3869,7 +4952,7 @@ void write_bitfield_value(block_t *parent, var_t *value, const var_t *field) { - if (field->type == TY_bool) + if (is_bool_type(field->type)) value = normalize_bool(parent, bb, value); var_t *old = require_var(parent); old->var_name = gen_name(); @@ -3962,7 +5045,17 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) : lvalue.is_const_qualified; vd->pointer_const_mask = lvalue.pointer_const_mask; opstack_push(vd); - add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); + if (lvalue.decl && is_array_declarator(lvalue.decl)) { + /* 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. + */ + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + } else + add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); } } @@ -4018,6 +5111,26 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + /* A prefix update is itself a unary expression, so `*++pointer` and + * `*--pointer` dereference its updated pointer result just like + * `*(pointer + 1)` dereferences a parenthesized operand. + */ + read_expr_operand(parent, bb); + rs1 = opstack_pop(); + 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))); + 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]; @@ -4243,6 +5356,13 @@ int read_sizeof_string_literal(void) return size; } +int read_sizeof_wstring_literal(void) +{ + int values[MAX_LINE_LEN]; + type_t *wide_type = find_type("wchar_t", true); + return (read_wstring_units(values, MAX_LINE_LEN) + 1) * wide_type->size; +} + int sizeof_array_object(const var_t *array) { int element_size = array->type->size; @@ -4310,6 +5430,16 @@ bool read_sizeof_member_array(block_t *parent, var_t *object; var_t *field = NULL; int object_ptr_level; + int nested_parentheses = 0; + + /* A parenthesized member-array expression remains an array lvalue for + * sizeof. Peel only enclosing parentheses here; the ordinary member path + * below still owns the dot/arrow grammar and rejects other expressions. + */ + while (object_token && object_token->kind == T_open_bracket) { + nested_parentheses++; + object_token = object_token->next; + } if (!object_token || object_token->kind != T_identifier || !object_token->next || @@ -4338,11 +5468,25 @@ bool read_sizeof_member_array(block_t *parent, return false; member_token = tail->next->next; } + if (field->is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + cur_token_loc()); if (field->array_size <= 0 || (tail->next && tail->next->kind == T_open_square) || (parenthesized && (!tail->next || tail->next->kind != T_close_bracket))) return false; + token_t *closing = tail->next; + for (int i = 0; i < nested_parentheses; i++) { + if (!closing || closing->kind != T_close_bracket) + return false; + closing = closing->next; + } + if (parenthesized && (!closing || closing->kind != T_close_bracket)) + return false; + + for (int i = 0; i < nested_parentheses; i++) + lex_expect(T_open_bracket); lex_expect(T_identifier); if (object_token->next->kind == T_arrow) lex_expect(T_arrow); @@ -4353,6 +5497,8 @@ bool read_sizeof_member_array(block_t *parent, lex_expect(T_dot); lex_expect(T_identifier); } + for (int i = 0; i < nested_parentheses; i++) + lex_expect(T_close_bracket); if (parenthesized) lex_expect(T_close_bracket); @@ -4507,6 +5653,9 @@ bool read_sizeof_dereference_member_array(block_t *parent, return false; member_token = tail->next->next; } + if (field->is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + cur_token_loc()); if (field->array_size <= 0 || (parenthesized && !sizeof_open_is_deref && (!tail->next || tail->next->kind != T_close_bracket))) @@ -4620,6 +5769,9 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) bool parenthesized = lex_accept(T_open_bracket); + if (parenthesized && floating_type_starts_here()) + error_at("Floating point types are not yet supported", cur_token_loc()); + /* 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 is the @@ -4641,6 +5793,22 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) return; } + token_t *after_wstring = cur_token->next; + while (after_wstring && after_wstring->kind == T_wstring) + after_wstring = after_wstring->next; + if (lex_peek(T_wstring, NULL) && + (!parenthesized || + (after_wstring && after_wstring->kind == T_close_bracket))) { + vd = require_var(parent); + vd->init_val = read_sizeof_wstring_literal(); + vd->var_name = gen_name(); + if (parenthesized) + lex_expect(T_close_bracket); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + return; + } + if (read_sizeof_address_dereference_array(parent, bb, parenthesized)) return; if (read_sizeof_member_array(parent, bb, parenthesized)) @@ -4910,8 +6078,12 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) /* sizeof(expression) - parse the expression and get its type */ basic_block_t *unevaluated_bb = bb_create(parent); int saved_side_effects = se_idx; - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); + unevaluated_expression_depth++; + if (!read_assignment_expression(parent, &unevaluated_bb)) { + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + } + unevaluated_expression_depth--; se_idx = saved_side_effects; var_t *expr_var = opstack_pop(); if (is_bitfield(expr_var)) @@ -4945,8 +6117,77 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) void read_expr_operand(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1; + var_t *compound_object = NULL; bool is_neg = false; + 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) { + 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) { + 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); + if (object->compound_literal_bitfield) + write_bitfield_value(parent, bb, + object->compound_literal_address, vd, + object->compound_literal_bitfield); + else + add_insn(parent, *bb, OP_write, NULL, + object->compound_literal_address, vd, + object->ptr_level ? PTR_SIZE : object->type->size, + NULL); + opstack_push(vd); + return; + } + if (!object->is_compound_literal || object->array_size || + is_record_type(object->type)) + 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); + 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(); @@ -4973,8 +6214,16 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (lex_peek(T_string, NULL)) read_literal_param(parent, *bb); + else if (lex_peek(T_wstring, NULL)) + read_wstring_param(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)) read_numeric_param(parent, *bb, is_neg); @@ -5051,11 +6300,20 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) type_t *cast_or_literal_type = NULL; int cast_ptr_level = 0; int cast_array_size = 0; + int cast_array_dim2 = 0; + int cast_array_dim3 = 0; + int cast_array_dim4 = 0; + int cast_array_dims = 0; + bool cast_array_outer_unsized = false; bool cast_const_qualified = false; bool cast_const_pointer = false; unsigned int cast_pointer_const_mask = 0; bool cast_volatile_qualified = false; + 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 typename followed by ) */ token_t *type_start = cur_token; bool has_const_type = false; @@ -5235,23 +6493,60 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) error_at("long long value needs 64-bit target lowering", cur_token_loc()); - /* Check for array brackets: [size] or [] */ + /* 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]`. + */ bool is_array = false; - if (lex_accept(T_open_square)) { - is_array = true; + while (lex_accept(T_open_square)) { + int bound = 0; - /* Preserve a declared bound for compound literals. */ + is_array = true; + if (cast_array_dims >= 4) + error_at( + "Array declarators support at most four dimensions", + cur_token_loc()); if (lex_peek(T_numeric, NULL)) { - char bound[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, bound, MAX_TOKEN_LEN); - cast_array_size = parse_numeric_constant(bound); - if (cast_array_size <= 0) + char bound_token[MAX_TOKEN_LEN]; + lex_ident_n(T_numeric, bound_token, MAX_TOKEN_LEN); + bound = parse_numeric_constant(bound_token); + if (bound <= 0) error_at( "Array compound literal needs a positive bound", next_token_loc()); } + if (!bound && cast_array_dims) + error_at("Only the outer array bound may be omitted", + cur_token_loc()); + if (!cast_array_dims) + cast_array_size = bound; + else { + if (cast_array_size) + cast_array_size *= bound; + else + cast_array_size = bound; + if (cast_array_dims == 1) + cast_array_dim2 = bound; + else if (cast_array_dims == 2) + cast_array_dim3 = bound; + else + cast_array_dim4 = bound; + } + if (!cast_array_dims && !bound) + cast_array_outer_unsized = true; + cast_array_dims++; lex_expect(T_close_square); } + if (!is_array && type->array_size) { + is_array = true; + cast_array_size = type->array_size; + cast_array_dim2 = type->array_dim2; + cast_array_dim3 = type->array_dim3; + cast_array_dim4 = type->array_dim4; + cast_array_dims = 1 + !!cast_array_dim2 + + !!cast_array_dim3 + !!cast_array_dim4; + } /* Check what follows the closing ) */ if (lex_accept(T_close_bracket)) { @@ -5260,6 +6555,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) is_compound_literal = true; cast_or_literal_type = type; cast_ptr_level = ptr_level; + cast_const_qualified = + has_const_type || type->is_const_qualified; /* Store is_array flag in cast_ptr_level if it's an * array @@ -5269,6 +6566,9 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) cast_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; cast_or_literal_type = type; @@ -5323,26 +6623,41 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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; + compound_var->is_const_qualified = cast_const_qualified; + compound_var->is_const_pointer = cast_const_pointer; + compound_var->pointer_const_mask = cast_pointer_const_mask; /* 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; + + /* parse_array_init() consumes its own opening brace. The older + * one-dimensional literal helper expects it already consumed. + */ + if (!is_array_literal || cast_array_dims <= 1) + lex_expect(T_open_curly); + if (!is_array_literal || cast_array_dims <= 1) + reject_empty_initializer_in_strict_c99(); /* Check if this is a pointer compound literal */ if (is_array_literal) { compound_var->array_size = cast_array_size; + compound_var->array_dim2 = cast_array_dim2; + compound_var->array_dim3 = cast_array_dim3; + compound_var->array_dim4 = cast_array_dim4; + compound_var->has_unsized_array = cast_array_outer_unsized; add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); - parse_array_compound_literal(compound_var, parent, bb); + if (cast_array_dims > 1) + parse_array_init(compound_var, parent, bb, true); + else + parse_array_compound_literal(compound_var, parent, bb); if (compound_var->array_size == 0) { compound_var->init_val = 0; @@ -5353,29 +6668,44 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) consumed_close_brace = true; } else if (cast_ptr_level > 0) { /* Pointer compound literal: (int*){&x} */ + var_t *initializer; + compound_var->ptr_level = cast_ptr_level; - /* Parse the pointer value (should be an address) */ + /* 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); - const var_t *ptr_val = opstack_pop(); - - /* For pointer compound literals, store the address */ - compound_var->init_val = ptr_val->init_val; + 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()); } else { /* Empty pointer compound literal: (int*){} */ - compound_var->init_val = 0; /* NULL pointer */ + initializer = + require_typed_var(parent, cast_or_literal_type); + initializer->ptr_level = cast_ptr_level; + initializer->var_name = gen_name(); + initializer->init_val = 0; + add_insn(parent, *bb, OP_load_constant, initializer, NULL, + NULL, 0, NULL); } - /* Generate code for pointer compound literal */ + add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, + NULL); + emit_object_assignment(parent, bb, compound_var, initializer); opstack_push(compound_var); - add_insn(parent, *bb, OP_load_constant, compound_var, NULL, - NULL, 0, NULL); + + /* 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(cast_or_literal_type)) { /* Record compound literals use the same aggregate initializer * path for structs, unions, and their typedef aliases. @@ -5394,6 +6724,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) compound_var, NULL, 0, NULL); parse_struct_field_init(parent, bb, struct_type, compound_addr, true); + compound_object = compound_var; opstack_push(compound_var); } else if (cast_or_literal_type->base_type == TYPE_int || cast_or_literal_type->base_type == TYPE_short || @@ -5403,9 +6734,17 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* 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, cast_or_literal_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); - add_insn(parent, *bb, OP_load_constant, compound_var, NULL, - NULL, 0, NULL); + compound_object = compound_var; } else if (lex_peek(T_numeric, NULL) || lex_peek(T_identifier, NULL) || lex_peek(T_char, NULL)) { @@ -5483,38 +6822,499 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) NULL); } - /* Store first element value for array-to-scalar */ - compound_var->init_val = first_element->init_val; + /* 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(); + 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)) { + 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 < cast_array_dims) { + /* A partially selected row decays to its first element. It + * remains a pointer value, not a scalar read. + */ + opstack_push(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); + if (!is_pointer_operation(compound_op, current, + value)) { + 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; + } + 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); + } + } + } + + /* 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(cast_or_literal_type) && + lex_accept(T_dot)) { + 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 = cast_or_literal_type; + + base_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, base_addr, base, NULL, 0, + NULL); + address = base_addr; + 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()); + record_type = field->type; + } while (true); + 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); + } + if (!is_pointer_operation(compound_op, current, + value)) { + 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 + 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; - /* 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; + 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); + } - /* 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); + /* 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 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(cast_or_literal_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 { - /* Single value: (int){42} - scalar compound literal */ - compound_var = opstack_pop(); - opstack_push(compound_var); + 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); } - - if (!consumed_close_brace) - lex_expect(T_close_curly); } else { /* Regular parenthesized expression */ - read_expr(parent, bb); - read_ternary_operation(parent, bb); + 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. + */ + opstack_pop(); + perform_side_effect(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + } lex_expect(T_close_bracket); /* A parenthesized pointer/string expression is still a postfix @@ -5619,7 +7419,11 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (var && var->is_extern_function_alias) var = NULL; - if (!strcmp(token, "__func__")) { + 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()); @@ -6030,7 +7834,7 @@ bool unsigned_int_operand(const var_t *var) */ void mark_var_mutated(var_t *var) { - if (var) + if (var && !unevaluated_expression_depth) var->is_const = false; } @@ -6524,7 +8328,8 @@ void read_lvalue(lvalue_t *lvalue, * just as for an explicitly spelled `T * const`. */ lvalue->is_const_qualified = - (var->ptr_level || (var->type && var->type->ptr_level)) + (var->ptr_level || (var->type && var->type->ptr_level) || + var->array_size || var->has_unsized_array) ? var->is_const_pointer : var->is_const_qualified; lvalue->pointer_const_mask = var->pointer_const_mask; @@ -6856,6 +8661,8 @@ void read_lvalue(lvalue_t *lvalue, vd->init_val = PTR_SIZE; else if (lvalue->ptr_level) vd->init_val = lvalue->type->size; + else if (lvalue->type && lvalue->type->ptr_level) + vd->init_val = get_pointer_element_size(var); else vd->init_val = 1; opstack_push(vd); @@ -6892,6 +8699,29 @@ void read_lvalue(lvalue_t *lvalue, 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)) { + opcode_t postfix_op = lex_accept(T_increment) ? OP_add : OP_sub; + 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; + } + /* This arm appends three entries, so check for room once before * writing any of them. */ @@ -7139,6 +8969,63 @@ void finalize_logical(opcode_t op, 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)) { + opstack_pop(); + perform_side_effect(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + } +} + +/* 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); + opstack_pop(); + perform_side_effect(parent, bb); + 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)) { + opstack_pop(); + perform_side_effect(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + } +} + void read_ternary_operation(block_t *parent, basic_block_t **bb) { var_t *vd; @@ -7153,12 +9040,12 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) 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); + basic_block_t *then_entry = then_; + basic_block_t *else_entry = else_; /* true branch */ - read_expr(parent, &then_); - bb_connect(*bb, then_, THEN); + 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 */ @@ -7168,8 +9055,14 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) var_t *true_val = opstack_pop(); /* false branch */ - read_expr(parent, &else_); - bb_connect(*bb, else_, ELSE); + 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(); bool true_array = is_array_literal_placeholder(true_val); bool false_array = is_array_literal_placeholder(false_val); @@ -7206,6 +9099,12 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) 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; @@ -7226,11 +9125,15 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) bool read_body_assignment(char *token, block_t *parent, opcode_t prefix_op, - basic_block_t **bb) + basic_block_t **bb, + var_t **assignment_result, + int lvalue_paren_depth) { var_t *var = find_local_var(token, parent), *vd, *rs1, *rs2, *t; if (!var) var = find_global_var(token); + if (assignment_result) + assignment_result[0] = NULL; if (var) { int one = 0; @@ -7240,6 +9143,8 @@ bool read_body_assignment(char *token, /* has memory address that we want to set */ read_lvalue(&lvalue, var, parent, bb, false, OP_generic, true); + while (lvalue_paren_depth-- > 0) + lex_expect(T_close_bracket); size = lvalue.size; if (lvalue.is_const_qualified && lvalue_write_follows(prefix_op)) @@ -7363,6 +9268,23 @@ bool read_body_assignment(char *token, 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 = + read_bitfield_value(parent, bb, t, lvalue.decl); + stored->is_bitfield = false; + 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(); + add_insn(parent, *bb, OP_read, stored, t, NULL, + lvalue.size, NULL); + assignment_result[0] = stored; + } + } } else { if (lvalue.is_reference) { t = opstack_pop(); @@ -7382,17 +9304,22 @@ bool read_body_assignment(char *token, } else t = operand_stack[operand_stack_idx - 1]; - read_expr(parent, bb); + 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); + /* 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(); rhs_val = scalarize_array_literal_if_needed( parent, bb, rhs_val, lvalue.type, - !lvalue.ptr_level && !lvalue.is_reference); + !lvalue.ptr_level && + !(lvalue.type && lvalue.type->ptr_level) && + !lvalue.is_reference); opstack_push(rhs_val); vd = require_var(parent); vd->init_val = increment_size; @@ -7440,10 +9367,27 @@ bool read_body_assignment(char *token, 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 = + read_bitfield_value(parent, bb, t, lvalue.decl); + stored->is_bitfield = false; + assignment_result[0] = stored; + } else { + assignment_result[0] = vd; + } + } } } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } if (lvalue.is_func) { rs2 = opstack_pop(); @@ -7465,47 +9409,449 @@ bool read_body_assignment(char *token, 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; - } + /* 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 (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); + else + add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); + 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 = + read_bitfield_value(parent, bb, rs1, lvalue.decl); + stored->is_bitfield = false; + 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(); + add_insn(parent, *bb, OP_read, stored, rs1, NULL, size, + NULL); + assignment_result[0] = stored; + } + } + } else { + rs1 = opstack_pop(); + vd = opstack_pop(); + 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_const_pointer_conversion(rs1, vd)) { + diagnose_const_pointer_conversion(rs1, vd); + } else { + 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; +} + +bool read_assignment_expression(block_t *parent, basic_block_t **bb) +{ + char token[MAX_ID_LEN]; + var_t *result; + int lvalue_paren_depth = 0; + + if (!assignment_expression_follows()) + return false; + if (compound_literal_starts_expression()) + return false; + + /* 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. + */ + lex_expect(T_asterisk); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + address = opstack_pop(); + 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); - add_insn(parent, *bb, OP_write, NULL, rs1, rs2, PTR_SIZE, NULL); - } else if (lvalue.is_reference) { - rs2 = opstack_pop(); - rs1 = opstack_pop(); - if (is_bitfield(lvalue.decl)) - write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); - else - add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); + /* 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. + */ + 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()); + 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; + } + + int address_depth = effective_pointer_depth(object_address); + unsigned int address_mask = + effective_pointer_const_mask(object_address); + if ((field && field->is_const_qualified) || + address->is_const_qualified || + (address_depth > 1 && address_depth <= 32 && + (address_mask & (1U << (address_depth - 2))))) + 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 { - rs1 = opstack_pop(); - vd = opstack_pop(); - if (is_record_object(vd) && is_record_object(rs1)) { - emit_record_copy(parent, bb, vd, rs1); - } else if (incompatible_character_pointer_conversion(rs1, vd)) { - error_at( - "incompatible character pointer types in assignment", - cur_token_loc()); - } else if (incompatible_const_pointer_conversion(rs1, vd)) { - diagnose_const_pointer_conversion(rs1, vd); - } else { - rs1 = resize_var(parent, bb, rs1, vd); - mark_var_mutated(vd); - add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); - } + 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); + } + if (!is_pointer_operation(compound_op, current, value)) { + 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 + 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 = read_bitfield_value(parent, bb, address, field); + result->is_bitfield = false; + } else { + result = require_typed_var(parent, value_type); + result->ptr_level = value_ptr_level; + result->var_name = gen_name(); + add_insn(parent, *bb, OP_read, result, address, NULL, store_size, + NULL); } + if (result->type == TY_bool && !result->ptr_level) + result = normalize_bool(parent, bb, result); + opstack_push(result); return true; } - return false; + + /* Identifier-rooted lvalues include direct objects, member access, and + * subscripts through the shared lvalue parser. + */ + if (!lex_peek(T_identifier, token)) + return false; + + if (!read_body_assignment(token, 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; +} + +/* Return the extent left after selecting leading array dimensions. The global + * constant evaluator retains dimensions in var_t instead of constructing an + * expression IR, so every sizeof object path uses this one calculation. + */ +static int sizeof_subscripted_array(var_t *object, int subscript_depth) +{ + int res; + + if (!subscript_depth) + return size_var(object); + + res = object->type->size; + if (subscript_depth == 1 && object->array_dim2 > 0) { + res *= object->array_dim2; + if (object->array_dim3 > 0) + res *= object->array_dim3; + if (object->array_dim4 > 0) + res *= object->array_dim4; + } else if (subscript_depth == 2 && object->array_dim3 > 0) { + res *= object->array_dim3; + if (object->array_dim4 > 0) + res *= object->array_dim4; + } else if (subscript_depth == 3 && object->array_dim4 > 0) { + res *= object->array_dim4; + } + return res; +} + +static void validate_global_sizeof_object(var_t *object) +{ + if (object->is_func) + error_at("sizeof(function) is invalid", cur_token_loc()); + if (is_bitfield(object)) + error_at("sizeof cannot be applied to a bit-field", cur_token_loc()); + if (object->is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + cur_token_loc()); +} + +/* Resolve unevaluated record postfixes while preserving an array member's + * declared dimensions for the global sizeof constant evaluator. + */ +static var_t *read_const_object_members(var_t *object) +{ + while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { + bool through_pointer = lex_accept(T_arrow); + char field_name[MAX_ID_LEN]; + var_t *field; + int pointer_depth = effective_pointer_depth(object); + + if ((through_pointer && pointer_depth != 1) || + (!through_pointer && pointer_depth != 0)) + error_at("Invalid record member access", cur_token_loc()); + if (!through_pointer) + lex_expect(T_dot); + lex_ident(T_identifier, field_name); + field = find_member(field_name, object->type); + if (!field) + error_at("Unknown record member", cur_token_loc()); + object = field; + } + return object; +} + +/* Parse the object portion of a constant address expression. sizeof only needs + * its pointer type, but still applies the ordinary addressability constraints. + */ +static var_t *read_const_addressed_object(block_t *scope, int *subscript_depth) +{ + char buffer[MAX_TOKEN_LEN]; + var_t *object; + + lex_expect(T_ampersand); + if (subscript_depth) + *subscript_depth = 0; + lex_ident(T_identifier, buffer); + object = find_var(buffer, scope); + if (!object) + error_at("sizeof address requires a declared object", cur_token_loc()); + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + if (subscript_depth) + *subscript_depth = *subscript_depth + 1; + } + if (is_bitfield(object)) + error_at("Cannot take address of bit-field", cur_token_loc()); + return object; +} + +/* 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); + int saved_side_effects = se_idx; + var_t *result; + + handle_sizeof_operator(scope, &unevaluated_bb); + result = opstack_pop(); + se_idx = saved_side_effects; + return result->init_val; +} + +static int read_const_string_size(void) +{ + char buffer[MAX_TOKEN_LEN]; + char unescaped[MAX_TOKEN_LEN]; + int res = 0; + + do { + int length; + + lex_ident(T_string, buffer); + length = unescape_string(buffer, unescaped, sizeof(unescaped)); + if (length < 0) + error_at("Invalid escape sequence", cur_token_loc()); + res += length; + } while (lex_peek(T_string, buffer)); + return res + 1; +} + +static int read_const_wstring_size(void) +{ + int values[MAX_LINE_LEN]; + type_t *wide_type = find_type("wchar_t", true); + return (read_wstring_units(values, MAX_LINE_LEN) + 1) * wide_type->size; } int read_primary_constant(block_t *scope) @@ -7515,20 +9861,294 @@ int read_primary_constant(block_t *scope) char buffer[MAX_TOKEN_LEN]; if (lex_accept(T_minus)) isneg = 1; - if (lex_accept(T_open_bracket)) { + if (lex_accept(T_sizeof)) { + /* The common scalar expression form needs no IR in a global constant: + * integer and character literals have type int. Type names continue + * through the declaration-only evaluator below. + */ + token_t *inside = + lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; + var_t *nested_array = NULL; + token_t *nested_after = NULL; + int nested_groups = 0; + + if (inside && inside->kind == T_open_bracket) { + token_t *token = inside; + + while (token && token->kind == T_open_bracket) { + nested_groups++; + token = token->next; + } + if (token && token->kind == T_identifier) + nested_array = find_var(token->literal, scope); + nested_after = token ? token->next : NULL; + for (int i = 0; i < nested_groups && nested_after && + nested_after->kind == T_close_bracket; + i++) + nested_after = nested_after->next; + } + if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_string) { + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + res = read_const_string_size(); + lex_expect(T_close_bracket); + lex_expect(T_close_bracket); + } else if (inside && inside->kind == T_wstring) { + res = read_const_wstring_size(); + } else if (nested_array && nested_array->array_size > 0 && + nested_groups > 0 && nested_after && + nested_after->kind == T_close_bracket) { + lex_expect(T_open_bracket); + for (int i = 0; i < nested_groups; i++) + lex_expect(T_open_bracket); + lex_expect(T_identifier); + for (int i = 0; i < nested_groups; i++) + lex_expect(T_close_bracket); + lex_expect(T_close_bracket); + res = size_var(nested_array); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_open_bracket && inside->next->next && + inside->next->next->kind == T_identifier) { + var_t *grouped_object = + find_var(inside->next->next->literal, scope); + + if (grouped_object && !grouped_object->array_size && + !grouped_object->has_unsized_array) + res = read_global_sizeof_expression(scope); + else + res = read_const_sizeof_type(scope); + } else if (inside && + (inside->kind == T_increment || + inside->kind == T_decrement || inside->kind == T_plus || + inside->kind == T_minus || inside->kind == T_bit_not || + inside->kind == T_log_not)) { + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + ((inside->next->kind == T_identifier && + find_type(inside->next->literal, true)) || + inside->next->kind == T_signed || + inside->next->kind == T_unsigned || + inside->next->kind == T_long || + inside->next->kind == T_const || + inside->next->kind == T_volatile || + inside->next->kind == T_struct || + inside->next->kind == T_union || + inside->next->kind == T_enum)) { + /* A cast begins with an extra parenthesis and cannot be evaluated + * as an integer constant when its operand is a declared object. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_identifier && + (find_var(inside->literal, scope) || + find_func(inside->literal)) && + inside->next && inside->next->kind != T_close_bracket && + inside->next->kind != T_dot && + inside->next->kind != T_arrow && + inside->next->kind != T_open_square) { + /* Parenthesized non-postfix operands need type derivation rather + * than constant evaluation: sizeof(global + 1), sizeof(global = 1), + * and calls are all unevaluated but need their expression type. + * Reuse the detached expression path used for local sizeof. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_asterisk && inside->next->next && + inside->next->next->kind == T_identifier && + find_var(inside->next->next->literal, scope)) { + var_t *pointer = find_var(inside->next->next->literal, scope); + var_t dereferenced; + var_t *object; + int subscript_depth = 0; + + if (effective_pointer_depth(pointer) != 1) + error_at("Cannot dereference non-pointer in sizeof", + cur_token_loc()); + dereferenced = *pointer; + dereferenced.type = + pointee_type_from_pointer_typedef(pointer->type); + dereferenced.ptr_level = 0; + object = &dereferenced; + + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_expect(T_identifier); + lex_expect(T_close_bracket); + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_asterisk && inside->next && + inside->next->kind == T_ampersand) { + var_t *object; + int subscript_depth; + + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + object = read_const_addressed_object(scope, &subscript_depth); + lex_expect(T_close_bracket); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_ampersand) { + lex_expect(T_open_bracket); + read_const_addressed_object(scope, NULL); + lex_expect(T_close_bracket); + res = PTR_SIZE; + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_identifier && + find_var(inside->next->literal, scope) && + inside->next->next && + inside->next->next->kind != T_close_bracket && + inside->next->next->kind != T_dot && + inside->next->next->kind != T_arrow && + inside->next->next->kind != T_open_square) { + /* A grouped object followed by an operator is an ordinary + * parenthesized expression, not the array/member postfix form + * handled below. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_identifier && + find_var(inside->next->literal, scope)) { + /* Preserve the array object type through an explicitly grouped + * object expression, e.g. sizeof((record.items)[0]). + */ + var_t *object = find_var(inside->next->literal, scope); + int subscript_depth = 0; + + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + lex_expect(T_identifier); + object = read_const_object_members(object); + lex_expect(T_close_bracket); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_open_bracket) { + lex_expect(T_open_bracket); + read_const_expr(scope); + lex_expect(T_close_bracket); + res = TY_int->size; + } else if (inside && + (inside->kind == T_numeric || inside->kind == T_char)) { + lex_expect(T_open_bracket); + read_const_expr(scope); + lex_expect(T_close_bracket); + res = TY_int->size; + } else if (inside && inside->kind == T_identifier) { + var_t *object = find_var(inside->literal, scope); + constant_t *constant = find_scoped_constant(inside->literal, scope); + + if (object || constant) { + lex_expect(T_open_bracket); + lex_expect(T_identifier); + if (object) { + int subscript_depth = 0; + + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at( + "Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else { + lex_expect(T_close_bracket); + res = TY_int->size; + } + } else { + res = read_const_sizeof_type(scope); + } + } else if (lex_peek(T_numeric, buffer) || lex_peek(T_char, buffer)) { + read_primary_constant(scope); + res = TY_int->size; + } else if (lex_peek(T_string, buffer)) { + res = read_const_string_size(); + } else if (lex_peek(T_wstring, buffer)) { + res = read_const_wstring_size(); + } else if (lex_peek(T_ampersand, NULL)) { + read_const_addressed_object(scope, NULL); + res = PTR_SIZE; + } else if (lex_accept(T_asterisk)) { + var_t *object; + int subscript_depth; + + object = read_const_addressed_object(scope, &subscript_depth); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (lex_peek(T_minus, NULL) || lex_peek(T_plus, NULL) || + lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL)) { + res = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, buffer)) { + var_t *object = find_var(buffer, scope); + constant_t *constant = find_scoped_constant(buffer, scope); + + lex_expect(T_identifier); + if (object) { + int subscript_depth = 0; + + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (constant) { + res = TY_int->size; + } else { + error_at("sizeof requires a type or declared object", + cur_token_loc()); + res = 0; + } + } else { + res = read_const_sizeof_type(scope); + } + } else if (lex_accept(T_open_bracket)) { res = read_primary_constant(scope); 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)) { + } else if (lex_peek(T_char, buffer) || lex_peek(T_wchar, buffer)) { char unescaped[MAX_TOKEN_LEN]; unescape_string(buffer, unescaped, MAX_TOKEN_LEN); res = parse_character_constant(buffer); - lex_expect(T_char); + lex_expect(lex_peek(T_wchar, NULL) ? T_wchar : T_char); } else if (lex_peek(T_identifier, buffer)) { constant_t *con; + if (!strcmp(buffer, "__builtin_offsetof")) { + res = read_const_expr_operand(scope); + if (isneg) + return (-1) * res; + return res; + } lex_expect(T_identifier); con = find_scoped_constant(buffer, scope); if (!con) @@ -7992,11 +10612,17 @@ bool read_global_assignment_var(var_t *var) block_t *parent = GLOBAL_BLOCK; basic_block_t *bb = GLOBAL_FUNC->bbs; - if ((var->array_size > 0 || var->has_unsized_array) && !var->ptr_level && - var->type == TY_char && lex_peek(T_string, NULL)) { + 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; + } /* global initialization must be constant */ { @@ -8122,13 +10748,16 @@ bool read_global_assignment_var(var_t *var) add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); return true; } - if (lex_peek(T_string, NULL)) { + if (lex_peek(T_string, NULL) || lex_peek(T_wstring, 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); + 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); @@ -8293,6 +10922,7 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) var_t *vd; var_t *rs1; var_t *rs2; + switch_case_value_t *seen_cases = NULL; bool is_default = false; @@ -8301,7 +10931,7 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) bb = n; lex_expect(T_open_bracket); - read_expr(parent, &bb); + read_control_expression(parent, &bb); lex_expect(T_close_bracket); /* create exit jump for breaks */ @@ -8338,6 +10968,19 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) fatal("Not a valid case value"); } + if (strict_c99) { + switch_case_value_t *seen; + + for (seen = seen_cases; seen; seen = seen->next) + if (seen->value == case_val) + error_at("duplicate case value in C99 switch", + cur_token_loc()); + seen = arena_alloc(GENERAL_ARENA, sizeof(*seen)); + seen->value = case_val; + seen->next = seen_cases; + seen_cases = seen; + } + vd = require_var(parent); vd->var_name = gen_name(); vd->init_val = case_val; @@ -8433,6 +11076,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) bool is_static = false; bool is_extern = false; bool is_register = false; + bool is_auto = false; bool is_volatile = false; bool for_decl_semicolon_consumed = false; @@ -8448,7 +11092,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) 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_register, NULL) || lex_peek(T_auto, NULL)) { if (lex_accept(T_static)) { if (is_static) error_at("duplicate static storage class specifier", @@ -8464,6 +11108,11 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) 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 { @@ -8471,8 +11120,8 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) is_const = true; } } - if ((is_static && is_register) || (is_static && is_extern) || - (is_register && is_extern)) + 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) || @@ -8528,6 +11177,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) NULL, NULL, 0, NULL); add_symbol(setup, var); if (lex_accept(T_assign)) { + validate_string_array_initializer(var); if (is_static) { if (lex_peek(T_open_curly, NULL) && (var->array_size > 0 || var->has_unsized_array || @@ -8538,10 +11188,15 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) parse_global_record_init(var, GLOBAL_BLOCK); else read_global_assignment_var(var); - } else if (var->has_unsized_array && !var->ptr_level && - var->type == TY_char && - lex_peek(T_string, NULL)) { + } else if ((var->has_unsized_array || + var->array_size > 0) && + is_char_array(var) && lex_peek(T_string, NULL)) { parse_string_array_init(var, blk, &setup); + } else if ((var->has_unsized_array || + var->array_size > 0) && + is_wchar_array(var) && + lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(var, blk, &setup); } else if (lex_peek(T_open_curly, NULL) && (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) { @@ -8574,6 +11229,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) nv, NULL, NULL, 0, NULL); add_symbol(setup, nv); if (lex_accept(T_assign)) { + validate_string_array_initializer(nv); if (is_static) { if (lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || @@ -8584,10 +11240,16 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) parse_global_record_init(nv, GLOBAL_BLOCK); else read_global_assignment_var(nv); - } else if (nv->has_unsized_array && !nv->ptr_level && - nv->type == TY_char && + } else if ((nv->has_unsized_array || + nv->array_size > 0) && + is_char_array(nv) && lex_peek(T_string, NULL)) { parse_string_array_init(nv, blk, &setup); + } else if ((nv->has_unsized_array || + nv->array_size > 0) && + is_wchar_array(nv) && + lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(nv, blk, &setup); } else if (lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || @@ -8604,7 +11266,14 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) } } } else { - read_body_assignment(token, blk, OP_generic, &setup); + do { + read_body_assignment(token, blk, OP_generic, &setup, NULL, 0); + perform_side_effect(blk, setup); + if (!lex_peek(T_comma, NULL)) + break; + lex_expect(T_comma); + lex_peek(T_identifier, token); + } while (true); } if (!for_decl_semicolon_consumed) @@ -8619,7 +11288,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) /* condition - check before the loop */ if (!lex_accept(T_semicolon)) { - read_expr(blk, &cond_); + read_control_expression(blk, &cond_); lex_expect(T_semicolon); } else { /* always true */ @@ -8639,12 +11308,16 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) /* 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_); + do { + prefix_op = OP_generic; + 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_, NULL, 0); + perform_side_effect(blk, inc_); + } while (lex_accept(T_comma)); lex_expect(T_close_bracket); } @@ -8706,7 +11379,7 @@ basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) lex_expect(T_while); lex_expect(T_open_bracket); - read_expr(parent, &cond_); + read_control_expression(parent, &cond_); lex_expect(T_close_bracket); vd = opstack_pop(); @@ -8869,6 +11542,7 @@ basic_block_t *handle_record_statement(block_t *parent, 0, NULL); add_symbol(bb, var); if (lex_accept(T_assign)) { + validate_string_array_initializer(var); if (is_static && lex_peek(T_open_curly, NULL) && (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) { @@ -8897,13 +11571,15 @@ basic_block_t *handle_record_statement(block_t *parent, true); lex_expect(T_close_curly); } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); + if (!read_assignment_expression(parent, &bb)) { + 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); + !has_effective_pointer(var) && var->array_size == 0); emit_object_assignment(parent, &bb, var, rhs); } @@ -8928,6 +11604,7 @@ basic_block_t *handle_record_statement(block_t *parent, NULL, 0, NULL); add_symbol(bb, nv); if (lex_accept(T_assign)) { + validate_string_array_initializer(nv); if (is_static && lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || nv->ptr_level > 0)) { @@ -8961,7 +11638,7 @@ basic_block_t *handle_record_statement(block_t *parent, var_t *rhs = opstack_pop(); rhs = scalarize_array_literal_if_needed( parent, &bb, rhs, nv->type, - !nv->ptr_level && nv->array_size == 0); + !has_effective_pointer(nv) && nv->array_size == 0); emit_object_assignment(parent, &bb, nv, rhs); } @@ -8992,6 +11669,7 @@ basic_block_t *handle_enum_declarators(block_t *parent, add_symbol(bb, var); if (lex_accept(T_assign)) { + validate_string_array_initializer(var); if (is_static) { if (lex_peek(T_open_curly, NULL) && (var->array_size > 0 || var->has_unsized_array || @@ -9001,9 +11679,12 @@ basic_block_t *handle_enum_declarators(block_t *parent, } else { read_global_assignment_var(var); } - } else if (var->has_unsized_array && !var->ptr_level && - var->type == TY_char && lex_peek(T_string, NULL)) { + } 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)) { @@ -9015,7 +11696,7 @@ basic_block_t *handle_enum_declarators(block_t *parent, var_t *rhs = opstack_pop(); rhs = scalarize_array_literal_if_needed( parent, &bb, rhs, var->type, - !var->ptr_level && var->array_size == 0); + !has_effective_pointer(var) && var->array_size == 0); emit_object_assignment(parent, &bb, var, rhs); } } @@ -9030,6 +11711,20 @@ basic_block_t *handle_enum_declarators(block_t *parent, return bb; } +/* Advance an implicitly numbered enumerator without wrapping past C99's + * required int domain. A trailing comma is valid and does not introduce an + * implicit successor. + */ +int next_enum_value(int value) +{ + if (value != INT_MAX) + return value + 1; + if (lex_peek(T_comma, NULL) && cur_token->next->next && + cur_token->next->next->kind != T_close_curly) + error_at("Enumerator value exceeds int range", cur_token_loc()); + return value; +} + /* A block-scope enum definition contributes integer constants to the current * expression parser just as a file-scope definition does. Like a record * definition, it may introduce declarators after the closing brace. @@ -9073,7 +11768,8 @@ basic_block_t *handle_enum_statement(block_t *parent, lex_ident(T_identifier, token); if (lex_accept(T_assign)) val = read_const_expr(parent); - add_scoped_constant(parent, token, val++); + add_scoped_constant(parent, token, val); + val = next_enum_value(val); } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); @@ -9123,11 +11819,12 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) bool is_static = false; bool is_extern = false; bool is_register = false; + bool is_auto = false; bool is_volatile = false; 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_register, NULL) || lex_peek(T_auto, NULL)) { if (lex_accept(T_static)) { if (is_static) error_at("duplicate static storage class specifier", @@ -9143,6 +11840,11 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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 { @@ -9150,10 +11852,13 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) is_const = true; } } - if ((is_static && is_register) || (is_static && is_extern) || - (is_register && is_extern)) + 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()); + 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, is_const, is_static); @@ -9164,11 +11869,6 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) return handle_record_statement(parent, bb, is_const, is_static); } - /* statement with prefix */ - if (!is_const && !is_static && lex_accept(T_increment)) - prefix_op = OP_add; - else if (!is_const && !is_static && 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); bool has_identifier = lex_peek(T_identifier, token); @@ -9280,6 +11980,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 0, NULL); add_symbol(bb, var); if (lex_accept(T_assign)) { + validate_string_array_initializer(var); if (is_static) { if (lex_peek(T_open_curly, NULL) && (var->array_size > 0 || var->has_unsized_array || @@ -9304,9 +12005,12 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } else { read_global_assignment_var(var); } - } else if (var->has_unsized_array && !var->ptr_level && - var->type == TY_char && lex_peek(T_string, NULL)) { + } 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)) { @@ -9328,11 +12032,20 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) true); lex_expect(T_close_curly); } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } var_t *expr_result = opstack_pop(); + 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 && @@ -9377,6 +12090,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) NULL, 0, NULL); add_symbol(bb, nv); if (lex_accept(T_assign)) { + validate_string_array_initializer(nv); if (is_static) { if (lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || @@ -9397,9 +12111,12 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } else { read_global_assignment_var(nv); } - } else if (nv->has_unsized_array && !nv->ptr_level && - nv->type == TY_char && lex_peek(T_string, NULL)) { + } else if ((nv->has_unsized_array || nv->array_size > 0) && + is_char_array(nv) && lex_peek(T_string, NULL)) { parse_string_array_init(nv, parent, &bb); + } else if ((nv->has_unsized_array || nv->array_size > 0) && + is_wchar_array(nv) && lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(nv, parent, &bb); } else if (lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || nv->ptr_level > 0)) { @@ -9421,7 +12138,10 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) struct_addr, true); lex_expect(T_close_curly); } else { - read_expr(parent, &bb); + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } emit_object_assignment(parent, &bb, nv, opstack_pop()); } @@ -9446,6 +12166,27 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *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); + 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); + /* handle pointer dereference expressions like *ptr = value */ if (lex_peek(T_asterisk, NULL)) { if (stmt_starts_assignment()) { @@ -9507,8 +12248,15 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } /* is an assignment? */ - if (read_body_assignment(token, parent, prefix_op, &bb)) { + if (read_body_assignment(token, parent, prefix_op, &bb, NULL, 0)) { perform_side_effect(parent, bb); + while (lex_accept(T_comma)) { + prefix_op = OP_generic; + lex_peek(T_identifier, token); + if (!read_body_assignment(token, parent, prefix_op, &bb, NULL, 0)) + error_at("Expected assignment after comma", next_token_loc()); + perform_side_effect(parent, bb); + } lex_expect(T_semicolon); return bb; } @@ -9590,6 +12338,19 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (lex_accept(T_semicolon)) return 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); + /* struct/union variable declaration */ if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) return handle_record_statement(parent, bb, false, false); @@ -9778,6 +12539,15 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) func->is_static = check_decl && func_tmp.is_static ? true : is_static; 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. + */ + func->num_params = 0; + func->va_args = 0; + func->has_prototype = false; + memset(func->param_defs, 0, sizeof(func->param_defs)); read_parameter_list_decl(func, 0); if (check_decl) { @@ -9788,19 +12558,52 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) func_tmp.return_def.is_const_qualified) error_at("conflicting types for function declaration", next_token_loc()); - 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_decl_type(now->type, before->type) || - now->ptr_level != before->ptr_level || - now->is_const_qualified != before->is_const_qualified) + 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]; + bool now_points_to_value = + now->ptr_level || now->type->ptr_level; + bool before_points_to_value = + before->ptr_level || before->type->ptr_level; + + if (!compatible_decl_type(now->type, before->type) || + now->ptr_level != before->ptr_level || + + /* C99 ignores top-level parameter qualifiers, but a + * qualifier on the object reached through a pointer is part + * of that parameter's pointed-to type. + */ + ((now_points_to_value || before_points_to_value) && + now->is_const_qualified != before->is_const_qualified) || + ((now_points_to_value || before_points_to_value) && + now->is_volatile != before->is_volatile)) + error_at("conflicting types for function declaration", + next_token_loc()); + } + } 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; } } @@ -10221,6 +13024,20 @@ void read_global_statement(void) error_at("static and extern storage classes cannot be combined", cur_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_accept(T_struct)) { int i = 0, size = 0, alignment = 1; bitfield_layout_t bits = {0}; @@ -10453,8 +13270,9 @@ void read_global_statement(void) lex_ident(T_identifier, token); if (lex_accept(T_assign)) val = read_const_expr(block); - add_constant(token, val++); - } while (lex_accept(T_comma)); + add_constant(token, val); + val = next_enum_value(val); + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); if (!lex_peek(T_semicolon, NULL)) { if (read_global_declarator(block, type, is_const, is_static, @@ -10477,8 +13295,9 @@ void read_global_statement(void) lex_ident(T_identifier, token); if (lex_accept(T_assign)) val = read_const_expr(block); - add_constant(token, val++); - } while (lex_accept(T_comma)); + add_constant(token, val); + val = next_enum_value(val); + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); lex_ident(T_identifier, token); set_type_name(type, token); @@ -10822,6 +13641,11 @@ void read_global_statement(void) typedef_const || base->is_const_qualified; type->is_unsigned = base->is_unsigned; 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; /* Handle pointer types in typedef: typedef char *string; */ while (lex_accept(T_asterisk)) { @@ -10841,6 +13665,76 @@ void read_global_statement(void) } lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + + /* 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. + */ + int base_bounds[4]; + int decl_bounds[4]; + int base_dims = 0; + int decl_dims = 0; + if (type->array_size) { + int trailing = 1; + + if (type->array_dim2) + trailing *= type->array_dim2; + if (type->array_dim3) + trailing *= type->array_dim3; + if (type->array_dim4) + trailing *= type->array_dim4; + base_bounds[base_dims++] = type->array_size / trailing; + if (type->array_dim2) + base_bounds[base_dims++] = type->array_dim2; + if (type->array_dim3) + base_bounds[base_dims++] = type->array_dim3; + if (type->array_dim4) + base_bounds[base_dims++] = type->array_dim4; + } + while (lex_accept(T_open_square)) { + int bound; + + if (decl_dims >= 4) + 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_bounds[decl_dims++] = bound; + lex_expect(T_close_square); + } + if (decl_dims) { + int all_bounds[4]; + int all_dims = decl_dims + base_dims; + + if (all_dims > 4) + error_at( + "Array declarators support at most four dimensions", + cur_token_loc()); + for (int i = 0; i < decl_dims; i++) + all_bounds[i] = decl_bounds[i]; + for (int i = 0; i < base_dims; i++) + all_bounds[decl_dims + i] = base_bounds[i]; + type->array_size = all_bounds[0]; + type->array_dim2 = 0; + type->array_dim3 = 0; + type->array_dim4 = 0; + for (int i = 1; i < all_dims; i++) { + type->array_size *= all_bounds[i]; + if (i == 1) + type->array_dim2 = all_bounds[i]; + else if (i == 2) + type->array_dim3 = all_bounds[i]; + else + type->array_dim4 = all_bounds[i]; + } + } lex_expect(T_semicolon); } } else if (lex_peek(T_identifier, NULL) || lex_peek(T_signed, NULL) || @@ -10927,6 +13821,150 @@ void parse_internal(void) 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'). @@ -10934,6 +13972,7 @@ void parse_internal(void) 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 */ diff --git a/src/preprocessor.c b/src/preprocessor.c index b136ae78..2184ae40 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -105,9 +105,10 @@ 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, "stdio.h") || - !strcmp(name, "stdlib.h") || !strcmp(name, "string.h") || - !strcmp(name, "sys/stat.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, @@ -123,6 +124,255 @@ void define_builtin_object_macro(const char *name, 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. @@ -154,6 +404,161 @@ void install_iso646_header(void) 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_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"); + + 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_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 raw * spelling from the immutable source buffer so dotted and slash-separated names * do not need special punctuation reconstruction here. @@ -194,6 +599,26 @@ bool resolve_angle_include(token_t *open, 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); @@ -530,9 +955,284 @@ token_t *pp_get_operator(token_t *tk, opcode_t *op, bool consume) 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; +} pp_integer_t; + +void pp_set_boolean(pp_integer_t *val, int truth) +{ + val->lo = truth != 0; + val->hi = 0; + val->is_unsigned = false; +} + +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; +} + +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); + lhs->lo = product.lo; + lhs->hi = product.hi; + lhs->is_unsigned = product.is_unsigned; +} + +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 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; +} + +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}; + + 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; +} + token_t *pp_read_constant_infix_expr(int precedence, token_t *tk, - int *val, + pp_integer_t *val, bool evaluate); /* Expand one function-like macro invocation in a #if token stream. The normal @@ -584,21 +1284,49 @@ token_t *pp_expand_function_macro_in_constant_expr(token_t *before) return before; } -token_t *pp_read_constant_expr_operand(token_t *tk, int *val, bool evaluate) +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); + 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)) { + 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); - val[0] = parse_character_constant(tk->literal); + int character = parse_character_constant(tk->literal); + + val->lo = character; + val->hi = character < 0 ? ~0U : 0; + val->is_unsigned = false; return tk; } @@ -620,7 +1348,7 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val, bool evaluate) 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); + pp_set_boolean(val, is_macro_defined(tk->literal)); if (parenthesized) tk = pp_lex_expect_token(tk, T_close_bracket, true); } else { @@ -650,7 +1378,7 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val, bool evaluate) return pp_read_constant_expr_operand(tk, val, evaluate); } - val[0] = 0; + pp_set_boolean(val, false); } return tk; @@ -666,10 +1394,11 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val, bool evaluate) token_t *pp_read_constant_infix_expr(int precedence, token_t *tk, - int *val, + pp_integer_t *val, bool evaluate) { - int lhs = 0, rhs = 0; + pp_integer_t lhs = {0}, rhs = {0}; + int comparison; /* Evaluate unary expression first */ opcode_t op; @@ -684,13 +1413,14 @@ token_t *pp_read_constant_infix_expr(int precedence, case OP_add: break; case OP_sub: - lhs = -lhs; + pp_negate(&lhs); break; case OP_bit_not: - lhs = ~lhs; + lhs.lo = ~lhs.lo; + lhs.hi = ~lhs.hi; break; case OP_log_not: - lhs = !lhs; + pp_set_boolean(&lhs, !pp_is_true(&lhs)); break; default: { source_location_t *loc = @@ -715,81 +1445,150 @@ token_t *pp_read_constant_infix_expr(int precedence, tk = pp_lex_next_token(tk, true); if (op == OP_ternary) { - int if_true, if_false; + 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 && 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 && !lhs); - if (evaluate) - lhs = lhs ? if_true : if_false; + &if_false, evaluate && !condition); + 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; + } else + lhs.lo = lhs.hi = 0; + lhs.is_unsigned = result_is_unsigned; continue; } bool rhs_evaluate = evaluate; - if (op == OP_log_and && !lhs) + if (op == OP_log_and && !pp_is_true(&lhs)) rhs_evaluate = false; - if (op == OP_log_or && lhs) + if (op == OP_log_or && pp_is_true(&lhs)) rhs_evaluate = false; tk = pp_read_constant_infix_expr(current_precedence, tk, &rhs, rhs_evaluate); + switch (op) { + 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: + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); + break; + default: + break; + } + if (evaluate) { switch (op) { case OP_add: - lhs += rhs; + pp_add(&lhs, &rhs); + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; case OP_sub: - lhs -= rhs; + pp_subtract(&lhs, &rhs); + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; case OP_mul: - lhs *= rhs; + pp_multiply(&lhs, &rhs); break; case OP_div: - lhs /= rhs; + if (!pp_is_true(&rhs)) + error_at("Division by zero in #if expression", + &tk->location); + pp_divmod(&lhs, &rhs, false); break; case OP_mod: - lhs %= rhs; + 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 &= rhs; + lhs.lo &= rhs.lo; + lhs.hi &= rhs.hi; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; case OP_bit_or: - lhs |= rhs; + lhs.lo |= rhs.lo; + lhs.hi |= rhs.hi; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; case OP_bit_xor: - lhs ^= rhs; + lhs.lo ^= rhs.lo; + lhs.hi ^= rhs.hi; + lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; case OP_lshift: - lhs <<= rhs; + if (rhs.hi || rhs.lo >= 64) + error_at("Shift count out of range in #if expression", + &tk->location); + for (unsigned int i = 0; i < rhs.lo; i++) + pp_shift_left_one(&lhs); break; case OP_rshift: - lhs >>= rhs; + if (rhs.hi || rhs.lo >= 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: - lhs = lhs > rhs; + 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: - lhs = lhs >= rhs; + 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: - lhs = lhs < rhs; + 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: - lhs = lhs <= rhs; + 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: - lhs = lhs == rhs; + pp_set_boolean(&lhs, lhs.lo == rhs.lo && lhs.hi == rhs.hi); break; case OP_neq: - lhs = lhs != rhs; + pp_set_boolean(&lhs, lhs.lo != rhs.lo || lhs.hi != rhs.hi); break; case OP_log_and: - lhs = lhs && rhs; + pp_set_boolean(&lhs, pp_is_true(&lhs) && pp_is_true(&rhs)); break; case OP_log_or: - lhs = lhs || rhs; + pp_set_boolean(&lhs, pp_is_true(&lhs) || pp_is_true(&rhs)); break; default: error_at("Unexpected infix token while evaluating constant", @@ -798,11 +1597,15 @@ token_t *pp_read_constant_infix_expr(int precedence, } } - val[0] = evaluate ? lhs : 0; + if (!evaluate) + lhs.lo = lhs.hi = 0; + val->lo = lhs.lo; + val->hi = lhs.hi; + val->is_unsigned = lhs.is_unsigned; 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, true); /* advance to fully consume constant expression */ @@ -1623,8 +2426,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; } @@ -1660,11 +2461,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; } @@ -1695,11 +2496,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); @@ -1819,7 +2620,9 @@ token_t *pp_strip_layout(token_t *tk) * already irrelevant there, and this covers both source-adjacent and * macro-produced strings without changing -E's token spelling. */ - if (cur != &head && cur->kind == T_string && tk->kind == T_string) { + if (cur != &head && + ((cur->kind == T_string && tk->kind == T_string) || + (cur->kind == T_wstring && tk->kind == T_wstring))) { char combined[MAX_TOKEN_LEN]; int left_len = strlen(cur->literal); int right_len = strlen(tk->literal); @@ -1832,6 +2635,10 @@ token_t *pp_strip_layout(token_t *tk) cur->location.len += tk->location.len; continue; } + if (cur != &head && ((cur->kind == T_string && tk->kind == T_wstring) || + (cur->kind == T_wstring && tk->kind == T_string))) + error_at("Cannot concatenate narrow and wide string literals", + &tk->location); cur->next = tk; cur = tk; } @@ -1943,15 +2750,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: @@ -2080,6 +2894,14 @@ char *token_to_string(token_t *tk, char *dest) return "goto"; case T_const: return "const"; + 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: diff --git a/tests/driver.sh b/tests/driver.sh index 69a68126..1fe3f977 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"]++)) @@ -385,6 +387,91 @@ 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" + 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" @@ -497,7 +584,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(); @@ -573,9 +660,196 @@ try_file 0 '' "$TESTS_DIR/strength-reduce.c" # Category: Basic Literals and Constants begin_category "Literals and Constants" "Testing integer, character, and string literals" +try_compile_error << EOF +int main(void) { return 1.5; } +EOF +try_compile_error << EOF +int main(void) { return 1e3; } +EOF +try_compile_error << EOF +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 +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_compile_error << EOF +int main(void) { return sizeof(double); } +EOF +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 main(void) { return sizeof(long double); } +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 + # just a number expr 0 0 +# 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 + +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 + +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 + +try_ 0 << EOF +typedef wchar_t wide_unit; +int main(void) { + wide_unit values[] = L"x"; + return values[0] != 'x' || values[1] != 0; +} +EOF + +try_ 0 << EOF +enum { wide_count = sizeof L"ab" / sizeof(wchar_t) }; +wchar_t *global_pointer = L"xy"; +int main(void) { + L"statement"; + return wide_count != 3 || global_pointer[1] != 'y'; +} +EOF + +try_ 0 << EOF +int main(void) { + wchar_t values[] = L"\u00e9"; + return sizeof values != 2 * sizeof(wchar_t) || values[0] != 0xe9 || + values[1] != 0; +} +EOF + +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 + +try_compile_error << EOF +int main(void) { char values[] = "a" L"b"; return 0; } +EOF + +try_compile_error << EOF +int main(void) { char values[] = L"x"; return 0; } +EOF + +try_compile_error << EOF +int main(void) { wchar_t values[] = "x"; return 0; } +EOF + +try_compile_error << EOF +int main(void) { wchar_t values[] = L"\U00110000"; return 0; } +EOF + +try_compile_error << EOF +int main(void) { wchar_t values[] = L"\xFFFFFFFF"; return 0; } +EOF + +try_compile_error << EOF +int main(void) { wchar_t values[2][3] = L"ab"; return 0; } +EOF + +try_compile_error << EOF +struct item { wchar_t values[2][3]; }; +int main(void) { struct item value = { L"ab" }; return 0; } +EOF + +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 + +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 + # C99 _Bool conversions store the truth value, not a truncated source byte. try_ 4 << EOF _Bool echo_bool(_Bool value) { return value; } @@ -1012,6 +1286,21 @@ int main(void) { (sizeof(0xffffffff) == 4) + (sizeof(0x100000000) == 8); } +EOF + try_ 2 << 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) { @@ -1864,6 +2153,11 @@ 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 @@ -1871,6 +2165,59 @@ run_items_tests return_tests # Category: Variables and Assignments begin_category "Variables and Assignments" "Testing variable declarations and assignments" +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;" @@ -1912,6 +2259,31 @@ int main(void) { } 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; } @@ -1921,25 +2293,88 @@ int main(void) { } EOF -try_compile_error << EOF -inline int invalid_inline_object; -int main(void) { return 0; } -EOF - try_ 13 << EOF -volatile int global_counter = 11, global_limit = 1; -int increment_volatile(volatile int *counter) { *counter += 1; return *counter; } +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) { - 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; + int values[3] = { 3, 4, 6 }; + return third(values) + sum_pair(values); } EOF -try_ 1 << EOF -int named_for_func(void) { return __func__[0] == 'n'; } -int main(void) { return named_for_func(); } +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 (*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: @@ -2133,6 +2568,264 @@ 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_ 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 +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 +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 + # 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 @@ -2935,6 +3628,8 @@ 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;" @@ -2992,6 +3687,13 @@ items 14 "int n = 0; for (int i = 0; i < 20;) { i++; if (i < 14) { continue; } 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 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;" # Category: Comments begin_category "Comments" "Testing C-style and C++-style comment parsing" @@ -3081,34 +3783,117 @@ int main(void) { } 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; -} +# 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) { - struct value input = {10}; - return first(input); + int value = 1; + return twice(value = 5) + (value = 6, value + 1); } 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); +try_ 3 << EOF +int main(void) { + int first = 0, second = 0; + return first = second = 3; } EOF - -try_ 120 << 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 + +# 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 + +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; @@ -3268,6 +4053,28 @@ items 7 "int a; a = 3; int *b; b = &a; b[0] = 7; exit(a);" 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 @@ -3484,6 +4291,18 @@ int main() { } 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; @@ -4644,6 +5463,349 @@ EOF 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 +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 +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; @@ -5979,6 +7141,57 @@ int main(void) { } 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; @@ -6384,6 +7597,7 @@ 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 holder { int values[2][3]; }; struct holder value; return sizeof value.values[0];" items 4 "struct holder { int values[2][3]; }; struct holder value; return sizeof(value.values[0][0]);" @@ -6417,10 +7631,16 @@ int main(void) } EOF items 4 "int arr[5]; return sizeof(arr[0]);" +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;" 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 @@ -6489,6 +7709,8 @@ 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; }" # Category: Enumerations begin_category "Enumerations" "Testing enum declarations and usage" @@ -6498,6 +7720,13 @@ try_ 6 << EOF typedef enum { enum1 = 5, enum2 } enum_t; int main() { enum_t v = enum2; return v; } 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 # Block-scope enum definitions supply integer constants to expressions and # accept C99's trailing comma after their final enumerator. @@ -6696,6 +7925,112 @@ int main(void) { ((1 xor 3) == 2) and ((compl 0) < 0); } EOF +try_flags 14 "--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) + + (LLONG_MIN < 0) + (ULLONG_MAX > LLONG_MAX); +} +EOF +try_flags 24 "--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 46 "--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 18 "--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) + + (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 16 "--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_LEAST64_MIN < 0) + (UINT_LEAST64_MAX > INT_LEAST64_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) + + (INT_FAST64_MIN < 0) + (UINT_FAST64_MAX > INT_FAST64_MAX); +} +EOF +try_flags 20 "--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) + + (INT64_C(12) == 12LL) + (UINT64_C(12) == 12ULL) + + (INTMAX_C(12) == 12LL) + (UINTMAX_C(12) == 12ULL) + + (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) + + (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 @@ -6722,6 +8057,56 @@ 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 @@ -6861,6 +8246,17 @@ try_ 16 << EOF 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 + # Character constants are integer constants in a C99 #if expression. try_ 14 << EOF #if 'A' == 65 && '\\n' == 10 @@ -6889,6 +8285,33 @@ try_ 2 << EOF 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 @@ -10712,6 +12135,25 @@ int main() EOF begin_category "Bit-fields" +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 +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; @@ -10768,6 +12210,21 @@ int main(void) { 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) { @@ -10776,6 +12233,26 @@ int main(void) { return old + value.left + value.right; } EOF +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 +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 try_ 2 << EOF struct flags { unsigned int value : 3; }; int main(void) { @@ -10839,6 +12316,11 @@ 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}}; @@ -10871,6 +12353,12 @@ try_compile_error << EOF struct invalid { _Bool value : 2; }; 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 @@ -10878,6 +12366,574 @@ 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 + +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 +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 +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 +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 + +begin_category "C99 Conformance Mode" "Testing --std=c99 extension diagnostics" + +# 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 + +try_compile_error_flag --std=c99 << EOF +int main(void) { return 0b10; } +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 +try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (0) { case 1: return 1; case 1: return 2; } + return 0; +} +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 + +# Ordinary C99 spellings remain accepted in strict mode. +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 +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 +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 +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 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 +try_ 0 << EOF +int variadic(...) { return 0; } +int main(void) { return variadic(); } +EOF +try_ 1 << EOF +int identity(int value) { return value; } +int main(void) { return identity(1,); } +EOF + +begin_category "C99 assert.h" "Testing freestanding assertion semantics" + +try_ 0 << EOF +#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 failed: 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 failed: 2 + 2 == 5" << EOF +#include +int main(void) { assert(2 + 2 == 5); return 0; } +EOF +try_failure_output "Assertion failed: SUM(1, 1) == 3" << EOF +#include +#define SUM(a, b) a + b +int main(void) { assert(SUM(1, 1) == 3); return 0; } +EOF + +begin_category "C99 stdarg.h" "Testing variadic argument macros" + +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 + +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 + +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 after_wide(long long 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 || + after_wide(0, 4) != 4; +} +EOF + +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 + +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 + +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 + +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 + +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 + +try_ 0 << EOF +#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 + +try_ 0 << EOF +#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(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 + +try_compile_error << EOF +#include +struct item { int first; int second; }; +typedef struct item rows[2]; +typedef rows *rows_pointer; +int unsupported_aggregate_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, rows_pointer)[0][1].second; +} +EOF + +try_compile_error << EOF +#include +struct item { int value; }; +int unsupported_direct_aggregate_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, struct item (*)[2])[0][1].value; +} +EOF + +try_compile_error << EOF +#include +int unsupported_pointer_element_array(int count, ...) { + va_list ap; + va_start(ap, count); + return *va_arg(ap, int * (*)[2])[0][1]; +} +EOF + +try_compile_error << EOF +#include +int unsupported_void_pointer_to_array(int count, ...) { + va_list ap; + va_start(ap, count); + return va_arg(ap, void (*)[2]) != 0; +} +EOF + +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 + +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 + +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 + +try_compile_error << EOF +#include +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, void); + return 0; +} +EOF + +try_compile_error << EOF +#include +struct incomplete; +int bad(int count, ...) { + va_list ap; + va_start(ap, count); + va_arg(ap, struct incomplete); + return 0; +} +EOF + +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(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 From dd0be372b0434a4d897e63e6c76adb1b20acdffe Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 12 Sep 2026 20:30:12 +0800 Subject: [PATCH 06/40] Support function designators and array pointers Function designators decay in call arguments, value contexts and scalar conversions, and survive dereference and grouping. Function pointer typedefs, unnamed and abstract prototype parameters, unprototyped redeclaration promotions and internal aggregate returns are supported, and functions returning typedef arrays are rejected. Pointer-to-array declarators, pointer-array va_arg type names and void pointer decay depth are handled, and sizeof parses abstract array, function pointer and callback array type names and keeps grouped rows. Labels are statements, default may appear anywhere in a switch, case labels take constant expressions, enum constants evaluate at their C width, and out-of-range enumerators are diagnosed. Pointer-valued record members dereference, incomplete records cannot define objects, external inline linkage is constrained, postfix operators apply to array call results, global compound arrays lower their addresses, and UCN wide character constants decode. --- COMPLIANCE.md | 12 +- lib/c.c | 7 +- src/defs.h | 67 +- src/globals.c | 3 + src/parser.c | 3891 +++++++++++++++++++++++++++++++++++++------- src/preprocessor.c | 155 +- src/reg-alloc.c | 63 +- src/ssa.c | 15 +- tests/driver.sh | 1929 +++++++++++++++++++++- 9 files changed, 5519 insertions(+), 623 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 7632c2de..b7c75ef1 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 @@ -75,10 +78,10 @@ This document tracks compliance gaps and non-standard behaviors. | Feature | Status | Current Behavior | |---------|--------|-----------------| | Integer suffixes (`u`, `l`, `ll`) | Partial | Common suffix spellings and x64 wide literals are parsed; full candidate-type selection remains incomplete. | -| Wide characters (`L'c'`) | Not supported | Single-byte only | -| Wide strings (`L"..."`) | Not supported | Single-byte only | +| 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 and identifiers are validated and decoded to UTF-8; wide literals remain unsupported. | +| 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 before narrowing. | ### Preprocessor Gaps @@ -98,6 +101,7 @@ This document tracks compliance gaps and non-standard behaviors. |---------|--------|-------------| | 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 | +| `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 | diff --git a/lib/c.c b/lib/c.c index d2653662..d97676ca 100644 --- a/lib/c.c +++ b/lib/c.c @@ -242,10 +242,11 @@ void __str_base10(char *pb, int val) 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. + * pointers and registers are 64-bit. Form it from int-range literals so + * 32-bit targets need no long-long lowering, and the digit loop never walks + * its stack buffer backwards indefinitely. */ - if (val == -2147483648) { + if (val == (-2147483647 - 1)) { strncpy(pb + INT_BUF_LEN - 11, "-2147483648", 11); return; } diff --git a/src/defs.h b/src/defs.h index 3ab6fe72..154832e4 100644 --- a/src/defs.h +++ b/src/defs.h @@ -445,6 +445,9 @@ typedef enum { TYPE_short, TYPE_long, TYPE_long_long, + TYPE_float, + TYPE_double, + TYPE_long_double, TYPE_struct, TYPE_union, TYPE_typedef @@ -591,7 +594,13 @@ struct var { */ bool is_extern_function_alias; 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 */ @@ -643,7 +652,21 @@ struct var { 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 */ - int offset; /* offset from stack or frame, index 0 is reserved */ + /* 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; + 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. @@ -743,6 +766,16 @@ struct var { */ void *func_signature; + /* When a function pointer was initialized from a visible function + * designator, retain that concrete target for internal-ABI validation. + */ + void *func_target; + + /* Once an assignment may have supplied an unknown/external target, parser + * order cannot prove provenance across later control-flow joins. + */ + bool func_target_invalid; + /* 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 @@ -768,6 +801,9 @@ struct block { }; typedef struct block block_t; + +int read_const_sizeof_type(block_t *scope); +int read_const_wstring_size(void); typedef struct basic_block basic_block_t; /* Definition of a growable buffer for a mutable null-terminated string @@ -862,6 +898,11 @@ 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; + /* 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. @@ -871,6 +912,11 @@ struct type { 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; + /* 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. @@ -886,6 +932,12 @@ struct type { */ 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. */ @@ -1130,6 +1182,13 @@ 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; @@ -1139,6 +1198,10 @@ struct func { */ bool has_prototype; 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 @@ -1171,6 +1234,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/globals.c b/src/globals.c index ee993b80..56798b16 100644 --- a/src/globals.c +++ b/src/globals.c @@ -48,6 +48,9 @@ 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 */ diff --git a/src/parser.c b/src/parser.c index 958b5f74..df171c96 100644 --- a/src/parser.c +++ b/src/parser.c @@ -41,6 +41,11 @@ int operand_stack_idx = 0; */ 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; + /* Forward declarations */ source_location_t *cur_token_loc(void); source_location_t *next_token_loc(void); @@ -48,6 +53,7 @@ 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); bool read_assignment_expression(block_t *parent, basic_block_t **bb); +bool is_null_pointer_constant(var_t *value); 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); @@ -83,6 +89,15 @@ 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); /* 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 @@ -105,6 +120,22 @@ bool floating_type_starts_here(void) cur_token->next->next->kind == T_double); } +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; @@ -112,8 +143,110 @@ bool global_compound_literal_starts_here(void) if (!lex_peek(T_open_bracket, NULL)) return false; next = cur_token->next->next; - return next && (next->kind == T_identifier || next->kind == T_struct || - next->kind == T_union); + return next && + ((next->kind == T_identifier && find_type(next->literal, true)) || + next->kind == T_struct || next->kind == T_union); +} + +/* 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) @@ -271,6 +404,23 @@ int effective_pointer_depth(const var_t *var) return var && var->type ? var->ptr_level + var->type->ptr_level : 0; } +/* 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) +{ + 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; +} + unsigned int effective_pointer_const_mask(const var_t *var) { if (!var || !var->type) @@ -320,6 +470,12 @@ type_t *pointee_type_from_pointer_typedef(type_t *type) 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; } @@ -328,6 +484,20 @@ type_t *pointee_type_from_pointer_typedef(type_t *type) /* 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); +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); + bool compatible_decl_type(const type_t *left, const type_t *right) { if (left == right) @@ -338,6 +508,12 @@ bool compatible_decl_type(const type_t *left, const type_t *right) 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 && @@ -346,6 +522,88 @@ bool compatible_decl_type(const type_t *left, const type_t *right) return true; } +/* 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. + */ +bool compatible_function_param_decl(const var_t *left, const var_t *right) +{ + bool left_points_to_value; + bool right_points_to_value; + + if (!left || !right || left->is_func != right->is_func || + !compatible_decl_type(left->type, right->type) || + left->ptr_level != right->ptr_level) + return false; + + if (left->is_func) { + if (!left->func_signature || !right->func_signature) + return false; + return compatible_function_signature(left->func_signature, + right->func_signature); + } + + left_points_to_value = left->ptr_level || left->type->ptr_level; + right_points_to_value = right->ptr_level || right->type->ptr_level; + + /* C99 ignores top-level parameter qualifiers, but qualifiers on the reached + * object remain part of a pointer parameter's type. + */ + return !((left_points_to_value || right_points_to_value) && + (left->is_const_qualified != right->is_const_qualified || + left->is_volatile != right->is_volatile)); +} + +/* 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) +{ + type_t *type; + + 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; + + switch (type->base_type) { + case TYPE_char: + case TYPE_short: + case TYPE_float: + return true; + default: + return false; + } +} + +bool compatible_function_signature(const func_t *left, const func_t *right) +{ + 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; + + 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; +} + /* 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. @@ -446,6 +704,13 @@ typedef struct switch_case_value { } 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); + +/* 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); @@ -764,6 +1029,32 @@ int decode_wstring_units(const char *text, int *units, int capacity) return out; } +/* A single wide character constant denotes one execution-wide unit. Keep the + * existing implementation-defined packing for multi-character constants, but do + * not route a UCN such as L'\u00e9' through the narrow UTF-8 decoder. + */ +int parse_wide_character_constant(const char *literal) +{ + int units[MAX_TOKEN_LEN]; + int length; + + bool has_ucn = false; + for (int i = 0; literal[i]; i++) + if (literal[i] == '\\' && + (literal[i + 1] == 'u' || literal[i + 1] == 'U')) { + has_ucn = true; + break; + } + if (!has_ucn) + return parse_character_constant(literal); + length = decode_wstring_units(literal, units, MAX_TOKEN_LEN); + if (length < 0) + error_at("Invalid wide character escape sequence", cur_token_loc()); + if (length == 1) + return units[0]; + return parse_character_constant(literal); +} + int read_wstring_units(int *units, int capacity) { char literal[MAX_TOKEN_LEN]; @@ -1250,6 +1541,8 @@ void emit_object_assignment(block_t *parent, var_t *dest, var_t *src) { + func_t *target = src->func_target; + if (is_record_object(dest) && is_record_object(src)) { emit_record_copy(parent, bb, dest, src); } else if (dest->is_func && src->is_func && @@ -1263,6 +1556,9 @@ void emit_object_assignment(block_t *parent, 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); + dest->func_target = src->func_target; + dest->func_target_invalid = + src->func_target_invalid || !target || !target->bbs; } else { src = resize_var(parent, bb, src, dest); add_insn(parent, *bb, OP_assign, dest, src, NULL, 0, NULL); @@ -1272,20 +1568,124 @@ void emit_object_assignment(block_t *parent, var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) { var_t *val = NULL; + 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) { + address_dereference_identifier = + consume_global_function_address_dereference(); + val = require_func_symbol_var(parent); + val->var_name = intern_string(address_dereference_identifier->literal); + val->is_func = true; + return val; + } + explicit_address = lex_accept(T_ampersand); - if (lex_peek(T_ampersand, NULL) || lex_peek(T_identifier, NULL)) { - bool explicit_address = lex_accept(T_ampersand); + if (grouped_global_function_designator_starts_here(false)) { + lex_expect(T_open_bracket); + grouped_function_designator = true; + } + + 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_func(name); 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) { + var_t *object = find_var(name, GLOBAL_BLOCK); + + if (object && object->is_global) { + int array_bounds[4] = {0, 0, 0, 0}; + int array_dims = 0; + int subscript_count = 0; + int element_size = + object->ptr_level ? PTR_SIZE : object->type->size; + + if (object->array_size) { + int inner_size = 1; + + if (object->array_dim2) + inner_size *= object->array_dim2; + if (object->array_dim3) + inner_size *= object->array_dim3; + if (object->array_dim4) + inner_size *= object->array_dim4; + array_bounds[array_dims++] = + object->array_size / inner_size; + if (object->array_dim2) + array_bounds[array_dims++] = object->array_dim2; + if (object->array_dim3) + array_bounds[array_dims++] = object->array_dim3; + if (object->array_dim4) + array_bounds[array_dims++] = object->array_dim4; + } + 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); + while (lex_accept(T_open_square)) { + int index; + int stride = element_size; + + if (!object->array_size || + subscript_count >= array_dims) + error_at( + "Subscripted global address needs an " + "array object", + cur_token_loc()); + for (int dim = subscript_count + 1; dim < array_dims; + dim++) + stride *= array_bounds[dim]; + index = read_const_expr(parent); + lex_expect(T_close_square); + val = compute_element_address(parent, bb, val, index, + stride); + + /* The offset still denotes an address constant. The + * generic helper only constructs arithmetic IR, so + * retain the pointer value metadata required by the + * global-store relocation path. + */ + val->ptr_level = object->ptr_level + 1; + val->is_global_address = true; + subscript_count++; + } + if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { + int index = + read_global_address_offset(parent, parent, *bb); + int stride = element_size; + + /* After a partial multidimensional subscript, the + * address still points at the remaining row/plane. C + * pointer arithmetic advances by that complete + * pointed-to object, not by its scalar leaf. + */ + for (int dim = subscript_count; dim < array_dims; dim++) + stride *= array_bounds[dim]; + val = compute_element_address(parent, bb, val, index, + stride); + val->ptr_level = object->ptr_level + 1; + val->is_global_address = true; + } + return val; + } + } } if (explicit_address) error_at("Expected function after '&' in global initializer", @@ -1306,14 +1706,16 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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)) { + } else if (lex_peek(T_char, NULL) || lex_peek(T_wchar, NULL)) { char chtok[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; - lex_ident(T_char, chtok); + token_kind_t kind = lex_peek(T_wchar, NULL) ? T_wchar : T_char; + lex_ident(kind, chtok); unescape_string(chtok, unescaped, MAX_TOKEN_LEN); val = require_typed_var(parent, TY_int); val->var_name = gen_name(); - val->init_val = parse_character_constant(chtok); + val->init_val = kind == T_wchar ? parse_wide_character_constant(chtok) + : parse_character_constant(chtok); add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); } else if (lex_peek(T_string, NULL)) { /* A character-pointer member has the same constant-expression form as a @@ -1339,8 +1741,8 @@ void consume_global_constant_syntax(void) 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 if (lex_peek(T_char, NULL) || lex_peek(T_wchar, NULL)) { + lex_next(); } else { error_at("Global array initialization requires constant values", next_token_loc()); @@ -1360,19 +1762,22 @@ void parse_struct_field_init(block_t *parent, var_t *target_addr, bool emit_code); -void parse_array_field_row_init(block_t *parent, - basic_block_t **bb, - const var_t *field, - var_t *target_addr, - int start, - bool emit_code) +void parse_array_field_row_values(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code, + bool braced) { int count = 0; int elem_size = (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; - lex_expect(T_open_curly); - reject_empty_initializer_in_strict_c99(); + if (braced) { + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + } while (!lex_peek(T_close_curly, NULL)) { var_t *value = NULL; var_t *elem_addr; @@ -1413,10 +1818,17 @@ void parse_array_field_row_init(block_t *parent, } count++; + if (count == field->array_dim2) { + if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in array initializer", + next_token_loc()); + break; + } if (!lex_accept(T_comma)) break; } - lex_expect(T_close_curly); + if (braced) + lex_expect(T_close_curly); if (emit_code) { var_t *zero = require_var(parent); @@ -1435,15 +1847,27 @@ void parse_array_field_row_init(block_t *parent, } } +void parse_array_field_row_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code) +{ + parse_array_field_row_values(parent, bb, field, target_addr, start, + emit_code, 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_init(block_t *parent, - basic_block_t **bb, - const var_t *field, - var_t *target_addr, - int start, - bool emit_code) +void parse_array_field_plane_values(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code, + bool braced) { var_t row; int rows = 0; @@ -1454,21 +1878,38 @@ void parse_array_field_plane_init(block_t *parent, memcpy(&row, field, sizeof(row)); row.array_dim2 = row_width; row.array_dim3 = 0; - lex_expect(T_open_curly); - reject_empty_initializer_in_strict_c99(); + if (braced) { + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + } while (!lex_peek(T_close_curly, NULL)) { + if (lex_accept(T_open_square)) { + int index = read_const_expr(field->scope ? field->scope : parent); + + lex_expect(T_close_square); + if (index < 0 || index >= field->array_dim2) + error_at("Array designator index is out of bounds", + cur_token_loc()); + rows = index; + lex_expect(T_assign); + } if (rows >= field->array_dim2) error_at("Too many rows in array initializer", next_token_loc()); - if (!lex_peek(T_open_curly, NULL)) - error_at("Three-dimensional array plane requires braced rows", - next_token_loc()); - parse_array_field_row_init(parent, bb, &row, target_addr, - start + rows * row_width, emit_code); + parse_array_field_row_values(parent, bb, &row, target_addr, + start + rows * row_width, emit_code, + lex_peek(T_open_curly, NULL)); rows++; + if (rows == field->array_dim2) { + if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many rows in array initializer", + next_token_loc()); + break; + } if (!lex_accept(T_comma)) break; } - lex_expect(T_close_curly); + if (braced) + lex_expect(T_close_curly); if (emit_code) { var_t *zero = require_var(parent); @@ -1491,6 +1932,17 @@ void parse_array_field_plane_init(block_t *parent, } } +void parse_array_field_plane_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code) +{ + parse_array_field_plane_values(parent, bb, field, target_addr, start, + emit_code, 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. @@ -1516,13 +1968,21 @@ void parse_array_field_hyperplane_init(block_t *parent, lex_expect(T_open_curly); reject_empty_initializer_in_strict_c99(); while (!lex_peek(T_close_curly, NULL)) { + if (lex_accept(T_open_square)) { + int index = read_const_expr(field->scope ? field->scope : parent); + + lex_expect(T_close_square); + if (index < 0 || index >= field->array_dim2) + error_at("Array designator index is out of bounds", + cur_token_loc()); + planes = index; + lex_expect(T_assign); + } if (planes >= field->array_dim2) error_at("Too many planes in array initializer", next_token_loc()); - if (!lex_peek(T_open_curly, NULL)) - error_at("Four-dimensional array slice requires braced planes", - next_token_loc()); - parse_array_field_plane_init(parent, bb, &plane, target_addr, - start + planes * plane_size, emit_code); + parse_array_field_plane_values(parent, bb, &plane, target_addr, + start + planes * plane_size, emit_code, + lex_peek(T_open_curly, NULL)); planes++; if (!lex_accept(T_comma)) break; @@ -2240,13 +2700,33 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) } perform_side_effect(parent, bb); + /* 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); + 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. */ - rs1 = resize_to(parent, &bb, rs1, parent->func->return_def.type, - parent->func->return_def.ptr_level); + 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); @@ -2409,12 +2889,11 @@ void parse_array_init(var_t *var, /* 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. + * first got a bogus address. `ptr_level` describes the array element type + * even when its outer bound is inferred. */ int elem_size = var->type->size; - if (!is_implicit && (var->ptr_level > 0 || var->is_func)) + if (var->ptr_level > 0 || var->is_func) elem_size = PTR_SIZE; if (emit_code) @@ -2601,68 +3080,234 @@ void parse_array_init(var_t *var, * the value left every global array zero-filled, while the same * initializer on a local worked. */ - char initializer_name[MAX_ID_LEN]; - bool function_initializer = - lex_peek(T_identifier, initializer_name) && - find_func(initializer_name); - if (parent == GLOBAL_BLOCK && - initializer_scope != GLOBAL_BLOCK && - !lex_peek(T_string, NULL) && !function_initializer && - !lex_peek(T_ampersand, NULL)) { - /* 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. - */ - 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); + (grouped_global_function_designator_starts_here(true) || + global_function_address_dereference_starts_here())) { + val = parse_global_constant_value(parent, bb); } 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_func(token); - - if (!lex_peek(T_numeric, NULL) && - !lex_peek(T_minus, NULL) && - !lex_peek(T_string, NULL) && - !lex_peek(T_char, NULL) && !enum_constant && - !function_constant && !lex_peek(T_ampersand, NULL)) - error_at( - "Global array initialization requires constant " - "values", - next_token_loc()); - } - - /* `&function` is an address constant, but the ordinary - * expression reader only treats a bare function designator - * as a value. Preserve the symbol through global lowering - * so its final code address can be relocated. - */ - if (parent == GLOBAL_BLOCK && lex_accept(T_ampersand)) { - char function_name[MAX_ID_LEN]; - - if (!lex_peek(T_identifier, function_name) || - !find_func(function_name)) - error_at( - "Global array address initializer requires " - "a declared function", - next_token_loc()); - lex_ident(T_identifier, function_name); - val = require_func_symbol_var(parent); - val->is_func = true; - val->var_name = intern_string(function_name); + char initializer_name[MAX_ID_LEN]; + char global_token[MAX_ID_LEN]; + bool function_initializer = + lex_peek(T_identifier, initializer_name) && + find_func(initializer_name); + var_t *object_constant = NULL; + + if (parent == GLOBAL_BLOCK && + lex_peek(T_identifier, global_token)) + object_constant = + find_var(global_token, initializer_scope); + + if (parent == GLOBAL_BLOCK && + initializer_scope != GLOBAL_BLOCK && + !lex_peek(T_string, NULL) && !function_initializer && + !lex_peek(T_ampersand, NULL)) { + /* 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. + */ + 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 { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - val = opstack_pop(); + 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_func(token); + + 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()); + } + + /* `&function` is an address constant, but the ordinary + * expression reader only treats a bare function + * designator as a value. Preserve the symbol through + * global lowering so its final code address can be + * relocated. + */ + if (parent == GLOBAL_BLOCK && lex_accept(T_ampersand)) { + char function_name[MAX_ID_LEN]; + + if (lex_peek(T_identifier, function_name) && + find_func(function_name)) { + lex_ident(T_identifier, function_name); + val = require_func_symbol_var(parent); + val->is_func = true; + val->var_name = intern_string(function_name); + } else { + int index = 0; + int address_elem_size; + + if (lex_peek(T_identifier, global_token)) + object_constant = find_var( + global_token, initializer_scope); + if (!object_constant || + !object_constant->is_global) + error_at( + "Global array address initializer " + "requires a declared global object " + "or function", + next_token_loc()); + address_elem_size = + object_constant->ptr_level + ? PTR_SIZE + : object_constant->type->size; + if (object_constant->array_dim2) + address_elem_size *= + object_constant->array_dim2; + if (object_constant->array_dim3) + address_elem_size *= + object_constant->array_dim3; + if (object_constant->array_dim4) + address_elem_size *= + object_constant->array_dim4; + + val = require_ref_var( + parent, object_constant->type, + object_constant->ptr_level); + val->var_name = gen_name(); + val->is_global_address = true; + lex_ident(T_identifier, global_token); + add_insn(parent, *bb, OP_address_of, val, + object_constant, NULL, 0, NULL); + if (object_constant->array_size) { + int array_bounds[4] = {0, 0, 0, 0}; + int array_dims = 0; + int subscript_count = 0; + int inner_size = 1; + + if (object_constant->array_dim2) + inner_size *= + object_constant->array_dim2; + if (object_constant->array_dim3) + inner_size *= + object_constant->array_dim3; + if (object_constant->array_dim4) + inner_size *= + object_constant->array_dim4; + array_bounds[array_dims++] = + object_constant->array_size / + inner_size; + if (object_constant->array_dim2) + array_bounds[array_dims++] = + object_constant->array_dim2; + if (object_constant->array_dim3) + array_bounds[array_dims++] = + object_constant->array_dim3; + if (object_constant->array_dim4) + array_bounds[array_dims++] = + object_constant->array_dim4; + while (lex_accept(T_open_square)) { + int stride = + object_constant->ptr_level + ? PTR_SIZE + : object_constant->type->size; + + if (subscript_count >= array_dims) + error_at( + "Subscripted global address " + "needs an array object", + cur_token_loc()); + for (int dim = subscript_count + 1; + dim < array_dims; dim++) + stride *= array_bounds[dim]; + index = + read_const_expr(initializer_scope); + lex_expect(T_close_square); + val = compute_element_address( + parent, bb, val, index, stride); + val->ptr_level = + object_constant->ptr_level + 1; + val->is_global_address = true; + subscript_count++; + } + } else if (lex_accept(T_open_square)) { + error_at( + "Subscripted global address needs an " + "array object", + cur_token_loc()); + } + if (lex_accept(T_plus)) { + index = read_const_expr(initializer_scope); + val = compute_element_address( + parent, bb, val, index, + address_elem_size); + val->ptr_level = + object_constant->ptr_level + 1; + val->is_global_address = true; + } else if (lex_accept(T_minus)) { + index = read_const_expr(initializer_scope); + val = compute_element_address( + parent, bb, val, -index, + address_elem_size); + val->ptr_level = + object_constant->ptr_level + 1; + val->is_global_address = true; + } + } + } else if (parent == GLOBAL_BLOCK && object_constant && + object_constant->is_global && + object_constant->array_size) { + int stride = (object_constant->ptr_level || + object_constant->is_func) + ? PTR_SIZE + : object_constant->type->size; + + /* 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 (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(); + } } } } @@ -2676,9 +3321,12 @@ void parse_array_init(var_t *var, * emit its deferred relocation. Treating an array-of-callback * element as an `int` conversion loses that provenance. */ - var_t *v = val->is_func - ? val - : resize_to(parent, bb, val, var->type, 0); + var_t *v; + + if (val->is_func || var->ptr_level > 0) + v = val; + else + v = resize_to(parent, bb, val, var->type, 0); /* A forward designator leaves a gap. Explicit arrays were * zeroed before parsing; inferred local arrays do not have a @@ -2728,8 +3376,6 @@ void parse_array_init(var_t *var, lex_expect(T_close_curly); if (is_implicit) { - if (var->ptr_level > 0) - var->ptr_level = 0; var->array_size = inferred_size; var->has_unsized_array = false; } @@ -2950,24 +3596,119 @@ var_t *scalarize_array_literal_if_needed(block_t *parent, int eval_expression_imm(opcode_t op, int op1, int op2); int read_const_expr(block_t *scope); -static int read_const_wstring_size(void); +int read_const_wstring_size(void); -/* 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) +void read_sizeof_function_prototype(void) { - char token[MAX_ID_LEN]; - type_t *type = NULL; - int ptr_level = 0; - bool is_unsigned = false; - bool is_signed = false; - int long_count = 0; + func_t *func = arena_alloc_func(); + bool saved_sizeof_signature = parsing_sizeof_function_signature; - lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { + parsing_sizeof_function_signature = true; + read_parameter_list_decl(func, true); + parsing_sizeof_function_signature = saved_sizeof_signature; +} + +/* Consume a nested direct-abstract-declarator in forms such as `int + * (*(*)(int))[2]` and `int (*(*(*)(int))(int))(int)`. The surrounding + * declarator already consumed its leading `(` and pointer chain. Each recursive + * invocation consumes its own parenthesized pointer declarator and function + * suffix, then the enclosing `)` and that declarator's suffix. The complete + * type remains pointer-sized, but its bounds and prototypes must still obey C99 + * syntax and the project's fixed-array-only policy. + */ +int read_sizeof_nested_function_pointer_suffix(block_t *scope, + int *outer_array_size) +{ + int inner_ptr_count = 0; + + lex_expect(T_open_bracket); + while (lex_accept(T_asterisk)) { + inner_ptr_count++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!inner_ptr_count) + error_at("sizeof nested abstract declarator needs a pointer", + cur_token_loc()); + + if (lex_peek(T_open_bracket, NULL)) + inner_ptr_count += + read_sizeof_nested_function_pointer_suffix(scope, outer_array_size); + else { + int direct_array_size = 0; + + while (lex_accept(T_open_square)) { + 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 (bound > 0 && direct_array_size && + direct_array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + direct_array_size = + direct_array_size ? direct_array_size * bound : bound; + } + if (direct_array_size && outer_array_size) + *outer_array_size = direct_array_size; + lex_expect(T_close_bracket); + if (lex_peek(T_open_bracket, NULL)) { + read_sizeof_function_prototype(); + } else if (lex_peek(T_open_square, NULL)) { + do { + int bound; + + lex_expect(T_open_square); + 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()); + } while (lex_peek(T_open_square, NULL)); + } else { + error_at( + "sizeof nested abstract declarator needs a function or " + "array suffix", + cur_token_loc()); + } + } + + lex_expect(T_close_bracket); + while (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + while (lex_accept(T_open_square)) { + 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()); + } + return inner_ptr_count; +} + +/* 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) +{ + char token[MAX_ID_LEN]; + type_t *type = NULL; + int ptr_level = 0; + int array_size = 0; + int array_element_size = 0; + bool is_unsigned = false; + bool is_signed = false; + int long_count = 0; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { lex_ident(T_identifier, token); type = find_type(token, 2); } else if (lex_accept(T_enum)) { @@ -2990,6 +3731,10 @@ int read_const_sizeof_type(block_t *scope) is_signed = true; } else if (lex_accept(T_long)) { long_count++; + } else if (lex_accept(T_float)) { + type = TY_float; + } else if (lex_accept(T_double)) { + type = TY_double; } else if (lex_accept(T_const) || lex_accept(T_volatile) || lex_accept(T_restrict)) { ; @@ -3008,7 +3753,14 @@ int read_const_sizeof_type(block_t *scope) error_at("both signed and unsigned specified", cur_token_loc()); if (long_count > 2) error_at("too many long type specifiers", cur_token_loc()); - if (long_count) { + if (type == TY_float && (is_unsigned || is_signed || long_count)) + error_at("invalid float type specifiers", cur_token_loc()); + if (type == TY_double) { + if (is_unsigned || is_signed || long_count > 1) + error_at("invalid double type specifiers", cur_token_loc()); + if (long_count) + type = TY_long_double; + } else if (long_count) { type = is_unsigned ? TY_ulong : TY_long; if (long_count == 2) type = is_unsigned ? TY_ulong_long : TY_long_long; @@ -3033,9 +3785,101 @@ int read_const_sizeof_type(block_t *scope) lex_accept(T_restrict)) ; } + while (ptr_level == 0 && lex_accept(T_open_square)) { + 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 (bound > 0 && array_size && array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + array_size = array_size ? array_size * bound : bound; + } + if (lex_accept(T_open_bracket)) { + int nested_array_size = 0; + int nested_ptr_count = 0; + int nested_function_ptr_count = 0; + int nested_outer_array_size = 0; + + while (lex_accept(T_asterisk)) { + nested_ptr_count++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!nested_ptr_count) + error_at("sizeof abstract declarator needs a pointer", + cur_token_loc()); + if (lex_peek(T_open_bracket, NULL)) { + nested_function_ptr_count = + read_sizeof_nested_function_pointer_suffix( + scope, &nested_outer_array_size); + if (nested_outer_array_size) { + array_size = nested_outer_array_size; + array_element_size = PTR_SIZE; + } else + ptr_level += nested_ptr_count + nested_function_ptr_count; + } else + while (lex_accept(T_open_square)) { + 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 (bound > 0 && nested_array_size && + nested_array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + nested_array_size = + nested_array_size ? nested_array_size * bound : bound; + } + if (!nested_function_ptr_count) { + lex_expect(T_close_bracket); + if (nested_array_size) { + array_size = nested_array_size; + array_element_size = PTR_SIZE; + + /* `int (*[2][3])(int)` is an array of function pointers: the + * parenthesized declarator owns the array bounds, while the + * following parameter list belongs to each pointed-to function. + * Consume that suffix before the enclosing sizeof parenthesis. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } else { + ptr_level += nested_ptr_count; + while (lex_accept(T_open_square)) { + 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()); + } + + /* A pointer declarator followed by a parameter list names a + * function pointer. Its function type has no object + * representation, but the pointer itself is an object and + * sizeof is pointer-sized. Reuse the declaration parser for + * complete prototype syntax, then discard the otherwise-unused + * signature. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } + } + } lex_expect(T_close_bracket); if (ptr_level) return PTR_SIZE; + if (array_size) + return array_size * + (array_element_size ? array_element_size : type->size); if (type->size) return type->size; return type->base_struct->size; @@ -3045,8 +3889,13 @@ int read_const_expr_operand(block_t *scope) { char buffer[MAX_TOKEN_LEN]; - if (lex_accept(T_minus)) - return -read_const_expr_operand(scope); + 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)) @@ -3074,7 +3923,8 @@ int read_const_expr_operand(block_t *scope) lex_expect(kind); if (unescape_string(buffer, unescaped, MAX_TOKEN_LEN) < 0) error_at("Invalid escape sequence", cur_token_loc()); - return parse_character_constant(buffer); + return kind == T_wchar ? parse_wide_character_constant(buffer) + : parse_character_constant(buffer); } if (lex_peek(T_identifier, buffer)) { if (!strcmp(buffer, "__builtin_offsetof")) { @@ -3149,9 +3999,13 @@ int read_const_expr(block_t *scope) 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; } @@ -3221,23 +4075,47 @@ void read_inner_var_decl(var_t *vd, } } + /* `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) { + vd->pointee_array_size = vd->type->array_size; + vd->pointee_array_dim2 = vd->type->array_dim2; + vd->pointee_array_dim3 = vd->type->array_dim3; + vd->pointee_array_dim4 = vd->type->array_dim4; + vd->array_size = 0; + vd->array_dim2 = 0; + vd->array_dim3 = 0; + vd->array_dim4 = 0; + } + /* is it function pointer declaration? */ if (lex_accept(T_open_bracket)) { func_t *func = arena_alloc_func(); char temp_name[MAX_VAR_LEN]; - lex_expect(T_asterisk); - while (true) { - if (lex_accept(T_const)) - vd->is_const_pointer = true; - 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; - } - lex_ident(T_identifier, temp_name); - vd->var_name = intern_string(temp_name); + int nested_ptr_level = 0; + + do { + lex_expect(T_asterisk); + nested_ptr_level++; + while (true) { + if (lex_accept(T_const)) + vd->is_const_pointer = true; + 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; + } + } 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 @@ -3307,16 +4185,63 @@ void read_inner_var_decl(var_t *vd, } 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; + + vd->ptr_level += nested_ptr_level; + vd->pointee_array_element_ptr_level = + vd->ptr_level - nested_ptr_level; + 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++; + } + 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); vd->func_signature = func; vd->is_func = true; } else { - if (!anon && !lex_peek(T_colon, NULL)) { + /* 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 ((!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); @@ -3443,7 +4368,21 @@ void read_inner_var_decl(var_t *vd, "A struct with a flexible array member cannot be an array " "element", cur_token_loc()); - vd->is_func = false; + + /* 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 || 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 @@ -3551,7 +4490,15 @@ void read_full_var_decl(var_t *vd, * spelling to be omitted, while `signed char` and `signed short` retain * their explicit base type. */ - if (is_enum_type) { + if (parsing_sizeof_function_signature && lex_accept(T_float)) { + if (is_signed || is_unsigned || is_long) + error_at("invalid float type specifiers", cur_token_loc()); + type = TY_float; + } else if (parsing_sizeof_function_signature && lex_accept(T_double)) { + if (is_signed || is_unsigned || is_long_long) + error_at("invalid double type specifiers", cur_token_loc()); + type = is_long ? TY_long_double : TY_double; + } else if (is_enum_type) { lex_ident(T_identifier, type_name); type = find_type_tag(type_name, vd->scope); } else if (is_unsigned) { @@ -3618,6 +4565,8 @@ void read_full_var_decl(var_t *vd, } vd->type = type; + vd->func_signature = type->ptr_level ? NULL : type->func_signature; + vd->is_func = vd->func_signature != NULL; vd->is_const_qualified = type->is_const_qualified; if (type->array_size) { vd->array_size = type->array_size; @@ -3661,6 +4610,17 @@ void read_full_var_decl(var_t *vd, 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()); + /* A 32-bit target can carry a pointer to an eight-byte object without a * paired-value register representation. Keep direct wide objects, arrays, * parameters, returns, and function-pointer returns rejected until that @@ -3708,10 +4668,12 @@ void read_parameter_list_decl(func_t *func, bool anon) cur_token_loc()); } - if (floating_type_starts_here()) + if (floating_type_starts_here() && !parsing_sizeof_function_signature) error_at("Floating point types are not yet supported", cur_token_loc()); - while (lex_peek(T_identifier, NULL) || lex_peek(T_const, NULL) || + while ((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) || @@ -3998,6 +4960,53 @@ bool numeric_mul_add_wide(unsigned int *hi, 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]; @@ -4151,7 +5160,7 @@ void read_wchar_param(block_t *parent, basic_block_t *bb) var_t *vd = require_typed_var(parent, TY_int); vd->var_name = gen_name(); - vd->init_val = parse_character_constant(literal); + 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); @@ -4172,7 +5181,13 @@ void read_wstring_param(block_t *parent, basic_block_t *bb) } 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) +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; @@ -4185,6 +5200,7 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **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 @@ -4279,6 +5295,8 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) : "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. @@ -4288,15 +5306,260 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) } } -void read_func_call(func_t *func, block_t *parent, basic_block_t **bb) +void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) +{ + read_func_parameters_with_sret(func, NULL, parent, bb); +} + +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(func, parent, bb); + 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) +{ + if (!result || !return_def) + return; + result->pointee_array_size = return_def->pointee_array_size + ? return_def->pointee_array_size + : return_def->type->array_size; + result->pointee_array_dim2 = return_def->pointee_array_dim2 + ? return_def->pointee_array_dim2 + : return_def->type->array_dim2; + result->pointee_array_dim3 = return_def->pointee_array_dim3 + ? return_def->pointee_array_dim3 + : return_def->type->array_dim3; + result->pointee_array_dim4 = return_def->pointee_array_dim4 + ? return_def->pointee_array_dim4 + : return_def->type->array_dim4; + result->pointee_array_element_ptr_level = + return_def->pointee_array_element_ptr_level + ? return_def->pointee_array_element_ptr_level + : return_def->type->array_element_ptr_level; + result->is_const_qualified = + return_def->is_const_qualified || return_def->type->is_const_qualified; +} + +void lower_call_result_array_postfix(var_t **value, + block_t *parent, + basic_block_t **bb) +{ + var_t *base; + var_t *address; + int dimensions; + int depth = 0; + + if (!value || !(base = *value) || !base->pointee_array_size || + !lex_peek(T_open_square, NULL)) + return; + + /* One subscript selects the pointed-to array; its outer bound and every + * inner bound then consume their own postfix subscript. + */ + dimensions = 2 + !!base->pointee_array_dim2 + !!base->pointee_array_dim3 + + !!base->pointee_array_dim4; + opstack_pop(); + address = base; + do { + var_t *index; + int stride = + base->pointee_array_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 if (depth == 1 && base->pointee_array_dim2) { + stride *= base->pointee_array_dim2; + if (base->pointee_array_dim3) + stride *= base->pointee_array_dim3; + if (base->pointee_array_dim4) + stride *= base->pointee_array_dim4; + } else if (depth == 2 && base->pointee_array_dim3) { + stride *= base->pointee_array_dim3; + if (base->pointee_array_dim4) + stride *= base->pointee_array_dim4; + } else if (depth == 3 && base->pointee_array_dim4) { + stride *= base->pointee_array_dim4; + } + + 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, base->type); + opcode_t compound_op = OP_generic; + bool assignment; + + element->ptr_level = base->pointee_array_element_ptr_level; + element->func_signature = base->type->func_signature; + element->is_const_qualified = + base->is_const_qualified || base->type->is_const_qualified; + element->var_name = gen_name(); + 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 (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 (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 { + *value = address; + } + 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) +{ + var_t *object; + var_t *one; + var_t *updated; + + if (op == OP_generic) + return; + object = opstack_pop(); + if (!object->is_compound_literal_reference) + 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); + 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); + add_insn(parent, *bb, OP_write, NULL, object->compound_literal_address, + updated, object->ptr_level ? PTR_SIZE : object->type->size, NULL); + *value = updated; + opstack_push(updated); +} + /* The freestanding maps offsetof(type, member-designator) here. Keep * it unevaluated: no null pointer or temporary object is materialized. */ @@ -4361,6 +5624,7 @@ typedef struct va_arg_type { 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) @@ -4384,6 +5648,7 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) result->pointee_array_dim3 = 0; result->pointee_array_dim4 = 0; result->pointee_element_size = 0; + result->pointee_element_ptr_level = 0; while (true) { if (lex_accept(T_const) || lex_accept(T_volatile)) continue; @@ -4480,20 +5745,6 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) error_at("va_arg pointer-to-array cannot have void elements", cur_token_loc()); - /* The temporary-expression path below can materialize scalar array - * elements directly. A record element needs its complete derived - * descriptor so a subsequent member designator can retain its object - * identity; do not silently use the pointer typedef's ABI width. - */ - if (is_record_type(type)) - error_at( - "va_arg aggregate pointer-to-array type is not yet supported", - cur_token_loc()); - - if (result->ptr_level + type->ptr_level > 1) - error_at("va_arg pointer-element array type is not yet supported", - 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. @@ -4511,6 +5762,7 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) */ 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 @@ -4539,18 +5791,12 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) int dims = 0; int total = 0; - if (is_record_type(type)) - error_at( - "va_arg aggregate pointer-to-array type is not yet " - "supported", - cur_token_loc()); - if (type->base_type == TYPE_void) error_at( "va_arg pointer-to-array cannot have void elements", cur_token_loc()); - if (return_ptr_level || nested_ptr != 1) + if (nested_ptr != 1) error_at( "va_arg pointer-element array type is not yet " "supported", @@ -4592,6 +5838,8 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) } 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(); @@ -4602,6 +5850,8 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) */ 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; @@ -4754,7 +6004,9 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) while (lex_accept(T_open_square)) { var_t *index; var_t *address; - int element_size = requested.pointee_element_size + 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; @@ -4788,12 +6040,16 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) NULL); index = scaled; } - address = require_typed_ptr_var(parent, requested.type, 1); + address = require_typed_ptr_var( + parent, requested.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) { - var_t *value = require_typed_var(parent, requested.type); + var_t *value = require_typed_ptr_var( + parent, requested.type, + requested.pointee_element_ptr_level); value->var_name = gen_name(); add_insn(parent, *bb, OP_read, value, address, NULL, element_size, NULL); @@ -4805,13 +6061,8 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) } if (requested.func_signature && lex_peek(T_open_bracket, NULL)) { - read_indirect_call(result, parent, bb); - result = require_typed_ptr_var( - parent, requested.func_signature->return_def.type, - requested.func_signature->return_def.ptr_level); - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_func_ret, result, NULL, NULL, 0, NULL); + result = emit_indirect_call_result(result, requested.func_signature, + true, parent, bb); } } } @@ -4836,18 +6087,80 @@ 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); - var_t *target = require_typed_ptr_var(parent, object->type, 1); + 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; + target->func_target = object->func_target; + target->func_target_invalid = object->func_target_invalid; add_insn(parent, *bb, OP_read, target, address, NULL, PTR_SIZE, NULL); return target; } -void read_indirect_call(var_t *callee, block_t *parent, basic_block_t **bb) +/* 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; + + if (!value || !value->is_func) + 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; + object->func_target = 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 @@ -4858,12 +6171,112 @@ void read_indirect_call(var_t *callee, block_t *parent, basic_block_t **bb) */ var_t *target = opstack_pop(); - func_t *signature = get_func_signature(callee); - read_func_parameters(signature, parent, bb); + 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; +} + +/* 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 = 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; + func_t *target = callee ? callee->func_target : NULL; + + if (callee->func_target_invalid || !target || !target->bbs || + !target->returns_aggregate) + error_at( + "aggregate-return indirect call requires a shecc-defined " + "target", + cur_token_loc()); + 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 = 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); @@ -5017,7 +6430,14 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) * objects and previously made `&function` fail outside file-scope scalar * initializers. */ - if (find_func(token)) { + func_t *addressed_func = find_func(token); + 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; @@ -5109,6 +6529,17 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) 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 (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { @@ -5122,13 +6553,22 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) 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; + 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))); + if (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } else { @@ -5138,11 +6578,51 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) 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))); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + 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_func(token)) { + 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, false); rs1 = opstack_pop(); - if (var->func_signature) { + if (var->is_func) { /* C99 6.3.2.1 makes a function-pointer dereference a function * designator. The pointer object itself therefore needs one load, * not a dereference of its return type. @@ -5150,40 +6630,48 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) opstack_push(load_function_pointer_object(parent, bb, rs1)); return; } - vd = require_deref_var(parent, var->type, var->ptr_level); - if (var->ptr_level + var->type->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; - } + + /* 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 = var->is_const_qualified; + 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->func_signature || + (deref_type && deref_type->func_signature)) && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = rs1->func_signature + ? rs1->func_signature + : deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } @@ -5275,6 +6763,17 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) vd->is_const_pointer = vd->ptr_level > 0 && vd->ptr_level <= 32 && (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + + /* A parenthesized pointer-arithmetic result can designate a + * callback object just like `*slot`. Restore its prototype only + * after consuming exactly that final object-pointer indirection. + */ + if (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } @@ -5291,43 +6790,41 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) /* 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; - } - } + + /* 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 final dereference of `**slots` (or deeper spelling) + * reaches the callback object. Earlier reads still produce a + * pointer-to-callback and must not be callable. + */ + if (i + 1 == deref_count && + (rs1->func_signature || + (deref_type && deref_type->func_signature)) && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = rs1->func_signature + ? rs1->func_signature + : deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); } @@ -5527,6 +7024,17 @@ bool read_sizeof_member_array_row(block_t *parent, int subscript_count = 0; int dimensions; int row_elements; + int nested_parentheses = 0; + + /* sizeof((object.member[index])) preserves the member-array lvalue just + * like the ungrouped spelling. Peel only groups that enclose the complete + * member expression; the trailing closes are consumed after its detached + * subscripts so index side effects remain unevaluated. + */ + while (object_token && object_token->kind == T_open_bracket) { + nested_parentheses++; + object_token = object_token->next; + } if (!object_token || object_token->kind != T_identifier || !object_token->next || @@ -5563,7 +7071,14 @@ bool read_sizeof_member_array_row(block_t *parent, return false; subscript_count++; } - if (!tail || (parenthesized && tail->kind != T_close_bracket)) + if (!tail) + return false; + for (int i = 0; i < nested_parentheses; i++) { + if (!tail || tail->kind != T_close_bracket) + return false; + tail = tail->next; + } + if (parenthesized && (!tail || tail->kind != T_close_bracket)) return false; dimensions = 2; if (field->array_dim3) @@ -5573,6 +7088,8 @@ bool read_sizeof_member_array_row(block_t *parent, if (subscript_count > dimensions) return false; + for (int i = 0; i < nested_parentheses; i++) + lex_expect(T_open_bracket); lex_expect(T_identifier); if (object_token->next->kind == T_arrow) lex_expect(T_arrow); @@ -5589,6 +7106,8 @@ bool read_sizeof_member_array_row(block_t *parent, se_idx = saved_side_effects; lex_expect(T_close_square); } + for (int i = 0; i < nested_parentheses; i++) + lex_expect(T_close_bracket); if (parenthesized) lex_expect(T_close_bracket); @@ -5762,6 +7281,7 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) char token[MAX_ID_LEN]; int ptr_cnt = 0; int array_size = 0; + int array_element_size = 0; token_t *sizeof_tk = cur_token; type_t *type = NULL; bool is_function = false; @@ -5769,9 +7289,6 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) bool parenthesized = lex_accept(T_open_bracket); - if (parenthesized && floating_type_starts_here()) - error_at("Floating point types are not yet supported", cur_token_loc()); - /* 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 is the @@ -5974,7 +7491,21 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (find_type_flag == 1 && lex_accept(T_union)) find_type_flag = 2; - if (has_enum_type) { + /* `sizeof` only consumes object representation metadata, so it can admit + * the C99 floating type names before value arithmetic and ABI lowering + * exist. Keep all expression/declaration admission gates intact. + */ + if (lex_accept(T_float)) { + if (has_signed_type || has_unsigned_type || has_long_type || + has_enum_type || find_type_flag != 1) + error_at("invalid float type specifiers", cur_token_loc()); + type = TY_float; + } else if (lex_accept(T_double)) { + if (has_signed_type || has_unsigned_type || has_enum_type || + find_type_flag != 1 || long_type_count > 1) + error_at("invalid double type specifiers", cur_token_loc()); + type = has_long_type ? TY_long_double : TY_double; + } else if (has_enum_type) { lex_ident(T_identifier, token); type = find_type_tag(token, parent); if (!type) @@ -6023,6 +7554,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = TY_uchar; else if (!strcmp(token, "short")) type = TY_ushort; + else + type = TY_uint; } else type = TY_uint; while (lex_accept(T_asterisk)) { @@ -6074,6 +7607,111 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) } } + /* The integer branches above consume their own abstract pointer declarators + * for historical reasons. Float/double type names select a type before that + * legacy code, so finish the common suffix here. + */ + while (type && lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + + /* VLA support is intentionally outside this compiler's C99 scope, but a + * type name in sizeof may still carry fixed abstract array bounds. Keep the + * simple direct form here rather than treating '[' as an expression token + * after the scalar type name. + */ + while (type && ptr_cnt == 0 && lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + lex_expect(T_close_square); + if (bound <= 0) + error_at("sizeof array type needs a positive constant bound", + cur_token_loc()); + if (bound > 0 && array_size && array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + array_size = array_size ? array_size * bound : bound; + } + + if (type && lex_accept(T_open_bracket)) { + int nested_array_size = 0; + int nested_ptr_count = 0; + int nested_function_ptr_count = 0; + int nested_outer_array_size = 0; + + while (lex_accept(T_asterisk)) { + nested_ptr_count++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!nested_ptr_count) + error_at("sizeof abstract declarator needs a pointer", + cur_token_loc()); + if (lex_peek(T_open_bracket, NULL)) { + nested_function_ptr_count = + read_sizeof_nested_function_pointer_suffix( + parent, &nested_outer_array_size); + if (nested_outer_array_size) { + array_size = nested_outer_array_size; + array_element_size = PTR_SIZE; + } else + ptr_cnt += nested_ptr_count + nested_function_ptr_count; + } else + while (lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + lex_expect(T_close_square); + if (bound <= 0) + error_at( + "sizeof array type needs a positive constant bound", + cur_token_loc()); + if (bound > 0 && nested_array_size && + nested_array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + nested_array_size = + nested_array_size ? nested_array_size * bound : bound; + } + if (!nested_function_ptr_count) { + lex_expect(T_close_bracket); + if (nested_array_size) { + array_size = nested_array_size; + array_element_size = PTR_SIZE; + + /* The grouped array declarator in `int (*[2][3])(int)` leaves + * its pointed-to function suffix outside the group. sizeof only + * needs pointer-sized elements, but still must parse that valid + * C99 prototype completely. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } else { + ptr_cnt += nested_ptr_count; + while (lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + lex_expect(T_close_square); + if (bound <= 0) + error_at( + "sizeof array type needs a positive constant bound", + cur_token_loc()); + } + + /* A pointer declarator followed by a parameter list names a + * function pointer. sizeof observes the pointer object, not the + * function type, so no expression lowering or function ABI is + * needed. Reuse the declaration parser for prototype syntax. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } + } + } + if (!type) { /* sizeof(expression) - parse the expression and get its type */ basic_block_t *unevaluated_bb = bb_create(parent); @@ -6105,9 +7743,15 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) vd = require_var(parent); vd->init_val = type->size; if (array_size > 0) - vd->init_val = array_size * type->size; + vd->init_val = + array_size * (array_element_size ? array_element_size : type->size); if (ptr_cnt) vd->init_val = PTR_SIZE; + + /* VLA is intentionally outside shecc's C99 scope, so every admitted sizeof + * result is an integer constant expression. + */ + vd->is_const = true; vd->var_name = gen_name(); opstack_push(vd); lex_expect(T_close_bracket); @@ -6123,7 +7767,9 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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_open_bracket || + (cur_token->next->next->next && + cur_token->next->next->next->kind == T_open_square))) { opcode_t op = prefix_increment ? OP_add : OP_sub; var_t *object; var_t *one; @@ -6231,6 +7877,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr_operand(parent, bb); rs1 = opstack_pop(); + rs1 = materialize_function_designator(parent, bb, rs1); /* Constant folding for logical NOT */ if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { @@ -6304,7 +7951,13 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) int cast_array_dim3 = 0; int cast_array_dim4 = 0; int cast_array_dims = 0; + int cast_array_element_ptr_level = 0; + int cast_pointee_array_size = 0; + int cast_pointee_array_dim2 = 0; + int cast_pointee_array_dim3 = 0; + int cast_pointee_array_dim4 = 0; bool cast_array_outer_unsized = false; + bool cast_parenthesized_array = false; bool cast_const_qualified = false; bool cast_const_pointer = false; unsigned int cast_pointer_const_mask = 0; @@ -6454,6 +8107,10 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } if (type) { + if (type->is_floating) + error_at("Floating point types are not yet supported", + cur_token_loc()); + /* Save current position to backtrack if needed Try to parse as * typename */ @@ -6493,13 +8150,78 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) error_at("long long value needs 64-bit target lowering", cur_token_loc()); + bool is_array = false; + + /* 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); + cast_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)) { + cast_array_size = read_const_expr(parent); + if (cast_array_size <= 0) + error_at( + "Array compound literal needs a positive bound", + cur_token_loc()); + } else { + cast_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) + cast_pointee_array_size = bound; + else { + if (pointee_dims == 1) + cast_pointee_array_dim2 = bound; + else if (pointee_dims == 2) + cast_pointee_array_dim3 = bound; + else + cast_pointee_array_dim4 = bound; + cast_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; + cast_array_dims = 1; + cast_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]`. */ - bool is_array = false; - while (lex_accept(T_open_square)) { + while (!cast_parenthesized_array && lex_accept(T_open_square)) { int bound = 0; is_array = true; @@ -6590,6 +8312,14 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* Get the expression result */ var_t *expr_var = opstack_pop(); + /* 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 (cast_or_literal_type->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, cast_or_literal_type, cast_ptr_level); @@ -6598,6 +8328,57 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) cast_var->is_const_pointer = cast_const_pointer; cast_var->pointer_const_mask = cast_pointer_const_mask; cast_var->is_volatile = cast_volatile_qualified; + if (cast_or_literal_type->func_signature) { + cast_var->ptr_level = 1; + cast_var->func_signature = cast_or_literal_type->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 (!cast_ptr_level && expr_var->is_const && + !is_pointer_like_value(expr_var) && !expr_var->is_func && + cast_var->type->base_type != TYPE_void) { + unsigned int lo = (unsigned int) expr_var->init_val; + unsigned int hi = (unsigned int) expr_var->init_val_hi; + + cast_var->is_const = true; + + /* 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. + */ + if (cast_var->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. + */ + cast_var->init_val = lo || hi; + cast_var->init_val_hi = 0; + } else if (cast_var->type->size < TY_int->size) { + unsigned int bits = cast_var->type->size * 8; + unsigned int mask = (1U << bits) - 1; + + lo &= mask; + if (!cast_var->type->is_unsigned && + (lo & (1U << (bits - 1)))) + lo |= ~mask; + cast_var->init_val = (int) lo; + cast_var->init_val_hi = cast_var->init_val < 0 ? -1 : 0; + } else if (cast_var->type->size == TY_int->size) { + cast_var->init_val = (int) lo; + cast_var->init_val_hi = + cast_var->type->is_unsigned + ? 0 + : (cast_var->init_val < 0 ? -1 : 0); + } else { + cast_var->init_val = (int) lo; + cast_var->init_val_hi = (int) hi; + } + } /* An explicit C cast is permitted to remove qualifiers, but it is * almost always a bug. Keep compiling it while making that loss @@ -6641,9 +8422,11 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* parse_array_init() consumes its own opening brace. The older * one-dimensional literal helper expects it already consumed. */ - if (!is_array_literal || cast_array_dims <= 1) + if (!is_array_literal || + (cast_array_dims <= 1 && !cast_array_element_ptr_level)) lex_expect(T_open_curly); - if (!is_array_literal || cast_array_dims <= 1) + if (!is_array_literal || + (cast_array_dims <= 1 && !cast_array_element_ptr_level)) reject_empty_initializer_in_strict_c99(); /* Check if this is a pointer compound literal */ if (is_array_literal) { @@ -6652,9 +8435,14 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) compound_var->array_dim3 = cast_array_dim3; compound_var->array_dim4 = cast_array_dim4; compound_var->has_unsized_array = cast_array_outer_unsized; + compound_var->ptr_level = cast_array_element_ptr_level; + compound_var->pointee_array_size = cast_pointee_array_size; + compound_var->pointee_array_dim2 = cast_pointee_array_dim2; + compound_var->pointee_array_dim3 = cast_pointee_array_dim3; + compound_var->pointee_array_dim4 = cast_pointee_array_dim4; add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); - if (cast_array_dims > 1) + if (cast_array_dims > 1 || cast_array_element_ptr_level) parse_array_init(compound_var, parent, bb, true); else parse_array_compound_literal(compound_var, parent, bb); @@ -7317,46 +9105,176 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } lex_expect(T_close_bracket); + bool lowered_call_result_postfix = false; + if (lex_peek(T_open_square, NULL)) { + var_t *grouped = operand_stack[operand_stack_idx - 1]; + + if (grouped->pointee_array_size) { + lower_call_result_array_postfix(&grouped, parent, bb); + lowered_call_result_postfix = true; + } + } + /* A parenthesized pointer/string expression is still a postfix * operand. Unlike identifier lvalues it has no declaration to feed * read_lvalue(), so lower its first subscript directly while * retaining the same explicit element-size scaling. */ - if (lex_accept(T_open_square)) { + if (!lowered_call_result_postfix && lex_accept(T_open_square)) { var_t *base = opstack_pop(); var_t *index; var_t *address; int element_size; - if (!base->ptr_level && !base->type->ptr_level) - error_at("Cannot apply square operator to non-pointer", - cur_token_loc()); - read_expr(parent, bb); - read_ternary_operation(parent, bb); - index = opstack_pop(); - lex_expect(T_close_square); - element_size = base->type->size; - if (element_size != 1) { - var_t *scale = require_var(parent); - scale->var_name = gen_name(); - scale->init_val = element_size; - 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, + 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; + + /* A grouping around an array compound literal preserves its + * array type. Keep lowering each following index against + * the original shape, just as the direct compound-literal + * postfix path does. + */ + address = base; + element_size = base->type->size; + 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, 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, + 0, NULL); + address = indexed; + subscript_depth++; + } while (lex_accept(T_open_square)); + + if (subscript_depth < array_dims) { + opstack_push(address); + } else { + vd = require_typed_var(parent, base->type); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, + element_size, NULL); + } + } else { + if (!base->ptr_level && !base->type->ptr_level) + error_at("Cannot apply square operator to non-pointer", + cur_token_loc()); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + index = opstack_pop(); + lex_expect(T_close_square); + element_size = base->type->size; + if (element_size != 1) { + var_t *scale = require_var(parent); + scale->var_name = gen_name(); + scale->init_val = element_size; + 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, base->type, 1); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); - index = scaled; + vd = require_typed_var(parent, base->type); + vd->ptr_level = + base->ptr_level > 1 ? base->ptr_level - 1 : 0; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, + vd->ptr_level ? PTR_SIZE : element_size, NULL); } - address = require_typed_ptr_var(parent, base->type, 1); + } + + /* Grouping does not stop a postfix member chain. The expression + * result is an aggregate value rather than an identifier lvalue, so + * materialize its address and lower the selected member here. This + * covers the ordinary C99 spellings `(*p).field` and + * `(*record.member).field`. + */ + while (lex_accept(T_dot)) { + char token[MAX_ID_LEN]; + var_t *base = opstack_pop(); + var_t *field; + var_t *base_address; + var_t *address; + var_t *offset; + + if (!base->type || !is_record_type(base->type) || + effective_pointer_depth(base)) + error_at("Cannot apply dot operator to non-record", + cur_token_loc()); + lex_ident(T_identifier, token); + field = find_member(token, base->type); + if (!field) + error_at("Unknown struct or union member", + next_token_loc()); + + base_address = require_typed_ptr_var(parent, base->type, 1); + base_address->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, base_address, base, NULL, + 0, NULL); + 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); + address = require_typed_ptr_var(parent, field->type, 1); address->var_name = gen_name(); - add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); - vd = require_typed_var(parent, base->type); - vd->ptr_level = base->ptr_level > 1 ? base->ptr_level - 1 : 0; + add_insn(parent, *bb, OP_add, address, base_address, offset, 0, + NULL); + + vd = require_typed_var(parent, field->type); + vd->ptr_level = field->ptr_level; + vd->func_signature = field->func_signature; + vd->is_const_qualified = field->is_const_qualified; vd->var_name = gen_name(); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, address, NULL, - vd->ptr_level ? PTR_SIZE : element_size, NULL); + field->ptr_level || field->type->ptr_level + ? PTR_SIZE + : get_size(field), + NULL); } /* Function calls are postfix expressions, so a parenthesized @@ -7373,24 +9291,20 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) opstack_push( load_function_pointer_object(parent, bb, callee)); } - read_indirect_call(callee, parent, bb); + vd = emit_indirect_call_result(callee, signature, true, + parent, bb); } else if (callee->is_func) { signature = find_func(callee->var_name); if (!signature) error_at("Called object is not a function", cur_token_loc()); opstack_pop(); - read_func_call(signature, parent, bb); + vd = emit_direct_call_result(signature, true, parent, bb); } else { error_at("Called object is not a function pointer", cur_token_loc()); } - - vd = require_typed_ptr_var(parent, signature->return_def.type, - signature->return_def.ptr_level); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_func_ret, vd, NULL, NULL, 0, NULL); + lower_call_result_array_postfix(&vd, parent, bb); } } } else if (lex_accept(T_sizeof)) { @@ -7462,10 +9376,31 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } else if (var) { /* evalue lvalue expression */ lvalue_t lvalue; - read_lvalue(&lvalue, var, parent, bb, true, prefix_op, true); + 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, true); /* 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; + + 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 @@ -7476,36 +9411,47 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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; } - read_indirect_call(lvalue.decl, parent, bb); - - func_t *signature = get_func_signature(lvalue.decl); - if (signature) - vd = require_typed_ptr_var(parent, - signature->return_def.type, - signature->return_def.ptr_level); - else - vd = require_var(parent); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_func_ret, vd, NULL, NULL, 0, NULL); + vd = emit_indirect_call_result(callee, signature, true, parent, + bb); + lower_call_result_array_postfix(&vd, 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)) { - 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); + func_t *returned_signature = + func->return_def.type->func_signature; + vd = emit_direct_call_result(func, true, parent, bb); + lower_call_result_array_postfix(&vd, parent, bb); + lower_call_result_prefix_update(&vd, prefix_op, parent, bb); + if (returned_signature && lex_peek(T_open_bracket, NULL)) { + vd = emit_indirect_call_result(vd, returned_signature, true, + parent, bb); + } } else { /* indirective function pointer assignment */ vd = require_func_symbol_var(parent); vd->is_func = true; vd->var_name = intern_string(token); + vd->func_target = func; opstack_push(vd); } } else if (lex_accept(T_open_curly)) { @@ -7600,14 +9546,18 @@ bool lvalue_write_follows(opcode_t prefix_op) 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); + /* An array of pointers decays to a pointer-to-pointer. The declaration - * records its element's indirection level, so account for the decay before - * deriving the pointed-to object size. + * 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->ptr_level) + if ((ptr_var->array_size || ptr_var->has_unsized_array) && pointer_depth) return PTR_SIZE; /* Direct pointer with type info. @@ -7618,10 +9568,15 @@ int get_pointer_element_size(var_t *ptr_var) * 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) + if (pointer_depth) { + if (pointer_depth > 1) return PTR_SIZE; - return ptr_var->type->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 */ @@ -7649,6 +9604,13 @@ 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); } @@ -7802,7 +9764,12 @@ void handle_pointer_arithmetic(block_t *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; + + /* 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->var_name = gen_name(); opstack_push(vd); @@ -8118,6 +10085,43 @@ void read_expr_body(block_t *parent, basic_block_t **bb) rs2 = opstack_pop(); rs1 = opstack_pop(); + /* 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); + } + + if ((top_op == OP_lt || top_op == OP_leq || top_op == OP_gt || + top_op == OP_geq) && + ((rs1 && (rs1->is_func || get_func_signature(rs1))) || + (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 = @@ -8312,6 +10316,18 @@ void read_lvalue(lvalue_t *lvalue, 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); @@ -8344,6 +10360,7 @@ void read_lvalue(lvalue_t *lvalue, lex_peek(T_dot, NULL)) { if (lex_accept(T_open_square)) { int indexed_ptr_level; + bool indexes_direct_pointee_array; /* if subscripted member's is not yet resolved, dereference to * resolve base address. e.g., dereference of "->" in "data->raw[0]" @@ -8355,6 +10372,14 @@ void read_lvalue(lvalue_t *lvalue, vd->var_name = gen_name(); 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). + */ + lvalue->size = + lvalue->value_ptr_level > 1 ? PTR_SIZE : lvalue->type->size; } /* var must be either a pointer or an array of some type For typedef @@ -8376,8 +10401,20 @@ void read_lvalue(lvalue_t *lvalue, else indexed_ptr_level = var->ptr_level + var->type->ptr_level + !!is_array_declarator(var); + 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; + + /* 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 = - indexed_ptr_level ? indexed_ptr_level - 1 : 0; + indexes_direct_pointee_array + ? 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 @@ -8415,6 +10452,22 @@ void read_lvalue(lvalue_t *lvalue, multiplier *= var->array_dim3; if (var->array_dim4 > 0) multiplier *= var->array_dim4; + } else if (var->pointee_array_size > 0 && + (indexes_direct_pointee_array || + indexed_ptr_level == 1)) { + multiplier = var->pointee_array_size * lvalue->size; + } else if (subscript_depth == 1 && var->pointee_array_dim2 > 0) { + multiplier = var->pointee_array_dim2 * lvalue->size; + if (var->pointee_array_dim3 > 0) + multiplier *= var->pointee_array_dim3; + if (var->pointee_array_dim4 > 0) + multiplier *= var->pointee_array_dim4; + } else if (subscript_depth == 2 && var->pointee_array_dim3 > 0) { + multiplier = var->pointee_array_dim3 * lvalue->size; + if (var->pointee_array_dim4 > 0) + multiplier *= var->pointee_array_dim4; + } else if (subscript_depth == 3 && var->pointee_array_dim4 > 0) { + multiplier = var->pointee_array_dim4 * lvalue->size; } else if (subscript_depth == 1 && var->array_dim3 > 0) { multiplier = var->array_dim3 * lvalue->size; if (var->array_dim4 > 0) @@ -8462,9 +10515,17 @@ void read_lvalue(lvalue_t *lvalue, lex_expect(T_close_square); is_address_got = true; - is_member = true; + + /* 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_direct_pointee_array && lvalue->value_ptr_level > 0; subscript_depth++; - lvalue->is_reference = true; + lvalue->is_reference = !indexes_direct_pointee_array; /* A subscript designates the pointee, whose qualification is the * declaration's base qualification rather than `* const`. @@ -8634,6 +10695,12 @@ void read_lvalue(lvalue_t *lvalue, t->var_name = gen_name(); t->type = pointee_type_from_pointer_typedef(lvalue->type); t->ptr_level = lvalue->value_ptr_level; + + /* 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; opstack_push(t); if (is_bitfield(lvalue->decl)) { /* Bit-fields are values, never independently addressable @@ -8825,6 +10892,7 @@ void read_logical(opcode_t op, block_t *parent, basic_block_t **bb) /* Test the operand before the logical-and/or operator */ vd = opstack_pop(); + 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 @@ -8988,6 +11056,15 @@ void read_control_expression(block_t *parent, basic_block_t **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 @@ -9026,6 +11103,16 @@ void read_conditional_true_expression(block_t *parent, basic_block_t **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; @@ -9064,6 +11151,19 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) 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); 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); @@ -9088,8 +11188,27 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) vd = require_var(parent); vd->var_name = gen_name(); - if (!true_ptr_like && !false_ptr_like && !true_val->ptr_level && - !false_val->ptr_level) { + 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); @@ -9173,7 +11292,9 @@ bool read_body_assignment(char *token, add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); } - read_indirect_call(lvalue.decl, parent, bb); + 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); @@ -9200,26 +11321,11 @@ bool read_body_assignment(char *token, /* 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; - } + /* Keep typedef-hidden depth: a void ** alias advances over + * pointer objects, whereas a direct void * was rejected above + * and never reaches this scaling path. + */ + increment_size = get_pointer_element_size(var); } /* If operand is a reference, read the value and push to stack for @@ -9405,6 +11511,7 @@ bool read_body_assignment(char *token, vd = require_var(parent); vd->var_name = gen_name(); + vd->func_target = rs2->func_target; add_insn(parent, *bb, OP_read, vd, t, NULL, PTR_SIZE, NULL); rs2 = vd; } @@ -9422,6 +11529,23 @@ bool read_body_assignment(char *token, } add_insn(parent, *bb, OP_write, NULL, rs1, rs2, PTR_SIZE, NULL); + + /* Only a named function-pointer object owns this metadata. A + * record member or array element shares lvalue.decl with every + * object of that type, so storing provenance there would let + * one object's assignment authorize another's call. Those + * reference forms intentionally remain unknown until SSA + * provenance is available per storage location. + */ + if (!lvalue.is_reference && !lvalue.decl->func_target_invalid) { + func_t *assigned_target = rs2->func_target; + + if (!assigned_target || !assigned_target->bbs) { + lvalue.decl->func_target = NULL; + lvalue.decl->func_target_invalid = true; + } else + lvalue.decl->func_target = assigned_target; + } if (assignment_result) assignment_result[0] = rs2; } else if (lvalue.is_reference) { @@ -9454,6 +11578,7 @@ bool read_body_assignment(char *token, } else { rs1 = opstack_pop(); vd = opstack_pop(); + rs1 = materialize_function_designator(parent, bb, rs1); if (is_record_object(vd) && is_record_object(rs1)) { emit_record_copy(parent, bb, vd, rs1); @@ -9847,7 +11972,7 @@ static int read_const_string_size(void) return res + 1; } -static int read_const_wstring_size(void) +int read_const_wstring_size(void) { int values[MAX_LINE_LEN]; type_t *wide_type = find_type("wchar_t", true); @@ -9964,7 +12089,15 @@ int read_primary_constant(block_t *scope) if (effective_pointer_depth(pointer) != 1) error_at("Cannot dereference non-pointer in sizeof", cur_token_loc()); - dereferenced = *pointer; + + /* This is compile-time descriptor manipulation, not a source + * aggregate expression. A direct `dereferenced = *pointer` becomes + * one wide OP_read while self-hosting; ARM's scalar load backend + * correctly admits only 1/2/4-byte reads. Copy through libc + * instead, which keeps the generated compiler IR word-sized and + * preserves every descriptor field. + */ + memcpy(&dereferenced, pointer, sizeof(dereferenced)); dereferenced.type = pointee_type_from_pointer_typedef(pointer->type); dereferenced.ptr_level = 0; @@ -10046,7 +12179,8 @@ int read_primary_constant(block_t *scope) lex_expect(T_close_bracket); res = TY_int->size; } else if (inside && - (inside->kind == T_numeric || inside->kind == T_char)) { + (inside->kind == T_numeric || inside->kind == T_char || + inside->kind == T_wchar)) { lex_expect(T_open_bracket); read_const_expr(scope); lex_expect(T_close_bracket); @@ -10081,7 +12215,8 @@ int read_primary_constant(block_t *scope) } else { res = read_const_sizeof_type(scope); } - } else if (lex_peek(T_numeric, buffer) || lex_peek(T_char, buffer)) { + } else if (lex_peek(T_numeric, buffer) || lex_peek(T_char, buffer) || + lex_peek(T_wchar, buffer)) { read_primary_constant(scope); res = TY_int->size; } else if (lex_peek(T_string, buffer)) { @@ -10138,7 +12273,8 @@ int read_primary_constant(block_t *scope) } else if (lex_peek(T_char, buffer) || lex_peek(T_wchar, buffer)) { char unescaped[MAX_TOKEN_LEN]; unescape_string(buffer, unescaped, MAX_TOKEN_LEN); - res = parse_character_constant(buffer); + res = lex_peek(T_wchar, NULL) ? parse_wide_character_constant(buffer) + : parse_character_constant(buffer); lex_expect(lex_peek(T_wchar, NULL) ? T_wchar : T_char); } else if (lex_peek(T_identifier, buffer)) { constant_t *con; @@ -10168,12 +12304,24 @@ int eval_expression_imm(opcode_t op, int op1, int 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: @@ -10210,6 +12358,9 @@ int eval_expression_imm(opcode_t op, int op1, int op2) 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()); res = op1 << op2; break; case OP_rshift: @@ -10272,7 +12423,7 @@ void emit_global_scalar_assignment(block_t *parent, * `(3 + 0x100000000LL)`: the old evaluator would consume the later token before * it had a chance to preserve its high word. */ -bool wide_global_literal_appears_before_initializer_end(token_t *token) +bool typed_global_literal_appears_before_initializer_end(token_t *token) { int bracket_depth = 0; @@ -10287,13 +12438,57 @@ bool wide_global_literal_appears_before_initializer_end(token_t *token) (token->kind == T_semicolon || token->kind == T_comma)) return false; else if (token->kind == T_numeric && - numeric_literal_needs_wide_path(token->literal)) + (numeric_literal_needs_wide_path(token->literal) || + numeric_literal_needs_typed_global_path(token->literal))) return true; } return false; } -var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb); +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); +} /* Fold the operations that only need word arithmetic before emitting global * setup code. That setup block is deliberately conservative about constants, @@ -10452,7 +12647,9 @@ bool emit_wide_global_word_arithmetic(block_t *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) +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; @@ -10463,7 +12660,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) * same semantics as ordinary expression parsing. */ if (lex_accept(T_plus)) - return read_wide_global_literal_primary(parent, bb); + return read_wide_global_literal_primary(parent, bb, scope); if (lex_accept(T_minus)) { var_t *zero = require_var(parent); var_t *result; @@ -10478,7 +12675,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) force_wide_global_literal_type(value, literal); return value; } - value = read_wide_global_literal_primary(parent, bb); + value = read_wide_global_literal_primary(parent, bb, scope); zero->var_name = gen_name(); zero->type = value->type; zero->init_val = 0; @@ -10493,7 +12690,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) if (lex_accept(T_bit_not)) { var_t *result; - value = read_wide_global_literal_primary(parent, bb); + value = read_wide_global_literal_primary(parent, bb, scope); result = require_var(parent); result->var_name = gen_name(); result->type = value->type; @@ -10504,17 +12701,31 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) if (lex_accept(T_log_not)) { var_t *result; - value = read_wide_global_literal_primary(parent, bb); + value = read_wide_global_literal_primary(parent, bb, scope); result = require_typed_var(parent, TY_int); result->var_name = gen_name(); add_insn(parent, bb, OP_log_not, result, value, NULL, 0, NULL); return result; } if (lex_accept(T_open_bracket)) { - value = read_wide_global_literal_expression(parent, bb); + 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(); + value->init_val = constant->value; + value->is_const = true; + add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); + return value; + } if (!lex_peek(T_numeric, literal)) error_at("Wide global initializer needs a literal operand", next_token_loc()); @@ -10528,14 +12739,17 @@ var_t *read_wide_global_literal_primary(block_t *parent, basic_block_t *bb) * 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) +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]; 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++] = read_wide_global_literal_primary(parent, bb); + val_stack[val_stack_index++] = + read_wide_global_literal_primary(parent, bb, scope); op = get_operator(); while (op != OP_generic) { if (op == OP_ternary || is_logical(op)) @@ -10562,7 +12776,7 @@ var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb) fatal("Wide global initializer is too complex"); op_stack[op_stack_index++] = op; val_stack[val_stack_index++] = - read_wide_global_literal_primary(parent, bb); + read_wide_global_literal_primary(parent, bb, scope); op = get_operator(); } while (op_stack_index > 0) { @@ -10631,7 +12845,37 @@ bool read_global_assignment_var(var_t *var) * relocation needed because the target function's code offset is not * known while global initializers are parsed. */ - bool explicit_address = lex_accept(T_ampersand); + 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 (!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); + 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_func(token); @@ -10648,6 +12892,8 @@ bool read_global_assignment_var(var_t *var) symbol->is_func = true; symbol->var_name = intern_string(token); 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); @@ -10661,25 +12907,56 @@ bool read_global_assignment_var(var_t *var) var_t *object = find_var(token, scope); if (object && object->is_global && (explicit_address || object->array_size)) { + int array_bounds[4] = {0, 0, 0, 0}; + int array_dims = 0; + int array_subscript = 0; var_t *object_addr = require_ref_var(parent, object->type, object->ptr_level); + if (object->array_size) { + int inner_size = 1; + + if (object->array_dim2) + inner_size *= object->array_dim2; + if (object->array_dim3) + inner_size *= object->array_dim3; + if (object->array_dim4) + inner_size *= object->array_dim4; + array_bounds[array_dims++] = + object->array_size / inner_size; + if (object->array_dim2) + array_bounds[array_dims++] = object->array_dim2; + if (object->array_dim3) + array_bounds[array_dims++] = object->array_dim3; + if (object->array_dim4) + array_bounds[array_dims++] = object->array_dim4; + } object_addr->var_name = gen_name(); + object_addr->is_global_address = true; lex_expect(T_identifier); add_insn(parent, bb, OP_address_of, object_addr, object, NULL, 0, NULL); if (!explicit_address && object->array_size && - lex_accept(T_plus)) { - int index = read_primary_constant(scope); + (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL))) { + int index = read_global_address_offset(scope, parent, bb); + int stride = + object->ptr_level ? 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); + if (object->array_dim2) + stride *= object->array_dim2; + if (object->array_dim3) + stride *= object->array_dim3; + if (object->array_dim4) + stride *= object->array_dim4; byte_offset->var_name = gen_name(); - byte_offset->init_val = index * object->type->size; + 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; add_insn(parent, bb, OP_add, offset_addr, object_addr, byte_offset, 0, NULL); object_addr = offset_addr; @@ -10696,15 +12973,42 @@ bool read_global_assignment_var(var_t *var) cur_token_loc()); object_addr = compute_field_address(parent, &bb, object_addr, field); + object_addr->is_global_address = true; object = field; + array_dims = 0; + array_subscript = 0; + if (object->array_size) { + int inner_size = 1; + + if (object->array_dim2) + inner_size *= object->array_dim2; + if (object->array_dim3) + inner_size *= object->array_dim3; + if (object->array_dim4) + inner_size *= object->array_dim4; + array_bounds[array_dims++] = + object->array_size / inner_size; + if (object->array_dim2) + array_bounds[array_dims++] = object->array_dim2; + if (object->array_dim3) + array_bounds[array_dims++] = object->array_dim3; + if (object->array_dim4) + array_bounds[array_dims++] = object->array_dim4; + } } else if (object->array_size && lex_accept(T_open_square)) { int index = read_primary_constant(scope); + int stride = + object->ptr_level ? PTR_SIZE : object->type->size; lex_expect(T_close_square); - object_addr = - compute_element_address(parent, &bb, object_addr, - index, object->type->size); + for (int dim = array_subscript + 1; dim < array_dims; + dim++) + stride *= array_bounds[dim]; + object_addr = compute_element_address( + parent, &bb, object_addr, index, stride); + object_addr->is_global_address = true; + array_subscript++; } else break; } @@ -10721,12 +13025,20 @@ bool read_global_assignment_var(var_t *var) * unary sign here and consumes the entire integer constant * expression, including grouping and enum constants. */ - int index = read_const_expr(scope); + int index = read_global_address_offset(scope, parent, bb); int elem_size = object->ptr_level ? PTR_SIZE : object->type->size; + /* An address of a partially subscripted array retains the + * remaining row dimensions. Its trailing constant offset + * must therefore use the row/plane stride. + */ + for (int dim = array_subscript; dim < array_dims; dim++) + elem_size *= array_bounds[dim]; + object_addr = compute_element_address( parent, &bb, object_addr, index, elem_size); + object_addr->is_global_address = true; } add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, NULL); @@ -10742,9 +13054,9 @@ bool read_global_assignment_var(var_t *var) * lower each reduction into the global setup block instead of trying to * narrow the expression through that evaluator. */ - if (wide_global_literal_appears_before_initializer_end( + if (typed_global_literal_appears_before_initializer_end( cur_token->next)) { - rs1 = read_wide_global_literal_expression(parent, bb); + rs1 = read_wide_global_literal_expression(parent, bb, scope); add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); return true; } @@ -10780,8 +13092,7 @@ bool read_global_assignment_var(var_t *var) add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); rs1 = vd; - vd = opstack_pop(); - emit_global_scalar_assignment(parent, bb, vd, rs1); + emit_global_scalar_assignment(parent, bb, var, rs1); return true; } if (op == OP_ternary) { @@ -10799,8 +13110,7 @@ bool read_global_assignment_var(var_t *var) 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); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); return true; } @@ -10867,8 +13177,7 @@ bool read_global_assignment_var(var_t *var) NULL); rs1 = vd; - vd = opstack_pop(); - emit_global_scalar_assignment(parent, bb, vd, rs1); + emit_global_scalar_assignment(parent, bb, var, rs1); } return true; } @@ -10887,8 +13196,7 @@ bool read_global_assignment_var(var_t *var) 0, NULL); rs1 = vd; - vd = opstack_pop(); - emit_global_scalar_assignment(parent, GLOBAL_FUNC->bbs, vd, rs1); + emit_global_scalar_assignment(parent, GLOBAL_FUNC->bbs, var, rs1); } return true; } @@ -10915,18 +13223,110 @@ 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) +/* 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; + +void reject_label_followed_by_declaration_in_strict_c99(void) { - char token[MAX_ID_LEN]; - var_t *vd; - var_t *rs1; - var_t *rs2; - switch_case_value_t *seen_cases = NULL; + 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_type(name, true))) + error_at("a C99 label must precede a statement, not a declaration", + cur_token_loc()); +} + +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 { + int case_val; + var_t *constant; + var_t *comparison; + basic_block_t *next_dispatch; + + lex_expect(T_case); + case_val = read_const_expr(parent); + if (strict_c99) { + switch_case_value_t *seen; + + for (seen = context->seen_cases; seen; seen = seen->next) + if (seen->value == case_val) + error_at("duplicate case value in C99 switch", + cur_token_loc()); + seen = arena_alloc(GENERAL_ARENA, sizeof(*seen)); + seen->value = case_val; + seen->next = context->seen_cases; + context->seen_cases = seen; + } - bool is_default = false; + constant = require_var(context->dispatch_parent); + constant->var_name = gen_name(); + constant->init_val = case_val; + 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(); + 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; @@ -10935,122 +13335,44 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) lex_expect(T_close_bracket); /* create exit jump for breaks */ - basic_block_t *switch_end = bb_create(parent); + switch_end = bb_create(parent); break_bb_push(switch_end); - basic_block_t *true_body_ = bb_create(parent); + 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 = operand_stack[operand_stack_idx - 1]; + 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. + */ + 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 = parse_character_constant(literal); - lex_expect(T_char); - } else if (lex_peek(T_identifier, token)) { - const constant_t *cd = find_scoped_constant(token, parent); - 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"); - } - - if (strict_c99) { - switch_case_value_t *seen; - - for (seen = seen_cases; seen; seen = seen->next) - if (seen->value == case_val) - error_at("duplicate case value in C99 switch", - cur_token_loc()); - seen = arena_alloc(GENERAL_ARENA, sizeof(*seen)); - seen->value = case_val; - seen->next = seen_cases; - seen_cases = seen; - } - - 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); - } + while (!lex_accept(T_close_curly)) { + body = read_body_statement(parent, body); + perform_side_effect(parent, body); } - /* remove the expression in switch() */ - opstack_pop(); - lex_expect(T_close_curly); + /* 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 (true_body_) + if (body) /* if the last label has no explicit break, connect it to the end */ - bb_connect(true_body_, switch_end, NEXT); + 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++) @@ -11172,6 +13494,10 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) } } } else { + if (is_incomplete_record_object(var)) + error_at( + "Incomplete struct/union type cannot define an object", + cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : blk, is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, var, NULL, NULL, 0, NULL); @@ -11224,6 +13550,11 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) nv->is_const_qualified = is_const; nv->is_volatile = is_volatile; read_partial_var_decl(nv, var); /* partial */ + if (is_incomplete_record_object(nv)) + error_at( + "Incomplete struct/union type cannot define an " + "object", + cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : blk, is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, nv, NULL, NULL, 0, NULL); @@ -11534,7 +13865,7 @@ basic_block_t *handle_record_statement(block_t *parent, var->is_static = is_static; var->is_global = is_static; read_partial_var_decl(var, NULL); - if (!type->size && !var->ptr_level) + if (is_incomplete_record_object(var)) error_at("Incomplete struct/union type cannot define an object", cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : parent, @@ -11596,7 +13927,7 @@ basic_block_t *handle_record_statement(block_t *parent, nv->is_global = is_static; nv->is_const_qualified = is_const; read_inner_var_decl(nv, false, false, false); - if (!type->size && !nv->ptr_level) + if (is_incomplete_record_object(nv)) error_at("Incomplete struct/union type cannot define an object", cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : parent, @@ -11711,17 +14042,52 @@ basic_block_t *handle_enum_declarators(block_t *parent, return bb; } +/* 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. A trailing comma is valid and does not introduce an - * implicit successor. + * required int domain. Callers invoke this only for an actual implicit value. */ int next_enum_value(int value) { - if (value != INT_MAX) - return value + 1; - if (lex_peek(T_comma, NULL) && cur_token->next->next && - cur_token->next->next->kind != T_close_curly) + 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; } @@ -11756,20 +14122,23 @@ basic_block_t *handle_enum_statement(block_t *parent, } if (!type) type = add_type(); - type->base_type = TYPE_int; - type->size = TY_int->size; + initialize_enum_type(type); if (has_tag) { set_type_name(type, token); if (!find_local_type_tag(token, parent)) add_type_tag(parent, token, type); } lex_expect(T_open_curly); + bool first = true; do { lex_ident(T_identifier, token); - if (lex_accept(T_assign)) - val = read_const_expr(parent); + if (!first && !lex_peek(T_assign, NULL)) + val = next_enum_value(val); + if (lex_accept(T_assign)) { + val = read_enum_constant(parent); + } add_scoped_constant(parent, token, val); - val = next_enum_value(val); + first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); @@ -11941,6 +14310,16 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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 (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()); if (is_extern) { if (var->is_func || lex_peek(T_open_bracket, NULL)) { /* The global helper owns function redeclaration compatibility @@ -11975,6 +14354,9 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) lex_expect(T_semicolon); return bb; } + if (is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : parent, is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, var, NULL, NULL, 0, NULL); @@ -12039,6 +14421,14 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) var_t *expr_result = opstack_pop(); + /* 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( @@ -12085,6 +14475,9 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) nv->is_const_qualified = var->is_const_qualified; nv->is_volatile = var->is_volatile; read_partial_var_decl(nv, var); /* partial */ + if (is_incomplete_record_object(nv)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : parent, is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, NULL, 0, NULL); @@ -12153,16 +14546,40 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) return bb; } - /* is a function call? Skip function call check when has_asterisk is true */ + /* 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)) { - 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; + 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_func(token); + if (func) { + lex_expect(T_identifier); + emit_direct_call_result(func, false, parent, &bb); + perform_side_effect(parent, bb); + lex_expect(T_semicolon); + return bb; + } } } @@ -12179,6 +14596,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) const label_t *l = find_label(token); if (l) error_at("label redefinition", &id_tk->location); + reject_label_followed_by_declaration_in_strict_c99(); basic_block_t *n = bb_create(parent); bb_connect(bb, n, NEXT); add_label(token, n); @@ -12283,14 +14701,17 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) { - if (!bb) - printf("Warning: unreachable code detected\n"); - /* 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); @@ -12370,7 +14791,9 @@ 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); - bb->scope = blk; + + if (bb) + bb->scope = blk; block_depth++; if (block_depth > MAX_BLOCK_DEPTH) @@ -12395,6 +14818,15 @@ void read_func_body(func_t *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 = @@ -12533,10 +14965,23 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) } 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--; @@ -12548,7 +14993,11 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) func->va_args = 0; func->has_prototype = false; memset(func->param_defs, 0, sizeof(func->param_defs)); - read_parameter_list_decl(func, 0); + + /* 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 (check_decl) { if (!compatible_decl_type(func->return_def.type, @@ -12566,25 +15015,25 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) 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]; - bool now_points_to_value = - now->ptr_level || now->type->ptr_level; - bool before_points_to_value = - before->ptr_level || before->type->ptr_level; - - if (!compatible_decl_type(now->type, before->type) || - now->ptr_level != before->ptr_level || - - /* C99 ignores top-level parameter qualifiers, but a - * qualifier on the object reached through a pointer is part - * of that parameter's pointed-to type. - */ - ((now_points_to_value || before_points_to_value) && - now->is_const_qualified != before->is_const_qualified) || - ((now_points_to_value || before_points_to_value) && - now->is_volatile != before->is_volatile)) + if (!compatible_function_param_decl(now, before)) 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_tmp.has_prototype) { /* An empty-list definition has no named parameters. It cannot * define a function previously declared with fixed parameters or an @@ -12610,6 +15059,18 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) if (lex_peek(T_open_curly, NULL)) { 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[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; } @@ -12658,6 +15119,10 @@ var_t *resolve_global_declarator(block_t *block, 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) error_at("conflicting types for global declaration", next_token_loc()); if (!previous->is_static && is_static) @@ -12683,7 +15148,8 @@ bool read_global_declarator(block_t *block, type_t *decl_type, bool is_const, bool is_static, - bool is_volatile) + bool is_volatile, + bool is_extern) { bool is_redeclaration; var_t *nv = require_typed_var(block, decl_type); @@ -12692,13 +15158,20 @@ bool read_global_declarator(block_t *block, 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)) { read_global_function_declarator(block, nv, is_static); return true; } + 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_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; @@ -12798,6 +15271,7 @@ void parse_global_compound_array_init(var_t *var, block_t *block) type_t *element_type; var_t *array; int find_type_flag = 1; + int element_ptr_level = 0; lex_expect(T_open_bracket); if (lex_accept(T_struct) || lex_accept(T_union)) { @@ -12807,23 +15281,149 @@ void parse_global_compound_array_init(var_t *var, block_t *block) lex_ident(T_identifier, type_name); } element_type = find_type(type_name, find_type_flag); - lex_expect(T_open_square); + 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, true); + 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; - if (lex_peek(T_numeric, NULL)) { - char bound[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, bound, MAX_TOKEN_LEN); - array->array_size = parse_numeric_constant(bound); - if (array->array_size <= 0) + 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_square); lex_expect(T_close_bracket); - if (!element_type || element_type != var->type) + /* 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", @@ -12839,10 +15439,11 @@ void parse_global_compound_array_init(var_t *var, block_t *block) * Keep their continuation declarators on the same path as the first one so that * linkage, qualifiers, and declarator-specific modifiers cannot diverge. */ -void read_global_record_declarator(block_t *block, +bool read_global_record_declarator(block_t *block, type_t *decl_type, bool is_const, - bool is_static) + bool is_static, + bool is_extern) { bool is_redeclaration; var_t *var = require_typed_var(block, decl_type); @@ -12850,16 +15451,24 @@ void read_global_record_declarator(block_t *block, var->is_static = is_static; var->is_const_qualified = is_const; read_inner_var_decl(var, false, false, false); - if (!decl_type->size && !var->ptr_level) + var->is_extern = is_extern && !lex_peek(T_assign, NULL); + if (lex_peek(T_open_bracket, NULL)) { + read_global_function_declarator(block, var, is_static); + return true; + } + 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_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; + return false; } var->has_initializer = true; @@ -12878,11 +15487,13 @@ void read_global_record_declarator(block_t *block, read_global_assignment_var(var); } discard_global_declarator_operand(var); + return false; } void read_global_decl(block_t *block, bool is_const, bool is_static, + bool is_extern, bool is_inline, bool is_volatile) { @@ -12896,19 +15507,26 @@ void read_global_decl(block_t *block, /* new function, or variables under parent */ read_full_var_decl(var, false, false, false); + var->is_extern = is_extern && !lex_peek(T_assign, NULL); if (lex_peek(T_open_bracket, NULL)) { read_global_function_declarator(block, var, is_static); 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_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 */ @@ -12926,7 +15544,7 @@ void read_global_decl(block_t *block, */ while (lex_accept(T_comma)) read_global_declarator(block, var->type, var->is_const_qualified, - is_static, var->is_volatile); + is_static, var->is_volatile, is_extern); lex_expect(T_semicolon); return; @@ -12946,8 +15564,8 @@ void consume_global_compound_literal(void) 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 if (lex_peek(T_char, NULL) || lex_peek(T_wchar, NULL)) { + lex_next(); } else { error_at( "Global struct initialization requires constant values", @@ -13059,11 +15677,12 @@ void read_global_statement(void) if (!decl_type) error_at("Unknown struct type", &id_tk->location); - read_global_record_declarator(block, decl_type, is_const, - is_static); + if (read_global_record_declarator(block, decl_type, is_const, + is_static, is_extern)) + return; while (lex_accept(T_comma)) read_global_record_declarator(block, decl_type, is_const, - is_static); + is_static, is_extern); lex_expect(T_semicolon); return; } @@ -13134,9 +15753,12 @@ void read_global_statement(void) * pair { int x, y; } first, *second;". */ if (!lex_peek(T_semicolon, NULL)) { - read_global_record_declarator(block, type, is_const, is_static); + if (read_global_record_declarator(block, type, is_const, is_static, + is_extern)) + return; while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static); + read_global_record_declarator(block, type, is_const, is_static, + is_extern); } lex_expect(T_semicolon); } else if (lex_accept(T_union)) { @@ -13162,11 +15784,12 @@ void read_global_statement(void) if (!decl_type) error_at("Unknown union type", &id_tk->location); - read_global_record_declarator(block, decl_type, is_const, - is_static); + if (read_global_record_declarator(block, decl_type, is_const, + is_static, is_extern)) + return; while (lex_accept(T_comma)) read_global_record_declarator(block, decl_type, is_const, - is_static); + is_static, is_extern); lex_expect(T_semicolon); return; } @@ -13223,9 +15846,12 @@ void read_global_statement(void) type->has_flexible_array_member = has_flexible_array_member; if (!lex_peek(T_semicolon, NULL)) { - read_global_record_declarator(block, type, is_const, is_static); + if (read_global_record_declarator(block, type, is_const, is_static, + is_extern)) + return; while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static); + read_global_record_declarator(block, type, is_const, is_static, + is_extern); } lex_expect(T_semicolon); } else if (lex_accept(T_enum)) { @@ -13249,11 +15875,11 @@ void read_global_statement(void) if (!type) error_at("Unknown enum type", cur_token_loc()); if (read_global_declarator(block, type, is_const, is_static, - is_volatile)) + is_volatile, is_extern)) return; while (lex_accept(T_comma)) read_global_declarator(block, type, is_const, is_static, - is_volatile); + is_volatile, is_extern); lex_expect(T_semicolon); return; } @@ -13261,26 +15887,28 @@ void read_global_statement(void) if (!type) type = add_type(); - type->base_type = TYPE_int; - type->size = 4; + initialize_enum_type(type); if (has_tag) set_type_name(type, token); 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_const_expr(block); + val = read_enum_constant(block); add_constant(token, val); - val = next_enum_value(val); + first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); if (!lex_peek(T_semicolon, NULL)) { if (read_global_declarator(block, type, is_const, is_static, - is_volatile)) + is_volatile, is_extern)) return; while (lex_accept(T_comma)) read_global_declarator(block, type, is_const, is_static, - is_volatile); + is_volatile, is_extern); } lex_expect(T_semicolon); } else if (lex_accept(T_typedef)) { @@ -13288,15 +15916,17 @@ void read_global_statement(void) int val = 0; type_t *type = add_type(); - type->base_type = TYPE_int; - type->size = 4; + initialize_enum_type(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_const_expr(block); + val = read_enum_constant(block); add_constant(token, val); - val = next_enum_value(val); + first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); lex_ident(T_identifier, token); @@ -13557,7 +16187,18 @@ void read_global_statement(void) lex_peek(T_identifier, base_type) && !strcmp(base_type, "char")) error_at("int cannot be combined with char", cur_token_loc()); - if (is_long) { + bool is_float = lex_accept(T_float); + bool is_double = lex_accept(T_double); + + if (is_float) { + if (is_signed || is_unsigned || is_long) + error_at("invalid float type specifiers", cur_token_loc()); + base = TY_float; + } else if (is_double) { + if (is_signed || is_unsigned || is_long_long) + error_at("invalid double type specifiers", cur_token_loc()); + base = is_long ? TY_long_double : TY_double; + } else if (is_long) { if (lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) lex_expect(T_identifier); @@ -13640,12 +16281,15 @@ void read_global_statement(void) type->is_const_qualified = typedef_const || base->is_const_qualified; 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->func_signature = base->func_signature; /* Handle pointer types in typedef: typedef char *string; */ while (lex_accept(T_asterisk)) { @@ -13664,6 +16308,60 @@ void read_global_statement(void) } } + /* 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; + if (!declarator.is_func) + error_at( + "Typedef parenthesized declarator must be a function " + "pointer", + cur_token_loc()); + 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 = 0; + lex_expect(T_semicolon); + return; + } + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); /* A typedef declarator may wrap an existing array typedef: `typedef @@ -13734,12 +16432,19 @@ void read_global_statement(void) else type->array_dim4 = all_bounds[i]; } + + /* 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; } lex_expect(T_semicolon); } } 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_inline, is_volatile); + 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()); } @@ -13785,6 +16490,27 @@ void parse_internal(void) 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. @@ -14018,6 +16744,17 @@ void parse_internal(void) 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) diff --git a/src/preprocessor.c b/src/preprocessor.c index 2184ae40..c4aaa5e3 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -64,6 +64,28 @@ token_t *pp_lex_expect_token(token_t *tk, token_kind_t kind, bool skip_space) return tk; } +/* A single wide UCN character constant represents its execution-wide unit in a + * preprocessing integer constant expression. + */ +int pp_parse_wide_character_constant(const char *literal) +{ + unsigned int value = 0; + int digits; + + if (literal[0] != '\\' || (literal[1] != 'u' && literal[1] != 'U')) + return parse_character_constant(literal); + digits = literal[1] == 'u' ? 4 : 8; + for (int i = 0; i < digits; i++) { + if (!isxdigit(literal[i + 2])) + return parse_character_constant(literal); + value = (value << 4) + hex_digit_value(literal[i + 2]); + } + if (literal[digits + 2] || value > 0x10ffff || + (value >= 0xd800 && value <= 0xdfff)) + return parse_character_constant(literal); + return (int) value; +} + /* Copies and isolate the given copied token */ token_t *copy_token(const token_t *tk) { @@ -963,13 +985,55 @@ 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) @@ -1103,9 +1167,11 @@ void pp_multiply(pp_integer_t *lhs, const pp_integer_t *rhs) 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) @@ -1140,6 +1206,7 @@ void pp_divmod_unsigned(const pp_integer_t *numerator, 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; @@ -1168,6 +1235,7 @@ void pp_divmod(pp_integer_t *lhs, const pp_integer_t *rhs, int remainder) 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) @@ -1206,7 +1274,7 @@ void pp_parse_integer_literal(token_t *tk, pp_integer_t *val) 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}; + pp_integer_t addend = {digit, 0, false, 0}; pp_add(&parsed, &addend); } @@ -1228,6 +1296,27 @@ void pp_parse_integer_literal(token_t *tk, pp_integer_t *val) } 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, @@ -1309,6 +1398,22 @@ token_t *pp_read_constant_expr_operand(token_t *tk, 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 type 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 = lex_peek(T_wstring, NULL) + ? read_const_wstring_size() + : read_const_sizeof_type(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); pp_parse_integer_literal(tk, val); @@ -1322,7 +1427,9 @@ token_t *pp_read_constant_expr_operand(token_t *tk, 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 = parse_character_constant(tk->literal); + int character = tk->kind == T_wchar + ? pp_parse_wide_character_constant(tk->literal) + : parse_character_constant(tk->literal); val->lo = character; val->hi = character < 0 ? ~0U : 0; @@ -1342,7 +1449,18 @@ token_t *pp_read_constant_expr_operand(token_t *tk, tk = pp_lex_next_token(tk, true); - if (!strcmp("defined", tk->literal)) { + 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) @@ -1352,8 +1470,8 @@ token_t *pp_read_constant_expr_operand(token_t *tk, 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->is_function_like) { @@ -1454,12 +1572,15 @@ token_t *pp_read_constant_infix_expr(int precedence, /* 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; @@ -1474,6 +1595,9 @@ token_t *pp_read_constant_infix_expr(int precedence, 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: case OP_sub: @@ -1529,14 +1653,26 @@ token_t *pp_read_constant_infix_expr(int precedence, lhs.is_unsigned = PP_USES_UNSIGNED(&lhs, &rhs); break; case OP_lshift: - if (rhs.hi || rhs.lo >= 64) + 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++) + 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 >= 64) + 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++) @@ -1594,6 +1730,8 @@ token_t *pp_read_constant_infix_expr(int precedence, error_at("Unexpected infix token while evaluating constant", &tk->location); } + if (pp_integer_constant_scope) + pp_enum_normalize(&lhs); } } @@ -1602,6 +1740,7 @@ token_t *pp_read_constant_infix_expr(int precedence, val->lo = lhs.lo; val->hi = lhs.hi; val->is_unsigned = lhs.is_unsigned; + val->enum_width = lhs.enum_width; return tk; } diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 74c28a3e..cc05ea79 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -183,7 +183,7 @@ int abi_arg_next(int cursor, const var_t *var) int abi_param_start(const func_t *func, int param_idx) { - int cursor = 0; + int cursor = func->returns_aggregate ? 1 : 0; for (int i = 0; i < param_idx; i++) cursor = abi_arg_next(cursor, &func->param_defs[i]); @@ -1029,7 +1029,7 @@ void pin_registers(func_t *func) * them; the variable would then read a parameter's value instead. */ int reg = REG_CNT - 1 - taken; - int arg_words_in_reg = 0; + 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); @@ -1690,13 +1690,16 @@ void prepare_pair_argument(basic_block_t *bb, var_t *var, int low) /* Return whether extra arguments are pushed onto stack. */ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) { - insn_t *pushes[MAX_PARAMS]; - int starts[MAX_PARAMS]; + /* 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[MAX_PARAMS + 1]; int num_of_args = 0; int cursor = 0; while (insn && insn->opcode == OP_push) { - if (num_of_args >= MAX_PARAMS) + if (num_of_args >= MAX_PARAMS + 1) fatal("Too many call arguments"); starts[num_of_args] = abi_arg_start(cursor, insn->rs1); pushes[num_of_args++] = insn; @@ -2439,7 +2442,16 @@ void reg_alloc_global(insn_t *global_insn) 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); ir->src0 = src0; @@ -2469,12 +2481,24 @@ void reg_alloc_global(insn_t *global_insn) 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; + } 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; @@ -2579,7 +2603,18 @@ void reg_alloc_global(insn_t *global_insn) } /* 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; @@ -2749,6 +2784,16 @@ 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) { + /* 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. + */ + REGS[dest].var = NULL; + REGS[dest].polluted = 0; + vreg_clear_phys(insn->rd); + } break; case OP_load_constant: case OP_load_data_address: @@ -3328,6 +3373,10 @@ void reg_alloc(void) * 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); diff --git a/src/ssa.c b/src/ssa.c index bca01494..c9f81011 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -727,6 +727,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; } @@ -1013,12 +1016,18 @@ 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; @@ -4467,6 +4476,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/tests/driver.sh b/tests/driver.sh index 1fe3f977..324cfbbc 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -711,8 +711,301 @@ 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_ "$((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 + +try_ 0 << EOF +int main(void) { + return sizeof(2147483648U) != sizeof(unsigned int) || + sizeof(4294967296U) != 8 || + 0xffffffff > -1 || (0xffffffff >> 31) != 1; +} +EOF + +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 + +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 + +try_ 0 << EOF +int main(void) { + return sizeof(int[2]) != 2 * sizeof(int) || + sizeof(int[2][3]) != 6 * sizeof(int); +} +EOF + +try_ 0 << EOF +int global_abstract_array_size = sizeof(int[2][3]); +int main(void) { return global_abstract_array_size != 6 * sizeof(int); } +EOF + +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 + +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 + +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 + +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 + +# 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_ 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_ 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 + +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 + +try_ 1 << EOF +int type_only_float_parameter = sizeof(int (*)(double)); +double still_an_unsupported_object; +EOF + +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 + +try_ 1 << EOF +typedef int (*float_callback_type)(double); +double still_an_unsupported_object; +EOF + +try_ 1 << EOF +typedef int (*float_callback_type)(double); +float_callback_type still_an_unsupported_callback_object; +EOF + +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 try_compile_error << EOF -int main(void) { return sizeof(double); } +int invalid_global_float_type_size = sizeof(unsigned double); EOF try_compile_error << EOF int identity(float value) { return 0; } @@ -724,9 +1017,6 @@ try_compile_error << EOF long double value; EOF try_compile_error << EOF -int main(void) { return sizeof(long double); } -EOF -try_compile_error << EOF int _Complex; EOF try_compile_error << EOF @@ -794,6 +1084,35 @@ int main(void) { 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 +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 try_ 0 << EOF #define LEFT L"a" @@ -1006,6 +1325,23 @@ EOF # Category: Arithmetic Operations begin_category "Arithmetic Operations" "Testing +, -, *, /, % operators" +# 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. @@ -2293,6 +2629,62 @@ int main(void) { } 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]; } @@ -2316,6 +2708,18 @@ 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 @@ -2655,6 +3059,21 @@ 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) { @@ -3220,6 +3639,68 @@ int main(void) { 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 +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}; @@ -3351,6 +3832,190 @@ int main(void) { global_compound_bounded[2] + global_compound_bounded[3]; } 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_ 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_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_ 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; }; @@ -3783,6 +4448,165 @@ int main(void) { } 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 @@ -4571,16 +5395,66 @@ int main() { } 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 @@ -4748,6 +5622,293 @@ int main() { } 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. @@ -4882,6 +6043,25 @@ int main(void) EOF fi +# 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 @@ -6039,13 +7219,16 @@ try_compile_error << EOF int too_many_dimensions[1][1][1][1][1]; EOF -# Forward record tags may be used through pointers before their complete -# definition; both global static storage and block scope retain that rule. +# 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) { @@ -6068,6 +7251,47 @@ 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 @@ -6353,6 +7577,29 @@ int static_compound_literals(void) 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) @@ -6732,6 +7979,66 @@ int main(void) { global_half; } EOF +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. @@ -7600,6 +8907,7 @@ items 12 "struct holder { int values[3]; }; struct holder value; return sizeof v 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 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 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);" @@ -7720,12 +9028,101 @@ try_ 6 << EOF typedef enum { enum1 = 5, enum2 } enum_t; int main() { enum_t v = enum2; return v; } EOF -try_ 2 << EOF -enum trailing_values { trailing_first = 2, }; -int main(void) { return trailing_first; } +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 +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 invalid_values { last_int = 2147483647, out_of_range }; +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 # Block-scope enum definitions supply integer constants to expressions and @@ -7837,6 +9234,47 @@ int main(void) } 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" @@ -12019,6 +13457,203 @@ EOF begin_category "Function parsing" "Forward declaration and implementation" +# 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) { + _Bool initialized = callback; + _Bool assigned = 0; + assigned = callback; + return initialized + assigned + returns_callback(); +} +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) { + struct pair value = make_pair(3, 4); + return value.left + value.right; +} +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) { + struct triple value = make_triple(3, 4, 5); + return value.first + value.second + value.third; +} +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) { + 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_ 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_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_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_ 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_ 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 +try_ 15 << EOF +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 +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 +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_ 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 +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 + # Normal case try_output 0 "Hello" << EOF void func(char *ptr); @@ -12134,6 +13769,49 @@ int main() } EOF +# 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 + +# 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 + begin_category "Bit-fields" try_ 0 << EOF typedef _Bool bool_alias; @@ -12353,6 +14031,15 @@ 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 @@ -12445,6 +14132,22 @@ 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 @@ -12521,14 +14224,68 @@ int main(void) { } EOF 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 +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 # 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 @@ -12574,6 +14331,45 @@ 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 +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 +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 +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; } @@ -12585,6 +14381,73 @@ int main(void) { return 0; } EOF +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 +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; + switch (value) { + case 1: total = 1; + default: total += 2; + case 2: total += 4; + } + return total; +} +int main(void) { return score(1) + score(2) + score(9); } +EOF +try_flags 7 --std=c99 << EOF +int select_label_statement(int value) { + int total = 0; + switch (value) { + total = 100; + case 1: total += 1; break; + { + 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(); } @@ -12787,34 +14650,58 @@ int typedef_alias_row3(int count, ...) { int main(void) { row3 value = { 1, 2, 9 }; return typedef_alias_row3(1, &value); } EOF -try_compile_error << EOF +try_ 0 << EOF #include struct item { int first; int second; }; -typedef struct item rows[2]; -typedef rows *rows_pointer; -int unsupported_aggregate_pointer_to_array(int count, ...) { +typedef struct item item_t; +int aggregate_pointer_to_array(int count, ...) { va_list ap; va_start(ap, count); - return va_arg(ap, rows_pointer)[0][1].second; + item_t result = va_arg(ap, item_t (*)[2])[0][1]; + return result.second != 9; } +int main(void) { item_t value[2] = {{1, 2}, {3, 9}}; return aggregate_pointer_to_array(1, &value); } EOF -try_compile_error << EOF +try_ 0 << EOF #include struct item { int value; }; -int unsupported_direct_aggregate_pointer_to_array(int count, ...) { +int direct_aggregate_pointer_to_array(int count, ...) { va_list ap; va_start(ap, count); - return va_arg(ap, struct item (*)[2])[0][1].value; + 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 -try_compile_error << EOF +try_ 0 << EOF +#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(void) { + int first = 4, second = 9; + int *value[2] = { &first, &second }; + return pointer_element_array(1, &value); +} +EOF + +try_ 0 << EOF #include -int unsupported_pointer_element_array(int count, ...) { +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, int * (*)[2])[0][1]; + return *va_arg(ap, row *)[0][1] != 8; +} +int main(void) { + int first = 3, second = 8; + row value = { &first, &second }; + return typedef_pointer_element_array(1, &value); } EOF From 65d7cc7c1dedfa464c189851ec29d2196ed14505 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sun, 13 Sep 2026 22:09:54 +0800 Subject: [PATCH 07/40] Add block typedefs and pointer-row lowering Typedef aliases are scoped to their block, including pointer-row, function and callback typedefs with their own slots, and function and array typedef declarators compose. Pointers to array rows are lowered, elements update through grouped array pointers, and one fixed-array shape descriptor serves them all. One sizeof postfix walker is shared, offsetof takes fixed array designators, and global constants fold wide conditionals and comparisons while keeping unsigned literals and address offsets. for clauses take full expressions under the C99 declaration constraint, switch controls must be integers, void objects and elements are rejected, x86-64 selects right shifts by the left operand, aggregate va_start slots are reserved, and block static initializers resolve their addresses. --- src/defs.h | 108 ++ src/globals.c | 99 + src/parser.c | 3111 +++++++++++++++++++++++++++----- src/preprocessor.c | 23 + src/reg-alloc.c | 52 +- src/x64-codegen.c | 10 +- tests/driver.sh | 4306 ++++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 7051 insertions(+), 658 deletions(-) diff --git a/src/defs.h b/src/defs.h index 154832e4..2bf0a545 100644 --- a/src/defs.h +++ b/src/defs.h @@ -589,10 +589,44 @@ struct var { 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 @@ -644,6 +678,7 @@ struct var { */ bool in_select_arm; int array_size; + 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. @@ -657,6 +692,7 @@ struct var { * 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; @@ -786,6 +822,12 @@ struct var { 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; @@ -795,6 +837,7 @@ struct block { */ void *type_tags; void *constants; + typedef_binding_t *typedefs; struct block *parent; func_t *func; struct block *next; @@ -804,6 +847,10 @@ typedef struct block block_t; int read_const_sizeof_type(block_t *scope); int read_const_wstring_size(void); +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); typedef struct basic_block basic_block_t; /* Definition of a growable buffer for a mutable null-terminated string @@ -903,6 +950,22 @@ struct type { */ 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. @@ -917,6 +980,40 @@ struct type { */ 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. @@ -964,6 +1061,11 @@ typedef struct { bool is_const_qualified; 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; @@ -1197,6 +1299,12 @@ struct func { * 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. diff --git a/src/globals.c b/src/globals.c index 56798b16..d984c3b1 100644 --- a/src/globals.c +++ b/src/globals.c @@ -15,6 +15,9 @@ #include "defs.h" +source_location_t *cur_token_loc(void); +__noreturn void error_at(char *msg, source_location_t *loc); + /* Forward declaration for string interning */ char *intern_string(char *str); @@ -669,6 +672,7 @@ block_t *add_block(block_t *parent, func_t *func) 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; @@ -1103,6 +1107,20 @@ type_t *find_type_tag(char name[], block_t *block) return find_type(name, 1); } +/* Struct and union tags use a namespace distinct from ordinary typedef names. + * Keep this narrower lookup separate from find_type_tag(), which also serves + * enum-tag parsing. + */ +type_t *find_record_tag(char name[], block_t *block) +{ + for (; block; block = block->parent) { + for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) + if (!strcmp(tag->name, name)) + return tag->type; + } + return find_type(name, 2); +} + type_t *find_local_type_tag(char name[], block_t *block) { for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) @@ -1111,6 +1129,87 @@ type_t *find_local_type_tag(char name[], block_t *block) return NULL; } +bool find_block_typedef(block_t *block, const char *name) +{ + for (typedef_binding_t *binding = block->typedefs; binding; + binding = binding->next) + if (!strcmp(binding->name, name)) + return true; + return false; +} + +static bool block_has_ordinary_name(block_t *block, const char *name) +{ + for (int i = 0; i < block->locals.size; i++) { + var_t *var = block->locals.elements[i]; + + if (var && var->var_name && !strcmp(var->var_name, name)) + return true; + } + + /* The function body's outermost block shares the parameter namespace. A + * nested block may, however, legally shadow a parameter with a typedef. + */ + if (!block->parent && block->func) { + for (int i = 0; i < block->func->num_params; i++) { + var_t *param = &block->func->param_defs[i]; + + if (param->var_name && !strcmp(param->var_name, name)) + return true; + } + } + return false; +} + +void add_block_typedef(block_t *block, char name[], type_t *type) +{ + if (find_block_typedef(block, name) || block_has_ordinary_name(block, name)) + 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) +{ + func_t *func = block ? block->func : NULL; + + for (; block; block = block->parent) { + for (int i = block->locals.size - 1; i >= 0; i--) { + var_t *var = block->locals.elements[i]; + if (var && var->var_name && !strcmp(var->var_name, name)) + return NULL; + } + for (typedef_binding_t *binding = block->typedefs; binding; + binding = binding->next) + if (!strcmp(binding->name, name)) + return binding->type; + } + + /* Parameters remain visible throughout the function body unless an inner + * lexical binding above has already hidden them. + */ + if (func) { + for (int i = 0; i < func->num_params; i++) { + var_t *param = &func->param_defs[i]; + + if (param->var_name && !strcmp(param->var_name, name)) + 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 diff --git a/src/parser.c b/src/parser.c index df171c96..acd1e70b 100644 --- a/src/parser.c +++ b/src/parser.c @@ -45,12 +45,168 @@ int unevaluated_expression_depth = 0; * 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); +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 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; +} + +basic_block_t *handle_block_typedef_statement(block_t *parent, + basic_block_t *bb); void perform_side_effect(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); @@ -485,6 +641,7 @@ type_t *pointee_type_from_pointer_typedef(type_t *type) * 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); @@ -498,10 +655,39 @@ var_t *emit_indirect_call_result(var_t *callee, block_t *parent, basic_block_t **bb); +static int callback_pointer_indirection(const var_t *var) +{ + 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; +} + bool compatible_decl_type(const type_t *left, const type_t *right) { if (left == right) return true; + + /* 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); 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 || @@ -533,18 +719,29 @@ bool compatible_function_param_decl(const var_t *left, const var_t *right) bool left_points_to_value; bool right_points_to_value; - if (!left || !right || left->is_func != right->is_func || - !compatible_decl_type(left->type, right->type) || - left->ptr_level != right->ptr_level) + if (!left || !right || left->is_func != right->is_func) return false; if (left->is_func) { if (!left->func_signature || !right->func_signature) return false; - return compatible_function_signature(left->func_signature, + return callback_pointer_indirection(left) == + callback_pointer_indirection(right) && + compatible_function_signature(left->func_signature, right->func_signature); } + 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; + } + + if (!compatible_decl_type(left->type, right->type) || + left->ptr_level != right->ptr_level) + return false; + left_points_to_value = left->ptr_level || left->type->ptr_level; right_points_to_value = right->ptr_level || right->type->ptr_level; @@ -604,6 +801,22 @@ bool compatible_function_signature(const func_t *left, const func_t *right) return true; } +/* 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. + */ +func_t *find_visible_func(char *name, block_t *scope) +{ + func_t *func = find_func(name); + var_t *binding; + + if (!func || !func->is_block_scope_only_declaration) + return func; + binding = find_var(name, scope); + return binding && binding->is_extern_function_alias ? func : NULL; +} + /* 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. @@ -1397,6 +1610,42 @@ bool incompatible_character_pointer_conversion(const var_t *from, !compatible_decl_type(from_pointee, to_pointee); } +bool incompatible_pointee_callback_conversion(const var_t *from, + const var_t *to) +{ + 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; + from_signature = from->pointee_func_signature; + to_signature = to->pointee_func_signature; + + /* 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; + if (!from_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 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); @@ -1568,6 +1817,9 @@ void emit_object_assignment(block_t *parent, var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) { var_t *val = NULL; + block_t *scope = global_constant_initializer_scope + ? global_constant_initializer_scope + : parent; bool address_dereference = global_function_address_dereference_starts_here(); token_t *address_dereference_identifier = NULL; @@ -1577,6 +1829,9 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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; @@ -1594,7 +1849,7 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) char name[MAX_ID_LEN]; if (lex_peek(T_identifier, name)) { - func_t *func = find_func(name); + func_t *func = find_visible_func(name, scope); if (func) { lex_expect(T_identifier); if (grouped_function_designator) @@ -1605,33 +1860,20 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) return val; } if (explicit_address) { - var_t *object = find_var(name, GLOBAL_BLOCK); + /* 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) { - int array_bounds[4] = {0, 0, 0, 0}; - int array_dims = 0; + fixed_array_shape_t shape = + fixed_array_shape_from_var(object); int subscript_count = 0; int element_size = object->ptr_level ? PTR_SIZE : object->type->size; - if (object->array_size) { - int inner_size = 1; - - if (object->array_dim2) - inner_size *= object->array_dim2; - if (object->array_dim3) - inner_size *= object->array_dim3; - if (object->array_dim4) - inner_size *= object->array_dim4; - array_bounds[array_dims++] = - object->array_size / inner_size; - if (object->array_dim2) - array_bounds[array_dims++] = object->array_dim2; - if (object->array_dim3) - array_bounds[array_dims++] = object->array_dim3; - if (object->array_dim4) - array_bounds[array_dims++] = object->array_dim4; - } lex_expect(T_identifier); val = require_ref_var(parent, object->type, object->ptr_level); @@ -1644,15 +1886,14 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) int stride = element_size; if (!object->array_size || - subscript_count >= array_dims) + subscript_count >= shape.rank) error_at( "Subscripted global address needs an " "array object", cur_token_loc()); - for (int dim = subscript_count + 1; dim < array_dims; - dim++) - stride *= array_bounds[dim]; - index = read_const_expr(parent); + stride = fixed_array_shape_stride( + &shape, subscript_count, element_size); + index = read_const_expr(scope); lex_expect(T_close_square); val = compute_element_address(parent, bb, val, index, stride); @@ -1668,16 +1909,16 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) } if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { int index = - read_global_address_offset(parent, parent, *bb); - int stride = element_size; + read_global_address_offset(scope, parent, *bb); + int stride; /* After a partial multidimensional subscript, the * address still points at the remaining row/plane. C * pointer arithmetic advances by that complete * pointed-to object, not by its scalar leaf. */ - for (int dim = subscript_count; dim < array_dims; dim++) - stride *= array_bounds[dim]; + stride = fixed_array_shape_stride( + &shape, subscript_count - 1, element_size); val = compute_element_address(parent, bb, val, index, stride); val->ptr_level = object->ptr_level + 1; @@ -1688,7 +1929,7 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) } } if (explicit_address) - error_at("Expected function after '&' in global initializer", + error_at("Expected a global object or function after '&'", cur_token_loc()); error_at("Global aggregate initializer requires a constant value", cur_token_loc()); @@ -2886,6 +3127,9 @@ void parse_array_init(var_t *var, 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 @@ -3081,7 +3325,8 @@ void parse_array_init(var_t *var, * initializer on a local worked. */ if (parent == GLOBAL_BLOCK && - (grouped_global_function_designator_starts_here(true) || + (lex_peek(T_ampersand, NULL) || + grouped_global_function_designator_starts_here(true) || global_function_address_dereference_starts_here())) { val = parse_global_constant_value(parent, bb); } else { @@ -3089,7 +3334,7 @@ void parse_array_init(var_t *var, char global_token[MAX_ID_LEN]; bool function_initializer = lex_peek(T_identifier, initializer_name) && - find_func(initializer_name); + find_visible_func(initializer_name, initializer_scope); var_t *object_constant = NULL; if (parent == GLOBAL_BLOCK && @@ -3121,7 +3366,7 @@ void parse_array_init(var_t *var, find_scoped_constant(token, parent); bool function_constant = lex_peek(T_identifier, token) && - find_func(token); + find_visible_func(token, initializer_scope); if (!lex_peek(T_numeric, NULL) && !lex_peek(T_minus, NULL) && @@ -3140,135 +3385,9 @@ void parse_array_init(var_t *var, next_token_loc()); } - /* `&function` is an address constant, but the ordinary - * expression reader only treats a bare function - * designator as a value. Preserve the symbol through - * global lowering so its final code address can be - * relocated. - */ - if (parent == GLOBAL_BLOCK && lex_accept(T_ampersand)) { - char function_name[MAX_ID_LEN]; - - if (lex_peek(T_identifier, function_name) && - find_func(function_name)) { - lex_ident(T_identifier, function_name); - val = require_func_symbol_var(parent); - val->is_func = true; - val->var_name = intern_string(function_name); - } else { - int index = 0; - int address_elem_size; - - if (lex_peek(T_identifier, global_token)) - object_constant = find_var( - global_token, initializer_scope); - if (!object_constant || - !object_constant->is_global) - error_at( - "Global array address initializer " - "requires a declared global object " - "or function", - next_token_loc()); - address_elem_size = - object_constant->ptr_level - ? PTR_SIZE - : object_constant->type->size; - if (object_constant->array_dim2) - address_elem_size *= - object_constant->array_dim2; - if (object_constant->array_dim3) - address_elem_size *= - object_constant->array_dim3; - if (object_constant->array_dim4) - address_elem_size *= - object_constant->array_dim4; - - val = require_ref_var( - parent, object_constant->type, - object_constant->ptr_level); - val->var_name = gen_name(); - val->is_global_address = true; - lex_ident(T_identifier, global_token); - add_insn(parent, *bb, OP_address_of, val, - object_constant, NULL, 0, NULL); - if (object_constant->array_size) { - int array_bounds[4] = {0, 0, 0, 0}; - int array_dims = 0; - int subscript_count = 0; - int inner_size = 1; - - if (object_constant->array_dim2) - inner_size *= - object_constant->array_dim2; - if (object_constant->array_dim3) - inner_size *= - object_constant->array_dim3; - if (object_constant->array_dim4) - inner_size *= - object_constant->array_dim4; - array_bounds[array_dims++] = - object_constant->array_size / - inner_size; - if (object_constant->array_dim2) - array_bounds[array_dims++] = - object_constant->array_dim2; - if (object_constant->array_dim3) - array_bounds[array_dims++] = - object_constant->array_dim3; - if (object_constant->array_dim4) - array_bounds[array_dims++] = - object_constant->array_dim4; - while (lex_accept(T_open_square)) { - int stride = - object_constant->ptr_level - ? PTR_SIZE - : object_constant->type->size; - - if (subscript_count >= array_dims) - error_at( - "Subscripted global address " - "needs an array object", - cur_token_loc()); - for (int dim = subscript_count + 1; - dim < array_dims; dim++) - stride *= array_bounds[dim]; - index = - read_const_expr(initializer_scope); - lex_expect(T_close_square); - val = compute_element_address( - parent, bb, val, index, stride); - val->ptr_level = - object_constant->ptr_level + 1; - val->is_global_address = true; - subscript_count++; - } - } else if (lex_accept(T_open_square)) { - error_at( - "Subscripted global address needs an " - "array object", - cur_token_loc()); - } - if (lex_accept(T_plus)) { - index = read_const_expr(initializer_scope); - val = compute_element_address( - parent, bb, val, index, - address_elem_size); - val->ptr_level = - object_constant->ptr_level + 1; - val->is_global_address = true; - } else if (lex_accept(T_minus)) { - index = read_const_expr(initializer_scope); - val = compute_element_address( - parent, bb, val, -index, - address_elem_size); - val->ptr_level = - object_constant->ptr_level + 1; - val->is_global_address = true; - } - } - } else if (parent == GLOBAL_BLOCK && object_constant && - object_constant->is_global && - object_constant->array_size) { + if (parent == GLOBAL_BLOCK && object_constant && + object_constant->is_global && + object_constant->array_size) { int stride = (object_constant->ptr_level || object_constant->is_func) ? PTR_SIZE @@ -3316,6 +3435,25 @@ void parse_array_init(var_t *var, error_at("Too many elements in array initializer", next_token_loc()); + if (val && var->type->array_element_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 = var->type->array_element_ptr_level; + element_target.pointee_func_signature = + var->type->array_element_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 && emit_code && (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 @@ -3379,6 +3517,7 @@ void parse_array_init(var_t *var, 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, @@ -3739,7 +3878,7 @@ int read_const_sizeof_type(block_t *scope) lex_accept(T_restrict)) { ; } else if (lex_peek(T_identifier, token)) { - type_t *candidate = find_type(token, true); + type_t *candidate = find_visible_type(token, scope); if (!candidate) break; @@ -3877,6 +4016,15 @@ int read_const_sizeof_type(block_t *scope) lex_expect(T_close_bracket); if (ptr_level) return PTR_SIZE; + if (type == TY_void) + error_at("sizeof cannot be applied to void", cur_token_loc()); + if (type->is_direct_function_type) + error_at("sizeof(function) is invalid", cur_token_loc()); + if (!type->size && (!type->base_struct || !type->base_struct->size)) + error_at("sizeof cannot be applied to an incomplete type", + cur_token_loc()); + if (!array_size && !ptr_level && type->array_size) + array_size = type->array_size; if (array_size) return array_size * (array_element_size ? array_element_size : type->size); @@ -3885,6 +4033,42 @@ int read_const_sizeof_type(block_t *scope) return type->base_struct->size; } +/* 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()); + } +} + int read_const_expr_operand(block_t *scope) { char buffer[MAX_TOKEN_LEN]; @@ -3954,6 +4138,7 @@ int read_const_expr_operand(block_t *scope) 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 && @@ -4041,11 +4226,15 @@ void read_inner_var_decl(var_t *vd, { /* 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; - /* Declarator continuations inherit base metadata. A new suffix must not - * retain a fourth bound from an earlier declarator. + /* 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. */ - vd->array_dim4 = 0; + 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. @@ -4081,14 +4270,11 @@ void read_inner_var_decl(var_t *vd, * allocation loads p's value and postfix indexing still advances a row. */ if (vd->ptr_level && vd->type && vd->type->array_size) { - vd->pointee_array_size = vd->type->array_size; - vd->pointee_array_dim2 = vd->type->array_dim2; - vd->pointee_array_dim3 = vd->type->array_dim3; - vd->pointee_array_dim4 = vd->type->array_dim4; - vd->array_size = 0; - vd->array_dim2 = 0; - vd->array_dim3 = 0; - vd->array_dim4 = 0; + 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); } /* is it function pointer declaration? */ @@ -4101,13 +4287,34 @@ void read_inner_var_decl(var_t *vd, lex_expect(T_asterisk); nested_ptr_level++; while (true) { - if (lex_accept(T_const)) + 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; - else if (lex_accept(T_volatile)) + 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)) - ; /* restrict is an aliasing contract, not storage state. */ - else + } 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)); @@ -4192,9 +4399,18 @@ void read_inner_var_decl(var_t *vd, 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; while (lex_accept(T_open_square)) { int bound; @@ -4221,6 +4437,9 @@ void read_inner_var_decl(var_t *vd, lex_expect(T_close_square); dims++; } + if (dims && void_array_element) + error_at("void type cannot be an array element", + cur_token_loc()); return; } @@ -4234,6 +4453,37 @@ void read_inner_var_decl(var_t *vd, read_parameter_list_decl(func, true); 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. + */ + if (vd->parenthesized_function_pointer_inner_qualified) + error_at( + "inner callback-slot pointer qualifiers are not yet " + "supported", + cur_token_loc()); + if (vd->parenthesized_function_pointer_restrict && + !vd->parenthesized_function_pointer_outer_restrict) + error_at("callback-slot restrict typedef is not yet supported", + cur_token_loc()); + + /* The second star is the outer slot pointer. After collapsing the + * compact callback representation to one retained pointer level, + * move that star's const bit from level two to level one. + */ + if (vd->parenthesized_function_pointer_outer_const) { + vd->pointer_const_mask &= ~2U; + vd->pointer_const_mask |= 1U; + vd->is_const_pointer = true; + } + vd->ptr_level += nested_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 @@ -4268,7 +4518,26 @@ void read_inner_var_decl(var_t *vd, 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; @@ -4358,6 +4627,14 @@ void read_inner_var_decl(var_t *vd, 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. @@ -4369,11 +4646,49 @@ void read_inner_var_decl(var_t *vd, "element", cur_token_loc()); + /* 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 || vd->type->ptr_level || vd->array_size) { + 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 || 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. @@ -4391,6 +4706,15 @@ void read_inner_var_decl(var_t *vd, 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()); } /* starting next_token, need to check the type */ @@ -4480,10 +4804,11 @@ void read_full_var_decl(var_t *vd, if (is_enum_type && (is_signed || is_unsigned || is_long)) error_at("enum type cannot be combined with integer specifiers", cur_token_loc()); + bool is_record_type = lex_peek(T_struct, NULL) || lex_peek(T_union, NULL); + bool is_union_type = lex_accept(T_union); int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { + if (is_union_type) find_type_flag = 2; - } type_t *type; /* `signed` has the existing signed scalar semantics. C permits its `int` @@ -4554,7 +4879,15 @@ void read_full_var_decl(var_t *vd, type = leading_scalar_type; } else { lex_ident(T_identifier, type_name); - type = find_type(type_name, find_type_flag); + type = find_type_flag == 2 ? find_record_tag(type_name, vd->scope) + : find_visible_type(type_name, vd->scope); + if (!type && find_type_flag == 2) + type = find_type(type_name, 2); + if (find_type_flag == 2 && + (!type || !is_record_type || + (is_union_type ? type->base_type != TYPE_union + : type->base_type != TYPE_struct))) + error_at("Unknown struct/union type", cur_token_loc()); } if (!type) { @@ -4566,13 +4899,25 @@ void read_full_var_decl(var_t *vd, 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) { - vd->array_size = type->array_size; - vd->array_dim2 = type->array_dim2; - vd->array_dim3 = type->array_dim3; - vd->array_dim4 = type->array_dim4; + 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 = @@ -4589,7 +4934,14 @@ void read_full_var_decl(var_t *vd, else if (lex_accept(T_volatile)) is_volatile = true; else if (lex_accept(T_restrict)) { - if (!type->ptr_level) + /* 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 || declaration_inline) @@ -4600,11 +4952,14 @@ void read_full_var_decl(var_t *vd, break; } vd->is_inline = declaration_inline || is_inline; - vd->is_volatile = declaration_volatile || is_volatile; + vd->is_volatile = + declaration_volatile || is_volatile || type->is_volatile_qualified; if (is_const || declaration_const) { - if (type->ptr_level) + if (type->ptr_level) { vd->is_const_pointer = true; - else + if (type->pointee_func_signature && type->ptr_level == 1) + vd->pointer_const_mask |= 1U; + } else vd->is_const_qualified = true; } @@ -4656,6 +5011,7 @@ void read_parameter_list_decl(func_t *func, bool anon) 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) @@ -4688,6 +5044,7 @@ void read_parameter_list_decl(func_t *func, bool anon) 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); if (func->param_defs[vn].is_inline) error_at("inline specifier requires a function declarator", @@ -4921,6 +5278,14 @@ bool numeric_literal_needs_wide_path(const char *token) */ 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; if (PTR_SIZE < 8) @@ -5219,6 +5584,11 @@ void read_func_parameters_with_sret(func_t *func, "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); if (is_record_type(target->type) && !has_effective_pointer(target)) { @@ -5335,20 +5705,14 @@ void read_func_call(func_t *func, block_t *parent, basic_block_t **bb) */ void copy_call_result_array_shape(var_t *result, const var_t *return_def) { + fixed_array_shape_t shape; + if (!result || !return_def) return; - result->pointee_array_size = return_def->pointee_array_size - ? return_def->pointee_array_size - : return_def->type->array_size; - result->pointee_array_dim2 = return_def->pointee_array_dim2 - ? return_def->pointee_array_dim2 - : return_def->type->array_dim2; - result->pointee_array_dim3 = return_def->pointee_array_dim3 - ? return_def->pointee_array_dim3 - : return_def->type->array_dim3; - result->pointee_array_dim4 = return_def->pointee_array_dim4 - ? return_def->pointee_array_dim4 - : return_def->type->array_dim4; + 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); result->pointee_array_element_ptr_level = return_def->pointee_array_element_ptr_level ? return_def->pointee_array_element_ptr_level @@ -5363,6 +5727,7 @@ void lower_call_result_array_postfix(var_t **value, { var_t *base; var_t *address; + fixed_array_shape_t shape; int dimensions; int depth = 0; @@ -5370,11 +5735,12 @@ void lower_call_result_array_postfix(var_t **value, !lex_peek(T_open_square, NULL)) return; + shape = fixed_array_shape_from_pointee_var(base); + /* One subscript selects the pointed-to array; its outer bound and every * inner bound then consume their own postfix subscript. */ - dimensions = 2 + !!base->pointee_array_dim2 + !!base->pointee_array_dim3 + - !!base->pointee_array_dim4; + dimensions = shape.rank + 1; opstack_pop(); address = base; do { @@ -5387,19 +5753,8 @@ void lower_call_result_array_postfix(var_t **value, cur_token_loc()); if (depth == 0) { stride *= base->pointee_array_size; - } else if (depth == 1 && base->pointee_array_dim2) { - stride *= base->pointee_array_dim2; - if (base->pointee_array_dim3) - stride *= base->pointee_array_dim3; - if (base->pointee_array_dim4) - stride *= base->pointee_array_dim4; - } else if (depth == 2 && base->pointee_array_dim3) { - stride *= base->pointee_array_dim3; - if (base->pointee_array_dim4) - stride *= base->pointee_array_dim4; - } else if (depth == 3 && base->pointee_array_dim4) { - stride *= base->pointee_array_dim4; - } + } else + stride = fixed_array_shape_stride(&shape, depth - 1, stride); lex_expect(T_open_square); if (!read_assignment_expression(parent, bb)) { @@ -5590,6 +5945,7 @@ void read_builtin_offsetof(block_t *parent, basic_block_t **bb) 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))) @@ -6430,7 +6786,7 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) * objects and previously made `&function` fail outside file-scope scalar * initializers. */ - func_t *addressed_func = find_func(token); + 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) @@ -6464,6 +6820,11 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) 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; opstack_push(vd); if (lvalue.decl && is_array_declarator(lvalue.decl)) { /* An array expression already denotes its backing address in the @@ -6479,6 +6840,136 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) } } +/* A scalar pointer-to-array dereference designates the addressed row; it does + * not load a scalar object. Retain that row as an array descriptor so a + * following grouped postfix subscript can use the ordinary array path. + */ +static bool lower_scalar_pointee_array_dereference(var_t *source, + block_t *parent, + basic_block_t **bb) +{ + type_t *element_type; + var_t *row; + + if (!source || !source->type || !source->pointee_array_size || + source->pointee_array_element_ptr_level || + effective_pointer_depth(source) != 1) + return false; + + element_type = source->type->pointee_array_element_type + ? source->type->pointee_array_element_type + : source->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->is_const_qualified = + source->is_const_qualified || element_type->is_const_qualified; + row->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, row, source, NULL, 0, NULL); + opstack_push(row); + return true; +} + +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; +} + +static bool is_scalar_pointee_array_pointer(const var_t *var) +{ + return var && var->type && var->pointee_array_size > 0 && + !var->pointee_array_element_ptr_level && + effective_pointer_depth(var) == 1; +} + +static int scalar_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; + + 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 scalar_pointee_array_shapes_compatible(const var_t *left, + const var_t *right) +{ + type_t *left_element; + type_t *right_element; + + if (!is_scalar_pointee_array_pointer(left) || + !is_scalar_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); +} + void handle_single_dereference(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1; @@ -6505,6 +6996,9 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) return; } + if (lower_scalar_pointee_array_dereference(rs1, parent, bb)) + 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 @@ -6611,7 +7105,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) * route it through the same symbol representation used by ordinary * direct calls instead of asking read_lvalue() to find object storage. */ - if (!var && find_func(token)) { + if (!var && find_visible_func(token, parent)) { lex_expect(T_identifier); rs1 = require_func_symbol_var(parent); rs1->var_name = intern_string(token); @@ -6630,6 +7124,8 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) opstack_push(load_function_pointer_object(parent, bb, rs1)); return; } + if (lower_scalar_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 @@ -6663,12 +7159,14 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) * cleared to prevent the invalid `slot(...)` form, so restore the * element signature only after the real dereference has occurred. */ - if ((rs1->func_signature || + if ((rs1->func_signature || rs1->pointee_func_signature || (deref_type && deref_type->func_signature)) && effective_pointer_depth(rs1) == 1) { vd->func_signature = rs1->func_signature ? rs1->func_signature - : deref_type->func_signature; + : (rs1->pointee_func_signature + ? rs1->pointee_func_signature + : deref_type->func_signature); vd->ptr_level = 1; sz = PTR_SIZE; } @@ -6869,40 +7367,311 @@ int sizeof_array_object(const var_t *array) return array->array_size * element_size; } -/* `&array` points to the complete array object, and dereferencing that pointer - * restores an array lvalue. Ordinary expression lowering deliberately decays - * arrays before it can represent the unary pair, so recognize the complete - * `*&identifier` operand here while sizeof still has access to its declaration. +/* 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. */ -bool read_sizeof_address_dereference_array(block_t *parent, - basic_block_t **bb, - bool parenthesized) +static bool scan_sizeof_postfix_operand(block_t *scope, + token_t **cursor, + var_t *object, + int *members) { - token_t *star = cur_token->next; - token_t *ampersand; - token_t *identifier; - var_t *array; - var_t *result; + token_t *token = *cursor; - if (!star || star->kind != T_asterisk || !star->next || - star->next->kind != T_ampersand || !star->next->next || - star->next->next->kind != T_identifier) - return false; - ampersand = star->next; - identifier = ampersand->next; - if (parenthesized && - (!identifier->next || identifier->next->kind != T_close_bracket)) + if (!token) return false; + if (token->kind == T_asterisk) { + int depth; - array = find_var(identifier->literal, parent); - if (!array || array->array_size <= 0) + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, members)) + return false; + depth = effective_pointer_depth(object); + if (depth <= 0) + return false; + object->type = pointee_type_from_pointer_typedef(object->type); + object->ptr_level = depth - 1; + } else if (token->kind == T_open_bracket) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, members) || + !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); + + if (!root) + return false; + memcpy(object, root, sizeof(*object)); + token = token->next; + } else return false; - lex_expect(T_asterisk); - lex_expect(T_ampersand); - lex_expect(T_identifier); - if (parenthesized) - lex_expect(T_close_bracket); + while (token && (token->kind == T_dot || token->kind == T_arrow || + token->kind == T_open_square)) { + if (token->kind == T_dot || token->kind == T_arrow) { + bool through_pointer = token->kind == T_arrow; + type_t *record; + var_t *field; + + token = token->next; + if (!token || token->kind != T_identifier || + (through_pointer && effective_pointer_depth(object) != 1) || + (!through_pointer && effective_pointer_depth(object) != 0)) + return false; + record = through_pointer + ? pointee_type_from_pointer_typedef(object->type) + : object->type; + if (!is_record_type(record)) + return false; + field = find_member(token->literal, record); + if (!field) + return false; + memcpy(object, field, sizeof(*object)); + *members = *members + 1; + token = token->next; + } else { + int depth = 0; + fixed_array_shape_t shape; + + /* An array may legitimately have pointer elements. Reject a scalar + * pointer here, but retain the element pointer depth until after + * this array subscript so a following `->` can consume it. + */ + if (object->array_size <= 0) + return false; + 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; + shape = fixed_array_shape_from_var(object); + if (!fixed_array_shape_drop_outer(&shape)) + return false; + fixed_array_shape_to_var(object, &shape); + } + } + *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. + */ +static void consume_sizeof_postfix_operand(block_t *parent, basic_block_t **bb) +{ + if (lex_accept(T_asterisk)) + consume_sizeof_postfix_operand(parent, bb); + else if (lex_accept(T_open_bracket)) { + consume_sizeof_postfix_operand(parent, bb); + 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; + int saved_side_effects; + + lex_expect(T_open_square); + unevaluated_bb = bb_create(parent); + saved_side_effects = se_idx; + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + opstack_pop(); + se_idx = saved_side_effects; + lex_expect(T_close_square); + } + } +} + +/* A non-mutating admission check for callers that need to hand a global sizeof + * operand to the detached local parser. Keep its accepted grammar in lockstep + * with read_sizeof_postfix_operand() so type names and scalar forms remain + * owned by their existing global constant paths. + */ +static bool can_scan_sizeof_postfix_extent(block_t *scope, + token_t *start, + bool parenthesized) +{ + token_t *tail = start; + var_t object; + int members = 0; + + return scan_sizeof_postfix_operand(scope, &tail, &object, &members) && + members && tail && + (parenthesized + ? tail->kind == T_close_bracket + : tail->kind != T_open_bracket && tail->kind != T_increment && + tail->kind != T_decrement) && + !effective_pointer_depth(&object) && object.array_size > 0; +} + +/* First staged type-only postfix walker: identifier roots, balanced grouping, + * record members, and fixed-array postfixes. Other sizeof forms keep their + * dedicated fallbacks until their primary operations are migrated here. + */ +static bool read_sizeof_postfix_operand(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + token_t *tail = cur_token->next; + var_t object; + int members = 0; + var_t *result; + + if (!can_scan_sizeof_postfix_extent(parent, tail, parenthesized)) + return false; + scan_sizeof_postfix_operand(parent, &tail, &object, &members); + if (object.is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + 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); + if (parenthesized) + lex_expect(T_close_bracket); + result = require_var(parent); + result->init_val = sizeof_array_object(&object); + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + 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)); +} + +/* A direct pointer-to-fixed-array dereference designates its complete row. + * Ordinary expression lowering decays that row before sizeof can observe it, so + * retain this exact unary form while the declaration's pointee extent is still + * available. + */ +static bool read_sizeof_dereference_pointee_array(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + token_t *star = cur_token->next; + token_t *identifier; + token_t *tail; + var_t *pointer; + type_t *element_type; + var_t *result; + int element_size; + bool indexes_fixed_array_pointer_slot = false; + + if (!star || star->kind != T_asterisk || !(identifier = star->next) || + identifier->kind != T_identifier) + return false; + pointer = find_var(identifier->literal, parent); + if (!pointer || !pointer->pointee_array_size) + return false; + tail = identifier->next; + if (tail && tail->kind == T_open_square) { + int depth = 0; + + indexes_fixed_array_pointer_slot = + is_direct_fixed_array_pointer_slot(pointer); + for (; tail; tail = tail->next) { + if (tail->kind == T_open_square) + depth++; + else if (tail->kind == T_close_square && --depth == 0) { + tail = tail->next; + break; + } + } + if (!indexes_fixed_array_pointer_slot || depth) + return false; + } + if (parenthesized && (!tail || tail->kind != T_close_bracket)) + return false; + if (effective_pointer_depth(pointer) != + pointer->pointee_array_element_ptr_level + 1) + return false; + + element_type = pointer->type->pointee_array_element_type + ? pointer->type->pointee_array_element_type + : pointer->type; + element_size = pointer->pointee_array_element_ptr_level + ? PTR_SIZE + : element_type->size; + lex_expect(T_asterisk); + lex_expect(T_identifier); + if (indexes_fixed_array_pointer_slot) { + basic_block_t *unevaluated_bb; + int saved_side_effects; + + lex_expect(T_open_square); + unevaluated_bb = bb_create(parent); + saved_side_effects = se_idx; + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + opstack_pop(); + se_idx = saved_side_effects; + lex_expect(T_close_square); + } + if (parenthesized) + lex_expect(T_close_bracket); + result = require_var(parent); + result->init_val = pointer->pointee_array_size * element_size; + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; +} + +/* `&array` points to the complete array object, and dereferencing that pointer + * restores an array lvalue. Ordinary expression lowering deliberately decays + * arrays before it can represent the unary pair, so recognize the complete + * `*&identifier` operand here while sizeof still has access to its declaration. + */ +bool read_sizeof_address_dereference_array(block_t *parent, + basic_block_t **bb, + bool parenthesized) +{ + token_t *star = cur_token->next; + token_t *ampersand; + token_t *identifier; + var_t *array; + var_t *result; + + if (!star || star->kind != T_asterisk || !star->next || + star->next->kind != T_ampersand || !star->next->next || + star->next->next->kind != T_identifier) + return false; + ampersand = star->next; + identifier = ampersand->next; + if (parenthesized && + (!identifier->next || identifier->next->kind != T_close_bracket)) + return false; + + array = find_var(identifier->literal, parent); + if (!array || array->array_size <= 0) + return false; + + lex_expect(T_asterisk); + lex_expect(T_ampersand); + lex_expect(T_identifier); + if (parenthesized) + lex_expect(T_close_bracket); result = require_var(parent); result->init_val = sizeof_array_object(array); @@ -7019,12 +7788,14 @@ bool read_sizeof_member_array_row(block_t *parent, token_t *member_token; token_t *tail; var_t *object; - var_t *field; + var_t *field = NULL; + type_t *member_record; int object_ptr_level; int subscript_count = 0; int dimensions; int row_elements; int nested_parentheses = 0; + int grouped_member_closes = 0; /* sizeof((object.member[index])) preserves the member-array lvalue just * like the ungrouped spelling. Peel only groups that enclose the complete @@ -7041,9 +7812,7 @@ bool read_sizeof_member_array_row(block_t *parent, (object_token->next->kind != T_dot && object_token->next->kind != T_arrow) || !object_token->next->next || - object_token->next->next->kind != T_identifier || - !object_token->next->next->next || - object_token->next->next->next->kind != T_open_square) + object_token->next->next->kind != T_identifier) return false; object = find_var(object_token->literal, parent); object_ptr_level = object ? object->ptr_level + object->type->ptr_level : 0; @@ -7052,11 +7821,39 @@ bool read_sizeof_member_array_row(block_t *parent, (object_token->next->kind == T_arrow && object_ptr_level != 1))) return false; member_token = object_token->next->next; - field = find_member(member_token->literal, object->type); - if (!field || field->ptr_level || field->array_dim2 <= 0) + member_record = object->type; + for (;;) { + field = find_member(member_token->literal, member_record); + if (!field) + return false; + tail = member_token->next; + if (!tail || (tail->kind != T_dot && tail->kind != T_arrow)) + break; + if (!tail->next || tail->next->kind != T_identifier) + return false; + if (tail->kind == T_dot) { + if (effective_pointer_depth(field) != 0) + return false; + member_record = field->type; + } else { + if (effective_pointer_depth(field) != 1) + return false; + member_record = pointee_type_from_pointer_typedef(field->type); + } + member_token = tail->next; + } + if (!field || effective_pointer_depth(field) || field->array_dim2 <= 0) return false; - tail = member_token->next; + /* A group can enclose the member lvalue before a following subscript, e.g. + * sizeof((((record.rows)))[1]). Consume only its matched closes; any + * remaining groups still enclose the complete postfix expression. + */ + while (grouped_member_closes < nested_parentheses && tail && + tail->kind == T_close_bracket) { + grouped_member_closes++; + tail = tail->next; + } while (tail && tail->kind == T_open_square) { int depth = 0; for (; tail; tail = tail->next) { @@ -7073,7 +7870,7 @@ bool read_sizeof_member_array_row(block_t *parent, } if (!tail) return false; - for (int i = 0; i < nested_parentheses; i++) { + for (int i = grouped_member_closes; i < nested_parentheses; i++) { if (!tail || tail->kind != T_close_bracket) return false; tail = tail->next; @@ -7096,6 +7893,15 @@ bool read_sizeof_member_array_row(block_t *parent, else lex_expect(T_dot); lex_expect(T_identifier); + while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { + if (lex_peek(T_arrow, NULL)) + lex_expect(T_arrow); + else + lex_expect(T_dot); + lex_expect(T_identifier); + } + for (int i = 0; i < grouped_member_closes; i++) + lex_expect(T_close_bracket); for (int i = 0; i < subscript_count; i++) { lex_expect(T_open_square); basic_block_t *unevaluated_bb = bb_create(parent); @@ -7106,7 +7912,7 @@ bool read_sizeof_member_array_row(block_t *parent, se_idx = saved_side_effects; lex_expect(T_close_square); } - for (int i = 0; i < nested_parentheses; i++) + for (int i = grouped_member_closes; i < nested_parentheses; i++) lex_expect(T_close_bracket); if (parenthesized) lex_expect(T_close_bracket); @@ -7326,8 +8132,12 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) return; } + if (read_sizeof_dereference_pointee_array(parent, bb, parenthesized)) + return; if (read_sizeof_address_dereference_array(parent, bb, parenthesized)) return; + if (read_sizeof_postfix_operand(parent, bb, parenthesized)) + return; if (read_sizeof_member_array(parent, bb, parenthesized)) return; if (read_sizeof_member_array_row(parent, bb, parenthesized)) @@ -7594,7 +8404,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) } } else if (lex_peek(T_identifier, token)) { /* Try to parse as a type first */ - type = find_type(token, find_type_flag); + type = find_type_flag == 2 ? find_type(token, find_type_flag) + : find_visible_type(token, parent); if (type) { /* sizeof(type) */ lex_expect(T_identifier); @@ -7731,15 +8542,20 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) 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 && ptr_cnt == 0) + 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; vd = require_var(parent); vd->init_val = type->size; if (array_size > 0) @@ -7758,18 +8574,288 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); } +/* 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(void) +{ + 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 || !(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_scalar_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(void) +{ + 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 || + !(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_scalar_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); +} + void read_expr_operand(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1; var_t *compound_object = NULL; bool is_neg = false; + if (grouped_scalar_pointee_row_postfix_starts()) { + 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()) { + 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->next && - cur_token->next->next->next->kind == T_open_square))) { + cur_token->next->next->kind == T_open_bracket) { opcode_t op = prefix_increment ? OP_add : OP_sub; var_t *object; var_t *one; @@ -8815,20 +9901,28 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_address_of, base_addr, base, NULL, 0, NULL); address = base_addr; - do { + 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); - if (!lex_accept(T_dot)) + + /* 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; - } while (true); + } value_type = field->type; value_ptr_level = field->ptr_level; value_size = field->type->size; @@ -9324,7 +10418,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* 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_func(token); + 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 @@ -9690,7 +10784,14 @@ void handle_pointer_arithmetic(block_t *parent, pointee_type_from_pointer_typedef(orig_rs2->type); int left_depth = orig_rs1->ptr_level + orig_rs1->type->ptr_level; int right_depth = orig_rs2->ptr_level + orig_rs2->type->ptr_level; + bool left_scalar_row = is_scalar_pointee_array_pointer(orig_rs1); + bool right_scalar_row = is_scalar_pointee_array_pointer(orig_rs2); + if ((left_scalar_row || right_scalar_row) && + !scalar_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( @@ -9699,7 +10800,9 @@ void handle_pointer_arithmetic(block_t *parent, element_size = PTR_SIZE; /* Default */ - element_size = get_pointer_element_size(orig_rs1); + element_size = left_scalar_row + ? scalar_pointee_array_row_stride(orig_rs1) + : get_pointer_element_size(orig_rs1); /* Perform subtraction first */ var_t *diff = require_var(parent); @@ -9729,13 +10832,25 @@ void handle_pointer_arithmetic(block_t *parent, if (is_pointer_like_value(rs1)) { ptr_var = rs1; int_var = rs2; - element_size = get_pointer_element_size(rs1); + element_size = + is_scalar_pointee_array_pointer(rs1) + ? scalar_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 = get_pointer_element_size(rs2); + element_size = + is_scalar_pointee_array_pointer(rs2) + ? scalar_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; @@ -9770,6 +10885,15 @@ void handle_pointer_arithmetic(block_t *parent, * typedefs cannot be mistaken for a direct callback value. */ vd->ptr_level = ptr_var->ptr_level + !!ptr_var->array_size; + if (is_scalar_pointee_array_pointer(ptr_var)) + 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); @@ -10306,6 +11430,10 @@ void read_lvalue(lvalue_t *lvalue, bool allow_ptr_arith) { 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; @@ -10338,6 +11466,7 @@ void read_lvalue(lvalue_t *lvalue, 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, @@ -10345,7 +11474,7 @@ void read_lvalue(lvalue_t *lvalue, */ lvalue->is_const_qualified = (var->ptr_level || (var->type && var->type->ptr_level) || - var->array_size || var->has_unsized_array) + 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; @@ -10360,16 +11489,41 @@ void read_lvalue(lvalue_t *lvalue, lex_peek(T_dot, NULL)) { if (lex_accept(T_open_square)) { int indexed_ptr_level; + 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. */ - if (lvalue->is_reference && lvalue->ptr_level && is_member) { + if (lvalue->is_reference && + (lvalue->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); @@ -10401,26 +11555,68 @@ void read_lvalue(lvalue_t *lvalue, 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); 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; + /* `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_direct_pointee_array + 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 ((var->ptr_level <= 1 || is_typedef_pointer) && - !is_array_declarator(var)) { + 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 */ @@ -10447,33 +11643,25 @@ void read_lvalue(lvalue_t *lvalue, int multiplier = lvalue->size; if (subscript_depth == 0 && var->array_dim2 > 0) { - multiplier = var->array_dim2 * lvalue->size; - if (var->array_dim3 > 0) - multiplier *= var->array_dim3; - if (var->array_dim4 > 0) - multiplier *= var->array_dim4; - } else if (var->pointee_array_size > 0 && - (indexes_direct_pointee_array || - indexed_ptr_level == 1)) { - multiplier = var->pointee_array_size * lvalue->size; - } else if (subscript_depth == 1 && var->pointee_array_dim2 > 0) { - multiplier = var->pointee_array_dim2 * lvalue->size; - if (var->pointee_array_dim3 > 0) - multiplier *= var->pointee_array_dim3; - if (var->pointee_array_dim4 > 0) - multiplier *= var->pointee_array_dim4; - } else if (subscript_depth == 2 && var->pointee_array_dim3 > 0) { - multiplier = var->pointee_array_dim3 * lvalue->size; - if (var->pointee_array_dim4 > 0) - multiplier *= var->pointee_array_dim4; - } else if (subscript_depth == 3 && var->pointee_array_dim4 > 0) { - multiplier = var->pointee_array_dim4 * lvalue->size; - } else if (subscript_depth == 1 && var->array_dim3 > 0) { - multiplier = var->array_dim3 * lvalue->size; - if (var->array_dim4 > 0) - multiplier *= var->array_dim4; - } else if (subscript_depth == 2 && var->array_dim4 > 0) { - multiplier = var->array_dim4 * lvalue->size; + 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) { @@ -10522,15 +11710,43 @@ void read_lvalue(lvalue_t *lvalue, * every subscript as a member would dereference row data on its * next index. */ - is_member = - !indexes_direct_pointee_array && lvalue->value_ptr_level > 0; + is_member = !indexes_pointee_row && lvalue->value_ptr_level > 0; subscript_depth++; - lvalue->is_reference = !indexes_direct_pointee_array; + lvalue->is_reference = !indexes_pointee_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`. */ - lvalue->is_const_qualified = var->is_const_qualified; + lvalue->is_const_qualified = + var->is_const_qualified || + (lvalue->pointee_func_signature && + var->type->array_element_is_const_pointer); } else { char token[MAX_ID_LEN]; @@ -10613,13 +11829,16 @@ void read_lvalue(lvalue_t *lvalue, * been dereferenced. After array indexing like arr[0], we have a value, not * a pointer. */ + bool scalar_pointee_row = is_scalar_pointee_array_pointer(var); if (allow_ptr_arith && lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var)) && !lvalue->is_reference) { + (var->ptr_level || is_array_declarator(var) || scalar_pointee_row) && + !lvalue->is_reference) { if (!is_array_declarator(var) && is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); while (lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var))) { + (var->ptr_level || is_array_declarator(var) || + scalar_pointee_row)) { lex_expect(T_plus); if (lvalue->is_reference) { rs1 = opstack_pop(); @@ -10636,8 +11855,13 @@ void read_lvalue(lvalue_t *lvalue, * 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 || - (is_array_declarator(var) && var->ptr_level)) + if (scalar_pointee_row) { + lvalue->size = scalar_pointee_array_row_stride(var); + } else if (is_array_declarator(var) && var->array_dim2 && + !has_effective_pointer(var) && !var->is_func) { + lvalue->size = fixed_array_decay_stride(var); + } else if (var->ptr_level > 1 || + (is_array_declarator(var) && var->ptr_level)) lvalue->size = PTR_SIZE; else lvalue->size = lvalue->type->size; @@ -10666,9 +11890,19 @@ void read_lvalue(lvalue_t *lvalue, * first decay to a pointer, adding one level to the declaration's * element indirection. */ - if (var->ptr_level || is_array_declarator(var)) { + if (var->ptr_level || is_array_declarator(var) || + scalar_pointee_row) { vd->type = lvalue->type; vd->ptr_level = var->ptr_level + !!is_array_declarator(var); + if (scalar_pointee_row) + copy_pointee_array_shape(vd, var); + else if (is_array_declarator(var) && var->array_dim2 && + !has_effective_pointer(var) && !var->is_func) { + fixed_array_shape_t shape = fixed_array_shape_from_var(var); + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_pointee_var(vd, &shape); + } } vd->var_name = gen_name(); vd->is_const_qualified = var->is_const_qualified; @@ -10693,14 +11927,30 @@ void read_lvalue(lvalue_t *lvalue, rs1 = operand_stack[operand_stack_idx - 1]; t = require_var(parent); t->var_name = gen_name(); - t->type = pointee_type_from_pointer_typedef(lvalue->type); + 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; /* 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; + 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; + } opstack_push(t); if (is_bitfield(lvalue->decl)) { /* Bit-fields are values, never independently addressable @@ -10724,7 +11974,9 @@ void read_lvalue(lvalue_t *lvalue, /* For pointer arithmetic, increment by the size of pointed-to type */ - if (lvalue->ptr_level > 1) + if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) + vd->init_val = scalar_pointee_array_row_stride(var); + else if (lvalue->ptr_level > 1) vd->init_val = PTR_SIZE; else if (lvalue->ptr_level) vd->init_val = lvalue->type->size; @@ -10802,7 +12054,9 @@ void read_lvalue(lvalue_t *lvalue, /* Calculate increment size based on pointer type */ int increment_size = 1; - if (lvalue->ptr_level > 1 && !lvalue->is_reference) { + if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) { + increment_size = scalar_pointee_array_row_stride(var); + } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { increment_size = PTR_SIZE; } else if (lvalue->ptr_level && !lvalue->is_reference) { increment_size = lvalue->type->size; @@ -10853,6 +12107,11 @@ void read_lvalue(lvalue_t *lvalue, side_effect[se_idx].rs1 = operand_stack[operand_stack_idx - 1]; vd = require_var(parent); vd->var_name = gen_name(); + if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) { + vd->type = lvalue->type; + vd->ptr_level = var->ptr_level; + copy_pointee_array_shape(vd, var); + } side_effect[se_idx].rd = vd; se_idx++; @@ -11081,6 +12340,124 @@ basic_block_t *read_full_expression_statement(block_t *parent, 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(void) +{ + 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 || !(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_scalar_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); + } + 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 @@ -11164,6 +12541,8 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) 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); @@ -11188,7 +12567,28 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) vd = require_var(parent); vd->var_name = gen_name(); - if (true_signature || false_signature) { + 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; @@ -11314,7 +12714,10 @@ bool read_body_assignment(char *token, /* 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 > 1 && !lvalue.is_reference) + if (!one && (op == OP_add || op == OP_sub) && + !lvalue.is_reference && is_scalar_pointee_array_pointer(var)) + increment_size = scalar_pointee_array_row_stride(var); + else if (lvalue.ptr_level > 1 && !lvalue.is_reference) increment_size = PTR_SIZE; else if (lvalue.ptr_level && !lvalue.is_reference) increment_size = lvalue.type->size; @@ -11551,6 +12954,27 @@ bool read_body_assignment(char *token, } else if (lvalue.is_reference) { rs2 = opstack_pop(); rs1 = opstack_pop(); + 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()); + } if (is_bitfield(lvalue.decl)) write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); else @@ -11593,6 +13017,9 @@ bool read_body_assignment(char *token, 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 if (incompatible_const_pointer_conversion(rs1, vd)) { diagnose_const_pointer_conversion(rs1, vd); } else { @@ -11606,6 +13033,7 @@ bool read_body_assignment(char *token, } return true; } + return false; } @@ -11713,6 +13141,7 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) var_t *field = NULL; opcode_t compound_op = OP_generic; int store_size; + int grouped_fixed_array_dimensions = 0; type_t *value_type; int value_ptr_level; @@ -11728,6 +13157,77 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) while (lvalue_paren_depth-- > 0) lex_expect(T_close_bracket); + /* The ordinary `(*pointer)` assignment path historically stopped at the + * grouping close. Admit only exact scalar fixed-array suffixes here, + * retaining its common store/result lowering below. + */ + if (lex_peek(T_open_square, NULL)) { + var_t shape = {0}; + fixed_array_shape_t array_shape; + int depth = 0; + int dimensions; + + if (!address->pointee_array_size || + address->pointee_array_element_ptr_level || + effective_pointer_depth(address) != 1) + error_at( + "Grouped subscript requires a scalar fixed array pointer", + cur_token_loc()); + shape.type = address->type->pointee_array_element_type + ? address->type->pointee_array_element_type + : address->type; + array_shape = fixed_array_shape_from_pointee_var(address); + fixed_array_shape_to_var(&shape, &array_shape); + dimensions = array_shape.rank; + grouped_fixed_array_dimensions = dimensions; + if (dimensions > 4) + error_at( + "Grouped fixed-array assignment supports at most four " + "dimensions", + cur_token_loc()); + while (lex_accept(T_open_square)) { + var_t *index; + var_t *scale; + var_t *offset; + var_t *next; + 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_subscript_stride(&shape, depth, + shape.type->size); + offset = index; + if (stride != 1) { + 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); + offset = require_var(parent); + offset->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, offset, index, scale, 0, + NULL); + } + next = require_typed_ptr_var(parent, shape.type, 1); + next->var_name = gen_name(); + next->is_const_qualified = object_address->is_const_qualified || + shape.type->is_const_qualified; + add_insn(parent, *bb, OP_add, next, address, offset, 0, NULL); + address = next; + depth++; + } + if (depth != dimensions) + error_at("Grouped fixed-array assignment needs all subscripts", + cur_token_loc()); + } + 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); @@ -11770,6 +13270,14 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) if (!lex_accept(T_assign) && !accept_compound_assign_op(&compound_op)) error_at("Expected assignment after pointer dereference", cur_token_loc()); + if (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && + compound_op != OP_add && compound_op != OP_sub && + compound_op != OP_mul && compound_op != OP_div && + compound_op != OP_mod && compound_op != OP_lshift && + compound_op != OP_rshift && compound_op != OP_bit_and && + compound_op != OP_bit_xor && compound_op != OP_bit_or) + error_at("Unsupported rank-three/four grouped compound assignment", + cur_token_loc()); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); @@ -11777,6 +13285,15 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) } value = opstack_pop(); + if (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && + (value_ptr_level != 0 || is_record_type(value_type) || + value_type->is_floating || value->is_func || + is_pointer_like_value(value) || is_record_type(value->type) || + value->type->is_floating)) + error_at( + "Invalid rank-three/four grouped compound assignment operand", + cur_token_loc()); + if (compound_op != OP_generic) { var_t *current; if (field && is_bitfield(field)) { @@ -11994,6 +13511,7 @@ int read_primary_constant(block_t *scope) token_t *inside = lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; var_t *nested_array = NULL; + token_t *nested_root = NULL; token_t *nested_after = NULL; int nested_groups = 0; @@ -12004,6 +13522,7 @@ int read_primary_constant(block_t *scope) nested_groups++; token = token->next; } + nested_root = token; if (token && token->kind == T_identifier) nested_array = find_var(token->literal, scope); nested_after = token ? token->next : NULL; @@ -12012,8 +13531,18 @@ int read_primary_constant(block_t *scope) i++) nested_after = nested_after->next; } - if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_string) { + if (can_scan_sizeof_postfix_extent(scope, + lex_peek(T_open_bracket, NULL) + ? cur_token->next->next + : cur_token->next, + lex_peek(T_open_bracket, NULL))) { + /* The shared postfix walker has already proved this is an + * identifier-rooted fixed array extent, so the detached parser can + * preserve it without a global-prefix spelling table. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_string) { lex_expect(T_open_bracket); lex_expect(T_open_bracket); res = read_const_string_size(); @@ -12032,6 +13561,16 @@ int read_primary_constant(block_t *scope) lex_expect(T_close_bracket); lex_expect(T_close_bracket); res = size_var(nested_array); + } else if (nested_array && nested_groups > 0 && nested_root && + nested_root->next && + (nested_root->next->kind == T_dot || + nested_root->next->kind == T_arrow)) { + /* The local sizeof helper preserves a member-array lvalue through + * arbitrary grouping before a postfix subscript. Reuse it in a + * detached block for a global constant instead of duplicating that + * type-only traversal here. + */ + res = read_global_sizeof_expression(scope); } else if (inside && inside->kind == T_open_bracket && inside->next && inside->next->kind == T_open_bracket && inside->next->next && inside->next->next->kind == T_identifier) { @@ -12120,6 +13659,15 @@ int read_primary_constant(block_t *scope) lex_expect(T_close_bracket); validate_global_sizeof_object(object); res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_asterisk && inside->next && + inside->next->kind == T_identifier && inside->next->next && + inside->next->next->kind == T_open_square && + is_direct_fixed_array_pointer_slot( + find_var(inside->next->literal, scope))) { + /* The shared direct-pointer helper also owns the supported global + * pointer-array slot form. + */ + res = read_global_sizeof_expression(scope); } else if (inside && inside->kind == T_asterisk && inside->next && inside->next->kind == T_ampersand) { var_t *object; @@ -12490,6 +14038,8 @@ int read_global_address_offset(block_t *scope, return read_const_expr(scope); } +static int wide_global_unevaluated_depth; + /* 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. @@ -12507,6 +14057,95 @@ bool emit_wide_global_word_arithmetic(block_t *parent, 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; + } + + /* 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; + + /* Materialize the usual arithmetic conversion in word form. A narrow + * signed source sign-extends to either signed long long or unsigned + * long long; a narrow unsigned source zero-extends in both cases. + */ + if (left->type->size <= TY_int->size) + hi = left->type->is_unsigned || !(lo & 0x80000000U) ? 0 + : 0xffffffffU; + if (right->type->size <= TY_int->size) + rhs_hi = right->type->is_unsigned || !(rhs_lo & 0x80000000U) + ? 0 + : 0xffffffffU; + + 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; + } + result->init_val = comparison; + result->init_val_hi = 0; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; + } + + if (op == OP_log_and || op == OP_log_or) { + bool left_true = lo || hi; + bool right_true = rhs_lo || rhs_hi; + + result->init_val = op == OP_log_and ? left_true && right_true + : left_true || right_true; + result->init_val_hi = 0; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; + } + if (op == OP_div || op == OP_mod) { unsigned int rem_lo = 0, rem_hi = 0, quo_lo = 0, quo_hi = 0; bool is_unsigned = left->type->is_unsigned || right->type->is_unsigned; @@ -12514,7 +14153,8 @@ bool emit_wide_global_word_arithmetic(block_t *parent, bool neg_right = !is_unsigned && (rhs_hi >> 31); if (rhs_lo == 0 && rhs_hi == 0) - return false; + error_at("division by zero in global constant expression", + cur_token_loc()); if (neg_left) { lo = ~lo + 1; hi = ~hi + (lo == 0); @@ -12704,9 +14344,84 @@ var_t *read_wide_global_literal_primary(block_t *parent, value = read_wide_global_literal_primary(parent, bb, scope); result = require_typed_var(parent, TY_int); result->var_name = gen_name(); - add_insn(parent, bb, OP_log_not, result, value, NULL, 0, NULL); + result->init_val = !(value->init_val || value->init_val_hi); + 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; } + if (lex_accept(T_sizeof)) { + char sizeof_name[MAX_ID_LEN]; + + value = require_typed_var(parent, find_type("size_t", true)); + value->var_name = gen_name(); + if (lex_peek(T_string, NULL)) + value->init_val = read_const_string_size(); + else if (lex_peek(T_wstring, NULL)) + value->init_val = read_const_wstring_size(); + else if (lex_peek(T_numeric, NULL)) + value->init_val = read_global_sizeof_expression(scope); + else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_string) { + lex_expect(T_open_bracket); + value->init_val = read_const_string_size(); + lex_expect(T_close_bracket); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_wstring) { + lex_expect(T_open_bracket); + value->init_val = read_const_wstring_size(); + lex_expect(T_close_bracket); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_numeric) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_open_bracket) { + /* A nested group is an expression operand, e.g. sizeof((void)1) or + * sizeof((*incomplete_pointer)). + */ + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_asterisk && + cur_token->next->next->next && + cur_token->next->next->next->kind == T_identifier && + cur_token->next->next->next->next && + cur_token->next->next->next->next->kind == T_open_square && + is_direct_fixed_array_pointer_slot( + find_var(cur_token->next->next->next->literal, scope))) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_identifier && + cur_token->next->next->next && + cur_token->next->next->next->kind == T_close_bracket && + find_var(cur_token->next->next->literal, scope)) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, sizeof_name) && + find_var(sizeof_name, scope)) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_identifier && + cur_token->next->next->next && + cur_token->next->next->next->kind == T_close_bracket && + find_func(cur_token->next->next->literal)) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, sizeof_name) && + find_func(sizeof_name)) { + value->init_val = read_global_sizeof_expression(scope); + } else + value->init_val = read_const_sizeof_type(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 (lex_accept(T_open_bracket)) { value = read_wide_global_literal_expression(parent, bb, scope); lex_expect(T_close_bracket); @@ -12744,6 +14459,7 @@ var_t *read_wide_global_literal_expression(block_t *parent, 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; @@ -12752,9 +14468,65 @@ var_t *read_wide_global_literal_expression(block_t *parent, read_wide_global_literal_primary(parent, bb, scope); op = get_operator(); while (op != OP_generic) { - if (op == OP_ternary || is_logical(op)) - error_at("Wide global initializer needs arithmetic literals", - cur_token_loc()); + 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 = condition->init_val || condition->init_val_hi; + + /* 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--; + 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)) { @@ -12762,9 +14534,12 @@ var_t *read_wide_global_literal_expression(block_t *parent, 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); + 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, @@ -12774,7 +14549,18 @@ var_t *read_wide_global_literal_expression(block_t *parent, if (op_stack_index >= MAX_OPERATOR_STACK_SIZE || val_stack_index >= MAX_OPERATOR_STACK_SIZE) fatal("Wide global initializer is too complex"); + protected_rhs[op_stack_index] = + is_logical(op) && + ((op == OP_log_and && + !(val_stack[val_stack_index - 1]->init_val || + val_stack[val_stack_index - 1]->init_val_hi)) || + (op == OP_log_or && + (val_stack[val_stack_index - 1]->init_val || + val_stack[val_stack_index - 1]->init_val_hi))); op_stack[op_stack_index++] = op; + if (protected_rhs[op_stack_index - 1]) { + wide_global_unevaluated_depth++; + } val_stack[val_stack_index++] = read_wide_global_literal_primary(parent, bb, scope); op = get_operator(); @@ -12784,9 +14570,12 @@ var_t *read_wide_global_literal_expression(block_t *parent, 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); + 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, @@ -12857,6 +14646,10 @@ bool read_global_assignment_var(var_t *var) 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", @@ -12878,7 +14671,7 @@ bool read_global_assignment_var(var_t *var) } char token[MAX_ID_LEN]; if (lex_peek(T_identifier, token)) { - func_t *func = find_func(token); + func_t *func = find_visible_func(token, scope); if (func) { if (!var->is_func && !var->ptr_level && !(var->type && var->type->ptr_level)) @@ -12907,32 +14700,23 @@ bool read_global_assignment_var(var_t *var) var_t *object = find_var(token, scope); if (object && object->is_global && (explicit_address || object->array_size)) { - int array_bounds[4] = {0, 0, 0, 0}; - int array_dims = 0; + fixed_array_shape_t shape = fixed_array_shape_from_var(object); int array_subscript = 0; var_t *object_addr = require_ref_var(parent, object->type, object->ptr_level); - if (object->array_size) { - int inner_size = 1; - - if (object->array_dim2) - inner_size *= object->array_dim2; - if (object->array_dim3) - inner_size *= object->array_dim3; - if (object->array_dim4) - inner_size *= object->array_dim4; - array_bounds[array_dims++] = - object->array_size / inner_size; - if (object->array_dim2) - array_bounds[array_dims++] = object->array_dim2; - if (object->array_dim3) - array_bounds[array_dims++] = object->array_dim3; - if (object->array_dim4) - array_bounds[array_dims++] = object->array_dim4; - } 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); @@ -12945,12 +14729,7 @@ bool read_global_assignment_var(var_t *var) var_t *offset_addr = require_ref_var(parent, object->type, object->ptr_level); - if (object->array_dim2) - stride *= object->array_dim2; - if (object->array_dim3) - stride *= object->array_dim3; - if (object->array_dim4) - stride *= object->array_dim4; + 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, @@ -12975,26 +14754,8 @@ bool read_global_assignment_var(var_t *var) object_addr, field); object_addr->is_global_address = true; object = field; - array_dims = 0; + shape = fixed_array_shape_from_var(object); array_subscript = 0; - if (object->array_size) { - int inner_size = 1; - - if (object->array_dim2) - inner_size *= object->array_dim2; - if (object->array_dim3) - inner_size *= object->array_dim3; - if (object->array_dim4) - inner_size *= object->array_dim4; - array_bounds[array_dims++] = - object->array_size / inner_size; - if (object->array_dim2) - array_bounds[array_dims++] = object->array_dim2; - if (object->array_dim3) - array_bounds[array_dims++] = object->array_dim3; - if (object->array_dim4) - array_bounds[array_dims++] = object->array_dim4; - } } else if (object->array_size && lex_accept(T_open_square)) { int index = read_primary_constant(scope); @@ -13002,9 +14763,8 @@ bool read_global_assignment_var(var_t *var) object->ptr_level ? PTR_SIZE : object->type->size; lex_expect(T_close_square); - for (int dim = array_subscript + 1; dim < array_dims; - dim++) - stride *= array_bounds[dim]; + stride = fixed_array_shape_stride( + &shape, array_subscript, stride); object_addr = compute_element_address( parent, &bb, object_addr, index, stride); object_addr->is_global_address = true; @@ -13033,13 +14793,16 @@ bool read_global_assignment_var(var_t *var) * remaining row dimensions. Its trailing constant offset * must therefore use the row/plane stride. */ - for (int dim = array_subscript; dim < array_dims; dim++) - elem_size *= array_bounds[dim]; + elem_size = fixed_array_shape_stride( + &shape, array_subscript - 1, elem_size); object_addr = compute_element_address( parent, &bb, object_addr, index, elem_size); object_addr->is_global_address = true; } + if (incompatible_pointee_callback_conversion(object_addr, var)) + error_at("incompatible callback slot types in initializer", + cur_token_loc()); add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, NULL); return true; @@ -13333,6 +15096,18 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) 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); @@ -13341,7 +15116,7 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) fatal("Too many nested switch statements"); context = &switch_label_contexts[switch_label_context_idx++]; context->dispatch_parent = parent; - context->control = operand_stack[operand_stack_idx - 1]; + context->control = control; context->dispatch_tail = bb; context->default_body = NULL; context->seen_cases = NULL; @@ -13393,13 +15168,13 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) var_t *vd; var_t *rs1; var_t *var; - opcode_t prefix_op = OP_generic; 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); @@ -13411,10 +15186,22 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) basic_block_t *setup = bb_create(blk); bb_connect(bb, setup, NEXT); - if (!lex_accept(T_semicolon)) { + 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", @@ -13449,28 +15236,54 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) bool has_builtin_type = lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL); - if (!has_builtin_type && !lex_peek(T_identifier, token) && - !lex_peek(T_struct, NULL) && !lex_peek(T_union, NULL)) - 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; + 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_type(token, 1) + : NULL; } - type = has_builtin_type ? TY_int : find_type(token, find_type_flag); + if (!type && saw_decl_specifier) + error_at("declaration specifier requires a type", cur_token_loc()); if (type) { + /* 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()); var = require_typed_var(blk, type); var->is_static = is_static; var->is_register = is_register; var->is_global = is_static; var->is_const_qualified = is_const; var->is_volatile = is_volatile; + parsing_for_initializer_declaration = true; read_full_var_decl(var, false, false, false); + parsing_for_initializer_declaration = false; + if (strict_c99 && var->is_func) + error_at("C99 for initializer cannot declare a function", + cur_token_loc()); if (is_extern) { if (var->is_func || lex_peek(T_open_bracket, NULL)) { /* This helper consumes the declaration's semicolon. */ blk->locals.size--; + var->is_block_scope_function_declaration = true; GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; read_global_function_declarator(GLOBAL_BLOCK, var, false); @@ -13597,14 +15410,9 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) } } } else { - do { - read_body_assignment(token, blk, OP_generic, &setup, NULL, 0); - perform_side_effect(blk, setup); - if (!lex_peek(T_comma, NULL)) - break; - lex_expect(T_comma); - lex_peek(T_identifier, token); - } while (true); + read_control_expression(blk, &setup); + opstack_pop(); + perform_side_effect(blk, setup); } if (!for_decl_semicolon_consumed) @@ -13639,16 +15447,9 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) /* increment after each loop */ if (!lex_accept(T_close_bracket)) { - do { - prefix_op = OP_generic; - 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_, NULL, 0); - perform_side_effect(blk, inc_); - } while (lex_accept(T_comma)); + read_control_expression(blk, &inc_); + opstack_pop(); + perform_side_effect(blk, inc_); lex_expect(T_close_bracket); } @@ -14174,6 +15975,13 @@ var_t *bind_block_extern_object(block_t *parent, var_t *var) return var; } +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()); +} + /* Everything a statement can still be: a declaration, an assignment, a call, or * an expression evaluated for its effect. */ @@ -14266,7 +16074,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) if (lex_peek(T_identifier, next_ident)) { /* Check if it's a type name */ - type = find_type(next_ident, 0); + type = find_visible_type(next_ident, parent); if (type) could_be_type = true; } @@ -14289,7 +16097,8 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) find_type_flag = 2; if (find_type_flag == 2) lex_peek(T_identifier, token); - type = find_type(token, find_type_flag); + type = find_type_flag == 2 ? find_type(token, find_type_flag) + : find_visible_type(token, parent); if (find_type_flag == 2) cur_token = type_token; } @@ -14307,6 +16116,53 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) var->is_const_qualified = is_const; var->is_volatile = is_volatile; read_full_var_decl(var, false, false, false); + reject_ordinary_typedef_collision(parent, var); + + /* 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 (is_static || is_register || is_auto) + error_at( + "invalid storage class for block function declaration", + cur_token_loc()); + if (is_const || is_volatile || var->is_const_qualified || + var->is_volatile) + error_at("function type cannot be qualified", + cur_token_loc()); + parent->locals.size--; + var->is_block_scope_function_declaration = true; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = + var; + read_global_function_declarator(GLOBAL_BLOCK, var, false); + var_t *alias = require_var(parent); + + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + if (!lex_accept(T_comma)) + return bb; + + var = require_typed_var(parent, type); + var->is_const_qualified = is_const; + var->is_volatile = 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()); @@ -14327,6 +16183,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) * consumes the trailing semicolon itself. */ parent->locals.size--; + var->is_block_scope_function_declaration = true; GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; read_global_function_declarator(GLOBAL_BLOCK, var, false); @@ -14455,6 +16312,9 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) expr_result = first_elem; } + if (incompatible_pointee_callback_conversion(expr_result, var)) + error_at("incompatible callback slot types in initializer", + cur_token_loc()); diagnose_const_pointer_conversion(expr_result, var); emit_object_assignment(parent, &bb, var, expr_result); } @@ -14475,6 +16335,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) nv->is_const_qualified = var->is_const_qualified; nv->is_volatile = var->is_volatile; read_partial_var_decl(nv, var); /* partial */ + reject_ordinary_typedef_collision(parent, nv); if (is_incomplete_record_object(nv)) error_at("Incomplete struct/union type cannot define an object", cur_token_loc()); @@ -14572,7 +16433,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) } } if (matched_call && end && end->kind == T_semicolon && call) { - func = find_func(token); + func = find_visible_func(token, parent); if (func) { lex_expect(T_identifier); emit_direct_call_result(func, false, parent, &bb); @@ -14699,6 +16560,307 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) return NULL; } +/* 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}; + + lex_expect(T_typedef); + decl.scope = parent; + parsing_block_typedef_declarator = true; + 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; + 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.ptr_level && !decl.array_size && + !decl.pointee_array_size && !base->ptr_level && + !is_record_type(base) && !base->is_floating && + !direct_function_signature->va_args && + !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, and no deeper pointer + * composition. + */ + bool callback_slot_alias = + callback_slot_signature && + decl.parenthesized_function_pointer_level == 2 && + decl.ptr_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_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)); + alias->base_type = TYPE_typedef; + alias->base_struct = is_record_type(base) ? base : base->base_struct; + alias->ptr_level = base->ptr_level + decl.ptr_level; + alias->pointer_const_mask = + base->pointer_const_mask | + (decl.pointer_const_mask << base->ptr_level); + 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) { + 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 (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; + + /* 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: @@ -14755,10 +16917,16 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (lex_accept(T_goto)) return handle_goto_statement(parent, bb); + 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()) + 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. @@ -14956,6 +17124,9 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) 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)); @@ -14963,8 +17134,18 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) } 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; - memcpy(&func->return_def, var, sizeof(var_t)); + 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); @@ -14987,17 +17168,26 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) /* 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. + * 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)); - - /* 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 (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 (check_decl) { if (!compatible_decl_type(func->return_def.type, @@ -15074,6 +17264,8 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) read_func_body(func); return; } + if (inherited_direct_function_type && lex_peek(T_comma, NULL)) + return; if (!lex_accept(T_semicolon)) error_at("Syntax error in global declaration", next_token_loc()); } @@ -15114,6 +17306,10 @@ var_t *resolve_global_declarator(block_t *block, *is_redeclaration = true; 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->array_size != var->array_size || previous->array_dim2 != var->array_dim2 || @@ -15186,12 +17382,16 @@ void consume_global_compound_literal(void); 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, true); lex_expect(T_close_curly); + global_constant_initializer_scope = saved_initializer_scope; } /* At file scope a compound literal has static storage duration. The target @@ -16289,6 +18489,7 @@ void read_global_statement(void) 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; /* Handle pointer types in typedef: typedef char *string; */ @@ -16357,7 +18558,8 @@ void read_global_statement(void) * pointer-sized callback object, not a derived pointer alias. */ type->ptr_level = 0; - type->pointer_const_mask = 0; + type->pointer_const_mask = declarator.pointer_const_mask; + type->is_volatile_qualified = declarator.is_volatile; lex_expect(T_semicolon); return; } @@ -16369,31 +18571,12 @@ void read_global_statement(void) * them to the base alias's bounds instead of overwriting the inner * extent. */ - int base_bounds[4]; - int decl_bounds[4]; - int base_dims = 0; - int decl_dims = 0; - if (type->array_size) { - int trailing = 1; - - if (type->array_dim2) - trailing *= type->array_dim2; - if (type->array_dim3) - trailing *= type->array_dim3; - if (type->array_dim4) - trailing *= type->array_dim4; - base_bounds[base_dims++] = type->array_size / trailing; - if (type->array_dim2) - base_bounds[base_dims++] = type->array_dim2; - if (type->array_dim3) - base_bounds[base_dims++] = type->array_dim3; - if (type->array_dim4) - base_bounds[base_dims++] = type->array_dim4; - } + 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_dims >= 4) + if (decl_shape.rank >= MAX_FIXED_ARRAY_RANK) error_at( "Array declarators support at most four dimensions", cur_token_loc()); @@ -16404,40 +18587,26 @@ void read_global_statement(void) if (bound <= 0) error_at("Typedef array needs a positive bound", cur_token_loc()); - decl_bounds[decl_dims++] = bound; + decl_shape.bounds[decl_shape.rank++] = bound; lex_expect(T_close_square); } - if (decl_dims) { - int all_bounds[4]; - int all_dims = decl_dims + base_dims; + if (decl_shape.rank) { + fixed_array_shape_t shape = + fixed_array_shape_prepend(&decl_shape, &base_shape); - if (all_dims > 4) - error_at( - "Array declarators support at most four dimensions", - cur_token_loc()); - for (int i = 0; i < decl_dims; i++) - all_bounds[i] = decl_bounds[i]; - for (int i = 0; i < base_dims; i++) - all_bounds[decl_dims + i] = base_bounds[i]; - type->array_size = all_bounds[0]; - type->array_dim2 = 0; - type->array_dim3 = 0; - type->array_dim4 = 0; - for (int i = 1; i < all_dims; i++) { - type->array_size *= all_bounds[i]; - if (i == 1) - type->array_dim2 = all_bounds[i]; - else if (i == 2) - type->array_dim3 = all_bounds[i]; - else - type->array_dim4 = all_bounds[i]; - } + 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); } lex_expect(T_semicolon); } diff --git a/src/preprocessor.c b/src/preprocessor.c index c4aaa5e3..9514886d 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -521,6 +521,8 @@ void install_stdarg_header(void) 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; @@ -537,6 +539,27 @@ void install_stdarg_header(void) 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, diff --git a/src/reg-alloc.c b/src/reg-alloc.c index cc05ea79..b53000b6 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -2484,6 +2484,18 @@ void reg_alloc_global(insn_t *global_insn) 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: { @@ -2633,6 +2645,18 @@ void reg_alloc_global(insn_t *global_insn) 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 */ @@ -2840,7 +2864,20 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) fatal("Aggregate parameter is not owned by its function"); dest = prepare_dest(bb, insn, insn->rd, -1, -1); - if (insn->rs1->space_is_allocated) { + + /* 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; @@ -3442,6 +3479,19 @@ void reg_alloc(void) ir->src0 = src0; ir->src1 = func->stack_size; func->stack_size += PTR_SIZE; + if (i < args_in_reg) { + var_t *param = var_subscript0(&func->param_defs[i]); + + if (i + 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; + } + } } } else { /* If the number of function arguments is fixed, the extra arguments diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 794e26c2..c52ffaa0 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -1994,13 +1994,13 @@ void emit_bitwise(ph2_ir_t *ph2_ir, * the save/restore around it: SAR r64, imm8 is one instruction. */ if (src1_const_known && src1_const >= 0 && src1_const < 64) { - if (ph2_ir->is_unsigned) + if (ph2_ir->src0_is_unsigned) emit_zero_extend(rd, rs1, ph2_ir->size_bytes); else emit_mov_reg(rd, rs1); - emit_shift_imm(rd, - ph2_ir->is_unsigned ? SHIFT_EXT_SHR : SHIFT_EXT_SAR, - src1_const); + 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); @@ -2026,7 +2026,7 @@ void emit_bitwise(ph2_ir_t *ph2_ir, emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), 1)); emit_byte(REX_W | REX_B); /* SAR/SHR r11, cl */ emit_byte(0xD3); - emit_byte(modrm(MOD_DIRECT, ph2_ir->is_unsigned ? 5 : 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)); diff --git a/tests/driver.sh b/tests/driver.sh index 324cfbbc..e0acf048 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -748,11 +748,20 @@ EOF try_ 0 << EOF int main(void) { return sizeof(2147483648U) != sizeof(unsigned int) || - sizeof(4294967296U) != 8 || 0xffffffff > -1 || (0xffffffff >> 31) != 1; } EOF +# A U-suffixed value above the one-word range selects unsigned long long. Keep +# that capability-specific assertion apart from the one-word unsigned tests +# above: Armv7 and RV32 deliberately reject direct wide values until +# paired-value parser admission is complete. +if [ "$PTR_SZ" -ge 8 ]; then + try_ 0 << EOF +int main(void) { return sizeof(4294967296U) != 8; } +EOF +fi + try_ 0 << EOF enum { global_unsigned_shift_count = 1 }; unsigned int global_unsigned_shift = 0xffffffff >> 31; @@ -1363,6 +1372,16 @@ int main(void) { } EOF +# 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 + try_ 1 << EOF int main(void) { unsigned int bits = 2147483647; @@ -1754,6 +1773,268 @@ 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 + 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 + 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); @@ -2200,6 +2481,14 @@ int main(void) { return (value.byte / 2 == 127) + (value.half / 2 == 32767); } 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 try_ 7 << EOF struct typedef_pair { int left; int right; }; typedef struct typedef_pair *pair_pointer; @@ -2209,6 +2498,43 @@ int main(void) { 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; @@ -2288,218 +2614,3188 @@ 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; } +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 +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 + +# 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 +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 +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 +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 +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 +try_compile_error << EOF +int main(void) { typedef struct missing_tag missing_alias; return 0; } +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 +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 +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 +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_ 16 << 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 +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 +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_ 8 << 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_ 8 << 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_ 8 << 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_ 16 << 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_ 32 << 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_ 64 << 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_ 128 << 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) != 128); +} +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) != 64); +} +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_compile_error << EOF +int main(void) { + typedef int scalar_t, variadic_t(int, ...); + return 0; +} +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_ 16 << 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_ 16 << 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 +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_ 4 << EOF -struct packet { int value; char data[]; }; -struct packet *packets[2]; -typedef struct packet *packet_ptr; -packet_ptr typedef_packets[2]; +try_ 7 << EOF 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)); + 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 -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; + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + int (*p)[2] = &rows[0]; + p[1][0] = 7; + return rows[1][0]; } EOF -try_ 2 << EOF +try_ 1 << EOF int main(void) { - unsigned char bytes[1] = {255}; - unsigned short halves[1] = {65535}; - return (bytes[0] / 2 == 127) + (halves[0] / 2 == 32767); + int rows[2][2] = { { 4, 9 }, { 6, 8 } }; + typedef int (*row_pointer)[2]; + row_pointer p = &rows[0]; + return p == &rows[0]; } EOF -try_ 2 << EOF +try_ 7 << EOF int main(void) { - unsigned int one = 1U; - int minus_two = -2; - int minus_one = -1; - return ((one + minus_two) >> 31) + (minus_one > one); + 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_ 4 << EOF -typedef unsigned short ushort; +try_compile_error << EOF +int main(void) { typedef int (*row_pointer)[0]; return 0; } +EOF +try_ 0 << EOF int main(void) { - ushort small = 65535; - return (small > 0) + (sizeof(unsigned char) == 1) + - (sizeof(unsigned short) == 2) + (sizeof(unsigned long) == 4); + 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 - -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); +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 value = 1 / 0; -int main() { return value; } +int main(void) { typedef int (*row_pointer)[2]; typedef row_pointer *rows; return 0; } EOF - try_compile_error << EOF -int value = 1 % 0; -int main() { return value; } +int main(void) { typedef const int readonly_t; readonly_t value = 1; value = 2; } 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; +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_output 0 "-32767" << EOF -int main() { - short a = 32767; - a += 2; - printf("%d\n", a); - return 0; +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_output 0 "-127" << EOF -int main() { - char a = 127; - a += 2; - printf("%d\n", a); +try_compile_error << EOF +int main(void) { + for (typedef int loop_only; 0; ) {} + loop_only expired; 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; }; +try_compile_error << EOF +int main(void) { for (typedef int invalid = 1; 0; ) {} return 0; } +EOF +try_ 3 << EOF int main(void) { - struct pair value; - struct pair *present = &value; - struct pair *absent = 0; - return (!present == 0) + (!absent == 1); + for (int value = 3; value; value = 0) + return value; + return 0; } 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 -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_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 -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" 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;" @@ -3855,6 +7151,23 @@ 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) { @@ -3907,6 +7220,18 @@ int main(void) { } 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, @@ -3984,6 +7309,26 @@ int main(void) { *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) { @@ -4356,6 +7701,81 @@ 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;" @@ -7047,8 +10467,8 @@ int main(void) { } EOF -# A declaration in a C99 for initializer has block scope too. Its extern object -# and function forms must bind later file-scope definitions and hide an +# 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) { @@ -7818,8 +11238,8 @@ int main(void) } EOF -# A C99 for-init declaration has block scope, so its static object persists -# across calls but remains visible only to the loop's clauses and body. +# 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) { @@ -7979,7 +11399,86 @@ int main(void) { 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. Keep the +# address-offset lowering check on targets that currently admit wide values; the +# surrounding one-word enum-offset regression remains portable. +if [ "$PTR_SZ" -ge 8 ]; then + try_ 0 << EOF int global_wide_offset_values[] = {17, 23}; int *global_wide_offset = &global_wide_offset_values[0] + (0x100000000ULL >> 32); @@ -7997,6 +11496,7 @@ int main(void) { *aggregate_value != 23; } EOF +fi try_ 0 << EOF struct pointer_member_holder { int *value; }; typedef char *pointer_member_char_ptr; @@ -8906,8 +12406,221 @@ 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;" +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);" @@ -8939,9 +12652,63 @@ int main(void) } 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);" @@ -9019,6 +12786,26 @@ items 10 "int a; a = 0; switch (3) { case 0: return 2; case 3: a = 10; break; ca 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 # Category: Enumerations begin_category "Enumerations" "Testing enum declarations and usage" @@ -9406,6 +13193,83 @@ int main(void) { (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 46 "--no-libc" << EOF #include int main(void) { @@ -13593,6 +17457,15 @@ grid *grids(void) { } int main(void) { return grids()[0][1][2]; } EOF +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 try_ 9 << EOF typedef int row[2]; row *rows(void) { static row value = {4, 5}; return &value; } @@ -13608,6 +17481,29 @@ row *pointers(void) { } int main(void) { return pointers()[0][1] == &second ? 4 : 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 + +# 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 try_ 15 << EOF typedef int row[2]; row *rows(void) { static row value = {4, 5}; return &value; } @@ -14116,6 +18012,26 @@ EOF 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) { 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_ 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 + # The default remains intentionally permissive for existing users and suite # coverage; strict mode makes the C99 boundary explicit. try_ 2 << EOF @@ -14548,6 +18464,34 @@ int main(void) { } EOF +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 + +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 + try_ 0 << EOF #include long long wide_argument(int count, ...) { From c5debf908fc4d242ef686fe8fe0ebb6e5de94bae Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 01:19:05 +0800 Subject: [PATCH 08/40] Fix initializer, sizeof and branch edge cases A detached Arm branch now jumps to both of its targets, and x86-64 initializer spills stay in the data area. Records are copied whole through conditionals and va_arg, braces may be elided for array members in initializers, and static initializers share address designators. Type name and joined string buffers are wide enough for the forms the parser now accepts, -2147483648 is typed as int on 32-bit targets, and every sizeof operand is walked by one scanner. --- src/arm-codegen.c | 8 +- src/defs.h | 12 +- src/lexer.c | 6 +- src/parser.c | 1123 +++++++++++++++----------------------------- src/preprocessor.c | 4 +- src/x64-codegen.c | 14 + tests/driver.sh | 155 ++++++ 7 files changed, 563 insertions(+), 759 deletions(-) diff --git a/src/arm-codegen.c b/src/arm-codegen.c index 1dd72a2c..c6a59ae6 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -537,7 +537,13 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_branch: 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)); diff --git a/src/defs.h b/src/defs.h index 2bf0a545..06b3e0fe 100644 --- a/src/defs.h +++ b/src/defs.h @@ -35,6 +35,12 @@ #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. */ @@ -45,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 diff --git a/src/lexer.c b/src/lexer.c index 04f66ed0..3f442ea2 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -1778,9 +1778,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) { diff --git a/src/parser.c b/src/parser.c index acd1e70b..daef05c0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1270,7 +1270,7 @@ int parse_wide_character_constant(const char *literal) int read_wstring_units(int *units, int capacity) { - char literal[MAX_TOKEN_LEN]; + char literal[MAX_STRING_LEN]; int length = 0; do { @@ -1814,6 +1814,75 @@ void emit_object_assignment(block_t *parent, } } +/* 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. + */ +var_t *read_global_address_designator(block_t *scope, + block_t *parent, + basic_block_t **bb, + var_t **object, + var_t *object_addr) +{ + 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 (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { + int element_size = target->ptr_level ? PTR_SIZE : target->type->size; + int index = read_global_address_offset(scope, parent, *bb); + + object_addr = compute_element_address( + parent, bb, object_addr, index, + fixed_array_shape_stride(&shape, subscripts - 1, element_size)); + object_addr->ptr_level = target->ptr_level + 1; + object_addr->is_global_address = true; + } + *object = target; + return object_addr; +} + var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) { var_t *val = NULL; @@ -1868,12 +1937,6 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) var_t *object = find_var(name, scope); if (object && object->is_global) { - fixed_array_shape_t shape = - fixed_array_shape_from_var(object); - int subscript_count = 0; - int element_size = - object->ptr_level ? PTR_SIZE : object->type->size; - lex_expect(T_identifier); val = require_ref_var(parent, object->type, object->ptr_level); @@ -1881,50 +1944,8 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) val->is_global_address = true; add_insn(parent, *bb, OP_address_of, val, object, NULL, 0, NULL); - while (lex_accept(T_open_square)) { - int index; - int stride = element_size; - - if (!object->array_size || - subscript_count >= shape.rank) - error_at( - "Subscripted global address needs an " - "array object", - cur_token_loc()); - stride = fixed_array_shape_stride( - &shape, subscript_count, element_size); - index = read_const_expr(scope); - lex_expect(T_close_square); - val = compute_element_address(parent, bb, val, index, - stride); - - /* The offset still denotes an address constant. The - * generic helper only constructs arithmetic IR, so - * retain the pointer value metadata required by the - * global-store relocation path. - */ - val->ptr_level = object->ptr_level + 1; - val->is_global_address = true; - subscript_count++; - } - if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { - int index = - read_global_address_offset(scope, parent, *bb); - int stride; - - /* After a partial multidimensional subscript, the - * address still points at the remaining row/plane. C - * pointer arithmetic advances by that complete - * pointed-to object, not by its scalar leaf. - */ - stride = fixed_array_shape_stride( - &shape, subscript_count - 1, element_size); - val = compute_element_address(parent, bb, val, index, - stride); - val->ptr_level = object->ptr_level + 1; - val->is_global_address = true; - } - return val; + return read_global_address_designator(scope, parent, bb, + &object, val); } } } @@ -2251,6 +2272,33 @@ void parse_array_field_hyperplane_init(block_t *parent, } } +/* 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. + */ +int read_concatenated_string(char *combined) +{ + char literal[MAX_STRING_LEN]; + int used; + + lex_ident(T_string, literal); + unescape_string(literal, combined, MAX_STRING_LEN); + used = strlen(combined); + while (lex_peek(T_string, NULL)) { + int added; + + lex_ident(T_string, literal); + unescape_string(literal, combined + used, MAX_STRING_LEN - used); + added = strlen(combined + used); + if (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 @@ -2262,21 +2310,10 @@ void parse_string_field_init(block_t *parent, var_t *target_addr, bool emit_code) { - char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN], - combined[MAX_LINE_LEN]; + char combined[MAX_STRING_LEN]; int len; - lex_ident(T_string, literal); - unescape_string(literal, combined, MAX_LINE_LEN); - while (lex_peek(T_string, NULL)) { - int used = strlen(combined); - - lex_ident(T_string, literal); - unescape_string(literal, unescaped, MAX_LINE_LEN - used); - if (used + (int) strlen(unescaped) >= MAX_LINE_LEN - 1) - error_at("Concatenated string literal too long", cur_token_loc()); - strcpy(combined + used, unescaped); - } + read_concatenated_string(combined); len = strlen(combined) + 1; if (len > field->array_size) @@ -2334,11 +2371,11 @@ void parse_wstring_field_init(block_t *parent, var_t *target_addr, bool emit_code) { - int values[MAX_LINE_LEN]; + int values[MAX_STRING_LEN]; int length; int units; - length = read_wstring_units(values, MAX_LINE_LEN); + length = read_wstring_units(values, MAX_STRING_LEN); units = length + 1; if (units > field->array_size) @@ -2679,13 +2716,43 @@ void parse_struct_field_init(block_t *parent, if (field_idx >= struct_type->num_fields || ((struct_type->base_type == TYPE_union || struct_type->is_union) && - initializer_count > 0)) + initializer_count > 0 && !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 && !designated_field_addr && !has_pending_array_element && + field->array_size && + (field->ptr_level || !is_record_type(field->type)) && + !lex_peek(T_open_curly, NULL) && !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 = (field->ptr_level || field->is_func) + ? PTR_SIZE + : field->type->size; + 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; + designated_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 (field && field->array_size && !field->ptr_level && (lex_peek(T_string, NULL) || lex_peek(T_wstring, NULL)) && ((lex_peek(T_string, NULL) && !is_char_array(field)) || @@ -5076,26 +5143,9 @@ void read_parameter_list_decl(func_t *func, bool anon) 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; + char combined[MAX_STRING_LEN]; - /* 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; - } + read_concatenated_string(combined); const int index = write_symbol(combined); @@ -5116,21 +5166,10 @@ void read_literal_param(block_t *parent, basic_block_t *bb) */ void parse_string_array_init(var_t *var, block_t *parent, basic_block_t **bb) { - char literal[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN], - combined[MAX_LINE_LEN]; + char combined[MAX_STRING_LEN]; int len; - lex_ident(T_string, literal); - unescape_string(literal, combined, MAX_LINE_LEN); - while (lex_peek(T_string, NULL)) { - int used = strlen(combined); - - lex_ident(T_string, literal); - unescape_string(literal, unescaped, MAX_LINE_LEN - used); - if (used + (int) strlen(unescaped) >= MAX_LINE_LEN - 1) - error_at("Concatenated string literal too long", cur_token_loc()); - strcpy(combined + used, unescaped); - } + read_concatenated_string(combined); len = strlen(combined) + 1; if (var->has_unsized_array) { @@ -5155,11 +5194,11 @@ 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) { - int values[MAX_LINE_LEN]; + int values[MAX_STRING_LEN]; int length; int units; - length = read_wstring_units(values, MAX_LINE_LEN); + length = read_wstring_units(values, MAX_STRING_LEN); units = length + 1; if (var->has_unsized_array) { @@ -5464,12 +5503,23 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool 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 (is_long_long || value_hi || - (is_decimal && !has_unsigned_suffix && - numeric_literal_needs_wide_path(token)) || - (is_decimal && !has_unsigned_suffix && value > 0x80000000U)) { + 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 (PTR_SIZE < 8) error_at("long long literal needs 64-bit target lowering", cur_token_loc()); @@ -5533,8 +5583,8 @@ void read_wchar_param(block_t *parent, basic_block_t *bb) void read_wstring_param(block_t *parent, basic_block_t *bb) { - int values[MAX_LINE_LEN]; - int length = read_wstring_units(values, MAX_LINE_LEN); + 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(); @@ -6402,7 +6452,21 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) address->var_name = gen_name(); add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); depth++; - if (depth == dimension_count + 1) { + 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, requested.type, requested.pointee_element_ptr_level); @@ -7338,13 +7402,13 @@ void read_expr_operand(block_t *parent, basic_block_t **bb); */ int read_sizeof_string_literal(void) { - char literal[MAX_TOKEN_LEN]; - char unescaped[MAX_TOKEN_LEN]; + char literal[MAX_STRING_LEN]; + char unescaped[MAX_STRING_LEN]; int size = 1; do { lex_ident(T_string, literal); - unescape_string(literal, unescaped, MAX_TOKEN_LEN); + unescape_string(literal, unescaped, MAX_STRING_LEN); size += strlen(unescaped); } while (lex_peek(T_string, NULL)); @@ -7353,9 +7417,9 @@ int read_sizeof_string_literal(void) int read_sizeof_wstring_literal(void) { - int values[MAX_LINE_LEN]; + int values[MAX_STRING_LEN]; type_t *wide_type = find_type("wchar_t", true); - return (read_wstring_units(values, MAX_LINE_LEN) + 1) * wide_type->size; + return (read_wstring_units(values, MAX_STRING_LEN) + 1) * wide_type->size; } int sizeof_array_object(const var_t *array) @@ -7367,6 +7431,14 @@ int sizeof_array_object(const var_t *array) return array->array_size * element_size; } +/* What a type-only walk over a sizeof operand has seen. */ +typedef struct { + int members; /* record member selections */ + int subscripts; /* array subscripts */ + bool element_leaf; /* the last subscript selected a non-array element */ + bool addressed; /* the operand so far is "&array" */ +} sizeof_walk_t; + /* 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 @@ -7375,26 +7447,64 @@ int sizeof_array_object(const var_t *array) static bool scan_sizeof_postfix_operand(block_t *scope, token_t **cursor, var_t *object, - int *members) + sizeof_walk_t *walk) { token_t *token = *cursor; if (!token) return false; if (token->kind == T_asterisk) { + bool addressed; int depth; token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, members)) + if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) return false; + addressed = walk->addressed; depth = effective_pointer_depth(object); if (depth <= 0) return false; - object->type = pointee_type_from_pointer_typedef(object->type); - object->ptr_level = depth - 1; + if (object->pointee_array_size > 0 && + depth == object->pointee_array_element_ptr_level + 1) { + /* A pointer to an array designates the complete array, so the + * dereference restores its bounds rather than decaying them. + */ + 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; + } + walk->addressed = false; + walk->element_leaf = false; + } else if (token->kind == T_ampersand) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) + return 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++; + walk->element_leaf = false; } else if (token->kind == T_open_bracket) { token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, members) || + if (!scan_sizeof_postfix_operand(scope, &token, object, walk) || !token || token->kind != T_close_bracket) return false; token = token->next; @@ -7410,6 +7520,7 @@ static bool scan_sizeof_postfix_operand(block_t *scope, while (token && (token->kind == T_dot || token->kind == T_arrow || token->kind == T_open_square)) { + walk->addressed = false; if (token->kind == T_dot || token->kind == T_arrow) { bool through_pointer = token->kind == T_arrow; type_t *record; @@ -7429,7 +7540,8 @@ static bool scan_sizeof_postfix_operand(block_t *scope, if (!field) return false; memcpy(object, field, sizeof(*object)); - *members = *members + 1; + walk->members++; + walk->element_leaf = false; token = token->next; } else { int depth = 0; @@ -7455,6 +7567,8 @@ static bool scan_sizeof_postfix_operand(block_t *scope, if (!fixed_array_shape_drop_outer(&shape)) return false; fixed_array_shape_to_var(object, &shape); + walk->subscripts++; + walk->element_leaf = !object->array_size; } } *cursor = token; @@ -7463,14 +7577,20 @@ static bool scan_sizeof_postfix_operand(block_t *scope, /* 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. + * 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) +static void consume_sizeof_postfix_operand(block_t *parent, + basic_block_t **bb, + bool open_consumed) { - if (lex_accept(T_asterisk)) - consume_sizeof_postfix_operand(parent, bb); + 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_accept(T_open_bracket)) { - consume_sizeof_postfix_operand(parent, bb); + consume_sizeof_postfix_operand(parent, bb, false); lex_expect(T_close_bracket); } else lex_expect(T_identifier); @@ -7495,55 +7615,101 @@ static void consume_sizeof_postfix_operand(block_t *parent, basic_block_t **bb) } } +/* The operand must end where sizeof's operand ends: at the closing parenthesis + * of a parenthesized one, 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) +{ + return tail && (parenthesized ? tail->kind == T_close_bracket + : tail->kind != T_open_bracket && + tail->kind != T_increment && + tail->kind != T_decrement); +} + /* A non-mutating admission check for callers that need to hand a global sizeof - * operand to the detached local parser. Keep its accepted grammar in lockstep - * with read_sizeof_postfix_operand() so type names and scalar forms remain - * owned by their existing global constant paths. + * operand to the detached local parser. It admits only a member array, so type + * names and scalar forms remain owned by their existing global constant paths. */ static bool can_scan_sizeof_postfix_extent(block_t *scope, token_t *start, - bool parenthesized) + bool parenthesized, + bool allow_flexible) { token_t *tail = start; var_t object; - int members = 0; + sizeof_walk_t walk = {0}; + + return scan_sizeof_postfix_operand(scope, &tail, &object, &walk) && + walk.members && sizeof_operand_tail_ends(tail, parenthesized) && + !effective_pointer_depth(&object) && + (object.array_size > 0 || + (allow_flexible && object.is_flexible_array_member)); +} + +/* Walk a local sizeof operand from @start and say whether this walker owns it: + * an operand that still designates an array, whatever the path to it, or a + * single element selected from one. Anything else keeps the ordinary + * unevaluated-expression path. + */ +static bool walk_local_sizeof_operand(block_t *parent, + token_t *start, + bool parenthesized, + var_t *object) +{ + token_t *tail = start; + sizeof_walk_t walk = {0}; - return scan_sizeof_postfix_operand(scope, &tail, &object, &members) && - members && tail && - (parenthesized - ? tail->kind == T_close_bracket - : tail->kind != T_open_bracket && tail->kind != T_increment && - tail->kind != T_decrement) && - !effective_pointer_depth(&object) && object.array_size > 0; + if (!scan_sizeof_postfix_operand(parent, &tail, object, &walk) || + !sizeof_operand_tail_ends(tail, parenthesized)) + return false; + if (object->is_flexible_array_member) + return walk.members > 0; + return object->array_size > 0 || walk.element_leaf; } -/* First staged type-only postfix walker: identifier roots, balanced grouping, - * record members, and fixed-array postfixes. Other sizeof forms keep their - * dedicated fallbacks until their primary operations are migrated here. +/* Every sizeof operand made of an identifier, grouping, dereference, address, + * 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) { - token_t *tail = cur_token->next; + bool open_consumed = false; var_t object; - int members = 0; var_t *result; + int size; - if (!can_scan_sizeof_postfix_extent(parent, tail, parenthesized)) - return false; - scan_sizeof_postfix_operand(parent, &tail, &object, &members); + if (!walk_local_sizeof_operand(parent, cur_token->next, parenthesized, + &object)) { + /* 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)) + return false; + open_consumed = true; + } if (object.is_flexible_array_member) error_at("sizeof cannot be applied to a flexible array member", 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); - if (parenthesized) + 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 + size = object.type->size; result = require_var(parent); - result->init_val = sizeof_array_object(&object); + result->init_val = size; result->var_name = gen_name(); opstack_push(result); add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); @@ -7560,528 +7726,6 @@ static bool is_direct_fixed_array_pointer_slot(const var_t *pointer) pointer->type->array_element_ptr_level == 1)); } -/* A direct pointer-to-fixed-array dereference designates its complete row. - * Ordinary expression lowering decays that row before sizeof can observe it, so - * retain this exact unary form while the declaration's pointee extent is still - * available. - */ -static bool read_sizeof_dereference_pointee_array(block_t *parent, - basic_block_t **bb, - bool parenthesized) -{ - token_t *star = cur_token->next; - token_t *identifier; - token_t *tail; - var_t *pointer; - type_t *element_type; - var_t *result; - int element_size; - bool indexes_fixed_array_pointer_slot = false; - - if (!star || star->kind != T_asterisk || !(identifier = star->next) || - identifier->kind != T_identifier) - return false; - pointer = find_var(identifier->literal, parent); - if (!pointer || !pointer->pointee_array_size) - return false; - tail = identifier->next; - if (tail && tail->kind == T_open_square) { - int depth = 0; - - indexes_fixed_array_pointer_slot = - is_direct_fixed_array_pointer_slot(pointer); - for (; tail; tail = tail->next) { - if (tail->kind == T_open_square) - depth++; - else if (tail->kind == T_close_square && --depth == 0) { - tail = tail->next; - break; - } - } - if (!indexes_fixed_array_pointer_slot || depth) - return false; - } - if (parenthesized && (!tail || tail->kind != T_close_bracket)) - return false; - if (effective_pointer_depth(pointer) != - pointer->pointee_array_element_ptr_level + 1) - return false; - - element_type = pointer->type->pointee_array_element_type - ? pointer->type->pointee_array_element_type - : pointer->type; - element_size = pointer->pointee_array_element_ptr_level - ? PTR_SIZE - : element_type->size; - lex_expect(T_asterisk); - lex_expect(T_identifier); - if (indexes_fixed_array_pointer_slot) { - basic_block_t *unevaluated_bb; - int saved_side_effects; - - lex_expect(T_open_square); - unevaluated_bb = bb_create(parent); - saved_side_effects = se_idx; - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); - opstack_pop(); - se_idx = saved_side_effects; - lex_expect(T_close_square); - } - if (parenthesized) - lex_expect(T_close_bracket); - result = require_var(parent); - result->init_val = pointer->pointee_array_size * element_size; - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - -/* `&array` points to the complete array object, and dereferencing that pointer - * restores an array lvalue. Ordinary expression lowering deliberately decays - * arrays before it can represent the unary pair, so recognize the complete - * `*&identifier` operand here while sizeof still has access to its declaration. - */ -bool read_sizeof_address_dereference_array(block_t *parent, - basic_block_t **bb, - bool parenthesized) -{ - token_t *star = cur_token->next; - token_t *ampersand; - token_t *identifier; - var_t *array; - var_t *result; - - if (!star || star->kind != T_asterisk || !star->next || - star->next->kind != T_ampersand || !star->next->next || - star->next->next->kind != T_identifier) - return false; - ampersand = star->next; - identifier = ampersand->next; - if (parenthesized && - (!identifier->next || identifier->next->kind != T_close_bracket)) - return false; - - array = find_var(identifier->literal, parent); - if (!array || array->array_size <= 0) - return false; - - lex_expect(T_asterisk); - lex_expect(T_ampersand); - lex_expect(T_identifier); - if (parenthesized) - lex_expect(T_close_bracket); - - result = require_var(parent); - result->init_val = sizeof_array_object(array); - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - -/* A member access is also an array lvalue before the usual array-to-pointer - * conversion. Resolve a complete dot-member path against its declarations so - * sizeof can observe the last member's extent without changing ordinary member - * expression lowering. - */ -bool read_sizeof_member_array(block_t *parent, - basic_block_t **bb, - bool parenthesized) -{ - token_t *object_token = cur_token->next; - token_t *member_token; - token_t *tail; - var_t *object; - var_t *field = NULL; - int object_ptr_level; - int nested_parentheses = 0; - - /* A parenthesized member-array expression remains an array lvalue for - * sizeof. Peel only enclosing parentheses here; the ordinary member path - * below still owns the dot/arrow grammar and rejects other expressions. - */ - while (object_token && object_token->kind == T_open_bracket) { - nested_parentheses++; - object_token = object_token->next; - } - - if (!object_token || object_token->kind != T_identifier || - !object_token->next || - (object_token->next->kind != T_dot && - object_token->next->kind != T_arrow) || - !object_token->next->next || - object_token->next->next->kind != T_identifier) - return false; - object = find_var(object_token->literal, parent); - object_ptr_level = object ? object->ptr_level + object->type->ptr_level : 0; - if (!object || - ((object_token->next->kind == T_dot && object_ptr_level != 0) || - (object_token->next->kind == T_arrow && object_ptr_level != 1))) - return false; - - member_token = object_token->next->next; - for (;;) { - field = find_member(member_token->literal, - field ? field->type : object->type); - if (!field || field->ptr_level) - return false; - tail = member_token; - if (!tail->next || tail->next->kind != T_dot) - break; - if (!tail->next->next || tail->next->next->kind != T_identifier) - return false; - member_token = tail->next->next; - } - if (field->is_flexible_array_member) - error_at("sizeof cannot be applied to a flexible array member", - cur_token_loc()); - if (field->array_size <= 0 || - (tail->next && tail->next->kind == T_open_square) || - (parenthesized && (!tail->next || tail->next->kind != T_close_bracket))) - return false; - - token_t *closing = tail->next; - for (int i = 0; i < nested_parentheses; i++) { - if (!closing || closing->kind != T_close_bracket) - return false; - closing = closing->next; - } - if (parenthesized && (!closing || closing->kind != T_close_bracket)) - return false; - - for (int i = 0; i < nested_parentheses; i++) - lex_expect(T_open_bracket); - lex_expect(T_identifier); - if (object_token->next->kind == T_arrow) - lex_expect(T_arrow); - else - lex_expect(T_dot); - lex_expect(T_identifier); - while (lex_peek(T_dot, NULL)) { - lex_expect(T_dot); - lex_expect(T_identifier); - } - for (int i = 0; i < nested_parentheses; i++) - lex_expect(T_close_bracket); - if (parenthesized) - lex_expect(T_close_bracket); - - var_t *result = require_var(parent); - result->init_val = sizeof_array_object(field); - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - -/* Preserve the remaining row type for a direct multidimensional array member. - * The index expressions are parsed in detached IR, just like direct arrays, so - * sizeof does not evaluate them. - */ -bool read_sizeof_member_array_row(block_t *parent, - basic_block_t **bb, - bool parenthesized) -{ - token_t *object_token = cur_token->next; - token_t *member_token; - token_t *tail; - var_t *object; - var_t *field = NULL; - type_t *member_record; - int object_ptr_level; - int subscript_count = 0; - int dimensions; - int row_elements; - int nested_parentheses = 0; - int grouped_member_closes = 0; - - /* sizeof((object.member[index])) preserves the member-array lvalue just - * like the ungrouped spelling. Peel only groups that enclose the complete - * member expression; the trailing closes are consumed after its detached - * subscripts so index side effects remain unevaluated. - */ - while (object_token && object_token->kind == T_open_bracket) { - nested_parentheses++; - object_token = object_token->next; - } - - if (!object_token || object_token->kind != T_identifier || - !object_token->next || - (object_token->next->kind != T_dot && - object_token->next->kind != T_arrow) || - !object_token->next->next || - object_token->next->next->kind != T_identifier) - return false; - object = find_var(object_token->literal, parent); - object_ptr_level = object ? object->ptr_level + object->type->ptr_level : 0; - if (!object || - ((object_token->next->kind == T_dot && object_ptr_level != 0) || - (object_token->next->kind == T_arrow && object_ptr_level != 1))) - return false; - member_token = object_token->next->next; - member_record = object->type; - for (;;) { - field = find_member(member_token->literal, member_record); - if (!field) - return false; - tail = member_token->next; - if (!tail || (tail->kind != T_dot && tail->kind != T_arrow)) - break; - if (!tail->next || tail->next->kind != T_identifier) - return false; - if (tail->kind == T_dot) { - if (effective_pointer_depth(field) != 0) - return false; - member_record = field->type; - } else { - if (effective_pointer_depth(field) != 1) - return false; - member_record = pointee_type_from_pointer_typedef(field->type); - } - member_token = tail->next; - } - if (!field || effective_pointer_depth(field) || field->array_dim2 <= 0) - return false; - - /* A group can enclose the member lvalue before a following subscript, e.g. - * sizeof((((record.rows)))[1]). Consume only its matched closes; any - * remaining groups still enclose the complete postfix expression. - */ - while (grouped_member_closes < nested_parentheses && tail && - tail->kind == T_close_bracket) { - grouped_member_closes++; - tail = tail->next; - } - while (tail && tail->kind == T_open_square) { - int depth = 0; - for (; tail; tail = tail->next) { - if (tail->kind == T_open_square) - depth++; - else if (tail->kind == T_close_square && --depth == 0) { - tail = tail->next; - break; - } - } - if (depth) - return false; - subscript_count++; - } - if (!tail) - return false; - for (int i = grouped_member_closes; i < nested_parentheses; i++) { - if (!tail || tail->kind != T_close_bracket) - return false; - tail = tail->next; - } - if (parenthesized && (!tail || tail->kind != T_close_bracket)) - return false; - dimensions = 2; - if (field->array_dim3) - dimensions = 3; - if (field->array_dim4) - dimensions = 4; - if (subscript_count > dimensions) - return false; - - for (int i = 0; i < nested_parentheses; i++) - lex_expect(T_open_bracket); - lex_expect(T_identifier); - if (object_token->next->kind == T_arrow) - lex_expect(T_arrow); - else - lex_expect(T_dot); - lex_expect(T_identifier); - while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { - if (lex_peek(T_arrow, NULL)) - lex_expect(T_arrow); - else - lex_expect(T_dot); - lex_expect(T_identifier); - } - for (int i = 0; i < grouped_member_closes; i++) - lex_expect(T_close_bracket); - for (int i = 0; i < subscript_count; i++) { - lex_expect(T_open_square); - basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); - opstack_pop(); - se_idx = saved_side_effects; - lex_expect(T_close_square); - } - for (int i = grouped_member_closes; i < nested_parentheses; i++) - lex_expect(T_close_bracket); - if (parenthesized) - lex_expect(T_close_bracket); - - row_elements = 1; - if (subscript_count < 2) - row_elements *= field->array_dim2; - if (subscript_count < 3 && field->array_dim3) - row_elements *= field->array_dim3; - if (subscript_count < 4 && field->array_dim4) - row_elements *= field->array_dim4; - var_t *result = require_var(parent); - result->init_val = row_elements * field->type->size; - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - -/* Parenthesized dereference is another spelling of a direct record member - * lvalue. Preserve a trailing array member for sizeof without teaching normal - * expression lowering to suppress its usual array conversion. - */ -bool read_sizeof_dereference_member_array(block_t *parent, - basic_block_t **bb, - bool parenthesized) -{ - bool sizeof_open_is_deref = - parenthesized && cur_token->next && cur_token->next->kind == T_asterisk; - token_t *open = sizeof_open_is_deref ? cur_token : cur_token->next; - token_t *star = open ? open->next : NULL; - token_t *pointer_token; - token_t *member_token; - token_t *tail; - var_t *pointer; - var_t *field = NULL; - int pointer_level; - - if (!open || open->kind != T_open_bracket || !star || - star->kind != T_asterisk || !star->next || - star->next->kind != T_identifier || !star->next->next || - star->next->next->kind != T_close_bracket || !star->next->next->next || - star->next->next->next->kind != T_dot || - !star->next->next->next->next || - star->next->next->next->next->kind != T_identifier) - return false; - pointer_token = star->next; - pointer = find_var(pointer_token->literal, parent); - pointer_level = pointer ? pointer->ptr_level + pointer->type->ptr_level : 0; - if (!pointer || pointer_level != 1) - return false; - - member_token = star->next->next->next->next; - for (;;) { - field = find_member(member_token->literal, - field ? field->type : pointer->type); - if (!field || field->ptr_level) - return false; - tail = member_token; - if (!tail->next || tail->next->kind != T_dot) - break; - if (!tail->next->next || tail->next->next->kind != T_identifier) - return false; - member_token = tail->next->next; - } - if (field->is_flexible_array_member) - error_at("sizeof cannot be applied to a flexible array member", - cur_token_loc()); - if (field->array_size <= 0 || - (parenthesized && !sizeof_open_is_deref && - (!tail->next || tail->next->kind != T_close_bracket))) - return false; - - if (!sizeof_open_is_deref) - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - lex_expect(T_identifier); - lex_expect(T_close_bracket); - do { - lex_expect(T_dot); - lex_expect(T_identifier); - } while (lex_peek(T_dot, NULL)); - if (parenthesized && !sizeof_open_is_deref) - lex_expect(T_close_bracket); - - var_t *result = require_var(parent); - result->init_val = sizeof_array_object(field); - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - -/* Subscripted multidimensional arrays retain their remaining inner array type - * under sizeof. Parse the index in detached IR for the ordinary expression - * constraints, but never emit its side effects. - */ -bool read_sizeof_array_row(block_t *parent, - basic_block_t **bb, - bool parenthesized) -{ - token_t *identifier = cur_token->next; - token_t *tail; - var_t *array; - int subscript_count = 0; - int dimensions; - int row_elements; - - if (!identifier || identifier->kind != T_identifier || !identifier->next || - identifier->next->kind != T_open_square) - return false; - tail = identifier->next; - while (tail && tail->kind == T_open_square) { - int depth = 0; - for (; tail; tail = tail->next) { - if (tail->kind == T_open_square) - depth++; - else if (tail->kind == T_close_square && --depth == 0) { - tail = tail->next; - break; - } - } - if (depth) - return false; - subscript_count++; - } - if (!tail || (parenthesized && tail->kind != T_close_bracket)) - return false; - - array = find_var(identifier->literal, parent); - if (!array || array->array_dim2 <= 0) - return false; - dimensions = 2; - if (array->array_dim3) - dimensions = 3; - if (array->array_dim4) - dimensions = 4; - if (subscript_count > dimensions) - return false; - - lex_expect(T_identifier); - for (int i = 0; i < subscript_count; i++) { - lex_expect(T_open_square); - basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); - opstack_pop(); - se_idx = saved_side_effects; - lex_expect(T_close_square); - } - if (parenthesized) - lex_expect(T_close_bracket); - - row_elements = 1; - if (subscript_count < 2) - row_elements *= array->array_dim2; - if (subscript_count < 3 && array->array_dim3) - row_elements *= array->array_dim3; - if (subscript_count < 4 && array->array_dim4) - row_elements *= array->array_dim4; - var_t *result = require_var(parent); - result->init_val = row_elements * array->type->size; - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - void handle_sizeof_operator(block_t *parent, basic_block_t **bb) { char token[MAX_ID_LEN]; @@ -8132,20 +7776,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) return; } - if (read_sizeof_dereference_pointee_array(parent, bb, parenthesized)) - return; - if (read_sizeof_address_dereference_array(parent, bb, parenthesized)) - return; if (read_sizeof_postfix_operand(parent, bb, parenthesized)) return; - if (read_sizeof_member_array(parent, bb, parenthesized)) - return; - if (read_sizeof_member_array_row(parent, bb, parenthesized)) - return; - if (read_sizeof_dereference_member_array(parent, bb, parenthesized)) - return; - if (read_sizeof_array_row(parent, bb, parenthesized)) - return; /* The type-name alternative requires parentheses, but C99 also permits a * unary expression directly after sizeof. Keep direct array identifiers @@ -12565,6 +12197,45 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) 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 = require_ref_var(parent, true_val->type, 0); + false_addr = require_ref_var(parent, false_val->type, 0); + selected = require_ref_var(parent, true_val->type, 0); + true_addr->var_name = gen_name(); + false_addr->var_name = gen_name(); + selected->var_name = gen_name(); + add_insn(parent, then_, OP_address_of, true_addr, true_val, NULL, 0, + NULL); + add_insn(parent, else_, OP_address_of, false_addr, false_val, NULL, 0, + NULL); + 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) { @@ -13473,8 +13144,8 @@ static int read_global_sizeof_expression(block_t *scope) static int read_const_string_size(void) { - char buffer[MAX_TOKEN_LEN]; - char unescaped[MAX_TOKEN_LEN]; + char buffer[MAX_STRING_LEN]; + char unescaped[MAX_STRING_LEN]; int res = 0; do { @@ -13491,15 +13162,21 @@ static int read_const_string_size(void) int read_const_wstring_size(void) { - int values[MAX_LINE_LEN]; + int values[MAX_STRING_LEN]; type_t *wide_type = find_type("wchar_t", true); - return (read_wstring_units(values, MAX_LINE_LEN) + 1) * wide_type->size; + return (read_wstring_units(values, MAX_STRING_LEN) + 1) * wide_type->size; } int read_primary_constant(block_t *scope) { /* return signed constant */ int isneg = 0, res; + + /* This recurses once per nesting level of a constant expression, so the + * buffer holds only what is copied into it: a number, a character constant + * or an identifier, each at most MAX_TOKEN_LEN. A string is only tested for + * and never copied. + */ char buffer[MAX_TOKEN_LEN]; if (lex_accept(T_minus)) isneg = 1; @@ -13531,11 +13208,11 @@ int read_primary_constant(block_t *scope) i++) nested_after = nested_after->next; } - if (can_scan_sizeof_postfix_extent(scope, - lex_peek(T_open_bracket, NULL) - ? cur_token->next->next + if (can_scan_sizeof_postfix_extent( + scope, + lex_peek(T_open_bracket, NULL) ? cur_token->next->next : cur_token->next, - lex_peek(T_open_bracket, NULL))) { + lex_peek(T_open_bracket, NULL), false)) { /* The shared postfix walker has already proved this is an * identifier-rooted fixed array extent, so the detached parser can * preserve it without a global-prefix spelling table. @@ -13767,9 +13444,9 @@ int read_primary_constant(block_t *scope) lex_peek(T_wchar, buffer)) { read_primary_constant(scope); res = TY_int->size; - } else if (lex_peek(T_string, buffer)) { + } else if (lex_peek(T_string, NULL)) { res = read_const_string_size(); - } else if (lex_peek(T_wstring, buffer)) { + } else if (lex_peek(T_wstring, NULL)) { res = read_const_wstring_size(); } else if (lex_peek(T_ampersand, NULL)) { read_const_addressed_object(scope, NULL); @@ -14701,7 +14378,6 @@ bool read_global_assignment_var(var_t *var) if (object && object->is_global && (explicit_address || object->array_size)) { fixed_array_shape_t shape = fixed_array_shape_from_var(object); - int array_subscript = 0; var_t *object_addr = require_ref_var(parent, object->type, object->ptr_level); @@ -14740,66 +14416,9 @@ bool read_global_assignment_var(var_t *var) byte_offset, 0, NULL); object_addr = offset_addr; } - while (explicit_address) { - 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, object->type); - if (!field) - error_at("Unknown struct or union member", - cur_token_loc()); - object_addr = compute_field_address(parent, &bb, - object_addr, field); - object_addr->is_global_address = true; - object = field; - shape = fixed_array_shape_from_var(object); - array_subscript = 0; - } else if (object->array_size && - lex_accept(T_open_square)) { - int index = read_primary_constant(scope); - int stride = - object->ptr_level ? PTR_SIZE : object->type->size; - - lex_expect(T_close_square); - stride = fixed_array_shape_stride( - &shape, array_subscript, stride); - object_addr = compute_element_address( - parent, &bb, object_addr, index, stride); - object_addr->is_global_address = true; - array_subscript++; - } else - break; - } - - /* An address constant may be offset after an explicitly - * addressed member or element too. Array decay had this - * handling above, but forms such as "&record.items[0] + 1" - * stopped at the plus token. Keep the offset byte-scaled here - * so global setup receives a literal byte offset. - */ - if (explicit_address && - (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL))) { - /* read_const_expr accepts the leading binary sign as a - * unary sign here and consumes the entire integer constant - * expression, including grouping and enum constants. - */ - int index = read_global_address_offset(scope, parent, bb); - int elem_size = - object->ptr_level ? PTR_SIZE : object->type->size; - - /* An address of a partially subscripted array retains the - * remaining row dimensions. Its trailing constant offset - * must therefore use the row/plane stride. - */ - elem_size = fixed_array_shape_stride( - &shape, array_subscript - 1, elem_size); - - object_addr = compute_element_address( - parent, &bb, object_addr, index, elem_size); - object_addr->is_global_address = true; - } + if (explicit_address) + object_addr = read_global_address_designator( + scope, parent, &bb, &object, object_addr); if (incompatible_pointee_callback_conversion(object_addr, var)) error_at("incompatible callback slot types in initializer", cur_token_loc()); diff --git a/src/preprocessor.c b/src/preprocessor.c index 9514886d..528c088a 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -2785,11 +2785,11 @@ token_t *pp_strip_layout(token_t *tk) if (cur != &head && ((cur->kind == T_string && tk->kind == T_string) || (cur->kind == T_wstring && tk->kind == T_wstring))) { - char combined[MAX_TOKEN_LEN]; + char combined[MAX_STRING_LEN]; int left_len = strlen(cur->literal); int right_len = strlen(tk->literal); - if (left_len + right_len >= MAX_TOKEN_LEN) + if (left_len + right_len >= MAX_STRING_LEN) error_at("Concatenated string literal too long", &tk->location); memcpy(combined, cur->literal, left_len); memcpy(combined + left_len, tk->literal, right_len + 1); diff --git a/src/x64-codegen.c b/src/x64-codegen.c index c52ffaa0..ea186c7d 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -3572,6 +3572,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); } diff --git a/tests/driver.sh b/tests/driver.sh index e0acf048..52d35944 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -6265,6 +6265,55 @@ int main(void) { } 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 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" @@ -7780,6 +7829,21 @@ 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" @@ -11579,6 +11643,30 @@ int main(void) { } 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 + # Category: Const Qualifiers begin_category "Const Qualifiers" "Testing const qualifier support for variables and parameters" @@ -12233,6 +12321,37 @@ begin_category "Ternary Operator" "Testing 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" @@ -12778,6 +12897,16 @@ int main() { } 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 + # Category: Switch Statements begin_category "Switch Statements" "Testing switch-case control flow" @@ -15552,6 +15681,15 @@ int main() { } EOF +# 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 + # va_list and variadic function tests Note: These tests demonstrate both direct # pointer arithmetic and va_list typedef forwarding between functions, now fully # supported. @@ -18492,6 +18630,23 @@ int main(void) { } EOF +# 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 + try_ 0 << EOF #include long long wide_argument(int count, ...) { From 6ab8267821e98dca450628883dbbbec7c3b96ab2 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 12 Sep 2026 20:51:01 +0800 Subject: [PATCH 09/40] Report failures and run the full check everywhere Internal failures print a diagnostic instead of aborting silently, and a compile-error test fails when the compiler crashes rather than reporting an error. Driver cases that need 64-bit values are gated on the pointer width, and check-all-targets runs the full check on arm, arm64, riscv and x64, as CI does. --- Makefile | 25 +++++++++- src/arm-codegen.c | 6 ++- src/arm64-codegen.c | 2 + src/globals.c | 11 ++++- src/parser.c | 12 +++-- src/reg-alloc.c | 1 + src/riscv-codegen.c | 5 +- src/ssa.c | 14 ++++++ src/x64-codegen.c | 1 + tests/driver.sh | 117 ++++++++++++++++++++++++++++++-------------- 10 files changed, 145 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 9f23ec5e..d3863431 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,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, @@ -117,7 +118,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) @@ -153,6 +154,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/src/arm-codegen.c b/src/arm-codegen.c index c6a59ae6..6b4251ad 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -358,6 +358,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, @@ -522,7 +524,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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->dest == 1) @@ -532,7 +534,7 @@ 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)); diff --git a/src/arm64-codegen.c b/src/arm64-codegen.c index 323beae7..762d3a01 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -431,6 +431,8 @@ void emit_ph2_ir(ph2_ir_t *p) switch (p->op) { case OP_define: { bool reload_global_base = dynlink && !strcmp(p->func_name, "main"); + + fatal_function_context = p->func_name; emit(0xa9bf7bfd); /* stp x29, x30, [sp, #-16]! */ emit(0x910003fd); /* mov x29, sp */ emit(0xa9bf57f4); /* stp x20, x21, [sp, #-16]! */ diff --git a/src/globals.c b/src/globals.c index d984c3b1..f96072ab 100644 --- a/src/globals.c +++ b/src/globals.c @@ -2038,6 +2038,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 @@ -2045,7 +2051,10 @@ 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 diff --git a/src/parser.c b/src/parser.c index daef05c0..33bf5b98 100644 --- a/src/parser.c +++ b/src/parser.c @@ -4958,10 +4958,11 @@ void read_full_var_decl(var_t *vd, } if (!type) { - printf("Could not find type %s%s\n", - find_type_flag == 2 ? "struct/union " : "", type_name); - fflush(stdout); /* see fatal() */ - abort(); + char message[MAX_LINE_LEN]; + + snprintf(message, MAX_LINE_LEN, "Could not find type %s%s", + find_type_flag == 2 ? "struct/union " : "", type_name); + error_at(message, cur_token_loc()); } vd->type = type; @@ -16872,7 +16873,8 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) 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[0]) + 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 && diff --git a/src/reg-alloc.c b/src/reg-alloc.c index b53000b6..56d1006f 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -3393,6 +3393,7 @@ 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; diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 449b1973..05b02a4c 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -387,6 +387,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))); @@ -502,7 +503,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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->dest == 1) @@ -512,7 +513,7 @@ 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; diff --git a/src/ssa.c b/src/ssa.c index c9f81011..aac9e360 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -3216,6 +3216,20 @@ 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); + } + func->next = NULL; if (func->bbs && !func->is_used) { func = next; diff --git a/src/x64-codegen.c b/src/x64-codegen.c index ea186c7d..03cb0193 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -3298,6 +3298,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); diff --git a/tests/driver.sh b/tests/driver.sh index 52d35944..09d99476 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -365,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 @@ -377,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"]++)) @@ -409,6 +413,9 @@ function try_compile_error_flag() 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"]++)) @@ -487,7 +494,8 @@ function try_compile_error_message() ((TOTAL_TESTS++)) ((CATEGORY_TESTS["$CURRENT_CATEGORY"]++)) - if [ "$exit_code" -eq 0 ] || ! grep -Fq -- "$expected" "$tmp_log"; then + 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 @@ -3989,7 +3997,7 @@ int main(void) { return 0; } EOF -try_ 16 << EOF +try_ "$((2 * PTR_SZ))" << EOF int main(void) { typedef int *pointer_row[2]; return sizeof(pointer_row); @@ -4686,7 +4694,7 @@ int main(void) { return callback(7); } EOF -try_ 8 << EOF +try_ "$PTR_SZ" << EOF int main(void) { typedef int (*callback_t)(int); return sizeof(callback_t); @@ -4701,7 +4709,7 @@ int main(void) { return apply(plus1, 7); } EOF -try_ 8 << EOF +try_ "$PTR_SZ" << EOF int main(void) { typedef int (*callback_t)(int); extern callback_t choose(void); @@ -5085,7 +5093,7 @@ int main(void) { return 0; } EOF -try_ 8 << EOF +try_ "$PTR_SZ" << EOF int main(void) { typedef int unary_t(int); return sizeof(unary_t *); } EOF try_ 9 << EOF @@ -5097,7 +5105,7 @@ int main(void) { return callbacks[0](3) + callbacks[1](3); } EOF -try_ 16 << EOF +try_ "$((2 * PTR_SZ))" << EOF int main(void) { typedef int (*callbacks_t[2])(int); return sizeof(callbacks_t); @@ -5112,7 +5120,7 @@ int main(void) { return callbacks[1][0](3) + callbacks[1][1](3); } EOF -try_ 32 << EOF +try_ "$((4 * PTR_SZ))" << EOF int main(void) { typedef int (*callbacks_t[2][2])(int); return sizeof(callbacks_t); @@ -5130,7 +5138,7 @@ int main(void) { return callbacks[1][1][0](3) + callbacks[1][0][0](3); } EOF -try_ 64 << EOF +try_ "$((8 * PTR_SZ))" << EOF int main(void) { typedef int (*callbacks_t[2][2][2])(int); return sizeof(callbacks_t); @@ -5148,7 +5156,7 @@ int main(void) { return callbacks[1][1][1][1](3) + callbacks[1][0][0][0](3); } EOF -try_ 128 << EOF +try_ "$((16 * PTR_SZ))" << EOF int main(void) { typedef int (*callbacks_t[2][2][2][2])(int); return sizeof(callbacks_t); @@ -5164,7 +5172,7 @@ int main(void) { {{{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) != 128); + return callbacks[0][1][0][1](6) + (sizeof(callbacks_alias_t) != 16 * sizeof(void *)); } EOF try_ 7 << EOF @@ -5203,7 +5211,7 @@ int main(void) { {{plus1, plus2}, {plus2, plus1}}, {{plus2, plus1}, {plus1, plus2}} }; - return callbacks[1][0][1](6) + (sizeof(callbacks_alias_t) != 64); + return callbacks[1][0][1](6) + (sizeof(callbacks_alias_t) != 8 * sizeof(void *)); } EOF try_ 7 << EOF @@ -5449,7 +5457,7 @@ int main(void) { } } EOF -try_ 16 << EOF +try_ "$((2 * PTR_SZ))" << EOF int main(void) { typedef int *(*rows_t)[2]; int first = 3, second = 7; @@ -5481,7 +5489,7 @@ EOF try_compile_error << EOF int main(void) { typedef int *(*bad)[]; return 0; } EOF -try_ 16 << 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 *); ) { @@ -8056,12 +8064,14 @@ int main(void) { return (1 ? narrow_nullable_increment : (unsigned char)256)(4); } EOF -try_ 5 << EOF +if [ "$PTR_SZ" -ge 8 ]; then + try_ 5 << EOF int wide_nullable_increment(int value) { return value + 1; } int main(void) { return (1 ? wide_nullable_increment : (int)4294967296LL)(4); } EOF +fi try_compile_error << EOF int void_cast_increment(int value) { return value + 1; } int main(void) { return (1 ? void_cast_increment : (void)0)(4); } @@ -11354,7 +11364,7 @@ static int helper(void); int helper(void) { return 42; } int main(void) { return helper(); } EOF -try_compile_error << 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 @@ -13279,7 +13289,7 @@ int main(void) { ((1 xor 3) == 2) and ((compl 0) < 0); } EOF -try_flags 14 "--no-libc" << EOF +try_flags 12 "--no-libc" << EOF #include #include #if CHAR_BIT != 8 || INT_MAX != 2147483647 @@ -13291,11 +13301,16 @@ int main(void) { (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) + - (LLONG_MIN < 0) + (ULLONG_MAX > LLONG_MAX); + (LONG_MIN < 0) + (ULONG_MAX > LONG_MAX); } EOF -try_flags 24 "--no-libc" << EOF +if [ "$PTR_SZ" -ge 8 ]; then + try_flags 2 "--no-libc" << EOF +#include +int main(void) { return (LLONG_MIN < 0) + (ULLONG_MAX > LLONG_MAX); } +EOF +fi +try_flags "$((8 + 2 * PTR_SZ))" "--no-libc" << EOF #include #include int main(void) { @@ -13399,7 +13414,7 @@ try_compile_error << EOF struct offsetof_cube_holder { int values[2][3][4]; }; int invalid_offsetof = offsetof(struct offsetof_cube_holder, values[1][3][0]); EOF -try_flags 46 "--no-libc" << EOF +try_flags "$((38 + PTR_SZ))" "--no-libc" << EOF #include int main(void) { return sizeof(int8_t) + sizeof(uint16_t) + sizeof(int32_t) + @@ -13408,17 +13423,24 @@ int main(void) { sizeof(uint_fast64_t) + sizeof(intmax_t) + sizeof(uintptr_t); } EOF -try_flags 18 "--no-libc" << 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) + - (INT64_MIN < 0) + (INT64_MAX > 0) + (UINT64_MAX > INT64_MAX) + + (INT32_MIN < 0) + (INT32_MAX > 0) + (UINT32_MAX > INT32_MAX); +} +EOF +if [ "$PTR_SZ" -ge 8 ]; then + try_flags 9 "--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 +fi try_flags 10 "--no-libc" << EOF #include #include @@ -13434,34 +13456,48 @@ int main(void) { (signal_value > 0 && wide_value == WINT_MAX); } EOF -try_flags 16 "--no-libc" << 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_LEAST64_MIN < 0) + (UINT_LEAST64_MAX > INT_LEAST64_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) + + (INT_FAST32_MIN < 0) + (UINT_FAST32_MAX > INT_FAST32_MAX); +} +EOF +if [ "$PTR_SZ" -ge 8 ]; then + 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 20 "--no-libc" << EOF +fi +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) + - (INT64_C(12) == 12LL) + (UINT64_C(12) == 12ULL) + - (INTMAX_C(12) == 12LL) + (UINTMAX_C(12) == 12ULL) + (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) + + (sizeof(INT32_C(12)) == 4) + (sizeof(UINT32_C(12)) == 4); +} +EOF +if [ "$PTR_SZ" -ge 8 ]; then + 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 +fi try_compile_error_message "Angle header not found in -I search paths" << EOF #include EOF @@ -18590,17 +18626,22 @@ int after_pointer(int *last, ...) { 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 +if [ "$PTR_SZ" -ge 8 ]; then + 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) { - int value = 0; - return after_char(0, 2) != 2 || after_pointer(&value, 3) != 3 || - after_wide(0, 4) != 4; -} +int main(void) { return after_wide(0, 4) != 4; } EOF +fi try_ 0 << EOF #include @@ -18647,7 +18688,8 @@ int main(void) { } EOF -try_ 0 << EOF +if [ "$PTR_SZ" -ge 8 ]; then + try_ 0 << EOF #include long long wide_argument(int count, ...) { va_list ap; @@ -18656,6 +18698,7 @@ long long wide_argument(int count, ...) { } int main(void) { return wide_argument(1, 1234567890123LL) != 1234567890123LL; } EOF +fi try_ 0 << EOF #include From fe6f79468a776e2939dc51a2e899105e368fd481 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 01:20:26 +0800 Subject: [PATCH 10/40] Speed up plain-source lexing and type lookup The lexer records that a source buffer has no trigraph and no line splice, so translation phases 1 and 2 are skipped for it, and type lookup compares first bytes before whole names. --- src/defs.h | 4 ++++ src/globals.c | 14 +++++++++++--- src/lexer.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/defs.h b/src/defs.h index 06b3e0fe..051b0742 100644 --- a/src/defs.h +++ b/src/defs.h @@ -867,11 +867,15 @@ typedef struct basic_block basic_block_t; * @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 */ diff --git a/src/globals.c b/src/globals.c index f96072ab..01802c23 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1183,16 +1183,22 @@ void add_block_typedef(block_t *block, char name[], type_t *type) type_t *find_visible_type(const char *name, block_t *block) { func_t *func = block ? block->func : NULL; + char head = name[0]; + /* Every declaration spelling passes through here, and a block's locals + * include all of its IR temporaries, so settle the first byte before the + * library call, as find_local_var() does. + */ for (; block; block = block->parent) { for (int i = block->locals.size - 1; i >= 0; i--) { var_t *var = block->locals.elements[i]; - if (var && var->var_name && !strcmp(var->var_name, name)) + if (var && var->var_name && var->var_name[0] == head && + !strcmp(var->var_name, name)) return NULL; } for (typedef_binding_t *binding = block->typedefs; binding; binding = binding->next) - if (!strcmp(binding->name, name)) + if (binding->name[0] == head && !strcmp(binding->name, name)) return binding->type; } @@ -1203,7 +1209,8 @@ type_t *find_visible_type(const char *name, block_t *block) for (int i = 0; i < func->num_params; i++) { var_t *param = &func->param_defs[i]; - if (param->var_name && !strcmp(param->var_name, name)) + if (param->var_name && param->var_name[0] == head && + !strcmp(param->var_name, name)) return NULL; } } @@ -1691,6 +1698,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); diff --git a/src/lexer.c b/src/lexer.c index 3f442ea2..113a84c0 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -168,8 +168,8 @@ char trigraph_char_at(strbuf_t *buf, int pos) { char third; - if (pos + 2 >= buf->capacity || buf->elements[pos] != '?' || - buf->elements[pos + 1] != '?') + if (buf->plain_source || pos + 2 >= buf->capacity || + buf->elements[pos] != '?' || buf->elements[pos + 1] != '?') return '\0'; third = buf->elements[pos + 2]; switch (third) { @@ -218,6 +218,8 @@ 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) != '\\' || @@ -230,7 +232,18 @@ int skip_splices(strbuf_t *buf, int pos) char peek_char(strbuf_t *buf, int offset) { - int pos = skip_splices(buf, buf->size); + 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); @@ -241,7 +254,16 @@ char peek_char(strbuf_t *buf, int offset) char read_char(strbuf_t *buf) { - int pos = skip_splices(buf, 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); @@ -250,6 +272,25 @@ char read_char(strbuf_t *buf) 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. * * The whole file is wanted and its size is already known, so it is asked for in @@ -1635,6 +1676,7 @@ 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; @@ -1694,6 +1736,7 @@ 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; From d7a4ca562613cda89c8af59895338df3cabd09d1 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 01:21:13 +0800 Subject: [PATCH 11/40] Split parser.c into ordered fragments parser.c had grown past 18,000 lines. First keep one copy of each shared helper, remove the unused ones, and move the parenthesized operand, lvalue tail, block declarator and typedef paths of the longest functions into named helpers. Then move all but the parser core into eight files that parser.c includes, in order, at its end. They are fragments of one translation unit rather than modules: each sees every definition before it, so the include order matters. Generated code is unchanged. --- src/lexer.c | 14 - src/parser-call.c | 1793 +++++ src/parser-const.c | 1566 ++++ src/parser-decl.c | 1820 +++++ src/parser-expr.c | 4834 ++++++++++++ src/parser-global.c | 1872 +++++ src/parser-init.c | 2118 ++++++ src/parser-sizeof.c | 825 ++ src/parser-stmt.c | 2089 ++++++ src/parser.c | 16953 +----------------------------------------- 10 files changed, 16960 insertions(+), 16924 deletions(-) create mode 100644 src/parser-call.c create mode 100644 src/parser-const.c create mode 100644 src/parser-decl.c create mode 100644 src/parser-expr.c create mode 100644 src/parser-global.c create mode 100644 src/parser-init.c create mode 100644 src/parser-sizeof.c create mode 100644 src/parser-stmt.c diff --git a/src/lexer.c b/src/lexer.c index 113a84c0..6f338e20 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -1834,20 +1834,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/parser-call.c b/src/parser-call.c new file mode 100644 index 00000000..d43d9717 --- /dev/null +++ b/src/parser-call.c @@ -0,0 +1,1793 @@ +/* + * 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); + if (is_record_type(target->type) && + !has_effective_pointer(target)) { + /* 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 = 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; + if (lex_accept(T_comma) && strict_c99 && + lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in function call is not permitted in C99", + 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); + result->pointee_array_element_ptr_level = + return_def->pointee_array_element_ptr_level + ? return_def->pointee_array_element_ptr_level + : return_def->type->array_element_ptr_level; + result->is_const_qualified = + return_def->is_const_qualified || return_def->type->is_const_qualified; +} + +void lower_call_result_array_postfix(var_t **value, + block_t *parent, + basic_block_t **bb) +{ + var_t *base; + var_t *address; + fixed_array_shape_t shape; + int dimensions; + 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); + + /* 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 = + base->pointee_array_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, base->type); + opcode_t compound_op = OP_generic; + bool assignment; + + element->ptr_level = base->pointee_array_element_ptr_level; + element->func_signature = base->type->func_signature; + element->is_const_qualified = + base->is_const_qualified || base->type->is_const_qualified; + element->var_name = gen_name(); + 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 (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 (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 { + *value = address; + } + 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) +{ + var_t *object; + var_t *one; + var_t *updated; + + if (op == OP_generic) + return; + object = opstack_pop(); + if (!object->is_compound_literal_reference) + 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); + 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); + add_insn(parent, *bb, OP_write, NULL, object->compound_literal_address, + updated, object->ptr_level ? PTR_SIZE : object->type->size, NULL); + *value = updated; + opstack_push(updated); +} + +/* 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); + if (lex_accept(T_struct) || lex_accept(T_union)) { + lex_ident(T_identifier, name); + record = find_type(name, 2); + } 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) +{ + char name[MAX_ID_LEN]; + type_t *type; + bool is_signed = false; + bool is_unsigned = false; + int long_count = 0; + bool is_short = false; + bool is_char = false; + bool saw_base = false; + + (void) parent; + 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; + while (true) { + if (lex_accept(T_const) || lex_accept(T_volatile)) + continue; + if (lex_accept(T_signed)) { + if (is_signed) + error_at("duplicate signed type specifier", cur_token_loc()); + is_signed = true; + continue; + } + if (lex_accept(T_unsigned)) { + if (is_unsigned) + error_at("duplicate unsigned type specifier", cur_token_loc()); + is_unsigned = true; + continue; + } + if (lex_accept(T_long)) { + long_count++; + continue; + } + if (lex_peek(T_identifier, name) && !strcmp(name, "short")) { + if (is_short) + error_at("duplicate short type specifier", cur_token_loc()); + lex_expect(T_identifier); + is_short = true; + continue; + } + if (lex_peek(T_identifier, name) && + (!strcmp(name, "int") || !strcmp(name, "char"))) { + if (saw_base) + error_at("duplicate type specifier", cur_token_loc()); + is_char = !strcmp(name, "char"); + saw_base = true; + lex_expect(T_identifier); + continue; + } + break; + } + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (long_count > 2 || (is_short && long_count)) + error_at("invalid va_arg integer type", cur_token_loc()); + + if (lex_accept(T_struct) || lex_accept(T_union)) { + if (is_signed || is_unsigned || long_count || is_short) + error_at("record type cannot have integer specifiers", + cur_token_loc()); + lex_ident(T_identifier, name); + type = find_type(name, 2); + } else if (lex_accept(T_enum)) { + if (is_signed || is_unsigned || long_count || is_short || saw_base) + error_at("enum type cannot have integer specifiers", + cur_token_loc()); + lex_ident(T_identifier, name); + type = find_type_tag(name, parent); + } else { + if (!saw_base && !is_signed && !is_unsigned && !long_count && + !is_short) { + lex_ident(T_identifier, name); + type = find_type(name, true); + goto parsed_base_type; + } + + if (long_count) + type = is_unsigned ? (long_count == 2 ? TY_ulong_long : TY_ulong) + : (long_count == 2 ? TY_long_long : TY_long); + else if (is_short) + type = is_unsigned ? TY_ushort : TY_short; + else if (is_char) + type = is_unsigned ? TY_uchar : (is_signed ? TY_schar : TY_char); + else + type = is_unsigned ? TY_uint : TY_int; + } +parsed_base_type: + 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); + } + } + 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)); +} + +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(); + + /* 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; + 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(); + + 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, requested.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, requested.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; + target->func_target = object->func_target; + target->func_target_invalid = object->func_target_invalid; + 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; + + if (!value || !value->is_func) + 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; + object->func_target = 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; +} + +/* 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 = 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; + func_t *target = callee ? callee->func_target : NULL; + + if (callee->func_target_invalid || !target || !target->bbs || + !target->returns_aggregate) + error_at( + "aggregate-return indirect call requires a shecc-defined " + "target", + cur_token_loc()); + 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 = 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; +} + +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, + 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. + */ +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()); + 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; + } + var_t *var = find_var(token, parent); + 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, true); + + if (is_bitfield(lvalue.decl)) + error_at("cannot take address of bit-field", next_token_loc()); + + 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; + opstack_push(vd); + if (lvalue.decl && is_array_declarator(lvalue.decl)) { + /* 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. + */ + add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); + } else + add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); + } +} + +/* A scalar pointer-to-array dereference designates the addressed row; it does + * not load a scalar object. Retain that row as an array descriptor so a + * following grouped postfix subscript can use the ordinary array path. + */ +static bool lower_scalar_pointee_array_dereference(var_t *source, + block_t *parent, + basic_block_t **bb) +{ + type_t *element_type; + var_t *row; + + if (!source || !source->type || !source->pointee_array_size || + source->pointee_array_element_ptr_level || + effective_pointer_depth(source) != 1) + return false; + + element_type = source->type->pointee_array_element_type + ? source->type->pointee_array_element_type + : source->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->is_const_qualified = + source->is_const_qualified || element_type->is_const_qualified; + row->var_name = gen_name(); + add_insn(parent, *bb, OP_assign, row, source, NULL, 0, NULL); + opstack_push(row); + return true; +} + +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; +} + +static bool is_scalar_pointee_array_pointer(const var_t *var) +{ + return var && var->type && var->pointee_array_size > 0 && + !var->pointee_array_element_ptr_level && + effective_pointer_depth(var) == 1; +} + +static int scalar_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; + + 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 scalar_pointee_array_shapes_compatible(const var_t *left, + const var_t *right) +{ + type_t *left_element; + type_t *right_element; + + if (!is_scalar_pointee_array_pointer(left) || + !is_scalar_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); +} + +void handle_single_dereference(block_t *parent, basic_block_t **bb) +{ + 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(); + + 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_scalar_pointee_array_dereference(rs1, parent, bb)) + 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); + if (deref_ptr > 0) + sz = PTR_SIZE; + else + sz = 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))); + + /* 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 (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { + /* A prefix update is itself a unary expression, so `*++pointer` and + * `*--pointer` dereference its updated pointer result just like + * `*(pointer + 1)` dereferences a parenthesized operand. + */ + read_expr_operand(parent, bb); + rs1 = opstack_pop(); + 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))); + if (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } + 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()); + + /* 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))); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + 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, false); + + rs1 = opstack_pop(); + if (var->is_func) { + /* C99 6.3.2.1 makes a function-pointer dereference a function + * designator. The pointer object itself therefore needs one load, + * not a dereference of its return type. + */ + opstack_push(load_function_pointer_object(parent, bb, rs1)); + return; + } + if (lower_scalar_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->func_signature || rs1->pointee_func_signature || + (deref_type && deref_type->func_signature)) && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = rs1->func_signature + ? rs1->func_signature + : (rs1->pointee_func_signature + ? rs1->pointee_func_signature + : deref_type->func_signature); + vd->ptr_level = 1; + sz = PTR_SIZE; + } + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + } +} + +/* 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. + */ +bool stmt_starts_assignment(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: + 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; +} + +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)) { + /* 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(); + 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))); + + /* A parenthesized pointer-arithmetic result can designate a + * callback object just like `*slot`. Restore its prototype only + * after consuming exactly that final object-pointer indirection. + */ + if (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } + 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(); + + /* 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 final dereference of `**slots` (or deeper spelling) + * reaches the callback object. Earlier reads still produce a + * pointer-to-callback and must not be callable. + */ + if (i + 1 == deref_count && + (rs1->func_signature || + (deref_type && deref_type->func_signature)) && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = rs1->func_signature + ? rs1->func_signature + : deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + } + } +} diff --git a/src/parser-const.c b/src/parser-const.c new file mode 100644 index 00000000..419425ec --- /dev/null +++ b/src/parser-const.c @@ -0,0 +1,1566 @@ +/* + * 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. + */ + +/* Return the extent left after selecting leading array dimensions. The global + * constant evaluator retains dimensions in var_t instead of constructing an + * expression IR, so every sizeof object path uses this one calculation. + */ +static int sizeof_subscripted_array(var_t *object, int subscript_depth) +{ + int res; + + if (!subscript_depth) + return size_var(object); + + res = object->type->size; + if (subscript_depth == 1 && object->array_dim2 > 0) { + res *= object->array_dim2; + if (object->array_dim3 > 0) + res *= object->array_dim3; + if (object->array_dim4 > 0) + res *= object->array_dim4; + } else if (subscript_depth == 2 && object->array_dim3 > 0) { + res *= object->array_dim3; + if (object->array_dim4 > 0) + res *= object->array_dim4; + } else if (subscript_depth == 3 && object->array_dim4 > 0) { + res *= object->array_dim4; + } + return res; +} + +static void validate_global_sizeof_object(var_t *object) +{ + if (object->is_func) + error_at("sizeof(function) is invalid", cur_token_loc()); + if (is_bitfield(object)) + error_at("sizeof cannot be applied to a bit-field", cur_token_loc()); + if (object->is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + cur_token_loc()); +} + +/* Resolve unevaluated record postfixes while preserving an array member's + * declared dimensions for the global sizeof constant evaluator. + */ +static var_t *read_const_object_members(var_t *object) +{ + while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { + bool through_pointer = lex_accept(T_arrow); + char field_name[MAX_ID_LEN]; + var_t *field; + int pointer_depth = effective_pointer_depth(object); + + if ((through_pointer && pointer_depth != 1) || + (!through_pointer && pointer_depth != 0)) + error_at("Invalid record member access", cur_token_loc()); + if (!through_pointer) + lex_expect(T_dot); + lex_ident(T_identifier, field_name); + field = find_member(field_name, object->type); + if (!field) + error_at("Unknown record member", cur_token_loc()); + object = field; + } + return object; +} + +/* Parse the object portion of a constant address expression. sizeof only needs + * its pointer type, but still applies the ordinary addressability constraints. + */ +static var_t *read_const_addressed_object(block_t *scope, int *subscript_depth) +{ + char buffer[MAX_TOKEN_LEN]; + var_t *object; + + lex_expect(T_ampersand); + if (subscript_depth) + *subscript_depth = 0; + lex_ident(T_identifier, buffer); + object = find_var(buffer, scope); + if (!object) + error_at("sizeof address requires a declared object", cur_token_loc()); + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + if (subscript_depth) + *subscript_depth = *subscript_depth + 1; + } + if (is_bitfield(object)) + error_at("Cannot take address of bit-field", cur_token_loc()); + return object; +} + +/* 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); + int saved_side_effects = se_idx; + var_t *result; + + handle_sizeof_operator(scope, &unevaluated_bb); + result = opstack_pop(); + se_idx = saved_side_effects; + return result->init_val; +} + +static int read_const_string_size(void) +{ + char buffer[MAX_STRING_LEN]; + char unescaped[MAX_STRING_LEN]; + int res = 0; + + do { + int length; + + lex_ident(T_string, buffer); + length = unescape_string(buffer, unescaped, sizeof(unescaped)); + if (length < 0) + error_at("Invalid escape sequence", cur_token_loc()); + res += length; + } while (lex_peek(T_string, buffer)); + return res + 1; +} + +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; +} + +int read_primary_constant(block_t *scope) +{ + /* return signed constant */ + int isneg = 0, res; + + /* This recurses once per nesting level of a constant expression, so the + * buffer holds only what is copied into it: a number, a character constant + * or an identifier, each at most MAX_TOKEN_LEN. A string is only tested for + * and never copied. + */ + char buffer[MAX_TOKEN_LEN]; + if (lex_accept(T_minus)) + isneg = 1; + if (lex_accept(T_sizeof)) { + /* The common scalar expression form needs no IR in a global constant: + * integer and character literals have type int. Type names continue + * through the declaration-only evaluator below. + */ + token_t *inside = + lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; + var_t *nested_array = NULL; + token_t *nested_root = NULL; + token_t *nested_after = NULL; + int nested_groups = 0; + + if (inside && inside->kind == T_open_bracket) { + token_t *token = inside; + + while (token && token->kind == T_open_bracket) { + nested_groups++; + token = token->next; + } + nested_root = token; + if (token && token->kind == T_identifier) + nested_array = find_var(token->literal, scope); + nested_after = token ? token->next : NULL; + for (int i = 0; i < nested_groups && nested_after && + nested_after->kind == T_close_bracket; + i++) + nested_after = nested_after->next; + } + if (can_scan_sizeof_postfix_extent( + scope, + lex_peek(T_open_bracket, NULL) ? cur_token->next->next + : cur_token->next, + lex_peek(T_open_bracket, NULL), false)) { + /* The shared postfix walker has already proved this is an + * identifier-rooted fixed array extent, so the detached parser can + * preserve it without a global-prefix spelling table. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_string) { + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + res = read_const_string_size(); + lex_expect(T_close_bracket); + lex_expect(T_close_bracket); + } else if (inside && inside->kind == T_wstring) { + res = read_const_wstring_size(); + } else if (nested_array && nested_array->array_size > 0 && + nested_groups > 0 && nested_after && + nested_after->kind == T_close_bracket) { + lex_expect(T_open_bracket); + for (int i = 0; i < nested_groups; i++) + lex_expect(T_open_bracket); + lex_expect(T_identifier); + for (int i = 0; i < nested_groups; i++) + lex_expect(T_close_bracket); + lex_expect(T_close_bracket); + res = size_var(nested_array); + } else if (nested_array && nested_groups > 0 && nested_root && + nested_root->next && + (nested_root->next->kind == T_dot || + nested_root->next->kind == T_arrow)) { + /* The local sizeof helper preserves a member-array lvalue through + * arbitrary grouping before a postfix subscript. Reuse it in a + * detached block for a global constant instead of duplicating that + * type-only traversal here. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_open_bracket && inside->next->next && + inside->next->next->kind == T_identifier) { + var_t *grouped_object = + find_var(inside->next->next->literal, scope); + + if (grouped_object && !grouped_object->array_size && + !grouped_object->has_unsized_array) + res = read_global_sizeof_expression(scope); + else + res = read_const_sizeof_type(scope); + } else if (inside && + (inside->kind == T_increment || + inside->kind == T_decrement || inside->kind == T_plus || + inside->kind == T_minus || inside->kind == T_bit_not || + inside->kind == T_log_not)) { + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + ((inside->next->kind == T_identifier && + find_type(inside->next->literal, true)) || + inside->next->kind == T_signed || + inside->next->kind == T_unsigned || + inside->next->kind == T_long || + inside->next->kind == T_const || + inside->next->kind == T_volatile || + inside->next->kind == T_struct || + inside->next->kind == T_union || + inside->next->kind == T_enum)) { + /* A cast begins with an extra parenthesis and cannot be evaluated + * as an integer constant when its operand is a declared object. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_identifier && + (find_var(inside->literal, scope) || + find_func(inside->literal)) && + inside->next && inside->next->kind != T_close_bracket && + inside->next->kind != T_dot && + inside->next->kind != T_arrow && + inside->next->kind != T_open_square) { + /* Parenthesized non-postfix operands need type derivation rather + * than constant evaluation: sizeof(global + 1), sizeof(global = 1), + * and calls are all unevaluated but need their expression type. + * Reuse the detached expression path used for local sizeof. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_asterisk && inside->next->next && + inside->next->next->kind == T_identifier && + find_var(inside->next->next->literal, scope)) { + var_t *pointer = find_var(inside->next->next->literal, scope); + var_t dereferenced; + var_t *object; + int subscript_depth = 0; + + if (effective_pointer_depth(pointer) != 1) + error_at("Cannot dereference non-pointer in sizeof", + cur_token_loc()); + + /* This is compile-time descriptor manipulation, not a source + * aggregate expression. A direct `dereferenced = *pointer` becomes + * one wide OP_read while self-hosting; ARM's scalar load backend + * correctly admits only 1/2/4-byte reads. Copy through libc + * instead, which keeps the generated compiler IR word-sized and + * preserves every descriptor field. + */ + memcpy(&dereferenced, pointer, sizeof(dereferenced)); + dereferenced.type = + pointee_type_from_pointer_typedef(pointer->type); + dereferenced.ptr_level = 0; + object = &dereferenced; + + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_expect(T_identifier); + lex_expect(T_close_bracket); + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_asterisk && inside->next && + inside->next->kind == T_identifier && inside->next->next && + inside->next->next->kind == T_open_square && + is_direct_fixed_array_pointer_slot( + find_var(inside->next->literal, scope))) { + /* The shared direct-pointer helper also owns the supported global + * pointer-array slot form. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_asterisk && inside->next && + inside->next->kind == T_ampersand) { + var_t *object; + int subscript_depth; + + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + object = read_const_addressed_object(scope, &subscript_depth); + lex_expect(T_close_bracket); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_ampersand) { + lex_expect(T_open_bracket); + read_const_addressed_object(scope, NULL); + lex_expect(T_close_bracket); + res = PTR_SIZE; + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_identifier && + find_var(inside->next->literal, scope) && + inside->next->next && + inside->next->next->kind != T_close_bracket && + inside->next->next->kind != T_dot && + inside->next->next->kind != T_arrow && + inside->next->next->kind != T_open_square) { + /* A grouped object followed by an operator is an ordinary + * parenthesized expression, not the array/member postfix form + * handled below. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_identifier && + find_var(inside->next->literal, scope)) { + /* Preserve the array object type through an explicitly grouped + * object expression, e.g. sizeof((record.items)[0]). + */ + var_t *object = find_var(inside->next->literal, scope); + int subscript_depth = 0; + + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + lex_expect(T_identifier); + object = read_const_object_members(object); + lex_expect(T_close_bracket); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_open_bracket) { + lex_expect(T_open_bracket); + read_const_expr(scope); + lex_expect(T_close_bracket); + res = TY_int->size; + } else if (inside && + (inside->kind == T_numeric || inside->kind == T_char || + inside->kind == T_wchar)) { + lex_expect(T_open_bracket); + read_const_expr(scope); + lex_expect(T_close_bracket); + res = TY_int->size; + } else if (inside && inside->kind == T_identifier) { + var_t *object = find_var(inside->literal, scope); + constant_t *constant = find_scoped_constant(inside->literal, scope); + + if (object || constant) { + lex_expect(T_open_bracket); + lex_expect(T_identifier); + if (object) { + int subscript_depth = 0; + + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at( + "Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else { + lex_expect(T_close_bracket); + res = TY_int->size; + } + } else { + res = read_const_sizeof_type(scope); + } + } else if (lex_peek(T_numeric, buffer) || lex_peek(T_char, buffer) || + lex_peek(T_wchar, buffer)) { + read_primary_constant(scope); + res = TY_int->size; + } else if (lex_peek(T_string, NULL)) { + res = read_const_string_size(); + } else if (lex_peek(T_wstring, NULL)) { + res = read_const_wstring_size(); + } else if (lex_peek(T_ampersand, NULL)) { + read_const_addressed_object(scope, NULL); + res = PTR_SIZE; + } else if (lex_accept(T_asterisk)) { + var_t *object; + int subscript_depth; + + object = read_const_addressed_object(scope, &subscript_depth); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (lex_peek(T_minus, NULL) || lex_peek(T_plus, NULL) || + lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL)) { + res = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, buffer)) { + var_t *object = find_var(buffer, scope); + constant_t *constant = find_scoped_constant(buffer, scope); + + lex_expect(T_identifier); + if (object) { + int subscript_depth = 0; + + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (constant) { + res = TY_int->size; + } else { + error_at("sizeof requires a type or declared object", + cur_token_loc()); + res = 0; + } + } else { + res = read_const_sizeof_type(scope); + } + } else if (lex_accept(T_open_bracket)) { + res = read_primary_constant(scope); + 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) || lex_peek(T_wchar, buffer)) { + char unescaped[MAX_TOKEN_LEN]; + unescape_string(buffer, unescaped, MAX_TOKEN_LEN); + res = lex_peek(T_wchar, NULL) ? parse_wide_character_constant(buffer) + : parse_character_constant(buffer); + lex_expect(lex_peek(T_wchar, NULL) ? T_wchar : T_char); + } else if (lex_peek(T_identifier, buffer)) { + constant_t *con; + + if (!strcmp(buffer, "__builtin_offsetof")) { + res = read_const_expr_operand(scope); + if (isneg) + return (-1) * res; + return res; + } + lex_expect(T_identifier); + con = find_scoped_constant(buffer, scope); + if (!con) + error_at("Identifier is not an integer constant", next_token_loc()); + res = con->value; + } else + error_at("Invalid value after assignment", next_token_loc()); + if (isneg) + return (-1) * res; + return res; +} + +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()); + res = op1 << op2; + break; + case OP_rshift: + 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); + +void emit_global_scalar_assignment(block_t *parent, + basic_block_t *bb, + var_t *dest, + var_t *src) +{ + if (!dest->ptr_level && dest->type == TY_bool) + src->init_val = src->init_val != 0; + add_insn(parent, bb, OP_assign, dest, src, NULL, 0, NULL); +} + +/* 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) +{ + int bracket_depth = 0; + + for (; 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)) + 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; +} + +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; + +/* 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; + } + + /* 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; + + /* Materialize the usual arithmetic conversion in word form. A narrow + * signed source sign-extends to either signed long long or unsigned + * long long; a narrow unsigned source zero-extends in both cases. + */ + if (left->type->size <= TY_int->size) + hi = left->type->is_unsigned || !(lo & 0x80000000U) ? 0 + : 0xffffffffU; + if (right->type->size <= TY_int->size) + rhs_hi = right->type->is_unsigned || !(rhs_lo & 0x80000000U) + ? 0 + : 0xffffffffU; + + 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; + } + result->init_val = comparison; + result->init_val_hi = 0; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; + } + + if (op == OP_log_and || op == OP_log_or) { + bool left_true = lo || hi; + bool right_true = rhs_lo || rhs_hi; + + result->init_val = op == OP_log_and ? left_true && right_true + : left_true || right_true; + result->init_val_hi = 0; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; + } + + if (op == OP_div || op == OP_mod) { + unsigned int rem_lo = 0, rem_hi = 0, quo_lo = 0, quo_hi = 0; + bool is_unsigned = left->type->is_unsigned || right->type->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()); + 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); + } + } + result->init_val = out_lo; + result->init_val_hi = out_hi; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + 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; + } + result->init_val = out_lo; + result->init_val_hi = out_hi; + result->is_const = true; + add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + return true; +} + +/* 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 = 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 = 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 = !(value->init_val || value->init_val_hi); + 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; + } + if (lex_accept(T_sizeof)) { + char sizeof_name[MAX_ID_LEN]; + + value = require_typed_var(parent, find_type("size_t", true)); + value->var_name = gen_name(); + if (lex_peek(T_string, NULL)) + value->init_val = read_const_string_size(); + else if (lex_peek(T_wstring, NULL)) + value->init_val = read_const_wstring_size(); + else if (lex_peek(T_numeric, NULL)) + value->init_val = read_global_sizeof_expression(scope); + else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_string) { + lex_expect(T_open_bracket); + value->init_val = read_const_string_size(); + lex_expect(T_close_bracket); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_wstring) { + lex_expect(T_open_bracket); + value->init_val = read_const_wstring_size(); + lex_expect(T_close_bracket); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_numeric) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_open_bracket) { + /* A nested group is an expression operand, e.g. sizeof((void)1) or + * sizeof((*incomplete_pointer)). + */ + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_asterisk && + cur_token->next->next->next && + cur_token->next->next->next->kind == T_identifier && + cur_token->next->next->next->next && + cur_token->next->next->next->next->kind == T_open_square && + is_direct_fixed_array_pointer_slot( + find_var(cur_token->next->next->next->literal, scope))) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_identifier && + cur_token->next->next->next && + cur_token->next->next->next->kind == T_close_bracket && + find_var(cur_token->next->next->literal, scope)) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, sizeof_name) && + find_var(sizeof_name, scope)) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && + cur_token->next->next && + cur_token->next->next->kind == T_identifier && + cur_token->next->next->next && + cur_token->next->next->next->kind == T_close_bracket && + find_func(cur_token->next->next->literal)) { + value->init_val = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, sizeof_name) && + find_func(sizeof_name)) { + value->init_val = read_global_sizeof_expression(scope); + } else + value->init_val = read_const_sizeof_type(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 (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(); + value->init_val = constant->value; + value->is_const = true; + add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); + 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++] = + 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 = condition->init_val || condition->init_val_hi; + + /* 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--; + 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"); + protected_rhs[op_stack_index] = + is_logical(op) && + ((op == OP_log_and && + !(val_stack[val_stack_index - 1]->init_val || + val_stack[val_stack_index - 1]->init_val_hi)) || + (op == OP_log_or && + (val_stack[val_stack_index - 1]->init_val || + val_stack[val_stack_index - 1]->init_val_hi))); + op_stack[op_stack_index++] = op; + if (protected_rhs[op_stack_index - 1]) { + wide_global_unevaluated_depth++; + } + val_stack[val_stack_index++] = + 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]; +} + +void eval_ternary_imm(int cond, var_t *var) +{ + if (cond == 0) { + while (!lex_peek(T_colon, NULL)) { + lex_next(); + } + lex_accept(T_colon); + read_global_assignment_var(var); + } else { + read_global_assignment_var(var); + lex_expect(T_colon); + while (!lex_peek(T_semicolon, NULL)) { + lex_next(); + } + } +} + +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; + } + + /* global initialization must be constant */ + { + /* 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); + 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); + 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_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 ? 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 = 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; + 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); + if (incompatible_pointee_callback_conversion(object_addr, var)) + error_at("incompatible callback slot types in initializer", + cur_token_loc()); + add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, + NULL); + return true; + } + } + if (explicit_address) + error_at("Expected a global object or function after '&'", + cur_token_loc()); + + /* 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 (typed_global_literal_appears_before_initializer_end( + cur_token->next)) { + rs1 = read_wide_global_literal_expression(parent, bb, scope); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + return true; + } + if (lex_peek(T_string, NULL) || lex_peek(T_wstring, 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";) + */ + 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_primary_constant(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_primary_constant(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; + 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_primary_constant(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..764dc5e5 --- /dev/null +++ b/src/parser-decl.c @@ -0,0 +1,1820 @@ +/* + * 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; +} + +/* Consume a nested direct-abstract-declarator in forms such as `int + * (*(*)(int))[2]` and `int (*(*(*)(int))(int))(int)`. The surrounding + * declarator already consumed its leading `(` and pointer chain. Each recursive + * invocation consumes its own parenthesized pointer declarator and function + * suffix, then the enclosing `)` and that declarator's suffix. The complete + * type remains pointer-sized, but its bounds and prototypes must still obey C99 + * syntax and the project's fixed-array-only policy. + */ +int read_sizeof_nested_function_pointer_suffix(block_t *scope, + int *outer_array_size) +{ + int inner_ptr_count = 0; + + lex_expect(T_open_bracket); + while (lex_accept(T_asterisk)) { + inner_ptr_count++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!inner_ptr_count) + error_at("sizeof nested abstract declarator needs a pointer", + cur_token_loc()); + + if (lex_peek(T_open_bracket, NULL)) + inner_ptr_count += + read_sizeof_nested_function_pointer_suffix(scope, outer_array_size); + else { + int direct_array_size = 0; + + while (lex_accept(T_open_square)) { + 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 (bound > 0 && direct_array_size && + direct_array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + direct_array_size = + direct_array_size ? direct_array_size * bound : bound; + } + if (direct_array_size && outer_array_size) + *outer_array_size = direct_array_size; + lex_expect(T_close_bracket); + if (lex_peek(T_open_bracket, NULL)) { + read_sizeof_function_prototype(); + } else if (lex_peek(T_open_square, NULL)) { + do { + int bound; + + lex_expect(T_open_square); + 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()); + } while (lex_peek(T_open_square, NULL)); + } else { + error_at( + "sizeof nested abstract declarator needs a function or " + "array suffix", + cur_token_loc()); + } + } + + lex_expect(T_close_bracket); + while (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + while (lex_accept(T_open_square)) { + 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()); + } + return inner_ptr_count; +} + +/* 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) +{ + char token[MAX_ID_LEN]; + type_t *type = NULL; + int ptr_level = 0; + int array_size = 0; + int array_element_size = 0; + bool is_unsigned = false; + bool is_signed = false; + int long_count = 0; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { + lex_ident(T_identifier, token); + type = find_type(token, 2); + } else if (lex_accept(T_enum)) { + lex_ident(T_identifier, token); + type = find_type_tag(token, scope); + } else { + /* Declaration specifiers may appear in any order: all of "unsigned long + * int", "long unsigned int", and "int unsigned" name the same type. + */ + while (true) { + 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_signed)) { + if (is_signed) + error_at("duplicate signed type specifier", + cur_token_loc()); + is_signed = true; + } else if (lex_accept(T_long)) { + long_count++; + } else if (lex_accept(T_float)) { + type = TY_float; + } else if (lex_accept(T_double)) { + type = TY_double; + } else if (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) { + ; + } else if (lex_peek(T_identifier, token)) { + type_t *candidate = find_visible_type(token, scope); + + if (!candidate) + break; + lex_expect(T_identifier); + type = candidate; + } else { + break; + } + } + if (is_unsigned && is_signed) + error_at("both signed and unsigned specified", cur_token_loc()); + if (long_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + if (type == TY_float && (is_unsigned || is_signed || long_count)) + error_at("invalid float type specifiers", cur_token_loc()); + if (type == TY_double) { + if (is_unsigned || is_signed || long_count > 1) + error_at("invalid double type specifiers", cur_token_loc()); + if (long_count) + type = TY_long_double; + } else if (long_count) { + type = is_unsigned ? TY_ulong : TY_long; + if (long_count == 2) + type = is_unsigned ? TY_ulong_long : TY_long_long; + } else if (is_unsigned) { + if (type == TY_char) + type = TY_uchar; + else if (type == TY_short) + type = TY_ushort; + else + type = TY_uint; + } else if (is_signed) { + type = type == TY_char ? TY_schar : (type ? type : TY_int); + } + } + if (!type) + error_at( + "sizeof in an integer constant expression requires a type name", + cur_token_loc()); + while (lex_accept(T_asterisk)) { + ptr_level++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + while (ptr_level == 0 && lex_accept(T_open_square)) { + 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 (bound > 0 && array_size && array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + array_size = array_size ? array_size * bound : bound; + } + if (lex_accept(T_open_bracket)) { + int nested_array_size = 0; + int nested_ptr_count = 0; + int nested_function_ptr_count = 0; + int nested_outer_array_size = 0; + + while (lex_accept(T_asterisk)) { + nested_ptr_count++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!nested_ptr_count) + error_at("sizeof abstract declarator needs a pointer", + cur_token_loc()); + if (lex_peek(T_open_bracket, NULL)) { + nested_function_ptr_count = + read_sizeof_nested_function_pointer_suffix( + scope, &nested_outer_array_size); + if (nested_outer_array_size) { + array_size = nested_outer_array_size; + array_element_size = PTR_SIZE; + } else + ptr_level += nested_ptr_count + nested_function_ptr_count; + } else + while (lex_accept(T_open_square)) { + 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 (bound > 0 && nested_array_size && + nested_array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + nested_array_size = + nested_array_size ? nested_array_size * bound : bound; + } + if (!nested_function_ptr_count) { + lex_expect(T_close_bracket); + if (nested_array_size) { + array_size = nested_array_size; + array_element_size = PTR_SIZE; + + /* `int (*[2][3])(int)` is an array of function pointers: the + * parenthesized declarator owns the array bounds, while the + * following parameter list belongs to each pointed-to function. + * Consume that suffix before the enclosing sizeof parenthesis. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } else { + ptr_level += nested_ptr_count; + while (lex_accept(T_open_square)) { + 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()); + } + + /* A pointer declarator followed by a parameter list names a + * function pointer. Its function type has no object + * representation, but the pointer itself is an object and + * sizeof is pointer-sized. Reuse the declaration parser for + * complete prototype syntax, then discard the otherwise-unused + * signature. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } + } + } + lex_expect(T_close_bracket); + if (ptr_level) + return PTR_SIZE; + if (type == TY_void) + error_at("sizeof cannot be applied to void", cur_token_loc()); + if (type->is_direct_function_type) + error_at("sizeof(function) is invalid", cur_token_loc()); + if (!type->size && (!type->base_struct || !type->base_struct->size)) + error_at("sizeof cannot be applied to an incomplete type", + cur_token_loc()); + if (!array_size && !ptr_level && type->array_size) + array_size = type->array_size; + if (array_size) + return array_size * + (array_element_size ? array_element_size : type->size); + if (type->size) + return type->size; + return type->base_struct->size; +} + +/* 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()); + } +} + +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)) { + if (lex_peek(T_wstring, NULL)) + return read_const_wstring_size(); + return read_const_sizeof_type(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); + if (lex_accept(T_struct) || lex_accept(T_union)) { + lex_ident(T_identifier, type_name); + record = find_type(type_name, 2); + } 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; +} + +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; + + 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]; +} + +void read_inner_var_decl(var_t *vd, + bool anon, + bool is_param, + bool is_record_member) +{ + /* 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; + } + + 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); + } + + /* is it function pointer declaration? */ + if (lex_accept(T_open_bracket)) { + func_t *func = arena_alloc_func(); + char temp_name[MAX_VAR_LEN]; + int nested_ptr_level = 0; + + do { + lex_expect(T_asterisk); + nested_ptr_level++; + while (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; + + 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; + 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; + } + + /* 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); + 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. + */ + if (vd->parenthesized_function_pointer_inner_qualified) + error_at( + "inner callback-slot pointer qualifiers are not yet " + "supported", + cur_token_loc()); + if (vd->parenthesized_function_pointer_restrict && + !vd->parenthesized_function_pointer_outer_restrict) + error_at("callback-slot restrict typedef is not yet supported", + cur_token_loc()); + + /* The second star is the outer slot pointer. After collapsing the + * compact callback representation to one retained pointer level, + * move that star's const bit from level two to level one. + */ + if (vd->parenthesized_function_pointer_outer_const) { + vd->pointer_const_mask &= ~2U; + vd->pointer_const_mask |= 1U; + vd->is_const_pointer = true; + } + vd->ptr_level += nested_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 ((!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 (!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; + } + + /* 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 (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()); + + /* 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 || 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()); +} + +/* 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_signed = false; + bool is_unsigned = false; + bool is_const = false; + bool is_inline = false; + bool is_volatile = false; + bool is_long = false; + bool is_long_long = false; + type_t *leading_scalar_type = NULL; + + /* The declaration dispatcher normally leaves the base type for this routine + * to consume. C99 also permits the modifier after that base, e.g. `short + * unsigned value`; retain the base while consuming its following scalar + * modifiers instead of mistaking `unsigned` for the declarator name. + */ + if (lex_peek(T_identifier, type_name) && + (!strcmp(type_name, "char") || !strcmp(type_name, "short") || + !strcmp(type_name, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && + (after_base->kind == T_signed || after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(type_name, true); + } + } + + /* C permits these declaration specifiers in either order. Consume the + * scalar set as a group so `const unsigned int` and `unsigned const int` + * follow the same path. + */ + while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_const, NULL) || lex_peek(T_inline, NULL) || + lex_peek(T_volatile, NULL) || lex_peek(T_long, NULL)) { + 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_const)) + is_const = true; + else if (lex_accept(T_volatile)) + is_volatile = true; + else if (lex_accept(T_inline)) { + if (is_inline || declaration_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + is_inline = true; + } else { + lex_expect(T_long); + if (is_long_long) + error_at("too many long type specifiers", cur_token_loc()); + if (is_long) + is_long_long = true; + else + is_long = true; + } + } + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && lex_peek(T_identifier, type_name) && + !strcmp(type_name, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + bool is_enum_type = lex_accept(T_enum); + if (is_enum_type && (is_signed || is_unsigned || is_long)) + error_at("enum type cannot be combined with integer specifiers", + cur_token_loc()); + bool is_record_type = lex_peek(T_struct, NULL) || lex_peek(T_union, NULL); + bool is_union_type = lex_accept(T_union); + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (is_union_type) + find_type_flag = 2; + type_t *type; + + /* `signed` has the existing signed scalar semantics. C permits its `int` + * spelling to be omitted, while `signed char` and `signed short` retain + * their explicit base type. + */ + if (parsing_sizeof_function_signature && lex_accept(T_float)) { + if (is_signed || is_unsigned || is_long) + error_at("invalid float type specifiers", cur_token_loc()); + type = TY_float; + } else if (parsing_sizeof_function_signature && lex_accept(T_double)) { + if (is_signed || is_unsigned || is_long_long) + error_at("invalid double type specifiers", cur_token_loc()); + type = is_long ? TY_long_double : TY_double; + } else if (is_enum_type) { + lex_ident(T_identifier, type_name); + type = find_type_tag(type_name, vd->scope); + } else if (is_unsigned) { + if (is_long) { + if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = is_long_long ? TY_ulong_long : TY_ulong; + } else if (leading_scalar_type == TY_char) { + type = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = TY_ushort; + } else if (lex_peek(T_identifier, type_name) && + (!strcmp(type_name, "char") || !strcmp(type_name, "short") || + !strcmp(type_name, "int"))) { + lex_expect(T_identifier); + if (!strcmp(type_name, "char")) + type = TY_uchar; + else if (!strcmp(type_name, "short")) + type = TY_ushort; + else + type = TY_uint; + } else + type = TY_uint; /* `unsigned` is `unsigned int` */ + } else if (is_long) { + /* The current ABI gives long the same 32-bit representation as int, + * while retaining its distinct C type and rank. + */ + if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = is_long_long ? TY_long_long : TY_long; + } else if (is_signed && leading_scalar_type && + (leading_scalar_type == TY_char || + leading_scalar_type == TY_short)) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) + lex_expect(T_identifier); + type = leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; + } else if (is_signed && lex_peek(T_identifier, type_name) && + (!strcmp(type_name, "char") || !strcmp(type_name, "short") || + !strcmp(type_name, "int"))) { + lex_expect(T_identifier); + type = !strcmp(type_name, "char") + ? TY_schar + : (!strcmp(type_name, "short") ? TY_short : TY_int); + } else if (is_signed && find_type_flag == 1 && + (!lex_peek(T_identifier, type_name) || + (strcmp(type_name, "int") && strcmp(type_name, "char") && + strcmp(type_name, "short")))) { + type = TY_int; + } else if (leading_scalar_type) { + type = leading_scalar_type; + } else { + lex_ident(T_identifier, type_name); + type = find_type_flag == 2 ? find_record_tag(type_name, vd->scope) + : find_visible_type(type_name, vd->scope); + if (!type && find_type_flag == 2) + type = find_type(type_name, 2); + if (find_type_flag == 2 && + (!type || !is_record_type || + (is_union_type ? type->base_type != TYPE_union + : type->base_type != TYPE_struct))) + error_at("Unknown struct/union type", cur_token_loc()); + } + + if (!type) { + char message[MAX_LINE_LEN]; + + snprintf(message, MAX_LINE_LEN, "Could not find type %s%s", + find_type_flag == 2 ? "struct/union " : "", 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 || declaration_inline) + error_at("duplicate inline function specifier", + cur_token_loc()); + is_inline = true; + } else + break; + } + vd->is_inline = declaration_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()); + + /* A 32-bit target can carry a pointer to an eight-byte object without a + * paired-value register representation. Keep direct wide objects, arrays, + * parameters, returns, and function-pointer returns rejected until that + * lowering exists, but admit declarations such as `long long *p` and + * typedef aliases used exclusively behind a pointer. + */ + if (PTR_SIZE < 8 && vd->type && vd->type->base_type == TYPE_long_long && + (!(vd->ptr_level || vd->type->ptr_level) || vd->is_func)) + error_at("long long value needs 64-bit target lowering", + 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); +} + +void read_parameter_list_decl(func_t *func, bool anon) +{ + int vn = 0; + 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; + if (lex_peek(T_identifier, token) && !strncmp(token, "void", 4)) { + 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++; + if (lex_accept(T_comma) && strict_c99 && + lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in parameter list is not permitted in C99", + cur_token_loc()); + } + + if (floating_type_starts_here() && !parsing_sizeof_function_signature) + error_at("Floating point types are not yet supported", cur_token_loc()); + + while ((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; + 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); + 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++; + if (lex_accept(T_comma) && strict_c99 && + lex_peek(T_close_bracket, NULL)) + error_at("trailing comma in parameter list is not permitted in C99", + cur_token_loc()); + } + func->num_params = vn; + + /* Up to 'MAX_PARAMS' parameters are accepted for the variadic function. */ + if (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]; + + read_concatenated_string(combined); + + 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; + 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; + + read_concatenated_string(combined); + + len = strlen(combined) + 1; + if (var->has_unsized_array) { + var->array_size = len; + var->has_unsized_array = false; + } else if (len > var->array_size) + error_at("String initializer is too long for character array", + cur_token_loc()); + + for (int i = 0; i < len; i++) { + var_t *value = require_var(parent); + var_t *addr; + + value->var_name = gen_name(); + value->init_val = (unsigned char) combined[i]; + 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); + + units = length + 1; + if (var->has_unsized_array) { + var->array_size = units; + var->has_unsized_array = false; + } else if (units > 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; + if (PTR_SIZE < 8) + error_at("long long literal needs 64-bit target lowering", + cur_token_loc()); + 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 (PTR_SIZE < 8) + error_at("long long literal needs 64-bit target lowering", + cur_token_loc()); + 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); + } + 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); +} diff --git a/src/parser-expr.c b/src/parser-expr.c new file mode 100644 index 00000000..306eb1b1 --- /dev/null +++ b/src/parser-expr.c @@ -0,0 +1,4834 @@ +/* + * 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. + */ + +/* 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(void) +{ + 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 || !(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_scalar_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(void) +{ + 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 || + !(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_scalar_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; +} paren_type_name_t; + +/* 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) +{ + var_t *vd; + + /* 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. + */ + opstack_pop(); + perform_side_effect(parent, *bb); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + } + lex_expect(T_close_bracket); + + bool lowered_call_result_postfix = false; + if (lex_peek(T_open_square, NULL)) { + var_t *grouped = operand_stack[operand_stack_idx - 1]; + + if (grouped->pointee_array_size) { + lower_call_result_array_postfix(&grouped, parent, bb); + lowered_call_result_postfix = true; + } + } + + /* A parenthesized pointer/string expression is still a postfix operand. + * Unlike identifier lvalues it has no declaration to feed read_lvalue(), so + * lower its first subscript directly while retaining the same explicit + * element-size scaling. + */ + if (!lowered_call_result_postfix && lex_accept(T_open_square)) { + var_t *base = opstack_pop(); + var_t *index; + var_t *address; + int element_size; + + 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; + + /* A grouping around an array compound literal preserves its array + * type. Keep lowering each following index against the original + * shape, just as the direct compound-literal postfix path does. + */ + address = base; + element_size = base->type->size; + 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, 1); + indexed->var_name = gen_name(); + add_insn(parent, *bb, OP_add, indexed, address, index, 0, NULL); + address = indexed; + subscript_depth++; + } while (lex_accept(T_open_square)); + + if (subscript_depth < array_dims) { + opstack_push(address); + } else { + vd = require_typed_var(parent, base->type); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, element_size, + NULL); + } + } else { + if (!base->ptr_level && !base->type->ptr_level) + error_at("Cannot apply square operator to non-pointer", + cur_token_loc()); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + index = opstack_pop(); + lex_expect(T_close_square); + element_size = base->type->size; + if (element_size != 1) { + var_t *scale = require_var(parent); + scale->var_name = gen_name(); + scale->init_val = element_size; + 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, base->type, 1); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); + vd = require_typed_var(parent, base->type); + vd->ptr_level = base->ptr_level > 1 ? base->ptr_level - 1 : 0; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, + vd->ptr_level ? PTR_SIZE : element_size, NULL); + } + } + + /* Grouping does not stop a postfix member chain. The expression result is + * an aggregate value rather than an identifier lvalue, so materialize its + * address and lower the selected member here. This covers the ordinary C99 + * spellings `(*p).field` and `(*record.member).field`. + */ + while (lex_accept(T_dot)) { + char token[MAX_ID_LEN]; + var_t *base = opstack_pop(); + var_t *field; + var_t *base_address; + var_t *address; + var_t *offset; + + if (!base->type || !is_record_type(base->type) || + effective_pointer_depth(base)) + error_at("Cannot apply dot operator to non-record", + cur_token_loc()); + lex_ident(T_identifier, token); + field = find_member(token, base->type); + if (!field) + error_at("Unknown struct or union member", next_token_loc()); + + base_address = require_typed_ptr_var(parent, base->type, 1); + base_address->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, base_address, base, NULL, 0, NULL); + 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); + address = require_typed_ptr_var(parent, field->type, 1); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_add, address, base_address, offset, 0, NULL); + + vd = require_typed_var(parent, field->type); + vd->ptr_level = field->ptr_level; + vd->func_signature = field->func_signature; + vd->is_const_qualified = field->is_const_qualified; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_read, vd, address, NULL, + field->ptr_level || field->type->ptr_level ? PTR_SIZE + : get_size(field), + NULL); + } + + /* Function calls are postfix expressions, so a parenthesized function + * designator remains callable: `(fn)(...)` and `(*fp)(...)` have the same + * meaning as `fn(...)` and `fp(...)`. + */ + if (lex_peek(T_open_bracket, NULL)) { + var_t *callee = operand_stack[operand_stack_idx - 1]; + func_t *signature = get_func_signature(callee); + + if (signature) { + if (!callee->ptr_level) { + opstack_pop(); + opstack_push(load_function_pointer_object(parent, bb, callee)); + } + vd = emit_indirect_call_result(callee, signature, true, parent, bb); + } else if (callee->is_func) { + signature = find_func(callee->var_name); + if (!signature) + error_at("Called object is not a function", cur_token_loc()); + opstack_pop(); + vd = emit_direct_call_result(signature, true, parent, bb); + } else { + error_at("Called object is not a function pointer", + cur_token_loc()); + } + lower_call_result_array_postfix(&vd, 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. + */ + opstack_push(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); + if (!is_pointer_operation(compound_op, current, value)) { + 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; + } + 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); + } + if (!is_pointer_operation(compound_op, current, value)) { + 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 + 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; + + /* 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, true); + 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->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()); + } 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, true); + 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) { + /* 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 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); + + /* 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(); + 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 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); + } +} + +/* 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(); + + /* 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) + 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; + if (tn->type->func_signature) { + cast_var->ptr_level = 1; + cast_var->func_signature = tn->type->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) { + unsigned int lo = (unsigned int) expr_var->init_val; + unsigned int hi = (unsigned int) expr_var->init_val_hi; + + cast_var->is_const = true; + + /* 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. + */ + if (cast_var->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. + */ + cast_var->init_val = lo || hi; + cast_var->init_val_hi = 0; + } else if (cast_var->type->size < TY_int->size) { + unsigned int bits = cast_var->type->size * 8; + unsigned int mask = (1U << bits) - 1; + + lo &= mask; + if (!cast_var->type->is_unsigned && (lo & (1U << (bits - 1)))) + lo |= ~mask; + cast_var->init_val = (int) lo; + cast_var->init_val_hi = cast_var->init_val < 0 ? -1 : 0; + } else if (cast_var->type->size == TY_int->size) { + cast_var->init_val = (int) lo; + cast_var->init_val_hi = cast_var->type->is_unsigned + ? 0 + : (cast_var->init_val < 0 ? -1 : 0); + } else { + cast_var->init_val = (int) lo; + cast_var->init_val_hi = (int) 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"); + + /* 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); +} + +/* 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 typename followed by ) */ + token_t *type_start = cur_token; + bool has_const_type = false; + bool has_signed_type = false; + bool has_unsigned_type = false; + int long_type_count = 0; + type_t *leading_scalar_type = NULL; + + if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short") || + !strcmp(lookahead_token, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && + (after_base->kind == T_signed || after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(lookahead_token, true); + } + } + while (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)) { + if (lex_accept(T_const)) + has_const_type = true; + else if (lex_accept(T_volatile)) + tn.volatile_qualified = true; + else if (lex_accept(T_signed)) { + if (has_signed_type) + error_at("duplicate signed type specifier", cur_token_loc()); + has_signed_type = true; + } else if (lex_accept(T_unsigned)) { + if (has_unsigned_type) + error_at("duplicate unsigned type specifier", cur_token_loc()); + has_unsigned_type = true; + } else { + lex_expect(T_long); + long_type_count++; + } + } + if (long_type_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + if (has_signed_type && has_unsigned_type) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && + lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + bool has_long_type = long_type_count > 0; + bool has_enum_type = lex_accept(T_enum); + if (has_enum_type && + (has_signed_type || has_unsigned_type || has_long_type)) + error_at("enum type cannot be combined with integer specifiers", + cur_token_loc()); + bool has_type_identifier = lex_peek(T_identifier, lookahead_token); + if (has_const_type || has_signed_type || has_unsigned_type || + has_long_type || has_enum_type || has_type_identifier || + lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { + /* Check if it's a basic type or typedef */ + token_t *saved_token = type_start; + bool is_record = lex_accept(T_struct); + if (!is_record) + is_record = lex_accept(T_union); + if (is_record) + lex_ident(T_identifier, lookahead_token); + + type_t *type; + if (has_enum_type) { + lex_ident(T_identifier, lookahead_token); + type = find_type_tag(lookahead_token, parent); + } else if (has_unsigned_type && !is_record) { + if (has_long_type) { + type = TY_ulong; + if (long_type_count > 1) { + type = TY_ulong_long; + } + if (lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + } else if (leading_scalar_type == TY_char) { + type = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + type = TY_ushort; + } else if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "int") || + !strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short"))) { + lex_expect(T_identifier); + if (!strcmp(lookahead_token, "char")) + type = TY_uchar; + else if (!strcmp(lookahead_token, "short")) + type = TY_ushort; + else + type = TY_uint; + } else + type = TY_uint; + } else if (has_long_type && !is_record) { + type = TY_long; + if (long_type_count > 1) { + type = TY_long_long; + } + lex_accept(T_signed); + lex_accept(T_const); + if (lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + } else if (has_signed_type && !is_record) { + if (leading_scalar_type == TY_char || + leading_scalar_type == TY_short) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, lookahead_token) && + !strcmp(lookahead_token, "int")) + lex_expect(T_identifier); + type = leading_scalar_type == TY_char ? TY_schar + : leading_scalar_type; + } else if (lex_peek(T_identifier, lookahead_token) && + (!strcmp(lookahead_token, "int") || + !strcmp(lookahead_token, "char") || + !strcmp(lookahead_token, "short"))) { + lex_expect(T_identifier); + type = !strcmp(lookahead_token, "char") + ? TY_schar + : find_type(lookahead_token, true); + } else { + type = TY_int; + } + } else if (leading_scalar_type) { + type = leading_scalar_type; + } else { + type = find_type(lookahead_token, is_record ? 2 : true); + } + + if (type) { + if (type->is_floating) + error_at("Floating point types are not yet supported", + cur_token_loc()); + + /* Save current position to backtrack if needed Try to parse as + * typename + */ + if (!is_record && !has_enum_type && !has_signed_type && + !has_unsigned_type && !has_long_type) + lex_expect(T_identifier); + + /* A qualifier may appear before or after the base type. */ + while (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL)) { + if (lex_accept(T_const)) + has_const_type = true; + else + tn.volatile_qualified = true; + } + + /* 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); + } + } + + if (PTR_SIZE < 8 && type->base_type == TYPE_long_long && + !(ptr_level || type->ptr_level)) + error_at("long long value needs 64-bit target lowering", + cur_token_loc()); + + bool is_array = false; + + /* 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)) { + char bound_token[MAX_TOKEN_LEN]; + lex_ident_n(T_numeric, bound_token, MAX_TOKEN_LEN); + bound = parse_numeric_constant(bound_token); + 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; + } + } + } + + 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); + } +} + +void read_expr_operand(block_t *parent, basic_block_t **bb) +{ + var_t *vd, *rs1; + bool is_neg = false; + + if (grouped_scalar_pointee_row_postfix_starts()) { + 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()) { + 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) { + 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) { + 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); + if (object->compound_literal_bitfield) + write_bitfield_value(parent, bb, + object->compound_literal_address, vd, + object->compound_literal_bitfield); + else + add_insn(parent, *bb, OP_write, NULL, + object->compound_literal_address, vd, + object->ptr_level ? PTR_SIZE : object->type->size, + NULL); + opstack_push(vd); + return; + } + if (!object->is_compound_literal || object->array_size || + is_record_type(object->type)) + 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); + 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(); + 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)) { + 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()); + } + } + + if (lex_peek(T_string, NULL)) + read_literal_param(parent, *bb); + else if (lex_peek(T_wstring, NULL)) + read_wstring_param(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)) + read_numeric_param(parent, *bb, is_neg); + else if (lex_accept(T_log_not)) { + read_expr_operand(parent, bb); + + rs1 = opstack_pop(); + 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(); + 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; + 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) { + 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; + 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, true); + + /* 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; + + 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_call_result_array_postfix(&vd, 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)) { + func_t *returned_signature = + func->return_def.type->func_signature; + vd = emit_direct_call_result(func, true, parent, bb); + lower_call_result_array_postfix(&vd, parent, bb); + lower_call_result_prefix_update(&vd, prefix_op, parent, bb); + if (returned_signature && lex_peek(T_open_bracket, NULL)) { + vd = emit_indirect_call_result(vd, returned_signature, true, + parent, bb); + } + } else { + /* indirective function pointer assignment */ + vd = require_func_symbol_var(parent); + vd->is_func = true; + vd->var_name = intern_string(token); + vd->func_target = func; + 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(); + 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); + + /* 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->type = rs1->type; + vd->is_const = true; + vd->init_val = -rs1->init_val; + vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); + 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_negate, vd, rs1, NULL, 0, NULL); + } + } + } +} + +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); + + /* 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)); +} + +/* 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; + + /* 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); + int left_depth = orig_rs1->ptr_level + orig_rs1->type->ptr_level; + int right_depth = orig_rs2->ptr_level + orig_rs2->type->ptr_level; + bool left_scalar_row = is_scalar_pointee_array_pointer(orig_rs1); + bool right_scalar_row = is_scalar_pointee_array_pointer(orig_rs2); + + if ((left_scalar_row || right_scalar_row) && + !scalar_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_scalar_row + ? scalar_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_scalar_pointee_array_pointer(rs1) + ? scalar_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_scalar_pointee_array_pointer(rs2) + ? scalar_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; + if (is_scalar_pointee_array_pointer(ptr_var)) + 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) +{ + if (!var || var->ptr_level || !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); +} + +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(); + + /* 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(); + + /* 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); + } + + if ((top_op == OP_lt || top_op == OP_leq || top_op == OP_gt || + top_op == OP_geq) && + ((rs1 && (rs1->is_func || get_func_signature(rs1))) || + (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); + 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; + 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->type = result_type; + vd->is_const = true; + 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(); + 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--; +} + + +/* What follows a completed lvalue: pointer arithmetic on it, or 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, + bool allow_ptr_arith, + type_t *pointer_row_element_type) +{ + var_t *vd, *rs1, *rs2; + + /* 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. + */ + bool scalar_pointee_row = is_scalar_pointee_array_pointer(var); + if (allow_ptr_arith && lex_peek(T_plus, NULL) && + (var->ptr_level || is_array_declarator(var) || scalar_pointee_row) && + !lvalue->is_reference) { + if (!is_array_declarator(var) && + is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) + error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); + while (lex_peek(T_plus, NULL) && + (var->ptr_level || is_array_declarator(var) || + scalar_pointee_row)) { + 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 (scalar_pointee_row) { + lvalue->size = scalar_pointee_array_row_stride(var); + } else if (is_array_declarator(var) && var->array_dim2 && + !has_effective_pointer(var) && !var->is_func) { + lvalue->size = fixed_array_decay_stride(var); + } else if (var->ptr_level > 1 || + (is_array_declarator(var) && var->ptr_level)) + 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. Array expressions + * first decay to a pointer, adding one level to the declaration's + * element indirection. + */ + if (var->ptr_level || is_array_declarator(var) || + scalar_pointee_row) { + vd->type = lvalue->type; + vd->ptr_level = var->ptr_level + !!is_array_declarator(var); + if (scalar_pointee_row) + copy_pointee_array_shape(vd, var); + else if (is_array_declarator(var) && var->array_dim2 && + !has_effective_pointer(var) && !var->is_func) { + fixed_array_shape_t shape = fixed_array_shape_from_var(var); + + fixed_array_shape_drop_outer(&shape); + fixed_array_shape_to_pointee_var(vd, &shape); + } + } + vd->var_name = gen_name(); + vd->is_const_qualified = var->is_const_qualified; + vd->pointer_const_mask = var->pointer_const_mask; + vd->is_const_pointer = + vd->ptr_level > 0 && vd->ptr_level <= 32 && + (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); + 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(); + 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; + + /* 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; + 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; + } + opstack_push(t); + if (is_bitfield(lvalue->decl)) { + /* Bit-fields are values, never independently addressable + * storage: extract their slice from the containing unit. + */ + opstack_pop(); + t = read_bitfield_value(parent, bb, rs1, lvalue->decl); + opstack_push(t); + } else { + add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, + NULL); + } + } + 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(); + + /* For pointer arithmetic, increment by the size of pointed-to type + */ + if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) + vd->init_val = scalar_pointee_array_row_stride(var); + else if (lvalue->ptr_level > 1) + vd->init_val = PTR_SIZE; + else if (lvalue->ptr_level) + vd->init_val = lvalue->type->size; + else if (lvalue->type && lvalue->type->ptr_level) + vd->init_val = get_pointer_element_size(var); + 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]; + 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)) { + opcode_t postfix_op = lex_accept(T_increment) ? OP_add : OP_sub; + 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; + } + + /* 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->is_reference && is_scalar_pointee_array_pointer(var)) { + increment_size = scalar_pointee_array_row_stride(var); + } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { + increment_size = PTR_SIZE; + } else 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(); + if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) { + vd->type = lvalue->type; + vd->ptr_level = var->ptr_level; + copy_pointee_array_shape(vd, var); + } + 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); + } + } + } +} + +/* Return the address that an expression points to, or evaluate its value. + * x =; + * x[] =; + * x[expr].field =; + * x[expr]->field =; + * + * @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. + */ +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) +{ + 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; + + /* 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 (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)) { + int indexed_ptr_level; + 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. + */ + if (lvalue->is_reference && + (lvalue->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). + */ + lvalue->size = + lvalue->value_ptr_level > 1 ? PTR_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); + 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; + + /* `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; + } + } + + 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; + + /* 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 && lvalue->value_ptr_level > 0; + subscript_depth++; + lvalue->is_reference = !indexes_pointee_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`. + */ + lvalue->is_const_qualified = + var->is_const_qualified || + (lvalue->pointee_func_signature && + var->type->array_element_is_const_pointer); + } 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->decl = 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->size = get_size(var); + lvalue->is_const_qualified |= var->is_const_qualified; + subscript_depth = 0; + + /* if it is an array, get the address of first element instead of + * its value. + */ + if (is_array_declarator(var)) + 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; + } + } + + 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, var, parent, bb, prefix_op, allow_ptr_arith, + 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(); + 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(); + 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)) { + opstack_pop(); + perform_side_effect(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); + opstack_pop(); + perform_side_effect(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(void) +{ + 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 || !(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_scalar_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); + } + 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)) { + opstack_pop(); + perform_side_effect(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(); + 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 = require_ref_var(parent, true_val->type, 0); + false_addr = require_ref_var(parent, false_val->type, 0); + selected = require_ref_var(parent, true_val->type, 0); + true_addr->var_name = gen_name(); + false_addr->var_name = gen_name(); + selected->var_name = gen_name(); + add_insn(parent, then_, OP_address_of, true_addr, true_val, NULL, 0, + NULL); + add_insn(parent, else_, OP_address_of, false_addr, false_val, NULL, 0, + NULL); + 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); + } + 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; +} + +bool read_body_assignment(char *token, + block_t *parent, + opcode_t prefix_op, + basic_block_t **bb, + var_t **assignment_result, + int lvalue_paren_depth) +{ + var_t *var = find_local_var(token, parent), *vd, *rs1, *rs2, *t; + if (!var) + var = find_global_var(token); + 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, true); + while (lvalue_paren_depth-- > 0) + lex_expect(T_close_bracket); + size = lvalue.size; + + 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()); + + /* if we have a pointer, shift it by element size But not if we are + * operating on a dereferenced value (array indexing) + */ + if (!one && (op == OP_add || op == OP_sub) && + !lvalue.is_reference && is_scalar_pointee_array_pointer(var)) + increment_size = scalar_pointee_array_row_stride(var); + else if (lvalue.ptr_level > 1 && !lvalue.is_reference) + increment_size = PTR_SIZE; + else 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) { + /* Keep typedef-hidden depth: a void ** alias advances over + * pointer objects, whereas a direct void * was rejected above + * and never reaches this scaling path. + */ + increment_size = get_pointer_element_size(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 + 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 = + read_bitfield_value(parent, bb, t, lvalue.decl); + stored->is_bitfield = false; + 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(); + 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(); + 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); + 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 + 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 = + read_bitfield_value(parent, bb, t, lvalue.decl); + stored->is_bitfield = false; + 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(); + + /* 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(); + vd->func_target = rs2->func_target; + 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); + + /* Only a named function-pointer object owns this metadata. A + * record member or array element shares lvalue.decl with every + * object of that type, so storing provenance there would let + * one object's assignment authorize another's call. Those + * reference forms intentionally remain unknown until SSA + * provenance is available per storage location. + */ + if (!lvalue.is_reference && !lvalue.decl->func_target_invalid) { + func_t *assigned_target = rs2->func_target; + + if (!assigned_target || !assigned_target->bbs) { + lvalue.decl->func_target = NULL; + lvalue.decl->func_target_invalid = true; + } else + lvalue.decl->func_target = assigned_target; + } + if (assignment_result) + assignment_result[0] = rs2; + } else if (lvalue.is_reference) { + rs2 = opstack_pop(); + rs1 = opstack_pop(); + 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()); + } + if (is_bitfield(lvalue.decl)) + write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); + else + add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); + 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 = + read_bitfield_value(parent, bb, rs1, lvalue.decl); + stored->is_bitfield = false; + 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(); + 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)) { + 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 if (incompatible_const_pointer_conversion(rs1, vd)) { + diagnose_const_pointer_conversion(rs1, vd); + } else { + 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; +} + +bool read_assignment_expression(block_t *parent, basic_block_t **bb) +{ + char token[MAX_ID_LEN]; + var_t *result; + int lvalue_paren_depth = 0; + + if (!assignment_expression_follows()) + return false; + if (compound_literal_starts_expression()) + return false; + + /* 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; + int grouped_fixed_array_dimensions = 0; + 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. + */ + lex_expect(T_asterisk); + read_expr(parent, bb); + read_ternary_operation(parent, bb); + address = opstack_pop(); + object_address = address; + while (lvalue_paren_depth-- > 0) + lex_expect(T_close_bracket); + + /* The ordinary `(*pointer)` assignment path historically stopped at the + * grouping close. Admit only exact scalar fixed-array suffixes here, + * retaining its common store/result lowering below. + */ + if (lex_peek(T_open_square, NULL)) { + var_t shape = {0}; + fixed_array_shape_t array_shape; + int depth = 0; + int dimensions; + + if (!address->pointee_array_size || + address->pointee_array_element_ptr_level || + effective_pointer_depth(address) != 1) + error_at( + "Grouped subscript requires a scalar fixed array pointer", + cur_token_loc()); + shape.type = address->type->pointee_array_element_type + ? address->type->pointee_array_element_type + : address->type; + array_shape = fixed_array_shape_from_pointee_var(address); + fixed_array_shape_to_var(&shape, &array_shape); + dimensions = array_shape.rank; + grouped_fixed_array_dimensions = dimensions; + if (dimensions > 4) + error_at( + "Grouped fixed-array assignment supports at most four " + "dimensions", + cur_token_loc()); + while (lex_accept(T_open_square)) { + var_t *index; + var_t *scale; + var_t *offset; + var_t *next; + 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_subscript_stride(&shape, depth, + shape.type->size); + offset = index; + if (stride != 1) { + 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); + offset = require_var(parent); + offset->var_name = gen_name(); + add_insn(parent, *bb, OP_mul, offset, index, scale, 0, + NULL); + } + next = require_typed_ptr_var(parent, shape.type, 1); + next->var_name = gen_name(); + next->is_const_qualified = object_address->is_const_qualified || + shape.type->is_const_qualified; + add_insn(parent, *bb, OP_add, next, address, offset, 0, NULL); + address = next; + depth++; + } + if (depth != dimensions) + error_at("Grouped fixed-array assignment needs all subscripts", + cur_token_loc()); + } + + 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. + */ + 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()); + 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; + } + + int address_depth = effective_pointer_depth(object_address); + unsigned int address_mask = + effective_pointer_const_mask(object_address); + if ((field && field->is_const_qualified) || + address->is_const_qualified || + (address_depth > 1 && address_depth <= 32 && + (address_mask & (1U << (address_depth - 2))))) + 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 (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && + compound_op != OP_add && compound_op != OP_sub && + compound_op != OP_mul && compound_op != OP_div && + compound_op != OP_mod && compound_op != OP_lshift && + compound_op != OP_rshift && compound_op != OP_bit_and && + compound_op != OP_bit_xor && compound_op != OP_bit_or) + error_at("Unsupported rank-three/four grouped compound assignment", + cur_token_loc()); + + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + } + value = opstack_pop(); + + if (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && + (value_ptr_level != 0 || is_record_type(value_type) || + value_type->is_floating || value->is_func || + is_pointer_like_value(value) || is_record_type(value->type) || + value->type->is_floating)) + error_at( + "Invalid rank-three/four grouped compound assignment operand", + cur_token_loc()); + + 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); + } + if (!is_pointer_operation(compound_op, current, value)) { + 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 + 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 = read_bitfield_value(parent, bb, address, field); + result->is_bitfield = false; + } else { + result = require_typed_var(parent, value_type); + result->ptr_level = value_ptr_level; + result->var_name = gen_name(); + add_insn(parent, *bb, OP_read, result, address, NULL, store_size, + NULL); + } + if (result->type == TY_bool && !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 (!read_body_assignment(token, 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..b2ea3732 --- /dev/null +++ b/src/parser-global.c @@ -0,0 +1,1872 @@ +/* + * 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, true); + 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); +} + +/* 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. + */ +void read_global_function_declarator(block_t *block, var_t *var, bool is_static) +{ + /* 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 (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 (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_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)) { + 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; + } + if (inherited_direct_function_type && lex_peek(T_comma, NULL)) + return; + if (!lex_accept(T_semicolon)) + error_at("Syntax error in global declaration", next_token_loc()); +} + +/* 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; + + 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->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) + 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()); + + /* 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 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)) { + read_global_function_declarator(block, nv, is_static); + return true; + } + 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, true); + 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]; + int find_type_flag = 1; + type_t *compound_type, *target_type; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { + find_type_flag = 2; + lex_ident(T_identifier, type_name); + } else { + lex_ident(T_identifier, type_name); + } + lex_expect(T_close_bracket); + + compound_type = find_type(type_name, find_type_flag); + 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) +{ + char type_name[MAX_ID_LEN]; + type_t *compound_type; + + UNUSED(block); + + lex_expect(T_open_bracket); + lex_ident(T_identifier, type_name); + compound_type = find_type(type_name, true); + 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) +{ + char type_name[MAX_ID_LEN]; + type_t *element_type; + var_t *array; + int find_type_flag = 1; + int element_ptr_level = 0; + + lex_expect(T_open_bracket); + if (lex_accept(T_struct) || lex_accept(T_union)) { + find_type_flag = 2; + lex_ident(T_identifier, type_name); + } else { + lex_ident(T_identifier, type_name); + } + element_type = find_type(type_name, find_type_flag); + 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, true); + 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, true); + 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_extern) +{ + 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; + 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)) { + read_global_function_declarator(block, var, is_static); + return true; + } + 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, true); + } 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; +} + +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); + + if (lex_peek(T_open_bracket, NULL)) { + read_global_function_declarator(block, var, is_static); + 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;". 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, var->is_const_qualified, + is_static, var->is_volatile, is_extern); + + lex_expect(T_semicolon); + return; +} + +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 declarators of a file-scope typedef whose base type is not a record or + * enum definition: scalar, pointer, array and function aliases. + */ +static void read_global_typedef_declarators(block_t *block) +{ + char base_type[MAX_ID_LEN]; + const type_t *base; + type_t *type = add_type(); + bool typedef_const = false; + bool is_signed = false; + bool is_unsigned = false; + bool is_long = false; + bool is_long_long = false; + type_t *leading_scalar_type = NULL; + + if (lex_peek(T_identifier, base_type) && + (!strcmp(base_type, "char") || !strcmp(base_type, "short") || + !strcmp(base_type, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && + (after_base->kind == T_signed || after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(base_type, true); + } + } + + /* Typedef declarations use the same freely ordered scalar specifier set as + * object declarations. Keeping this in a loop admits C99 spellings such as + * `long unsigned long` rather than treating the second specifier as the + * typedef name. + */ + while (lex_peek(T_const, NULL) || lex_peek(T_signed, NULL) || + lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { + if (lex_accept(T_const)) + typedef_const = true; + else 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 { + lex_expect(T_long); + if (is_long_long) + error_at("too many long type specifiers", cur_token_loc()); + if (is_long) + is_long_long = true; + else + is_long = true; + } + } + if (is_signed && is_unsigned) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && lex_peek(T_identifier, base_type) && + !strcmp(base_type, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + + bool is_float = lex_accept(T_float); + bool is_double = lex_accept(T_double); + + if (is_float) { + if (is_signed || is_unsigned || is_long) + error_at("invalid float type specifiers", cur_token_loc()); + base = TY_float; + } else if (is_double) { + if (is_signed || is_unsigned || is_long_long) + error_at("invalid double type specifiers", cur_token_loc()); + base = is_long ? TY_long_double : TY_double; + } else if (is_long) { + if (lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) + lex_expect(T_identifier); + if (is_long_long) + base = is_unsigned ? TY_ulong_long : TY_long_long; + else + base = is_unsigned ? TY_ulong : TY_long; + } else if (is_unsigned) { + if (leading_scalar_type == TY_char) { + base = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) + lex_expect(T_identifier); + base = TY_ushort; + } else { + if (lex_peek(T_identifier, base_type) && + (!strcmp(base_type, "int") || !strcmp(base_type, "char") || + !strcmp(base_type, "short"))) { + lex_expect(T_identifier); + if (!strcmp(base_type, "char")) + base = TY_uchar; + else if (!strcmp(base_type, "short")) + base = TY_ushort; + else + base = TY_uint; + } else { + base = TY_uint; + } + } + } else if (is_signed && lex_peek(T_identifier, base_type) && + (!strcmp(base_type, "char") || !strcmp(base_type, "short") || + !strcmp(base_type, "int"))) { + lex_expect(T_identifier); + base = !strcmp(base_type, "char") + ? TY_schar + : (!strcmp(base_type, "short") ? TY_short : TY_int); + } else if (is_signed && (leading_scalar_type == TY_char || + leading_scalar_type == TY_short)) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) + lex_expect(T_identifier); + base = leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; + } else if (is_signed && + (!lex_peek(T_identifier, base_type) || + (strcmp(base_type, "int") && strcmp(base_type, "char") && + strcmp(base_type, "short")))) { + base = TY_int; + } else if (leading_scalar_type) { + base = leading_scalar_type; + } else { + 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; + + /* 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_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; + + /* Handle pointer types in typedef: typedef char *string; */ + 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); + } else if (lex_accept(T_volatile) || lex_accept(T_restrict)) { + ; + } else + break; + } + } + + /* 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; + if (!declarator.is_func) + error_at( + "Typedef parenthesized declarator must be a function " + "pointer", + cur_token_loc()); + 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; + lex_expect(T_semicolon); + return; + } + + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + + /* 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); + + 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); + } + lex_expect(T_semicolon); +} + +/* A file-scope typedef, after its keyword: record, enum, function and scalar + * aliases, with every comma-separated declarator. + */ +static void read_global_typedef(block_t *block) +{ + char token[MAX_ID_LEN]; + + if (lex_accept(T_enum)) { + int val = 0; + type_t *type = add_type(); + + initialize_enum_type(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(block); + add_constant(token, val); + first = false; + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); + 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, alignment = 1; + bitfield_layout_t bits = {0}; + bool has_struct_def = false; + bool has_flexible_array_member = 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); + var_t *last = v; + read_full_var_decl(v, false, false, true); + read_bitfield_width(v, block); + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, false); + size = is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), v, + &alignment); + + /* Handle multiple variable declarations with same base type */ + while (lex_accept(T_comma)) { + if (last->is_flexible_array_member) + error_at( + "Flexible array member must be the final " + "struct member", + cur_token_loc()); + var_t *nv = type_add_field(type, &i); + initialize_struct_field(nv, v, 0); + read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, block); + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, false); + last = nv; + size = + is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); + } + + lex_expect(T_semicolon); + if (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)); + } + + while (lex_accept(T_asterisk)) { + type->ptr_level++; + type->size = PTR_SIZE; + } + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + size = flush_bitfield_layout(size, &bits); + type->alignment = type->ptr_level ? PTR_SIZE : alignment; + type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(size, alignment); + type->num_fields = i; + type->base_type = TYPE_typedef; + type->has_flexible_array_member = has_flexible_array_member; + + 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, alignment = 1; + bool has_union_def = false; + bool has_flexible_array_member = 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, false, true); + read_bitfield_width(v, block); + has_flexible_array_member |= + is_flexible_array_member_container(v); + mark_flexible_array_member(v, true); + v->offset = 0; /* All union fields start at offset 0 */ + int field_size = is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); + + /* 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, false, true); + read_bitfield_width(nv, block); + has_flexible_array_member |= + is_flexible_array_member_container(nv); + mark_flexible_array_member(nv, true); + field_size = + is_bitfield(nv) + ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); + } + + lex_expect(T_semicolon); + } while (!lex_accept(T_close_curly)); + } + + while (lex_accept(T_asterisk)) { + type->ptr_level++; + type->size = PTR_SIZE; + } + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + type->alignment = type->ptr_level ? PTR_SIZE : alignment; + type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(max_size, alignment); + type->num_fields = i; + type->base_type = TYPE_typedef; + type->is_union = true; + type->has_flexible_array_member = has_flexible_array_member; + + 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 { + read_global_typedef_declarators(block); + } +} + +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. */ + 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()); + + 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_accept(T_struct)) { + int i = 0, size = 0, alignment = 1; + bitfield_layout_t bits = {0}; + bool has_flexible_array_member = false; + + 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 && lex_peek(T_semicolon, NULL)) { + decl_type = add_type(); + decl_type->base_type = TYPE_struct; + set_type_name(decl_type, token); + lex_expect(T_semicolon); + return; + } + if (!decl_type) + error_at("Unknown struct type", &id_tk->location); + + if (read_global_record_declarator(block, decl_type, is_const, + is_static, is_extern)) + return; + while (lex_accept(T_comma)) + read_global_record_declarator(block, decl_type, is_const, + is_static, is_extern); + 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); + var_t *last = v; + read_full_var_decl(v, false, false, true); + read_bitfield_width(v, block); + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, false); + size = is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field(flush_bitfield_layout(size, &bits), + v, &alignment); + + /* Handle multiple variable declarations with same base type */ + while (lex_accept(T_comma)) { + if (last->is_flexible_array_member) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); + var_t *nv = type_add_field(type, &i); + initialize_struct_field(nv, v, 0); + read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, block); + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, false); + last = nv; + size = is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); + } + + lex_expect(T_semicolon); + if (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)); + + size = flush_bitfield_layout(size, &bits); + type->alignment = alignment; + type->size = ALIGN_UP(size, alignment); + type->num_fields = i; + type->has_flexible_array_member = has_flexible_array_member; + + /* A record definition may be followed by its declarators, as in "struct + * pair { int x, y; } first, *second;". + */ + if (!lex_peek(T_semicolon, NULL)) { + if (read_global_record_declarator(block, type, is_const, is_static, + is_extern)) + return; + while (lex_accept(T_comma)) + read_global_record_declarator(block, type, is_const, is_static, + is_extern); + } + lex_expect(T_semicolon); + } else if (lex_accept(T_union)) { + int i = 0, max_size = 0, alignment = 1; + bool has_flexible_array_member = false; + + lex_ident(T_identifier, token); + token_t *id_tk = cur_token; + + /* A tagged union declaration may name an already-complete tag, just + * like `struct tag object;`. Do not require a second definition body + * before routing its declarators through the shared record path. + */ + if (!lex_peek(T_open_curly, NULL)) { + type_t *decl_type = find_type(token, 2); + if (!decl_type && lex_peek(T_semicolon, NULL)) { + decl_type = add_type(); + decl_type->base_type = TYPE_union; + set_type_name(decl_type, token); + lex_expect(T_semicolon); + return; + } + if (!decl_type) + error_at("Unknown union type", &id_tk->location); + + if (read_global_record_declarator(block, decl_type, is_const, + is_static, is_extern)) + return; + while (lex_accept(T_comma)) + read_global_record_declarator(block, decl_type, is_const, + is_static, is_extern); + lex_expect(T_semicolon); + return; + } + + /* 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, false, true); + read_bitfield_width(v, block); + has_flexible_array_member |= is_flexible_array_member_container(v); + mark_flexible_array_member(v, true); + v->offset = 0; /* All union fields start at offset 0 */ + int field_size = is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); + + /* 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, false, true); + read_bitfield_width(nv, block); + has_flexible_array_member |= + is_flexible_array_member_container(nv); + mark_flexible_array_member(nv, true); + field_size = is_bitfield(nv) + ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); + } + + lex_expect(T_semicolon); + } while (!lex_accept(T_close_curly)); + + type->alignment = alignment; + type->size = ALIGN_UP(max_size, alignment); + type->num_fields = i; + type->has_flexible_array_member = has_flexible_array_member; + + if (!lex_peek(T_semicolon, NULL)) { + if (read_global_record_declarator(block, type, is_const, is_static, + is_extern)) + return; + while (lex_accept(T_comma)) + read_global_record_declarator(block, type, is_const, is_static, + is_extern); + } + lex_expect(T_semicolon); + } else if (lex_accept(T_enum)) { + /* An enum definition is a declaration in its own right; it need not + * introduce a typedef. Its enumerators are integer constants and may + * use the same integer constant expressions accepted for array bounds + * and case labels. + */ + int val = 0; + bool has_tag = false; + type_t *type; + + if (lex_peek(T_identifier, token)) { + lex_expect(T_identifier); + has_tag = true; + } + if (!lex_peek(T_open_curly, NULL)) { + if (!has_tag) + error_at("Expected enum tag or definition", cur_token_loc()); + type = find_type(token, true); + if (!type) + error_at("Unknown enum type", cur_token_loc()); + if (read_global_declarator(block, type, is_const, is_static, + is_volatile, is_extern)) + return; + while (lex_accept(T_comma)) + read_global_declarator(block, type, is_const, is_static, + is_volatile, is_extern); + lex_expect(T_semicolon); + return; + } + type = has_tag ? find_type(token, true) : NULL; + if (!type) + type = add_type(); + + initialize_enum_type(type); + if (has_tag) + set_type_name(type, token); + 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(block); + add_constant(token, val); + first = false; + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); + lex_expect(T_close_curly); + if (!lex_peek(T_semicolon, NULL)) { + if (read_global_declarator(block, type, is_const, is_static, + is_volatile, is_extern)) + return; + while (lex_accept(T_comma)) + read_global_declarator(block, type, is_const, is_static, + is_volatile, is_extern); + } + lex_expect(T_semicolon); + } 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..6021c30b --- /dev/null +++ b/src/parser-init.c @@ -0,0 +1,2118 @@ +/* + * 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); +void read_global_function_declarator(block_t *block, + var_t *var, + bool is_static); +var_t *bind_block_extern_object(block_t *parent, var_t *var); +int read_const_expr(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 && !var->ptr_level && !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; + } +} + +/* 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 = require_ref_var(parent, src->type, 0); + + src_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); + 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 = require_ref_var(parent, src->type, 0); + + dest_addr->var_name = gen_name(); + src_addr->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); + add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); + 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) +{ + func_t *target = src->func_target; + + 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); + dest->func_target = src->func_target; + dest->func_target_invalid = + src->func_target_invalid || !target || !target->bbs; + } else { + src = resize_var(parent, bb, src, dest); + add_insn(parent, *bb, OP_assign, dest, src, NULL, 0, NULL); + } +} + +/* 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. + */ +var_t *read_global_address_designator(block_t *scope, + block_t *parent, + basic_block_t **bb, + var_t **object, + var_t *object_addr) +{ + 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 (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { + int element_size = target->ptr_level ? PTR_SIZE : target->type->size; + int index = read_global_address_offset(scope, parent, *bb); + + object_addr = compute_element_address( + parent, bb, object_addr, index, + fixed_array_shape_stride(&shape, subscripts - 1, element_size)); + object_addr->ptr_level = target->ptr_level + 1; + object_addr->is_global_address = true; + } + *object = target; + return object_addr; +} + +var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) +{ + var_t *val = NULL; + block_t *scope = global_constant_initializer_scope + ? global_constant_initializer_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; + + 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; + } + explicit_address = lex_accept(T_ampersand); + + if (grouped_global_function_designator_starts_here(false)) { + lex_expect(T_open_bracket); + grouped_function_designator = true; + } + + 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); + } + } + } + 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_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) || lex_peek(T_wchar, NULL)) { + char chtok[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; + token_kind_t kind = lex_peek(T_wchar, NULL) ? T_wchar : T_char; + lex_ident(kind, chtok); + unescape_string(chtok, unescaped, MAX_TOKEN_LEN); + + val = require_typed_var(parent, TY_int); + val->var_name = gen_name(); + val->init_val = kind == T_wchar ? parse_wide_character_constant(chtok) + : parse_character_constant(chtok); + add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); + } 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; +} + +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_peek(T_wchar, NULL)) { + lex_next(); + } else { + error_at("Global array initialization requires constant values", + next_token_loc()); + } +} + +bool is_record_type(const type_t *type) +{ + return type && + (type->base_type == TYPE_struct || type->base_type == TYPE_union || + (type->base_type == TYPE_typedef && type->num_fields > 0)); +} + +void parse_struct_field_init(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr, + bool emit_code); + +void parse_array_field_row_values(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code, + bool braced) +{ + int count = 0; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + + if (braced) { + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + } + while (!lex_peek(T_close_curly, NULL)) { + var_t *value = NULL; + var_t *elem_addr; + + if (count >= field->array_dim2) + error_at("Too many elements in array initializer", + next_token_loc()); + + elem_addr = compute_element_address(parent, bb, target_addr, + start + count, elem_size); + if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { + type_t *record_type = field->type; + 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(parent, bb, record_type, elem_addr, + emit_code); + lex_expect(T_close_curly); + } else if (parent == GLOBAL_BLOCK) { + if (emit_code) + value = parse_global_constant_value(parent, bb); + else + consume_global_constant_syntax(); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + + if (value && emit_code) { + 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 == field->array_dim2) { + if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many elements in array initializer", + next_token_loc()); + break; + } + if (!lex_accept(T_comma)) + break; + } + if (braced) + lex_expect(T_close_curly); + + if (emit_code) { + 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); + for (; count < field->array_dim2; count++) { + var_t *elem_addr = compute_element_address( + parent, bb, target_addr, start + count, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } +} + +void parse_array_field_row_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code) +{ + parse_array_field_row_values(parent, bb, field, target_addr, start, + emit_code, 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 emit_code, + bool braced) +{ + var_t row; + int rows = 0; + int row_width = field->array_dim3; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + + 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)) { + if (lex_accept(T_open_square)) { + int index = read_const_expr(field->scope ? field->scope : parent); + + lex_expect(T_close_square); + if (index < 0 || index >= field->array_dim2) + error_at("Array designator index is out of bounds", + cur_token_loc()); + rows = index; + lex_expect(T_assign); + } + if (rows >= field->array_dim2) + error_at("Too many rows in array initializer", next_token_loc()); + parse_array_field_row_values(parent, bb, &row, target_addr, + start + rows * row_width, emit_code, + lex_peek(T_open_curly, NULL)); + rows++; + if (rows == field->array_dim2) { + if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + error_at("Too many rows in array initializer", + next_token_loc()); + break; + } + if (!lex_accept(T_comma)) + break; + } + if (braced) + lex_expect(T_close_curly); + + if (emit_code) { + 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); + for (; rows < field->array_dim2; rows++) { + for (int col = 0; col < row_width; col++) { + var_t *addr = compute_element_address( + parent, bb, target_addr, start + rows * row_width + col, + elem_size); + for (int byte = 0; byte < elem_size; byte++) { + var_t *byte_addr = + compute_element_address(parent, bb, addr, byte, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, + NULL); + } + } + } + } +} + +void parse_array_field_plane_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + int start, + bool emit_code) +{ + parse_array_field_plane_values(parent, bb, field, target_addr, start, + emit_code, 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, + bool emit_code) +{ + var_t plane; + int planes = 0; + int plane_size = field->array_dim3 * field->array_dim4; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + + 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)) { + if (lex_accept(T_open_square)) { + int index = read_const_expr(field->scope ? field->scope : parent); + + lex_expect(T_close_square); + if (index < 0 || index >= field->array_dim2) + error_at("Array designator index is out of bounds", + cur_token_loc()); + planes = index; + lex_expect(T_assign); + } + if (planes >= field->array_dim2) + error_at("Too many planes in array initializer", next_token_loc()); + parse_array_field_plane_values(parent, bb, &plane, target_addr, + start + planes * plane_size, emit_code, + lex_peek(T_open_curly, NULL)); + planes++; + if (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + if (emit_code) { + 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); + for (; planes < field->array_dim2; planes++) { + for (int element = 0; element < plane_size; element++) { + var_t *addr = compute_element_address( + parent, bb, target_addr, + start + planes * plane_size + element, elem_size); + for (int byte = 0; byte < elem_size; byte++) { + var_t *byte_addr = + compute_element_address(parent, bb, addr, byte, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, + NULL); + } + } + } + } +} + +/* 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. + */ +int read_concatenated_string(char *combined) +{ + char literal[MAX_STRING_LEN]; + int used; + + lex_ident(T_string, literal); + unescape_string(literal, combined, MAX_STRING_LEN); + used = strlen(combined); + while (lex_peek(T_string, NULL)) { + int added; + + lex_ident(T_string, literal); + unescape_string(literal, combined + used, MAX_STRING_LEN - used); + added = strlen(combined + used); + if (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, + bool emit_code) +{ + char combined[MAX_STRING_LEN]; + int len; + + read_concatenated_string(combined); + + len = strlen(combined) + 1; + if (len > field->array_size) + error_at("String initializer is too long for character array", + cur_token_loc()); + if (!emit_code) + return; + + 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, + bool emit_code) +{ + int values[MAX_STRING_LEN]; + int length; + int units; + + length = read_wstring_units(values, MAX_STRING_LEN); + + units = length + 1; + if (units > field->array_size) + error_at("Wide string initializer is too long for array", + cur_token_loc()); + if (!emit_code) + return; + + 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); + } +} + +void parse_array_field_init(block_t *parent, + basic_block_t **bb, + const var_t *field, + var_t *target_addr, + bool emit_code) +{ + int count = 0; + int elem_size = + (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + + lex_expect(T_open_curly); + reject_empty_initializer_in_strict_c99(); + while (!lex_peek(T_close_curly, NULL)) { + var_t *value = NULL; + + if (count >= field->array_size) + error_at("Too many elements in array initializer", + next_token_loc()); + + var_t *elem_addr = + compute_element_address(parent, bb, target_addr, count, elem_size); + if (field->array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, field, target_addr, + count, emit_code); + count += fixed_array_inner_count(field); + if (!lex_accept(T_comma)) + break; + continue; + } else if (field->array_dim3 && lex_peek(T_open_curly, NULL)) { + parse_array_field_plane_init(parent, bb, field, target_addr, count, + emit_code); + count += fixed_array_inner_count(field); + if (!lex_accept(T_comma)) + break; + continue; + } else if (field->array_dim2 && lex_peek(T_open_curly, NULL)) { + parse_array_field_row_init(parent, bb, field, target_addr, count, + emit_code); + count += fixed_array_inner_count(field); + if (!lex_accept(T_comma)) + break; + continue; + } else if (lex_peek(T_open_curly, NULL) && + is_record_type(field->type)) { + type_t *record_type = field->type; + 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(parent, bb, record_type, elem_addr, + emit_code); + lex_expect(T_close_curly); + } else if (parent == GLOBAL_BLOCK) { + if (emit_code) + value = parse_global_constant_value(parent, bb); + else + consume_global_constant_syntax(); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } + + if (value && emit_code) { + 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 (!lex_accept(T_comma)) + break; + } + lex_expect(T_close_curly); + + if (emit_code) { + 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); + + for (; count < field->array_size; count++) { + var_t *elem_addr = compute_element_address(parent, bb, target_addr, + count, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } +} + +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; + int initializer_count = 0; + + 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. + */ + var_t *global_bitfield_unit[MAX_FIELDS]; + unsigned global_bitfield_value[MAX_FIELDS]; + int global_bitfield_count = 0; + + /* 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. + */ + if (emit_code && parent != GLOBAL_BLOCK && + struct_type->base_type != TYPE_union && !struct_type->is_union) { + 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); + + for (int i = 0; i < struct_type->num_fields; i++) { + var_t *field = &struct_type->fields[i]; + var_t *field_addr = + compute_field_address(parent, bb, target_addr, field); + int field_size = size_var(field); + + for (int offset = 0; offset < field_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, field_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } + + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + var_t *field_val_raw = NULL; + var_t *field = NULL; + var_t *designated_field_addr = NULL; + bool consumed_pending_array_element = false; + + if (lex_accept(T_dot)) { + char field_name[MAX_ID_LEN]; + type_t *designator_type = struct_type; + var_t *designator_base = target_addr; + bool first = true; + + for (;;) { + bool found = false; + + lex_ident(T_identifier, field_name); + for (int i = 0; i < designator_type->num_fields; i++) { + if (!strcmp(designator_type->fields[i].var_name, + field_name)) { + if (first) + field_idx = i; + field = &designator_type->fields[i]; + found = true; + break; + } + } + if (!found) + error_at("Unknown field in record initializer", + cur_token_loc()); + + designated_field_addr = compute_field_address( + parent, bb, designator_base, field); + if (lex_accept(T_open_square)) { + int index, element_count, linear_index, stride; + int total_element_count; + int elem_size; + var_t *array_base_addr = designated_field_addr; + + if (!field->array_size) + error_at( + "Array designator requires an array member", + cur_token_loc()); + index = read_const_expr(parent); + lex_expect(T_close_square); + total_element_count = field->array_size; + element_count = total_element_count; + stride = 1; + if (field->array_dim2) { + stride = field->array_dim2; + if (field->array_dim3) { + stride *= field->array_dim3; + if (field->array_dim4) + stride *= field->array_dim4; + } + element_count /= stride; + } + if (index < 0 || index >= element_count) + error_at("Array designator index is out of bounds", + cur_token_loc()); + elem_size = (field->ptr_level || field->is_func) + ? PTR_SIZE + : field->type->size; + linear_index = index * stride; + designated_field_addr = compute_element_address( + parent, bb, designated_field_addr, linear_index, + elem_size); + memcpy(&array_element, field, sizeof(var_t)); + fixed_array_var_drop_outer(&array_element); + field = &array_element; + + /* Each subscript leaves the remaining inner array in + * `field`, so rows, planes, and a final scalar can all + * share the same bounds and continuation path. + */ + while (field->array_size && lex_accept(T_open_square)) { + index = read_const_expr(parent); + lex_expect(T_close_square); + stride = field->array_dim2 ? field->array_dim2 : 1; + if (field->array_dim3) + stride *= field->array_dim3; + element_count = field->array_size / stride; + if (index < 0 || index >= element_count) + error_at( + "Array designator index is out of bounds", + cur_token_loc()); + designated_field_addr = compute_element_address( + parent, bb, designated_field_addr, + index * stride, elem_size); + linear_index += index * stride; + fixed_array_var_drop_outer(&array_element); + } + if (!field->array_size) { + memcpy(&pending_array_element, field, + sizeof(var_t)); + pending_array_base = array_base_addr; + pending_array_index = linear_index + 1; + pending_array_count = total_element_count; + pending_array_elem_size = elem_size; + has_pending_array_element = true; + } + } + if (!lex_accept(T_dot)) + break; + if (!is_record_type(field->type)) + error_at("Nested designator requires a record member", + cur_token_loc()); + designator_type = field->type; + if (designator_type->base_type == TYPE_typedef && + designator_type->base_struct) + designator_type = designator_type->base_struct; + designator_base = designated_field_addr; + first = false; + } + lex_expect(T_assign); + } else if (has_pending_array_element) { + field = &pending_array_element; + designated_field_addr = compute_element_address( + parent, bb, pending_array_base, pending_array_index, + pending_array_elem_size); + consumed_pending_array_element = true; + } + + /* An unnamed bit-field is padding, not an aggregate member. It + * participates in layout but consumes no positional initializer; + * otherwise `{ low, high }` would incorrectly assign `high` to the + * padding field and leave the named field zero-initialized. + */ + if (!field && !has_pending_array_element) + while (field_idx < struct_type->num_fields && + is_bitfield(&struct_type->fields[field_idx]) && + !struct_type->fields[field_idx].var_name[0]) + field_idx++; + + if (field_idx >= struct_type->num_fields || + ((struct_type->base_type == TYPE_union || + struct_type->is_union) && + initializer_count > 0 && !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 && !designated_field_addr && !has_pending_array_element && + field->array_size && + (field->ptr_level || !is_record_type(field->type)) && + !lex_peek(T_open_curly, NULL) && !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 = (field->ptr_level || field->is_func) + ? PTR_SIZE + : field->type->size; + 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; + designated_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 (field && field->array_size && !field->ptr_level && + (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 (field && field->array_size && is_char_array(field) && + lex_peek(T_string, NULL)) { + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + parse_string_field_init(parent, bb, field, field_addr, + emit_code); + } else if (field && field->array_size && !field->ptr_level && + compatible_decl_type(field->type, + find_type("wchar_t", true)) && + lex_peek(T_wstring, NULL)) { + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + parse_wstring_field_init(parent, bb, field, field_addr, + emit_code); + } else if (field && lex_peek(T_open_curly, NULL) && + field->array_size) { + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + parse_array_field_init(parent, bb, field, field_addr, + emit_code); + } else if (field && lex_peek(T_open_curly, NULL) && + is_record_type(field->type)) { + type_t *nested_type = field->type; + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + + if (nested_type->base_type == TYPE_typedef && + nested_type->base_struct) + nested_type = nested_type->base_struct; + lex_expect(T_open_curly); + parse_struct_field_init(parent, bb, nested_type, field_addr, + emit_code); + lex_expect(T_close_curly); + } else 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_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, 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 (parent == GLOBAL_BLOCK && 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. + */ + 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 < global_bitfield_count; i++) + if (global_bitfield_unit[i]->offset == + field->offset && + global_bitfield_unit[i]->bit_storage_size == + field->bit_storage_size) { + unit = i; + break; + } + if (unit < 0) { + unit = global_bitfield_count++; + global_bitfield_unit[unit] = field; + global_bitfield_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; + global_bitfield_value[unit] = + (global_bitfield_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++; + if (!lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + + if (emit_code && parent == GLOBAL_BLOCK) { + for (int i = 0; i < global_bitfield_count; i++) { + var_t *unit = global_bitfield_unit[i]; + var_t *unit_addr = + compute_field_address(parent, bb, target_addr, unit); + var_t *value = + bitfield_constant(parent, bb, global_bitfield_value[i]); + add_insn(parent, *bb, OP_write, NULL, unit_addr, value, + unit->bit_storage_size, NULL); + } + } +} + +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)) { + opstack_pop(); + perform_side_effect(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; + } + + /* "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); + + /* 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); + 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. + */ + 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(); + 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(); + 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); + +void parse_array_init(var_t *var, + block_t *parent, + basic_block_t **bb, + bool emit_code) +{ + 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 = var->type->size; + if (var->ptr_level > 0 || var->is_func) + elem_size = PTR_SIZE; + + if (emit_code) + 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 && emit_code && !is_implicit) { + 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); + for (int i = 0; i < var->array_size; i++) { + var_t *elem_addr = + compute_element_address(parent, bb, base_addr, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } + + 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; + int prior_count = count; + + if (lex_accept(T_open_square)) { + int index; + int stride = 1; + int elements; + int remaining_dims = 0; + + index = read_const_expr(initializer_scope); + lex_expect(T_close_square); + if (var->array_dim2) { + stride = var->array_dim2; + remaining_dims = 1; + if (var->array_dim3) { + stride *= var->array_dim3; + remaining_dims = 2; + if (var->array_dim4) { + stride *= var->array_dim4; + remaining_dims = 3; + } + } + } + elements = var->array_size / stride; + if (index < 0 || (!is_implicit && index >= elements)) + error_at("Array designator index is out of bounds", + cur_token_loc()); + count = index * stride; + + /* A complete multidimensional designator names a scalar + * element. Keep the outer-only form on the existing braced + * slice path, but turn every complete path into the row-major + * flat offset used by the ordinary store path. + */ + while (remaining_dims && lex_accept(T_open_square)) { + index = read_const_expr(initializer_scope); + lex_expect(T_close_square); + if (remaining_dims == 3) { + elements = var->array_dim2; + stride = var->array_dim3 * var->array_dim4; + } else if (remaining_dims == 2) { + elements = var->array_dim2; + stride = var->array_dim3; + if (var->array_dim4) { + elements = var->array_dim3; + stride = var->array_dim4; + } + } else { + elements = var->array_dim2; + if (var->array_dim3) + elements = var->array_dim3; + if (var->array_dim4) + elements = var->array_dim4; + stride = 1; + } + if (index < 0 || index >= elements) + error_at("Array designator index is out of bounds", + cur_token_loc()); + count += index * stride; + remaining_dims--; + } + if (remaining_dims == 1 && + (var->array_dim3 || var->array_dim4)) { + /* A path ending one dimension short of a scalar names a + * row. Present it to the braced-row helper rather than + * asking a scalar initializer to consume `{ ... }`. + */ + int row_width = + var->array_dim4 ? var->array_dim4 : var->array_dim3; + memcpy(&designated_array, var, sizeof(designated_array)); + designated_array.array_size = row_width; + designated_array.array_dim2 = row_width; + designated_array.array_dim3 = 0; + designated_array.array_dim4 = 0; + initializer_var = &designated_array; + } + lex_expect(T_assign); + } + + if (!is_implicit && count >= var->array_size) + error_at("Too many elements in array initializer", + next_token_loc()); + + if (initializer_var->array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, initializer_var, + base_addr, count, emit_code); + 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, emit_code); + 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, emit_code); + 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 (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; + + 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_ampersand, NULL) || + grouped_global_function_designator_starts_here(true) || + global_function_address_dereference_starts_here())) { + 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); + + if (parent == GLOBAL_BLOCK && + initializer_scope != GLOBAL_BLOCK && + !lex_peek(T_string, NULL) && !function_initializer && + !lex_peek(T_ampersand, NULL)) { + /* 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. + */ + 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 = (object_constant->ptr_level || + object_constant->is_func) + ? PTR_SIZE + : object_constant->type->size; + + /* 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 (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()); + + if (val && var->type->array_element_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 = var->type->array_element_ptr_level; + element_target.pointee_func_signature = + var->type->array_element_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 && emit_code && (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); + + /* A forward designator leaves a gap. Explicit arrays were + * zeroed before parsing; inferred local arrays do not have a + * known bound yet, so zero just the newly skipped elements. + * Global storage begins zeroed. + */ + if (is_implicit && parent != GLOBAL_BLOCK && + count > prior_count) { + 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); + for (int i = prior_count; i < count; i++) { + var_t *gap_addr = compute_element_address( + parent, bb, base_addr, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = compute_element_address( + parent, bb, gap_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, + zero, 1, NULL); + } + } + } + + 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 (is_implicit && count > inferred_size) + inferred_size = count; + if (!lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + + lex_expect(T_close_curly); + + if (is_implicit) { + 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; + + /* A designated element can leave holes before or after it, so initialize + * the declared object before parsing any explicit elements. + */ + if (declared_size) { + 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); + for (int i = 0; i < declared_size; i++) { + var_t *elem_addr = + compute_element_address(parent, bb, var, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } + } + + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + if (lex_accept(T_open_square)) { + count = read_const_expr(parent); + lex_expect(T_close_square); + 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 && count > inferred_size) { + 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); + for (int i = inferred_size; i < count; i++) { + var_t *gap_addr = + compute_element_address(parent, bb, var, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = compute_element_address( + parent, bb, gap_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, + 1, NULL); + } + } + } + + 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 = var->type; + 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(parent, bb, record_type, elem_addr, + true); + lex_expect(T_close_curly); + } 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 (!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..b7de80c2 --- /dev/null +++ b/src/parser-sizeof.c @@ -0,0 +1,825 @@ +/* + * 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 { + lex_ident(T_string, literal); + unescape_string(literal, unescaped, MAX_STRING_LEN); + size += strlen(unescaped); + } while (lex_peek(T_string, NULL)); + + return size; +} + +int read_sizeof_wstring_literal(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; +} + +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 members; /* record member selections */ + int subscripts; /* array subscripts */ + bool element_leaf; /* the last subscript selected a non-array element */ + bool addressed; /* the operand so far is "&array" */ +} sizeof_walk_t; + +/* 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. + */ +static bool scan_sizeof_postfix_operand(block_t *scope, + token_t **cursor, + var_t *object, + sizeof_walk_t *walk) +{ + token_t *token = *cursor; + + if (!token) + return false; + if (token->kind == T_asterisk) { + bool addressed; + int depth; + + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) + return false; + addressed = walk->addressed; + depth = effective_pointer_depth(object); + if (depth <= 0) + return false; + if (object->pointee_array_size > 0 && + depth == object->pointee_array_element_ptr_level + 1) { + /* A pointer to an array designates the complete array, so the + * dereference restores its bounds rather than decaying them. + */ + 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; + } + walk->addressed = false; + walk->element_leaf = false; + } else if (token->kind == T_ampersand) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) + return 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++; + walk->element_leaf = false; + } else if (token->kind == T_open_bracket) { + token = token->next; + if (!scan_sizeof_postfix_operand(scope, &token, object, walk) || + !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); + + if (!root) + return false; + memcpy(object, root, sizeof(*object)); + token = token->next; + } else + return false; + + while (token && (token->kind == T_dot || token->kind == T_arrow || + token->kind == T_open_square)) { + walk->addressed = false; + if (token->kind == T_dot || token->kind == T_arrow) { + bool through_pointer = token->kind == T_arrow; + type_t *record; + var_t *field; + + token = token->next; + if (!token || token->kind != T_identifier || + (through_pointer && effective_pointer_depth(object) != 1) || + (!through_pointer && effective_pointer_depth(object) != 0)) + return false; + record = through_pointer + ? pointee_type_from_pointer_typedef(object->type) + : object->type; + if (!is_record_type(record)) + return false; + field = find_member(token->literal, record); + if (!field) + return false; + memcpy(object, field, sizeof(*object)); + walk->members++; + walk->element_leaf = false; + token = token->next; + } else { + int depth = 0; + fixed_array_shape_t shape; + + /* An array may legitimately have pointer elements. Reject a scalar + * pointer here, but retain the element pointer depth until after + * this array subscript so a following `->` can consume it. + */ + if (object->array_size <= 0) + return false; + 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; + shape = fixed_array_shape_from_var(object); + if (!fixed_array_shape_drop_outer(&shape)) + return false; + fixed_array_shape_to_var(object, &shape); + walk->subscripts++; + walk->element_leaf = !object->array_size; + } + } + *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_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; + int saved_side_effects; + + lex_expect(T_open_square); + unevaluated_bb = bb_create(parent); + saved_side_effects = se_idx; + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + opstack_pop(); + se_idx = saved_side_effects; + lex_expect(T_close_square); + } + } +} + +/* The operand must end where sizeof's operand ends: at the closing parenthesis + * of a parenthesized one, 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) +{ + return tail && (parenthesized ? tail->kind == T_close_bracket + : tail->kind != T_open_bracket && + tail->kind != T_increment && + tail->kind != T_decrement); +} + +/* A non-mutating admission check for callers that need to hand a global sizeof + * operand to the detached local parser. It admits only a member array, so type + * names and scalar forms remain owned by their existing global constant paths. + */ +static bool can_scan_sizeof_postfix_extent(block_t *scope, + token_t *start, + bool parenthesized, + bool allow_flexible) +{ + token_t *tail = start; + var_t object; + sizeof_walk_t walk = {0}; + + return scan_sizeof_postfix_operand(scope, &tail, &object, &walk) && + walk.members && sizeof_operand_tail_ends(tail, parenthesized) && + !effective_pointer_depth(&object) && + (object.array_size > 0 || + (allow_flexible && object.is_flexible_array_member)); +} + +/* Walk a local sizeof operand from @start and say whether this walker owns it: + * an operand that still designates an array, whatever the path to it, or a + * single element selected from one. Anything else keeps the ordinary + * unevaluated-expression path. + */ +static bool walk_local_sizeof_operand(block_t *parent, + token_t *start, + bool parenthesized, + var_t *object) +{ + token_t *tail = start; + sizeof_walk_t walk = {0}; + + if (!scan_sizeof_postfix_operand(parent, &tail, object, &walk) || + !sizeof_operand_tail_ends(tail, parenthesized)) + return false; + if (object->is_flexible_array_member) + return walk.members > 0; + return object->array_size > 0 || walk.element_leaf; +} + +/* Every sizeof operand made of an identifier, grouping, dereference, address, + * 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; + var_t *result; + int size; + + if (!walk_local_sizeof_operand(parent, cur_token->next, parenthesized, + &object)) { + /* 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)) + return false; + open_consumed = true; + } + if (object.is_flexible_array_member) + error_at("sizeof cannot be applied to a flexible array member", + 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 + size = object.type->size; + result = require_var(parent); + result->init_val = size; + result->var_name = gen_name(); + opstack_push(result); + add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + 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; + int array_element_size = 0; + token_t *sizeof_tk = cur_token; + type_t *type = NULL; + bool is_function = false; + var_t *vd; + + 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 is the + * complete operand. + */ + token_t *after_string = cur_token->next; + while (after_string && after_string->kind == T_string) + after_string = after_string->next; + if (lex_peek(T_string, NULL) && + (!parenthesized || + (after_string && after_string->kind == T_close_bracket))) { + vd = require_var(parent); + vd->init_val = read_sizeof_string_literal(); + vd->var_name = gen_name(); + if (parenthesized) + lex_expect(T_close_bracket); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + return; + } + + token_t *after_wstring = cur_token->next; + while (after_wstring && after_wstring->kind == T_wstring) + after_wstring = after_wstring->next; + if (lex_peek(T_wstring, NULL) && + (!parenthesized || + (after_wstring && after_wstring->kind == T_close_bracket))) { + vd = require_var(parent); + vd->init_val = read_sizeof_wstring_literal(); + vd->var_name = gen_name(); + if (parenthesized) + lex_expect(T_close_bracket); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + 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); + vd = require_var(parent); + vd->init_val = strlen(parent->func->return_def.var_name) + 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + return; + } + + if (lex_peek(T_identifier, token)) { + var_t *array = find_var(token, parent); + + if (array && array->array_size > 0) { + lex_expect(T_identifier); + vd = require_var(parent); + vd->init_val = sizeof_array_object(array); + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, + NULL); + return; + } + } + + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + read_expr_operand(parent, &unevaluated_bb); + se_idx = saved_side_effects; + 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); + + vd = require_var(parent); + vd->init_val = type->size; + if (array_size > 0) + vd->init_val = array_size * type->size; + if (ptr_cnt) + vd->init_val = PTR_SIZE; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + 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); + vd = require_var(parent); + vd->init_val = strlen(parent->func->return_def.var_name) + 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + 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); + vd = require_var(parent); + vd->init_val = sizeof_array_object(array); + 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); + return; + } + } + + /* Check if this is sizeof(type) or sizeof(expression) */ + bool has_signed_type = false; + bool has_unsigned_type = false; + int long_type_count = 0; + type_t *leading_scalar_type = NULL; + + if (lex_peek(T_identifier, token) && + (!strcmp(token, "char") || !strcmp(token, "short") || + !strcmp(token, "int"))) { + token_t *after_base = cur_token->next->next; + + while (after_base && after_base->kind == T_const) + after_base = after_base->next; + if (after_base && + (after_base->kind == T_signed || after_base->kind == T_unsigned)) { + lex_expect(T_identifier); + leading_scalar_type = find_type(token, true); + } + } + while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL) || lex_peek(T_const, NULL) || + lex_peek(T_volatile, NULL)) { + if (lex_accept(T_signed)) { + if (has_signed_type) + error_at("duplicate signed type specifier", cur_token_loc()); + has_signed_type = true; + } else if (lex_accept(T_unsigned)) { + if (has_unsigned_type) + error_at("duplicate unsigned type specifier", cur_token_loc()); + has_unsigned_type = true; + } else if (lex_accept(T_long)) + long_type_count++; + else if (lex_accept(T_const)) + ; + else + lex_expect(T_volatile); + } + if (long_type_count > 2) + error_at("too many long type specifiers", cur_token_loc()); + if (has_signed_type && has_unsigned_type) + error_at("both signed and unsigned specified", cur_token_loc()); + if (leading_scalar_type == TY_int && lex_peek(T_identifier, token) && + !strcmp(token, "char")) + error_at("int cannot be combined with char", cur_token_loc()); + bool has_long_type = long_type_count > 0; + bool has_enum_type = lex_accept(T_enum); + if (has_enum_type && + (has_signed_type || has_unsigned_type || has_long_type)) + error_at("enum type cannot be combined with integer specifiers", + cur_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; + + /* `sizeof` only consumes object representation metadata, so it can admit + * the C99 floating type names before value arithmetic and ABI lowering + * exist. Keep all expression/declaration admission gates intact. + */ + if (lex_accept(T_float)) { + if (has_signed_type || has_unsigned_type || has_long_type || + has_enum_type || find_type_flag != 1) + error_at("invalid float type specifiers", cur_token_loc()); + type = TY_float; + } else if (lex_accept(T_double)) { + if (has_signed_type || has_unsigned_type || has_enum_type || + find_type_flag != 1 || long_type_count > 1) + error_at("invalid double type specifiers", cur_token_loc()); + type = has_long_type ? TY_long_double : TY_double; + } else if (has_enum_type) { + lex_ident(T_identifier, token); + type = find_type_tag(token, parent); + if (!type) + error_at("Unknown enum type", cur_token_loc()); + while (lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + } else if (has_long_type) { + type = has_unsigned_type ? TY_ulong : TY_long; + if (long_type_count > 1) { + /* sizeof only consumes type metadata. It does not materialize a + * long-long value, so 32-bit targets can correctly report the + * required eight-byte object size before their paired-register + * value ABI is implemented. + */ + if (has_unsigned_type) + type = TY_ulong_long; + else + type = TY_long_long; + } + lex_accept(T_signed); + lex_accept(T_const); + if (lex_peek(T_identifier, token) && !strcmp(token, "int")) + lex_expect(T_identifier); + while (lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + } else if (has_unsigned_type) { + if (leading_scalar_type == TY_char) { + type = TY_uchar; + } else if (leading_scalar_type == TY_short) { + if (lex_peek(T_identifier, token) && !strcmp(token, "int")) + lex_expect(T_identifier); + type = TY_ushort; + } else if (lex_peek(T_identifier, token) && + (!strcmp(token, "int") || !strcmp(token, "char") || + !strcmp(token, "short"))) { + lex_expect(T_identifier); + if (!strcmp(token, "char")) + type = TY_uchar; + else if (!strcmp(token, "short")) + type = TY_ushort; + else + type = TY_uint; + } else + type = TY_uint; + while (lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + } else if (has_signed_type) { + if (leading_scalar_type == TY_char || leading_scalar_type == TY_short) { + if (leading_scalar_type == TY_short && + lex_peek(T_identifier, token) && !strcmp(token, "int")) + lex_expect(T_identifier); + type = + leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; + } else if (lex_peek(T_identifier, token) && + (!strcmp(token, "int") || !strcmp(token, "char") || + !strcmp(token, "short"))) { + lex_expect(T_identifier); + type = !strcmp(token, "char") ? TY_schar : find_type(token, true); + } else + type = TY_int; + while (lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + } else if (leading_scalar_type) { + type = leading_scalar_type; + while (lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + } else if (lex_peek(T_identifier, token)) { + /* Try to parse as a type first */ + type = find_type_flag == 2 ? find_type(token, find_type_flag) + : find_visible_type(token, parent); + if (type) { + /* sizeof(type) */ + lex_expect(T_identifier); + while (lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + } + } + + /* The integer branches above consume their own abstract pointer declarators + * for historical reasons. Float/double type names select a type before that + * legacy code, so finish the common suffix here. + */ + while (type && lex_accept(T_asterisk)) { + ptr_cnt++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + + /* VLA support is intentionally outside this compiler's C99 scope, but a + * type name in sizeof may still carry fixed abstract array bounds. Keep the + * simple direct form here rather than treating '[' as an expression token + * after the scalar type name. + */ + while (type && ptr_cnt == 0 && lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + lex_expect(T_close_square); + if (bound <= 0) + error_at("sizeof array type needs a positive constant bound", + cur_token_loc()); + if (bound > 0 && array_size && array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + array_size = array_size ? array_size * bound : bound; + } + + if (type && lex_accept(T_open_bracket)) { + int nested_array_size = 0; + int nested_ptr_count = 0; + int nested_function_ptr_count = 0; + int nested_outer_array_size = 0; + + while (lex_accept(T_asterisk)) { + nested_ptr_count++; + while (lex_accept(T_const) || lex_accept(T_volatile) || + lex_accept(T_restrict)) + ; + } + if (!nested_ptr_count) + error_at("sizeof abstract declarator needs a pointer", + cur_token_loc()); + if (lex_peek(T_open_bracket, NULL)) { + nested_function_ptr_count = + read_sizeof_nested_function_pointer_suffix( + parent, &nested_outer_array_size); + if (nested_outer_array_size) { + array_size = nested_outer_array_size; + array_element_size = PTR_SIZE; + } else + ptr_cnt += nested_ptr_count + nested_function_ptr_count; + } else + while (lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + lex_expect(T_close_square); + if (bound <= 0) + error_at( + "sizeof array type needs a positive constant bound", + cur_token_loc()); + if (bound > 0 && nested_array_size && + nested_array_size > INT_MAX / bound) + error_at("sizeof array type is too large", cur_token_loc()); + if (bound > 0) + nested_array_size = + nested_array_size ? nested_array_size * bound : bound; + } + if (!nested_function_ptr_count) { + lex_expect(T_close_bracket); + if (nested_array_size) { + array_size = nested_array_size; + array_element_size = PTR_SIZE; + + /* The grouped array declarator in `int (*[2][3])(int)` leaves + * its pointed-to function suffix outside the group. sizeof only + * needs pointer-sized elements, but still must parse that valid + * C99 prototype completely. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } else { + ptr_cnt += nested_ptr_count; + while (lex_accept(T_open_square)) { + int bound = read_const_expr(parent); + + lex_expect(T_close_square); + if (bound <= 0) + error_at( + "sizeof array type needs a positive constant bound", + cur_token_loc()); + } + + /* A pointer declarator followed by a parameter list names a + * function pointer. sizeof observes the pointer object, not the + * function type, so no expression lowering or function ABI is + * needed. Reuse the declaration parser for prototype syntax. + */ + if (lex_peek(T_open_bracket, NULL)) + read_sizeof_function_prototype(); + } + } + } + + if (!type) { + /* sizeof(expression) - parse the expression and get its type */ + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + unevaluated_expression_depth++; + if (!read_assignment_expression(parent, &unevaluated_bb)) { + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); + } + unevaluated_expression_depth--; + se_idx = saved_side_effects; + 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; + vd = require_var(parent); + vd->init_val = type->size; + if (array_size > 0) + vd->init_val = + array_size * (array_element_size ? array_element_size : type->size); + if (ptr_cnt) + vd->init_val = PTR_SIZE; + + /* VLA is intentionally outside shecc's C99 scope, so every admitted sizeof + * result is an integer constant expression. + */ + vd->is_const = true; + 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); +} diff --git a/src/parser-stmt.c b/src/parser-stmt.c new file mode 100644 index 00000000..f6d8ac44 --- /dev/null +++ b/src/parser-stmt.c @@ -0,0 +1,2089 @@ +/* + * 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. + */ + +void perform_side_effect(block_t *parent, basic_block_t *bb) +{ + 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; +} + +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; + +void reject_label_followed_by_declaration_in_strict_c99(void) +{ + 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_type(name, true))) + error_at("a C99 label must precede a statement, not a declaration", + cur_token_loc()); +} + +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 { + int case_val; + var_t *constant; + var_t *comparison; + basic_block_t *next_dispatch; + + lex_expect(T_case); + case_val = read_const_expr(parent); + if (strict_c99) { + switch_case_value_t *seen; + + for (seen = context->seen_cases; seen; seen = seen->next) + if (seen->value == case_val) + error_at("duplicate case value in C99 switch", + cur_token_loc()); + seen = arena_alloc(GENERAL_ARENA, sizeof(*seen)); + seen->value = case_val; + seen->next = context->seen_cases; + context->seen_cases = seen; + } + + constant = require_var(context->dispatch_parent); + constant->var_name = gen_name(); + constant->init_val = case_val; + 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(); + 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. + */ + body = bb_create(parent); + + lex_expect(T_open_curly); + while (!lex_accept(T_close_curly)) { + body = read_body_statement(parent, body); + perform_side_effect(parent, 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; +} + +/* 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; + var_t *rs1; + var_t *var; + 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); + + 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_type(token, 1) + : NULL; + } + if (!type && saw_decl_specifier) + error_at("declaration specifier requires a type", cur_token_loc()); + if (type) { + /* 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()); + var = require_typed_var(blk, type); + var->is_static = is_static; + var->is_register = is_register; + var->is_global = is_static; + var->is_const_qualified = is_const; + var->is_volatile = is_volatile; + parsing_for_initializer_declaration = true; + read_full_var_decl(var, false, false, false); + parsing_for_initializer_declaration = false; + if (strict_c99 && var->is_func) + error_at("C99 for initializer cannot declare a function", + cur_token_loc()); + if (is_extern) { + if (var->is_func || lex_peek(T_open_bracket, NULL)) { + /* This helper consumes the declaration's semicolon. */ + blk->locals.size--; + var->is_block_scope_function_declaration = true; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = + var; + read_global_function_declarator(GLOBAL_BLOCK, var, false); + var_t *alias = require_var(blk); + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + for_decl_semicolon_consumed = true; + } else { + for (;;) { + var = bind_block_extern_object(blk, var); + if (lex_peek(T_assign, NULL)) + error_at( + "extern declaration cannot have an initializer", + next_token_loc()); + if (!lex_accept(T_comma)) + break; + var = require_typed_var(blk, type); + var->is_const_qualified = is_const; + var->is_volatile = is_volatile; + read_partial_var_decl(var, NULL); + } + } + } else { + if (is_incomplete_record_object(var)) + error_at( + "Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : blk, + is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, var, + NULL, NULL, 0, NULL); + add_symbol(setup, var); + if (lex_accept(T_assign)) { + validate_string_array_initializer(var); + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) + parse_array_init(var, GLOBAL_BLOCK, + &GLOBAL_FUNC->bbs, true); + 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, blk, &setup); + } else if ((var->has_unsized_array || + var->array_size > 0) && + is_wchar_array(var) && + lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(var, blk, &setup); + } else if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, blk, &setup, true); + } else { + 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); + nv->is_static = is_static; + nv->is_register = is_register; + nv->is_global = is_static; + nv->is_const_qualified = is_const; + nv->is_volatile = is_volatile; + read_partial_var_decl(nv, var); /* partial */ + if (is_incomplete_record_object(nv)) + error_at( + "Incomplete struct/union type cannot define an " + "object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : blk, + is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, + nv, NULL, NULL, 0, NULL); + add_symbol(setup, nv); + if (lex_accept(T_assign)) { + validate_string_array_initializer(nv); + if (is_static) { + if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) + parse_array_init(nv, GLOBAL_BLOCK, + &GLOBAL_FUNC->bbs, true); + else if (lex_peek(T_open_curly, NULL)) + parse_global_record_init(nv, GLOBAL_BLOCK); + else + read_global_assignment_var(nv); + } else if ((nv->has_unsized_array || + nv->array_size > 0) && + is_char_array(nv) && + lex_peek(T_string, NULL)) { + parse_string_array_init(nv, blk, &setup); + } else if ((nv->has_unsized_array || + nv->array_size > 0) && + is_wchar_array(nv) && + lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(nv, blk, &setup); + } else if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || + nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, blk, &setup, true); + } else { + 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_control_expression(blk, &setup); + opstack_pop(); + perform_side_effect(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(); + 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_); + opstack_pop(); + perform_side_effect(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(); + 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, + bool is_const, + bool is_static) +{ + char token[MAX_ID_LEN]; + type_t *type; + var_t *var; + + bool is_union = false; + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) { + find_type_flag = 2; + is_union = true; + } + lex_ident(T_identifier, token); + type = find_type(token, find_type_flag); + if (lex_peek(T_open_curly, NULL)) { + int i = 0; + int size = 0; + int alignment = 1; + int max_size = 0; + bitfield_layout_t bits = {0}; + bool has_flexible_array_member = false; + + if (!type) + type = add_type(); + set_type_name(type, token); + type->base_type = is_union ? TYPE_union : TYPE_struct; + + lex_expect(T_open_curly); + do { + var_t *v = type_add_field(type, &i); + var_t *last = v; + + read_full_var_decl(v, false, false, true); + read_bitfield_width(v, parent); + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, is_union); + if (is_union) { + v->offset = 0; + int field_size = is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); + } else { + size = is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), v, + &alignment); + } + + while (lex_accept(T_comma)) { + if (!is_union && last->is_flexible_array_member) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); + var_t *nv = type_add_field(type, &i); + initialize_struct_field(nv, v, 0); + read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, parent); + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, is_union); + last = nv; + if (is_union) { + nv->offset = 0; + int field_size = + is_bitfield(nv) + ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); + } else { + size = + is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); + } + } + + 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->has_flexible_array_member = has_flexible_array_member; + + if (lex_peek(T_semicolon, NULL)) { + lex_expect(T_semicolon); + return bb; + } + } + if (!type && lex_peek(T_semicolon, NULL)) { + /* A block-scope `struct tag;` or `union tag;` introduces an incomplete + * tag. Its pointer declarators become valid immediately; the later + * complete definition reuses this type record. + */ + type = add_type(); + type->base_type = is_union ? TYPE_union : TYPE_struct; + set_type_name(type, token); + lex_expect(T_semicolon); + return bb; + } + if (type) { + var = require_typed_var(parent, type); + var->is_const_qualified = is_const; + var->is_static = is_static; + var->is_global = is_static; + read_partial_var_decl(var, NULL); + if (is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : parent, + 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 (is_static && lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (is_static && lex_peek(T_open_curly, NULL)) { + parse_global_record_init(var, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + 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_union || + 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; + + 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, + true); + lex_expect(T_close_curly); + } else { + if (!read_assignment_expression(parent, &bb)) { + 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, + !has_effective_pointer(var) && var->array_size == 0); + + emit_object_assignment(parent, &bb, var, rhs); + } + } + 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); + nv->is_static = is_static; + nv->is_global = is_static; + nv->is_const_qualified = is_const; + read_inner_var_decl(nv, false, false, false); + if (is_incomplete_record_object(nv)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : parent, + is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, + NULL, 0, NULL); + add_symbol(bb, nv); + if (lex_accept(T_assign)) { + validate_string_array_initializer(nv); + if (is_static && lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (is_static && lex_peek(T_open_curly, NULL)) { + parse_global_record_init(nv, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + 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_union || + nv->type->base_type == TYPE_typedef)) { + type_t *struct_type = nv->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, nv, NULL, + 0, NULL); + lex_expect(T_open_curly); + parse_struct_field_init(parent, &bb, struct_type, + struct_addr, true); + 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, + !has_effective_pointer(nv) && nv->array_size == 0); + + emit_object_assignment(parent, &bb, nv, rhs); + } + } + } + lex_expect(T_semicolon); + return bb; + } + error_at("Unknown struct/union type", next_token_loc()); +} + +basic_block_t *handle_enum_declarators(block_t *parent, + basic_block_t *bb, + type_t *type, + bool is_const, + bool is_static) +{ + for (;;) { + var_t *var = require_typed_var(parent, type); + + var->is_const_qualified = is_const; + var->is_static = is_static; + var->is_global = is_static; + read_partial_var_decl(var, NULL); + add_insn(is_static ? GLOBAL_BLOCK : parent, + 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 (is_static) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, + true); + } 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)) { + parse_array_init(var, parent, &bb, true); + } 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, + !has_effective_pointer(var) && var->array_size == 0); + emit_object_assignment(parent, &bb, var, rhs); + } + } + if (is_static) + discard_global_declarator_operand(var); + + if (!lex_accept(T_comma)) + break; + perform_side_effect(parent, bb); + } + lex_expect(T_semicolon); + return bb; +} + +/* 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; +} + +/* A block-scope enum definition contributes integer constants to the current + * expression parser just as a file-scope definition does. Like a record + * definition, it may introduce declarators after the closing brace. + */ +basic_block_t *handle_enum_statement(block_t *parent, + basic_block_t *bb, + bool is_const, + bool is_static) +{ + char token[MAX_ID_LEN]; + int val = 0; + type_t *type = NULL; + bool has_tag = false; + + lex_expect(T_enum); + if (lex_peek(T_identifier, token)) { + lex_expect(T_identifier); + type = find_local_type_tag(token, parent); + has_tag = true; + } + if (!lex_peek(T_open_curly, NULL)) { + if (!has_tag) + error_at("Unknown enum type", next_token_loc()); + if (!type) + type = find_type_tag(token, parent); + if (!type) + error_at("Unknown enum type", next_token_loc()); + return handle_enum_declarators(parent, bb, type, is_const, is_static); + } + if (!type) + type = add_type(); + initialize_enum_type(type); + if (has_tag) { + set_type_name(type, token); + if (!find_local_type_tag(token, parent)) + add_type_tag(parent, 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(parent); + } + add_scoped_constant(parent, token, val); + first = false; + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); + lex_expect(T_close_curly); + + if (lex_accept(T_semicolon)) + return bb; + return handle_enum_declarators(parent, bb, type, is_const, is_static); +} + +/* 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; + + 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; +} + +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()); +} + +/* 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; + +/* A block-scope declaration whose type handle_declaration() has resolved: every + * declarator, its initializer, and its storage. + * + * 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) +{ + var_t *var; + + 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_full_var_decl(var, false, false, false); + reject_ordinary_typedef_collision(parent, var); + + /* 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()); + parent->locals.size--; + var->is_block_scope_function_declaration = true; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; + read_global_function_declarator(GLOBAL_BLOCK, var, false); + var_t *alias = require_var(parent); + + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + if (!lex_accept(T_comma)) + return bb; + + 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()); + if (spec->is_extern) { + if (var->is_func || lex_peek(T_open_bracket, NULL)) { + /* The global helper owns function redeclaration compatibility and + * parameter parsing. Unlike an object declaration it consumes the + * trailing semicolon itself. + */ + parent->locals.size--; + var->is_block_scope_function_declaration = true; + GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; + read_global_function_declarator(GLOBAL_BLOCK, var, false); + + /* Keep a lexical marker so this declaration hides an outer + * automatic object of the same name. + */ + var_t *alias = require_var(parent); + alias->var_name = var->var_name; + alias->is_extern_function_alias = true; + return bb; + } + for (;;) { + var = bind_block_extern_object(parent, var); + if (lex_peek(T_assign, NULL)) + error_at("extern declaration cannot have an initializer", + next_token_loc()); + if (!lex_accept(T_comma)) + break; + var = require_typed_var(parent, type); + var->is_const_qualified = spec->is_const; + var->is_volatile = spec->is_volatile; + read_partial_var_decl(var, NULL); + } + lex_expect(T_semicolon); + return bb; + } + if (is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + 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, true); + } 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, 1); + } 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, + true); + lex_expect(T_close_curly); + } else { + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } + + var_t *expr_result = opstack_pop(); + + /* 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; + } + + if (incompatible_pointee_callback_conversion(expr_result, var)) + error_at("incompatible callback slot types in initializer", + cur_token_loc()); + diagnose_const_pointer_conversion(expr_result, var); + emit_object_assignment(parent, &bb, var, expr_result); + } + } + if (spec->is_static) + discard_global_declarator_operand(var); + 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); + 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 */ + reject_ordinary_typedef_collision(parent, nv); + if (is_incomplete_record_object(nv)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(spec->is_static ? GLOBAL_BLOCK : parent, + spec->is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, + NULL, 0, NULL); + add_symbol(bb, nv); + if (lex_accept(T_assign)) { + validate_string_array_initializer(nv); + if (spec->is_static) { + if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (global_compound_literal_starts_here() && + !(nv->ptr_level || nv->type->ptr_level) && + is_record_type(nv->type)) { + parse_global_compound_record_init(nv, GLOBAL_BLOCK); + } else if (global_compound_literal_starts_here() && + (nv->ptr_level || nv->type->ptr_level)) { + parse_global_compound_array_init(nv, GLOBAL_BLOCK); + } else if (global_compound_literal_starts_here()) { + parse_global_compound_scalar_init(nv, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL)) { + parse_global_record_init(nv, GLOBAL_BLOCK); + } else { + read_global_assignment_var(nv); + } + } else if ((nv->has_unsized_array || nv->array_size > 0) && + is_char_array(nv) && lex_peek(T_string, NULL)) { + parse_string_array_init(nv, parent, &bb); + } else if ((nv->has_unsized_array || nv->array_size > 0) && + is_wchar_array(nv) && lex_peek(T_wstring, NULL)) { + parse_wstring_array_init(nv, parent, &bb); + } else if (lex_peek(T_open_curly, NULL) && + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { + /* Emit code for locals */ + parse_array_init(nv, parent, &bb, 1); + } else if (lex_peek(T_open_curly, NULL) && + is_record_type(nv->type)) { + type_t *struct_type = nv->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, nv, NULL, 0, + NULL); + lex_expect(T_open_curly); + parse_struct_field_init(parent, &bb, struct_type, struct_addr, + true); + lex_expect(T_close_curly); + } else { + if (!read_assignment_expression(parent, &bb)) { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + } + + emit_object_assignment(parent, &bb, nv, opstack_pop()); + } + } + if (spec->is_static) + discard_global_declarator_operand(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; + opcode_t prefix_op = OP_generic; + 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.is_const, spec.is_static); + + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { + if (spec.is_extern || spec.is_register) + error_at("Unsupported storage class on record definition", + next_token_loc()); + return handle_record_statement(parent, bb, spec.is_const, + spec.is_static); + } + + /* 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 */ + token_t *type_token = cur_token; + if (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || + lex_peek(T_long, NULL)) { + type = TY_int; + } else { + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) + find_type_flag = 2; + if (find_type_flag == 2) + lex_peek(T_identifier, token); + type = find_type_flag == 2 ? find_type(token, find_type_flag) + : find_visible_type(token, parent); + if (find_type_flag == 2) + cur_token = type_token; + } + } + + 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); + + /* 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); + perform_side_effect(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(); + 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); + + /* 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(); + + int addr_depth = effective_pointer_depth(addr); + unsigned int addr_mask = effective_pointer_const_mask(addr); + if (addr->is_const_qualified || + (addr_depth > 1 && addr_depth <= 32 && + (addr_mask & (1U << (addr_depth - 2))))) + error_at("assignment of read-only location", next_token_loc()); + + /* 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, NULL, 0)) { + perform_side_effect(parent, bb); + while (lex_accept(T_comma)) { + prefix_op = OP_generic; + lex_peek(T_identifier, token); + if (!read_body_assignment(token, parent, prefix_op, &bb, NULL, 0)) + error_at("Expected assignment after comma", next_token_loc()); + 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; +} + +/* 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}; + + lex_expect(T_typedef); + decl.scope = parent; + parsing_block_typedef_declarator = true; + 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; + 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.ptr_level && !decl.array_size && + !decl.pointee_array_size && !base->ptr_level && + !is_record_type(base) && !base->is_floating && + !direct_function_signature->va_args && + !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, and no deeper pointer + * composition. + */ + bool callback_slot_alias = + callback_slot_signature && + decl.parenthesized_function_pointer_level == 2 && + decl.ptr_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_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)); + alias->base_type = TYPE_typedef; + alias->base_struct = is_record_type(base) ? base : base->base_struct; + alias->ptr_level = base->ptr_level + decl.ptr_level; + alias->pointer_const_mask = + base->pointer_const_mask | + (decl.pointer_const_mask << base->ptr_level); + 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) { + 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 (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; + + /* 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); + + 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()) + 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); + + /* struct/union variable declaration */ + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) + return handle_record_statement(parent, bb, false, false); + + if (lex_peek(T_enum, NULL)) + return handle_enum_statement(parent, bb, false, false); + + /* Handle const qualifier for local variable declarations */ + 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); + perform_side_effect(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; + 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 33bf5b98..31885755 100644 --- a/src/parser.c +++ b/src/parser.c @@ -190,6 +190,35 @@ static fixed_array_shape_t fixed_array_shape_prepend( 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, @@ -1646,16913 +1675,17 @@ bool incompatible_pointee_callback_conversion(const var_t *from, !compatible_function_signature(from_signature, to_signature); } -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); -void read_global_function_declarator(block_t *block, - var_t *var, - bool is_static); -var_t *bind_block_extern_object(block_t *parent, var_t *var); -int read_const_expr(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 && !var->ptr_level && !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)); -} - -void emit_record_copy(block_t *parent, - basic_block_t **bb, - var_t *dest, - var_t *src) -{ - int size = size_var(dest); - var_t *dest_addr = require_ref_var(parent, dest->type, 0); - var_t *src_addr = require_ref_var(parent, src->type, 0); - - dest_addr->var_name = gen_name(); - src_addr->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); - add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); - - 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; - } -} - -/* Copy a record into an address which is already known, such as a nested record - * member. A compound literal is an object, not an integer value, so lowering it - * through one OP_write with the member's aggregate size leaves narrow backends - * with an impossible 8-byte store. - */ -void emit_record_copy_to_address(block_t *parent, - basic_block_t **bb, - var_t *dest_addr, - var_t *src) -{ - int size = size_var(src); - var_t *src_addr = require_ref_var(parent, src->type, 0); - - src_addr->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); - - 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; - } -} - -void emit_object_assignment(block_t *parent, - basic_block_t **bb, - var_t *dest, - var_t *src) -{ - func_t *target = src->func_target; - - 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); - dest->func_target = src->func_target; - dest->func_target_invalid = - src->func_target_invalid || !target || !target->bbs; - } else { - src = resize_var(parent, bb, src, dest); - add_insn(parent, *bb, OP_assign, dest, src, NULL, 0, NULL); - } -} - -/* 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. - */ -var_t *read_global_address_designator(block_t *scope, - block_t *parent, - basic_block_t **bb, - var_t **object, - var_t *object_addr) -{ - 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 (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { - int element_size = target->ptr_level ? PTR_SIZE : target->type->size; - int index = read_global_address_offset(scope, parent, *bb); - - object_addr = compute_element_address( - parent, bb, object_addr, index, - fixed_array_shape_stride(&shape, subscripts - 1, element_size)); - object_addr->ptr_level = target->ptr_level + 1; - object_addr->is_global_address = true; - } - *object = target; - return object_addr; -} - -var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) -{ - var_t *val = NULL; - block_t *scope = global_constant_initializer_scope - ? global_constant_initializer_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; - - 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; - } - explicit_address = lex_accept(T_ampersand); - - if (grouped_global_function_designator_starts_here(false)) { - lex_expect(T_open_bracket); - grouped_function_designator = true; - } - - 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); - } - } - } - 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_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) || lex_peek(T_wchar, NULL)) { - char chtok[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; - token_kind_t kind = lex_peek(T_wchar, NULL) ? T_wchar : T_char; - lex_ident(kind, chtok); - unescape_string(chtok, unescaped, MAX_TOKEN_LEN); - - val = require_typed_var(parent, TY_int); - val->var_name = gen_name(); - val->init_val = kind == T_wchar ? parse_wide_character_constant(chtok) - : parse_character_constant(chtok); - add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); - } 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; -} - -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_peek(T_wchar, NULL)) { - lex_next(); - } else { - error_at("Global array initialization requires constant values", - next_token_loc()); - } -} - -bool is_record_type(const type_t *type) -{ - return type && - (type->base_type == TYPE_struct || type->base_type == TYPE_union || - (type->base_type == TYPE_typedef && type->num_fields > 0)); -} - -void parse_struct_field_init(block_t *parent, - basic_block_t **bb, - type_t *struct_type, - var_t *target_addr, - bool emit_code); - -void parse_array_field_row_values(block_t *parent, - basic_block_t **bb, - const var_t *field, - var_t *target_addr, - int start, - bool emit_code, - bool braced) -{ - int count = 0; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; - - if (braced) { - lex_expect(T_open_curly); - reject_empty_initializer_in_strict_c99(); - } - while (!lex_peek(T_close_curly, NULL)) { - var_t *value = NULL; - var_t *elem_addr; - - if (count >= field->array_dim2) - error_at("Too many elements in array initializer", - next_token_loc()); - - elem_addr = compute_element_address(parent, bb, target_addr, - start + count, elem_size); - if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { - type_t *record_type = field->type; - 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(parent, bb, record_type, elem_addr, - emit_code); - lex_expect(T_close_curly); - } else if (parent == GLOBAL_BLOCK) { - if (emit_code) - value = parse_global_constant_value(parent, bb); - else - consume_global_constant_syntax(); - } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - value = opstack_pop(); - } - - if (value && emit_code) { - 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 == field->array_dim2) { - if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) - error_at("Too many elements in array initializer", - next_token_loc()); - break; - } - if (!lex_accept(T_comma)) - break; - } - if (braced) - lex_expect(T_close_curly); - - if (emit_code) { - 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); - for (; count < field->array_dim2; count++) { - var_t *elem_addr = compute_element_address( - parent, bb, target_addr, start + count, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } -} - -void parse_array_field_row_init(block_t *parent, - basic_block_t **bb, - const var_t *field, - var_t *target_addr, - int start, - bool emit_code) -{ - parse_array_field_row_values(parent, bb, field, target_addr, start, - emit_code, 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 emit_code, - bool braced) -{ - var_t row; - int rows = 0; - int row_width = field->array_dim3; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; - - 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)) { - if (lex_accept(T_open_square)) { - int index = read_const_expr(field->scope ? field->scope : parent); - - lex_expect(T_close_square); - if (index < 0 || index >= field->array_dim2) - error_at("Array designator index is out of bounds", - cur_token_loc()); - rows = index; - lex_expect(T_assign); - } - if (rows >= field->array_dim2) - error_at("Too many rows in array initializer", next_token_loc()); - parse_array_field_row_values(parent, bb, &row, target_addr, - start + rows * row_width, emit_code, - lex_peek(T_open_curly, NULL)); - rows++; - if (rows == field->array_dim2) { - if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) - error_at("Too many rows in array initializer", - next_token_loc()); - break; - } - if (!lex_accept(T_comma)) - break; - } - if (braced) - lex_expect(T_close_curly); - - if (emit_code) { - 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); - for (; rows < field->array_dim2; rows++) { - for (int col = 0; col < row_width; col++) { - var_t *addr = compute_element_address( - parent, bb, target_addr, start + rows * row_width + col, - elem_size); - for (int byte = 0; byte < elem_size; byte++) { - var_t *byte_addr = - compute_element_address(parent, bb, addr, byte, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, - NULL); - } - } - } - } -} - -void parse_array_field_plane_init(block_t *parent, - basic_block_t **bb, - const var_t *field, - var_t *target_addr, - int start, - bool emit_code) -{ - parse_array_field_plane_values(parent, bb, field, target_addr, start, - emit_code, 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, - bool emit_code) -{ - var_t plane; - int planes = 0; - int plane_size = field->array_dim3 * field->array_dim4; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; - - 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)) { - if (lex_accept(T_open_square)) { - int index = read_const_expr(field->scope ? field->scope : parent); - - lex_expect(T_close_square); - if (index < 0 || index >= field->array_dim2) - error_at("Array designator index is out of bounds", - cur_token_loc()); - planes = index; - lex_expect(T_assign); - } - if (planes >= field->array_dim2) - error_at("Too many planes in array initializer", next_token_loc()); - parse_array_field_plane_values(parent, bb, &plane, target_addr, - start + planes * plane_size, emit_code, - lex_peek(T_open_curly, NULL)); - planes++; - if (!lex_accept(T_comma)) - break; - } - lex_expect(T_close_curly); - - if (emit_code) { - 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); - for (; planes < field->array_dim2; planes++) { - for (int element = 0; element < plane_size; element++) { - var_t *addr = compute_element_address( - parent, bb, target_addr, - start + planes * plane_size + element, elem_size); - for (int byte = 0; byte < elem_size; byte++) { - var_t *byte_addr = - compute_element_address(parent, bb, addr, byte, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, - NULL); - } - } - } - } -} - -/* 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. - */ -int read_concatenated_string(char *combined) -{ - char literal[MAX_STRING_LEN]; - int used; - - lex_ident(T_string, literal); - unescape_string(literal, combined, MAX_STRING_LEN); - used = strlen(combined); - while (lex_peek(T_string, NULL)) { - int added; - - lex_ident(T_string, literal); - unescape_string(literal, combined + used, MAX_STRING_LEN - used); - added = strlen(combined + used); - if (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, - bool emit_code) -{ - char combined[MAX_STRING_LEN]; - int len; - - read_concatenated_string(combined); - - len = strlen(combined) + 1; - if (len > field->array_size) - error_at("String initializer is too long for character array", - cur_token_loc()); - if (!emit_code) - return; - - 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, - bool emit_code) -{ - int values[MAX_STRING_LEN]; - int length; - int units; - - length = read_wstring_units(values, MAX_STRING_LEN); - - units = length + 1; - if (units > field->array_size) - error_at("Wide string initializer is too long for array", - cur_token_loc()); - if (!emit_code) - return; - - 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); - } -} - -void parse_array_field_init(block_t *parent, - basic_block_t **bb, - const var_t *field, - var_t *target_addr, - bool emit_code) -{ - int count = 0; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; - - lex_expect(T_open_curly); - reject_empty_initializer_in_strict_c99(); - while (!lex_peek(T_close_curly, NULL)) { - var_t *value = NULL; - - if (count >= field->array_size) - error_at("Too many elements in array initializer", - next_token_loc()); - - var_t *elem_addr = - compute_element_address(parent, bb, target_addr, count, elem_size); - if (field->array_dim4 && lex_peek(T_open_curly, NULL)) { - parse_array_field_hyperplane_init(parent, bb, field, target_addr, - count, emit_code); - count += field->array_dim2 * field->array_dim3 * field->array_dim4; - if (!lex_accept(T_comma)) - break; - continue; - } else if (field->array_dim3 && lex_peek(T_open_curly, NULL)) { - parse_array_field_plane_init(parent, bb, field, target_addr, count, - emit_code); - count += field->array_dim2 * field->array_dim3; - if (!lex_accept(T_comma)) - break; - continue; - } else if (field->array_dim2 && lex_peek(T_open_curly, NULL)) { - parse_array_field_row_init(parent, bb, field, target_addr, count, - emit_code); - count += field->array_dim2; - if (!lex_accept(T_comma)) - break; - continue; - } else if (lex_peek(T_open_curly, NULL) && - is_record_type(field->type)) { - type_t *record_type = field->type; - 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(parent, bb, record_type, elem_addr, - emit_code); - lex_expect(T_close_curly); - } else if (parent == GLOBAL_BLOCK) { - if (emit_code) - value = parse_global_constant_value(parent, bb); - else - consume_global_constant_syntax(); - } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - value = opstack_pop(); - } - - if (value && emit_code) { - 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 (!lex_accept(T_comma)) - break; - } - lex_expect(T_close_curly); - - if (emit_code) { - 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); - - for (; count < field->array_size; count++) { - var_t *elem_addr = compute_element_address(parent, bb, target_addr, - count, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } -} - -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; - int initializer_count = 0; - - 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. - */ - var_t *global_bitfield_unit[MAX_FIELDS]; - unsigned global_bitfield_value[MAX_FIELDS]; - int global_bitfield_count = 0; - - /* 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. - */ - if (emit_code && parent != GLOBAL_BLOCK && - struct_type->base_type != TYPE_union && !struct_type->is_union) { - 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); - - for (int i = 0; i < struct_type->num_fields; i++) { - var_t *field = &struct_type->fields[i]; - var_t *field_addr = - compute_field_address(parent, bb, target_addr, field); - int field_size = size_var(field); - - for (int offset = 0; offset < field_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, field_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - var_t *field_val_raw = NULL; - var_t *field = NULL; - var_t *designated_field_addr = NULL; - bool consumed_pending_array_element = false; - - if (lex_accept(T_dot)) { - char field_name[MAX_ID_LEN]; - type_t *designator_type = struct_type; - var_t *designator_base = target_addr; - bool first = true; - - for (;;) { - bool found = false; - - lex_ident(T_identifier, field_name); - for (int i = 0; i < designator_type->num_fields; i++) { - if (!strcmp(designator_type->fields[i].var_name, - field_name)) { - if (first) - field_idx = i; - field = &designator_type->fields[i]; - found = true; - break; - } - } - if (!found) - error_at("Unknown field in record initializer", - cur_token_loc()); - - designated_field_addr = compute_field_address( - parent, bb, designator_base, field); - if (lex_accept(T_open_square)) { - int index, element_count, linear_index, stride; - int total_element_count; - int elem_size; - var_t *array_base_addr = designated_field_addr; - - if (!field->array_size) - error_at( - "Array designator requires an array member", - cur_token_loc()); - index = read_const_expr(parent); - lex_expect(T_close_square); - total_element_count = field->array_size; - element_count = total_element_count; - stride = 1; - if (field->array_dim2) { - stride = field->array_dim2; - if (field->array_dim3) { - stride *= field->array_dim3; - if (field->array_dim4) - stride *= field->array_dim4; - } - element_count /= stride; - } - if (index < 0 || index >= element_count) - error_at("Array designator index is out of bounds", - cur_token_loc()); - elem_size = (field->ptr_level || field->is_func) - ? PTR_SIZE - : field->type->size; - linear_index = index * stride; - designated_field_addr = compute_element_address( - parent, bb, designated_field_addr, linear_index, - elem_size); - memcpy(&array_element, field, sizeof(var_t)); - if (field->array_dim4) { - array_element.array_size = field->array_dim2 * - field->array_dim3 * - field->array_dim4; - array_element.array_dim2 = field->array_dim3; - array_element.array_dim3 = field->array_dim4; - array_element.array_dim4 = 0; - } else if (field->array_dim3) { - array_element.array_size = - field->array_dim2 * field->array_dim3; - array_element.array_dim2 = field->array_dim3; - array_element.array_dim3 = 0; - } else if (field->array_dim2) { - array_element.array_size = field->array_dim2; - array_element.array_dim2 = 0; - } else { - array_element.array_size = 0; - } - field = &array_element; - - /* Each subscript leaves the remaining inner array in - * `field`, so rows, planes, and a final scalar can all - * share the same bounds and continuation path. - */ - while (field->array_size && lex_accept(T_open_square)) { - index = read_const_expr(parent); - lex_expect(T_close_square); - stride = field->array_dim2 ? field->array_dim2 : 1; - if (field->array_dim3) - stride *= field->array_dim3; - element_count = field->array_size / stride; - if (index < 0 || index >= element_count) - error_at( - "Array designator index is out of bounds", - cur_token_loc()); - designated_field_addr = compute_element_address( - parent, bb, designated_field_addr, - index * stride, elem_size); - linear_index += index * stride; - if (field->array_dim3) { - array_element.array_size = - field->array_dim2 * field->array_dim3; - array_element.array_dim2 = field->array_dim3; - array_element.array_dim3 = 0; - } else if (field->array_dim2) { - array_element.array_size = field->array_dim2; - array_element.array_dim2 = 0; - } else { - array_element.array_size = 0; - } - } - if (!field->array_size) { - memcpy(&pending_array_element, field, - sizeof(var_t)); - pending_array_base = array_base_addr; - pending_array_index = linear_index + 1; - pending_array_count = total_element_count; - pending_array_elem_size = elem_size; - has_pending_array_element = true; - } - } - if (!lex_accept(T_dot)) - break; - if (!is_record_type(field->type)) - error_at("Nested designator requires a record member", - cur_token_loc()); - designator_type = field->type; - if (designator_type->base_type == TYPE_typedef && - designator_type->base_struct) - designator_type = designator_type->base_struct; - designator_base = designated_field_addr; - first = false; - } - lex_expect(T_assign); - } else if (has_pending_array_element) { - field = &pending_array_element; - designated_field_addr = compute_element_address( - parent, bb, pending_array_base, pending_array_index, - pending_array_elem_size); - consumed_pending_array_element = true; - } - - /* An unnamed bit-field is padding, not an aggregate member. It - * participates in layout but consumes no positional initializer; - * otherwise `{ low, high }` would incorrectly assign `high` to the - * padding field and leave the named field zero-initialized. - */ - if (!field && !has_pending_array_element) - while (field_idx < struct_type->num_fields && - is_bitfield(&struct_type->fields[field_idx]) && - !struct_type->fields[field_idx].var_name[0]) - field_idx++; - - if (field_idx >= struct_type->num_fields || - ((struct_type->base_type == TYPE_union || - struct_type->is_union) && - initializer_count > 0 && !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 && !designated_field_addr && !has_pending_array_element && - field->array_size && - (field->ptr_level || !is_record_type(field->type)) && - !lex_peek(T_open_curly, NULL) && !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 = (field->ptr_level || field->is_func) - ? PTR_SIZE - : field->type->size; - 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; - designated_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 (field && field->array_size && !field->ptr_level && - (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 (field && field->array_size && is_char_array(field) && - lex_peek(T_string, NULL)) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - parse_string_field_init(parent, bb, field, field_addr, - emit_code); - } else if (field && field->array_size && !field->ptr_level && - compatible_decl_type(field->type, - find_type("wchar_t", true)) && - lex_peek(T_wstring, NULL)) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - parse_wstring_field_init(parent, bb, field, field_addr, - emit_code); - } else if (field && lex_peek(T_open_curly, NULL) && - field->array_size) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - parse_array_field_init(parent, bb, field, field_addr, - emit_code); - } else if (field && lex_peek(T_open_curly, NULL) && - is_record_type(field->type)) { - type_t *nested_type = field->type; - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - - if (nested_type->base_type == TYPE_typedef && - nested_type->base_struct) - nested_type = nested_type->base_struct; - lex_expect(T_open_curly); - parse_struct_field_init(parent, bb, nested_type, field_addr, - emit_code); - lex_expect(T_close_curly); - } else 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_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, 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 (parent == GLOBAL_BLOCK && 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. - */ - 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 < global_bitfield_count; i++) - if (global_bitfield_unit[i]->offset == - field->offset && - global_bitfield_unit[i]->bit_storage_size == - field->bit_storage_size) { - unit = i; - break; - } - if (unit < 0) { - unit = global_bitfield_count++; - global_bitfield_unit[unit] = field; - global_bitfield_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; - global_bitfield_value[unit] = - (global_bitfield_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++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - - if (emit_code && parent == GLOBAL_BLOCK) { - for (int i = 0; i < global_bitfield_count; i++) { - var_t *unit = global_bitfield_unit[i]; - var_t *unit_addr = - compute_field_address(parent, bb, target_addr, unit); - var_t *value = - bitfield_constant(parent, bb, global_bitfield_value[i]); - add_insn(parent, *bb, OP_write, NULL, unit_addr, value, - unit->bit_storage_size, NULL); - } - } -} - -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)) { - opstack_pop(); - perform_side_effect(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; - } - - /* "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); - - /* 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); - 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. - */ - 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(); - 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(); - 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); - -void parse_array_init(var_t *var, - block_t *parent, - basic_block_t **bb, - bool emit_code) -{ - 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 = var->type->size; - if (var->ptr_level > 0 || var->is_func) - elem_size = PTR_SIZE; - - if (emit_code) - 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 && emit_code && !is_implicit) { - 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); - for (int i = 0; i < var->array_size; i++) { - var_t *elem_addr = - compute_element_address(parent, bb, base_addr, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } - - 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; - int prior_count = count; - - if (lex_accept(T_open_square)) { - int index; - int stride = 1; - int elements; - int remaining_dims = 0; - - index = read_const_expr(initializer_scope); - lex_expect(T_close_square); - if (var->array_dim2) { - stride = var->array_dim2; - remaining_dims = 1; - if (var->array_dim3) { - stride *= var->array_dim3; - remaining_dims = 2; - if (var->array_dim4) { - stride *= var->array_dim4; - remaining_dims = 3; - } - } - } - elements = var->array_size / stride; - if (index < 0 || (!is_implicit && index >= elements)) - error_at("Array designator index is out of bounds", - cur_token_loc()); - count = index * stride; - - /* A complete multidimensional designator names a scalar - * element. Keep the outer-only form on the existing braced - * slice path, but turn every complete path into the row-major - * flat offset used by the ordinary store path. - */ - while (remaining_dims && lex_accept(T_open_square)) { - index = read_const_expr(initializer_scope); - lex_expect(T_close_square); - if (remaining_dims == 3) { - elements = var->array_dim2; - stride = var->array_dim3 * var->array_dim4; - } else if (remaining_dims == 2) { - elements = var->array_dim2; - stride = var->array_dim3; - if (var->array_dim4) { - elements = var->array_dim3; - stride = var->array_dim4; - } - } else { - elements = var->array_dim2; - if (var->array_dim3) - elements = var->array_dim3; - if (var->array_dim4) - elements = var->array_dim4; - stride = 1; - } - if (index < 0 || index >= elements) - error_at("Array designator index is out of bounds", - cur_token_loc()); - count += index * stride; - remaining_dims--; - } - if (remaining_dims == 1 && - (var->array_dim3 || var->array_dim4)) { - /* A path ending one dimension short of a scalar names a - * row. Present it to the braced-row helper rather than - * asking a scalar initializer to consume `{ ... }`. - */ - int row_width = - var->array_dim4 ? var->array_dim4 : var->array_dim3; - memcpy(&designated_array, var, sizeof(designated_array)); - designated_array.array_size = row_width; - designated_array.array_dim2 = row_width; - designated_array.array_dim3 = 0; - designated_array.array_dim4 = 0; - initializer_var = &designated_array; - } - lex_expect(T_assign); - } - - if (!is_implicit && count >= var->array_size) - error_at("Too many elements in array initializer", - next_token_loc()); - - if (initializer_var->array_dim4 && lex_peek(T_open_curly, NULL)) { - parse_array_field_hyperplane_init(parent, bb, initializer_var, - base_addr, count, emit_code); - count += initializer_var->array_dim2 * - initializer_var->array_dim3 * - initializer_var->array_dim4; - 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, emit_code); - count += - initializer_var->array_dim2 * initializer_var->array_dim3; - 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, emit_code); - count += initializer_var->array_dim2; - if (is_implicit && count > inferred_size) - inferred_size = count; - if (!lex_accept(T_comma)) - break; - continue; - } 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; - - 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_ampersand, NULL) || - grouped_global_function_designator_starts_here(true) || - global_function_address_dereference_starts_here())) { - 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); - - if (parent == GLOBAL_BLOCK && - initializer_scope != GLOBAL_BLOCK && - !lex_peek(T_string, NULL) && !function_initializer && - !lex_peek(T_ampersand, NULL)) { - /* 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. - */ - 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 = (object_constant->ptr_level || - object_constant->is_func) - ? PTR_SIZE - : object_constant->type->size; - - /* 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 (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()); - - if (val && var->type->array_element_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 = var->type->array_element_ptr_level; - element_target.pointee_func_signature = - var->type->array_element_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 && emit_code && (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); - - /* A forward designator leaves a gap. Explicit arrays were - * zeroed before parsing; inferred local arrays do not have a - * known bound yet, so zero just the newly skipped elements. - * Global storage begins zeroed. - */ - if (is_implicit && parent != GLOBAL_BLOCK && - count > prior_count) { - 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); - for (int i = prior_count; i < count; i++) { - var_t *gap_addr = compute_element_address( - parent, bb, base_addr, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = compute_element_address( - parent, bb, gap_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, - zero, 1, NULL); - } - } - } - - 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 (is_implicit && count > inferred_size) - inferred_size = count; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - - lex_expect(T_close_curly); - - if (is_implicit) { - 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; - - /* A designated element can leave holes before or after it, so initialize - * the declared object before parsing any explicit elements. - */ - if (declared_size) { - 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); - for (int i = 0; i < declared_size; i++) { - var_t *elem_addr = - compute_element_address(parent, bb, var, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - if (lex_accept(T_open_square)) { - count = read_const_expr(parent); - lex_expect(T_close_square); - 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 && count > inferred_size) { - 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); - for (int i = inferred_size; i < count; i++) { - var_t *gap_addr = - compute_element_address(parent, bb, var, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = compute_element_address( - parent, bb, gap_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, - 1, NULL); - } - } - } - - 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 = var->type; - 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(parent, bb, record_type, elem_addr, - true); - lex_expect(T_close_curly); - } 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 (!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); -} - -/* 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; -} - -/* Consume a nested direct-abstract-declarator in forms such as `int - * (*(*)(int))[2]` and `int (*(*(*)(int))(int))(int)`. The surrounding - * declarator already consumed its leading `(` and pointer chain. Each recursive - * invocation consumes its own parenthesized pointer declarator and function - * suffix, then the enclosing `)` and that declarator's suffix. The complete - * type remains pointer-sized, but its bounds and prototypes must still obey C99 - * syntax and the project's fixed-array-only policy. - */ -int read_sizeof_nested_function_pointer_suffix(block_t *scope, - int *outer_array_size) -{ - int inner_ptr_count = 0; - - lex_expect(T_open_bracket); - while (lex_accept(T_asterisk)) { - inner_ptr_count++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - if (!inner_ptr_count) - error_at("sizeof nested abstract declarator needs a pointer", - cur_token_loc()); - - if (lex_peek(T_open_bracket, NULL)) - inner_ptr_count += - read_sizeof_nested_function_pointer_suffix(scope, outer_array_size); - else { - int direct_array_size = 0; - - while (lex_accept(T_open_square)) { - 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 (bound > 0 && direct_array_size && - direct_array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - direct_array_size = - direct_array_size ? direct_array_size * bound : bound; - } - if (direct_array_size && outer_array_size) - *outer_array_size = direct_array_size; - lex_expect(T_close_bracket); - if (lex_peek(T_open_bracket, NULL)) { - read_sizeof_function_prototype(); - } else if (lex_peek(T_open_square, NULL)) { - do { - int bound; - - lex_expect(T_open_square); - 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()); - } while (lex_peek(T_open_square, NULL)); - } else { - error_at( - "sizeof nested abstract declarator needs a function or " - "array suffix", - cur_token_loc()); - } - } - - lex_expect(T_close_bracket); - while (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - while (lex_accept(T_open_square)) { - 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()); - } - return inner_ptr_count; -} - -/* 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) -{ - char token[MAX_ID_LEN]; - type_t *type = NULL; - int ptr_level = 0; - int array_size = 0; - int array_element_size = 0; - bool is_unsigned = false; - bool is_signed = false; - int long_count = 0; - - lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { - lex_ident(T_identifier, token); - type = find_type(token, 2); - } else if (lex_accept(T_enum)) { - lex_ident(T_identifier, token); - type = find_type_tag(token, scope); - } else { - /* Declaration specifiers may appear in any order: all of "unsigned long - * int", "long unsigned int", and "int unsigned" name the same type. - */ - while (true) { - 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_signed)) { - if (is_signed) - error_at("duplicate signed type specifier", - cur_token_loc()); - is_signed = true; - } else if (lex_accept(T_long)) { - long_count++; - } else if (lex_accept(T_float)) { - type = TY_float; - } else if (lex_accept(T_double)) { - type = TY_double; - } else if (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) { - ; - } else if (lex_peek(T_identifier, token)) { - type_t *candidate = find_visible_type(token, scope); - - if (!candidate) - break; - lex_expect(T_identifier); - type = candidate; - } else { - break; - } - } - if (is_unsigned && is_signed) - error_at("both signed and unsigned specified", cur_token_loc()); - if (long_count > 2) - error_at("too many long type specifiers", cur_token_loc()); - if (type == TY_float && (is_unsigned || is_signed || long_count)) - error_at("invalid float type specifiers", cur_token_loc()); - if (type == TY_double) { - if (is_unsigned || is_signed || long_count > 1) - error_at("invalid double type specifiers", cur_token_loc()); - if (long_count) - type = TY_long_double; - } else if (long_count) { - type = is_unsigned ? TY_ulong : TY_long; - if (long_count == 2) - type = is_unsigned ? TY_ulong_long : TY_long_long; - } else if (is_unsigned) { - if (type == TY_char) - type = TY_uchar; - else if (type == TY_short) - type = TY_ushort; - else - type = TY_uint; - } else if (is_signed) { - type = type == TY_char ? TY_schar : (type ? type : TY_int); - } - } - if (!type) - error_at( - "sizeof in an integer constant expression requires a type name", - cur_token_loc()); - while (lex_accept(T_asterisk)) { - ptr_level++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - while (ptr_level == 0 && lex_accept(T_open_square)) { - 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 (bound > 0 && array_size && array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - array_size = array_size ? array_size * bound : bound; - } - if (lex_accept(T_open_bracket)) { - int nested_array_size = 0; - int nested_ptr_count = 0; - int nested_function_ptr_count = 0; - int nested_outer_array_size = 0; - - while (lex_accept(T_asterisk)) { - nested_ptr_count++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - if (!nested_ptr_count) - error_at("sizeof abstract declarator needs a pointer", - cur_token_loc()); - if (lex_peek(T_open_bracket, NULL)) { - nested_function_ptr_count = - read_sizeof_nested_function_pointer_suffix( - scope, &nested_outer_array_size); - if (nested_outer_array_size) { - array_size = nested_outer_array_size; - array_element_size = PTR_SIZE; - } else - ptr_level += nested_ptr_count + nested_function_ptr_count; - } else - while (lex_accept(T_open_square)) { - 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 (bound > 0 && nested_array_size && - nested_array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - nested_array_size = - nested_array_size ? nested_array_size * bound : bound; - } - if (!nested_function_ptr_count) { - lex_expect(T_close_bracket); - if (nested_array_size) { - array_size = nested_array_size; - array_element_size = PTR_SIZE; - - /* `int (*[2][3])(int)` is an array of function pointers: the - * parenthesized declarator owns the array bounds, while the - * following parameter list belongs to each pointed-to function. - * Consume that suffix before the enclosing sizeof parenthesis. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } else { - ptr_level += nested_ptr_count; - while (lex_accept(T_open_square)) { - 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()); - } - - /* A pointer declarator followed by a parameter list names a - * function pointer. Its function type has no object - * representation, but the pointer itself is an object and - * sizeof is pointer-sized. Reuse the declaration parser for - * complete prototype syntax, then discard the otherwise-unused - * signature. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } - } - } - lex_expect(T_close_bracket); - if (ptr_level) - return PTR_SIZE; - if (type == TY_void) - error_at("sizeof cannot be applied to void", cur_token_loc()); - if (type->is_direct_function_type) - error_at("sizeof(function) is invalid", cur_token_loc()); - if (!type->size && (!type->base_struct || !type->base_struct->size)) - error_at("sizeof cannot be applied to an incomplete type", - cur_token_loc()); - if (!array_size && !ptr_level && type->array_size) - array_size = type->array_size; - if (array_size) - return array_size * - (array_element_size ? array_element_size : type->size); - if (type->size) - return type->size; - return type->base_struct->size; -} - -/* 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()); - } -} - -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)) { - if (lex_peek(T_wstring, NULL)) - return read_const_wstring_size(); - return read_const_sizeof_type(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); - if (lex_accept(T_struct) || lex_accept(T_union)) { - lex_ident(T_identifier, type_name); - record = find_type(type_name, 2); - } 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; -} - -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; - - 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]; -} - -void read_inner_var_decl(var_t *vd, - bool anon, - bool is_param, - bool is_record_member) -{ - /* 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; - } - - 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); - } - - /* is it function pointer declaration? */ - if (lex_accept(T_open_bracket)) { - func_t *func = arena_alloc_func(); - char temp_name[MAX_VAR_LEN]; - int nested_ptr_level = 0; - - do { - lex_expect(T_asterisk); - nested_ptr_level++; - while (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; - - 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; - 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; - } - - /* 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); - 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. - */ - if (vd->parenthesized_function_pointer_inner_qualified) - error_at( - "inner callback-slot pointer qualifiers are not yet " - "supported", - cur_token_loc()); - if (vd->parenthesized_function_pointer_restrict && - !vd->parenthesized_function_pointer_outer_restrict) - error_at("callback-slot restrict typedef is not yet supported", - cur_token_loc()); - - /* The second star is the outer slot pointer. After collapsing the - * compact callback representation to one retained pointer level, - * move that star's const bit from level two to level one. - */ - if (vd->parenthesized_function_pointer_outer_const) { - vd->pointer_const_mask &= ~2U; - vd->pointer_const_mask |= 1U; - vd->is_const_pointer = true; - } - vd->ptr_level += nested_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 ((!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 (!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; - } - - /* 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 (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()); - - /* 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 || 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()); -} - -/* 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_signed = false; - bool is_unsigned = false; - bool is_const = false; - bool is_inline = false; - bool is_volatile = false; - bool is_long = false; - bool is_long_long = false; - type_t *leading_scalar_type = NULL; - - /* The declaration dispatcher normally leaves the base type for this routine - * to consume. C99 also permits the modifier after that base, e.g. `short - * unsigned value`; retain the base while consuming its following scalar - * modifiers instead of mistaking `unsigned` for the declarator name. - */ - if (lex_peek(T_identifier, type_name) && - (!strcmp(type_name, "char") || !strcmp(type_name, "short") || - !strcmp(type_name, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && - (after_base->kind == T_signed || after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(type_name, true); - } - } - - /* C permits these declaration specifiers in either order. Consume the - * scalar set as a group so `const unsigned int` and `unsigned const int` - * follow the same path. - */ - while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_const, NULL) || lex_peek(T_inline, NULL) || - lex_peek(T_volatile, NULL) || lex_peek(T_long, NULL)) { - 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_const)) - is_const = true; - else if (lex_accept(T_volatile)) - is_volatile = true; - else if (lex_accept(T_inline)) { - if (is_inline || declaration_inline) - error_at("duplicate inline function specifier", - cur_token_loc()); - is_inline = true; - } else { - lex_expect(T_long); - if (is_long_long) - error_at("too many long type specifiers", cur_token_loc()); - if (is_long) - is_long_long = true; - else - is_long = true; - } - } - if (is_signed && is_unsigned) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && lex_peek(T_identifier, type_name) && - !strcmp(type_name, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - bool is_enum_type = lex_accept(T_enum); - if (is_enum_type && (is_signed || is_unsigned || is_long)) - error_at("enum type cannot be combined with integer specifiers", - cur_token_loc()); - bool is_record_type = lex_peek(T_struct, NULL) || lex_peek(T_union, NULL); - bool is_union_type = lex_accept(T_union); - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (is_union_type) - find_type_flag = 2; - type_t *type; - - /* `signed` has the existing signed scalar semantics. C permits its `int` - * spelling to be omitted, while `signed char` and `signed short` retain - * their explicit base type. - */ - if (parsing_sizeof_function_signature && lex_accept(T_float)) { - if (is_signed || is_unsigned || is_long) - error_at("invalid float type specifiers", cur_token_loc()); - type = TY_float; - } else if (parsing_sizeof_function_signature && lex_accept(T_double)) { - if (is_signed || is_unsigned || is_long_long) - error_at("invalid double type specifiers", cur_token_loc()); - type = is_long ? TY_long_double : TY_double; - } else if (is_enum_type) { - lex_ident(T_identifier, type_name); - type = find_type_tag(type_name, vd->scope); - } else if (is_unsigned) { - if (is_long) { - if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = is_long_long ? TY_ulong_long : TY_ulong; - } else if (leading_scalar_type == TY_char) { - type = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = TY_ushort; - } else if (lex_peek(T_identifier, type_name) && - (!strcmp(type_name, "char") || !strcmp(type_name, "short") || - !strcmp(type_name, "int"))) { - lex_expect(T_identifier); - if (!strcmp(type_name, "char")) - type = TY_uchar; - else if (!strcmp(type_name, "short")) - type = TY_ushort; - else - type = TY_uint; - } else - type = TY_uint; /* `unsigned` is `unsigned int` */ - } else if (is_long) { - /* The current ABI gives long the same 32-bit representation as int, - * while retaining its distinct C type and rank. - */ - if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = is_long_long ? TY_long_long : TY_long; - } else if (is_signed && leading_scalar_type && - (leading_scalar_type == TY_char || - leading_scalar_type == TY_short)) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; - } else if (is_signed && lex_peek(T_identifier, type_name) && - (!strcmp(type_name, "char") || !strcmp(type_name, "short") || - !strcmp(type_name, "int"))) { - lex_expect(T_identifier); - type = !strcmp(type_name, "char") - ? TY_schar - : (!strcmp(type_name, "short") ? TY_short : TY_int); - } else if (is_signed && find_type_flag == 1 && - (!lex_peek(T_identifier, type_name) || - (strcmp(type_name, "int") && strcmp(type_name, "char") && - strcmp(type_name, "short")))) { - type = TY_int; - } else if (leading_scalar_type) { - type = leading_scalar_type; - } else { - lex_ident(T_identifier, type_name); - type = find_type_flag == 2 ? find_record_tag(type_name, vd->scope) - : find_visible_type(type_name, vd->scope); - if (!type && find_type_flag == 2) - type = find_type(type_name, 2); - if (find_type_flag == 2 && - (!type || !is_record_type || - (is_union_type ? type->base_type != TYPE_union - : type->base_type != TYPE_struct))) - error_at("Unknown struct/union type", cur_token_loc()); - } - - if (!type) { - char message[MAX_LINE_LEN]; - - snprintf(message, MAX_LINE_LEN, "Could not find type %s%s", - find_type_flag == 2 ? "struct/union " : "", 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 || declaration_inline) - error_at("duplicate inline function specifier", - cur_token_loc()); - is_inline = true; - } else - break; - } - vd->is_inline = declaration_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()); - - /* A 32-bit target can carry a pointer to an eight-byte object without a - * paired-value register representation. Keep direct wide objects, arrays, - * parameters, returns, and function-pointer returns rejected until that - * lowering exists, but admit declarations such as `long long *p` and - * typedef aliases used exclusively behind a pointer. - */ - if (PTR_SIZE < 8 && vd->type && vd->type->base_type == TYPE_long_long && - (!(vd->ptr_level || vd->type->ptr_level) || vd->is_func)) - error_at("long long value needs 64-bit target lowering", - 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); -} - -void read_parameter_list_decl(func_t *func, bool anon) -{ - int vn = 0; - 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; - if (lex_peek(T_identifier, token) && !strncmp(token, "void", 4)) { - 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++; - if (lex_accept(T_comma) && strict_c99 && - lex_peek(T_close_bracket, NULL)) - error_at("trailing comma in parameter list is not permitted in C99", - cur_token_loc()); - } - - if (floating_type_starts_here() && !parsing_sizeof_function_signature) - error_at("Floating point types are not yet supported", cur_token_loc()); - - while ((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; - 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); - 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++; - if (lex_accept(T_comma) && strict_c99 && - lex_peek(T_close_bracket, NULL)) - error_at("trailing comma in parameter list is not permitted in C99", - cur_token_loc()); - } - func->num_params = vn; - - /* Up to 'MAX_PARAMS' parameters are accepted for the variadic function. */ - if (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]; - - read_concatenated_string(combined); - - 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; - 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; - - read_concatenated_string(combined); - - len = strlen(combined) + 1; - if (var->has_unsized_array) { - var->array_size = len; - var->has_unsized_array = false; - } else if (len > var->array_size) - error_at("String initializer is too long for character array", - cur_token_loc()); - - for (int i = 0; i < len; i++) { - var_t *value = require_var(parent); - var_t *addr; - - value->var_name = gen_name(); - value->init_val = (unsigned char) combined[i]; - 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); - - units = length + 1; - if (var->has_unsized_array) { - var->array_size = units; - var->has_unsized_array = false; - } else if (units > 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; - if (PTR_SIZE < 8) - error_at("long long literal needs 64-bit target lowering", - cur_token_loc()); - 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 (PTR_SIZE < 8) - error_at("long long literal needs 64-bit target lowering", - cur_token_loc()); - 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); - } - 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); -} - -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); - if (is_record_type(target->type) && - !has_effective_pointer(target)) { - /* 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 = 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; - if (lex_accept(T_comma) && strict_c99 && - lex_peek(T_close_bracket, NULL)) - error_at("trailing comma in function call is not permitted in C99", - 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_parameters(func_t *func, block_t *parent, basic_block_t **bb) -{ - read_func_parameters_with_sret(func, NULL, parent, bb); -} - -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); - result->pointee_array_element_ptr_level = - return_def->pointee_array_element_ptr_level - ? return_def->pointee_array_element_ptr_level - : return_def->type->array_element_ptr_level; - result->is_const_qualified = - return_def->is_const_qualified || return_def->type->is_const_qualified; -} - -void lower_call_result_array_postfix(var_t **value, - block_t *parent, - basic_block_t **bb) -{ - var_t *base; - var_t *address; - fixed_array_shape_t shape; - int dimensions; - 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); - - /* 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 = - base->pointee_array_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, base->type); - opcode_t compound_op = OP_generic; - bool assignment; - - element->ptr_level = base->pointee_array_element_ptr_level; - element->func_signature = base->type->func_signature; - element->is_const_qualified = - base->is_const_qualified || base->type->is_const_qualified; - element->var_name = gen_name(); - 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 (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 (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 { - *value = address; - } - 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) -{ - var_t *object; - var_t *one; - var_t *updated; - - if (op == OP_generic) - return; - object = opstack_pop(); - if (!object->is_compound_literal_reference) - 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); - 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); - add_insn(parent, *bb, OP_write, NULL, object->compound_literal_address, - updated, object->ptr_level ? PTR_SIZE : object->type->size, NULL); - *value = updated; - opstack_push(updated); -} - -/* 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); - if (lex_accept(T_struct) || lex_accept(T_union)) { - lex_ident(T_identifier, name); - record = find_type(name, 2); - } 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) -{ - char name[MAX_ID_LEN]; - type_t *type; - bool is_signed = false; - bool is_unsigned = false; - int long_count = 0; - bool is_short = false; - bool is_char = false; - bool saw_base = false; - - (void) parent; - 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; - while (true) { - if (lex_accept(T_const) || lex_accept(T_volatile)) - continue; - if (lex_accept(T_signed)) { - if (is_signed) - error_at("duplicate signed type specifier", cur_token_loc()); - is_signed = true; - continue; - } - if (lex_accept(T_unsigned)) { - if (is_unsigned) - error_at("duplicate unsigned type specifier", cur_token_loc()); - is_unsigned = true; - continue; - } - if (lex_accept(T_long)) { - long_count++; - continue; - } - if (lex_peek(T_identifier, name) && !strcmp(name, "short")) { - if (is_short) - error_at("duplicate short type specifier", cur_token_loc()); - lex_expect(T_identifier); - is_short = true; - continue; - } - if (lex_peek(T_identifier, name) && - (!strcmp(name, "int") || !strcmp(name, "char"))) { - if (saw_base) - error_at("duplicate type specifier", cur_token_loc()); - is_char = !strcmp(name, "char"); - saw_base = true; - lex_expect(T_identifier); - continue; - } - break; - } - if (is_signed && is_unsigned) - error_at("both signed and unsigned specified", cur_token_loc()); - if (long_count > 2 || (is_short && long_count)) - error_at("invalid va_arg integer type", cur_token_loc()); - - if (lex_accept(T_struct) || lex_accept(T_union)) { - if (is_signed || is_unsigned || long_count || is_short) - error_at("record type cannot have integer specifiers", - cur_token_loc()); - lex_ident(T_identifier, name); - type = find_type(name, 2); - } else if (lex_accept(T_enum)) { - if (is_signed || is_unsigned || long_count || is_short || saw_base) - error_at("enum type cannot have integer specifiers", - cur_token_loc()); - lex_ident(T_identifier, name); - type = find_type_tag(name, parent); - } else { - if (!saw_base && !is_signed && !is_unsigned && !long_count && - !is_short) { - lex_ident(T_identifier, name); - type = find_type(name, true); - goto parsed_base_type; - } - - if (long_count) - type = is_unsigned ? (long_count == 2 ? TY_ulong_long : TY_ulong) - : (long_count == 2 ? TY_long_long : TY_long); - else if (is_short) - type = is_unsigned ? TY_ushort : TY_short; - else if (is_char) - type = is_unsigned ? TY_uchar : (is_signed ? TY_schar : TY_char); - else - type = is_unsigned ? TY_uint : TY_int; - } -parsed_base_type: - 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); - } - } - result->type = type; -} - -void emit_record_copy_from_address(block_t *parent, - basic_block_t **bb, - var_t *dest, - var_t *src_addr) -{ - int size = size_var(dest); - 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); - for (int offset = 0; offset < size;) { - int width = size - offset >= 4 ? 4 : size - offset >= 2 ? 2 : 1; - 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; - } -} - -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(); - - /* 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; - 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(); - - 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, requested.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, requested.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; - target->func_target = object->func_target; - target->func_target_invalid = object->func_target_invalid; - 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; - - if (!value || !value->is_func) - 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; - object->func_target = 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; -} - -/* 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 = 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; - func_t *target = callee ? callee->func_target : NULL; - - if (callee->func_target_invalid || !target || !target->bbs || - !target->returns_aggregate) - error_at( - "aggregate-return indirect call requires a shecc-defined " - "target", - cur_token_loc()); - 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 = 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; -} - -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, - 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. - */ -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()); - 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; - } - var_t *var = find_var(token, parent); - 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, true); - - if (is_bitfield(lvalue.decl)) - error_at("cannot take address of bit-field", next_token_loc()); - - 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; - opstack_push(vd); - if (lvalue.decl && is_array_declarator(lvalue.decl)) { - /* 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. - */ - add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); - } else - add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); - } -} - -/* A scalar pointer-to-array dereference designates the addressed row; it does - * not load a scalar object. Retain that row as an array descriptor so a - * following grouped postfix subscript can use the ordinary array path. - */ -static bool lower_scalar_pointee_array_dereference(var_t *source, - block_t *parent, - basic_block_t **bb) -{ - type_t *element_type; - var_t *row; - - if (!source || !source->type || !source->pointee_array_size || - source->pointee_array_element_ptr_level || - effective_pointer_depth(source) != 1) - return false; - - element_type = source->type->pointee_array_element_type - ? source->type->pointee_array_element_type - : source->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->is_const_qualified = - source->is_const_qualified || element_type->is_const_qualified; - row->var_name = gen_name(); - add_insn(parent, *bb, OP_assign, row, source, NULL, 0, NULL); - opstack_push(row); - return true; -} - -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; -} - -static bool is_scalar_pointee_array_pointer(const var_t *var) -{ - return var && var->type && var->pointee_array_size > 0 && - !var->pointee_array_element_ptr_level && - effective_pointer_depth(var) == 1; -} - -static int scalar_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; - - 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 scalar_pointee_array_shapes_compatible(const var_t *left, - const var_t *right) -{ - type_t *left_element; - type_t *right_element; - - if (!is_scalar_pointee_array_pointer(left) || - !is_scalar_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); -} - -void handle_single_dereference(block_t *parent, basic_block_t **bb) -{ - 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(); - - 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_scalar_pointee_array_dereference(rs1, parent, bb)) - 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); - if (deref_ptr > 0) - sz = PTR_SIZE; - else - sz = 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))); - - /* 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 (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { - /* A prefix update is itself a unary expression, so `*++pointer` and - * `*--pointer` dereference its updated pointer result just like - * `*(pointer + 1)` dereferences a parenthesized operand. - */ - read_expr_operand(parent, bb); - rs1 = opstack_pop(); - 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))); - if (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - 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()); - - /* 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))); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - 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, false); - - rs1 = opstack_pop(); - if (var->is_func) { - /* C99 6.3.2.1 makes a function-pointer dereference a function - * designator. The pointer object itself therefore needs one load, - * not a dereference of its return type. - */ - opstack_push(load_function_pointer_object(parent, bb, rs1)); - return; - } - if (lower_scalar_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->func_signature || rs1->pointee_func_signature || - (deref_type && deref_type->func_signature)) && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = rs1->func_signature - ? rs1->func_signature - : (rs1->pointee_func_signature - ? rs1->pointee_func_signature - : deref_type->func_signature); - vd->ptr_level = 1; - sz = PTR_SIZE; - } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } -} - -/* 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. - */ -bool stmt_starts_assignment(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: - 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; -} - -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)) { - /* 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(); - 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))); - - /* A parenthesized pointer-arithmetic result can designate a - * callback object just like `*slot`. Restore its prototype only - * after consuming exactly that final object-pointer indirection. - */ - if (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - 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(); - - /* 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 final dereference of `**slots` (or deeper spelling) - * reaches the callback object. Earlier reads still produce a - * pointer-to-callback and must not be callable. - */ - if (i + 1 == deref_count && - (rs1->func_signature || - (deref_type && deref_type->func_signature)) && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = rs1->func_signature - ? rs1->func_signature - : deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } - } -} - -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 { - lex_ident(T_string, literal); - unescape_string(literal, unescaped, MAX_STRING_LEN); - size += strlen(unescaped); - } while (lex_peek(T_string, NULL)); - - return size; -} - -int read_sizeof_wstring_literal(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; -} - -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 members; /* record member selections */ - int subscripts; /* array subscripts */ - bool element_leaf; /* the last subscript selected a non-array element */ - bool addressed; /* the operand so far is "&array" */ -} sizeof_walk_t; - -/* 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. - */ -static bool scan_sizeof_postfix_operand(block_t *scope, - token_t **cursor, - var_t *object, - sizeof_walk_t *walk) -{ - token_t *token = *cursor; - - if (!token) - return false; - if (token->kind == T_asterisk) { - bool addressed; - int depth; - - token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) - return false; - addressed = walk->addressed; - depth = effective_pointer_depth(object); - if (depth <= 0) - return false; - if (object->pointee_array_size > 0 && - depth == object->pointee_array_element_ptr_level + 1) { - /* A pointer to an array designates the complete array, so the - * dereference restores its bounds rather than decaying them. - */ - 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; - } - walk->addressed = false; - walk->element_leaf = false; - } else if (token->kind == T_ampersand) { - token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) - return 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++; - walk->element_leaf = false; - } else if (token->kind == T_open_bracket) { - token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, walk) || - !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); - - if (!root) - return false; - memcpy(object, root, sizeof(*object)); - token = token->next; - } else - return false; - - while (token && (token->kind == T_dot || token->kind == T_arrow || - token->kind == T_open_square)) { - walk->addressed = false; - if (token->kind == T_dot || token->kind == T_arrow) { - bool through_pointer = token->kind == T_arrow; - type_t *record; - var_t *field; - - token = token->next; - if (!token || token->kind != T_identifier || - (through_pointer && effective_pointer_depth(object) != 1) || - (!through_pointer && effective_pointer_depth(object) != 0)) - return false; - record = through_pointer - ? pointee_type_from_pointer_typedef(object->type) - : object->type; - if (!is_record_type(record)) - return false; - field = find_member(token->literal, record); - if (!field) - return false; - memcpy(object, field, sizeof(*object)); - walk->members++; - walk->element_leaf = false; - token = token->next; - } else { - int depth = 0; - fixed_array_shape_t shape; - - /* An array may legitimately have pointer elements. Reject a scalar - * pointer here, but retain the element pointer depth until after - * this array subscript so a following `->` can consume it. - */ - if (object->array_size <= 0) - return false; - 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; - shape = fixed_array_shape_from_var(object); - if (!fixed_array_shape_drop_outer(&shape)) - return false; - fixed_array_shape_to_var(object, &shape); - walk->subscripts++; - walk->element_leaf = !object->array_size; - } - } - *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_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; - int saved_side_effects; - - lex_expect(T_open_square); - unevaluated_bb = bb_create(parent); - saved_side_effects = se_idx; - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); - opstack_pop(); - se_idx = saved_side_effects; - lex_expect(T_close_square); - } - } -} - -/* The operand must end where sizeof's operand ends: at the closing parenthesis - * of a parenthesized one, 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) -{ - return tail && (parenthesized ? tail->kind == T_close_bracket - : tail->kind != T_open_bracket && - tail->kind != T_increment && - tail->kind != T_decrement); -} - -/* A non-mutating admission check for callers that need to hand a global sizeof - * operand to the detached local parser. It admits only a member array, so type - * names and scalar forms remain owned by their existing global constant paths. - */ -static bool can_scan_sizeof_postfix_extent(block_t *scope, - token_t *start, - bool parenthesized, - bool allow_flexible) -{ - token_t *tail = start; - var_t object; - sizeof_walk_t walk = {0}; - - return scan_sizeof_postfix_operand(scope, &tail, &object, &walk) && - walk.members && sizeof_operand_tail_ends(tail, parenthesized) && - !effective_pointer_depth(&object) && - (object.array_size > 0 || - (allow_flexible && object.is_flexible_array_member)); -} - -/* Walk a local sizeof operand from @start and say whether this walker owns it: - * an operand that still designates an array, whatever the path to it, or a - * single element selected from one. Anything else keeps the ordinary - * unevaluated-expression path. - */ -static bool walk_local_sizeof_operand(block_t *parent, - token_t *start, - bool parenthesized, - var_t *object) -{ - token_t *tail = start; - sizeof_walk_t walk = {0}; - - if (!scan_sizeof_postfix_operand(parent, &tail, object, &walk) || - !sizeof_operand_tail_ends(tail, parenthesized)) - return false; - if (object->is_flexible_array_member) - return walk.members > 0; - return object->array_size > 0 || walk.element_leaf; -} - -/* Every sizeof operand made of an identifier, grouping, dereference, address, - * 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; - var_t *result; - int size; - - if (!walk_local_sizeof_operand(parent, cur_token->next, parenthesized, - &object)) { - /* 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)) - return false; - open_consumed = true; - } - if (object.is_flexible_array_member) - error_at("sizeof cannot be applied to a flexible array member", - 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 - size = object.type->size; - result = require_var(parent); - result->init_val = size; - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); - 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; - int array_element_size = 0; - token_t *sizeof_tk = cur_token; - type_t *type = NULL; - bool is_function = false; - var_t *vd; - - 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 is the - * complete operand. - */ - token_t *after_string = cur_token->next; - while (after_string && after_string->kind == T_string) - after_string = after_string->next; - if (lex_peek(T_string, NULL) && - (!parenthesized || - (after_string && after_string->kind == T_close_bracket))) { - vd = require_var(parent); - vd->init_val = read_sizeof_string_literal(); - vd->var_name = gen_name(); - if (parenthesized) - lex_expect(T_close_bracket); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - return; - } - - token_t *after_wstring = cur_token->next; - while (after_wstring && after_wstring->kind == T_wstring) - after_wstring = after_wstring->next; - if (lex_peek(T_wstring, NULL) && - (!parenthesized || - (after_wstring && after_wstring->kind == T_close_bracket))) { - vd = require_var(parent); - vd->init_val = read_sizeof_wstring_literal(); - vd->var_name = gen_name(); - if (parenthesized) - lex_expect(T_close_bracket); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - 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); - vd = require_var(parent); - vd->init_val = strlen(parent->func->return_def.var_name) + 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - return; - } - - if (lex_peek(T_identifier, token)) { - var_t *array = find_var(token, parent); - - if (array && array->array_size > 0) { - lex_expect(T_identifier); - vd = require_var(parent); - vd->init_val = sizeof_array_object(array); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); - return; - } - } - - basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; - read_expr_operand(parent, &unevaluated_bb); - se_idx = saved_side_effects; - 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); - - vd = require_var(parent); - vd->init_val = type->size; - if (array_size > 0) - vd->init_val = array_size * type->size; - if (ptr_cnt) - vd->init_val = PTR_SIZE; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - 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); - vd = require_var(parent); - vd->init_val = strlen(parent->func->return_def.var_name) + 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - 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); - vd = require_var(parent); - vd->init_val = sizeof_array_object(array); - 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); - return; - } - } - - /* Check if this is sizeof(type) or sizeof(expression) */ - bool has_signed_type = false; - bool has_unsigned_type = false; - int long_type_count = 0; - type_t *leading_scalar_type = NULL; - - if (lex_peek(T_identifier, token) && - (!strcmp(token, "char") || !strcmp(token, "short") || - !strcmp(token, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && - (after_base->kind == T_signed || after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(token, true); - } - } - while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_long, NULL) || lex_peek(T_const, NULL) || - lex_peek(T_volatile, NULL)) { - if (lex_accept(T_signed)) { - if (has_signed_type) - error_at("duplicate signed type specifier", cur_token_loc()); - has_signed_type = true; - } else if (lex_accept(T_unsigned)) { - if (has_unsigned_type) - error_at("duplicate unsigned type specifier", cur_token_loc()); - has_unsigned_type = true; - } else if (lex_accept(T_long)) - long_type_count++; - else if (lex_accept(T_const)) - ; - else - lex_expect(T_volatile); - } - if (long_type_count > 2) - error_at("too many long type specifiers", cur_token_loc()); - if (has_signed_type && has_unsigned_type) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && lex_peek(T_identifier, token) && - !strcmp(token, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - bool has_long_type = long_type_count > 0; - bool has_enum_type = lex_accept(T_enum); - if (has_enum_type && - (has_signed_type || has_unsigned_type || has_long_type)) - error_at("enum type cannot be combined with integer specifiers", - cur_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; - - /* `sizeof` only consumes object representation metadata, so it can admit - * the C99 floating type names before value arithmetic and ABI lowering - * exist. Keep all expression/declaration admission gates intact. - */ - if (lex_accept(T_float)) { - if (has_signed_type || has_unsigned_type || has_long_type || - has_enum_type || find_type_flag != 1) - error_at("invalid float type specifiers", cur_token_loc()); - type = TY_float; - } else if (lex_accept(T_double)) { - if (has_signed_type || has_unsigned_type || has_enum_type || - find_type_flag != 1 || long_type_count > 1) - error_at("invalid double type specifiers", cur_token_loc()); - type = has_long_type ? TY_long_double : TY_double; - } else if (has_enum_type) { - lex_ident(T_identifier, token); - type = find_type_tag(token, parent); - if (!type) - error_at("Unknown enum type", cur_token_loc()); - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - } else if (has_long_type) { - type = has_unsigned_type ? TY_ulong : TY_long; - if (long_type_count > 1) { - /* sizeof only consumes type metadata. It does not materialize a - * long-long value, so 32-bit targets can correctly report the - * required eight-byte object size before their paired-register - * value ABI is implemented. - */ - if (has_unsigned_type) - type = TY_ulong_long; - else - type = TY_long_long; - } - lex_accept(T_signed); - lex_accept(T_const); - if (lex_peek(T_identifier, token) && !strcmp(token, "int")) - lex_expect(T_identifier); - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - } else if (has_unsigned_type) { - if (leading_scalar_type == TY_char) { - type = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, token) && !strcmp(token, "int")) - lex_expect(T_identifier); - type = TY_ushort; - } else if (lex_peek(T_identifier, token) && - (!strcmp(token, "int") || !strcmp(token, "char") || - !strcmp(token, "short"))) { - lex_expect(T_identifier); - if (!strcmp(token, "char")) - type = TY_uchar; - else if (!strcmp(token, "short")) - type = TY_ushort; - else - type = TY_uint; - } else - type = TY_uint; - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - } else if (has_signed_type) { - if (leading_scalar_type == TY_char || leading_scalar_type == TY_short) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, token) && !strcmp(token, "int")) - lex_expect(T_identifier); - type = - leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; - } else if (lex_peek(T_identifier, token) && - (!strcmp(token, "int") || !strcmp(token, "char") || - !strcmp(token, "short"))) { - lex_expect(T_identifier); - type = !strcmp(token, "char") ? TY_schar : find_type(token, true); - } else - type = TY_int; - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - } else if (leading_scalar_type) { - type = leading_scalar_type; - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - } else if (lex_peek(T_identifier, token)) { - /* Try to parse as a type first */ - type = find_type_flag == 2 ? find_type(token, find_type_flag) - : find_visible_type(token, parent); - if (type) { - /* sizeof(type) */ - lex_expect(T_identifier); - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - } - } - - /* The integer branches above consume their own abstract pointer declarators - * for historical reasons. Float/double type names select a type before that - * legacy code, so finish the common suffix here. - */ - while (type && lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - - /* VLA support is intentionally outside this compiler's C99 scope, but a - * type name in sizeof may still carry fixed abstract array bounds. Keep the - * simple direct form here rather than treating '[' as an expression token - * after the scalar type name. - */ - while (type && ptr_cnt == 0 && lex_accept(T_open_square)) { - int bound = read_const_expr(parent); - - lex_expect(T_close_square); - if (bound <= 0) - error_at("sizeof array type needs a positive constant bound", - cur_token_loc()); - if (bound > 0 && array_size && array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - array_size = array_size ? array_size * bound : bound; - } - - if (type && lex_accept(T_open_bracket)) { - int nested_array_size = 0; - int nested_ptr_count = 0; - int nested_function_ptr_count = 0; - int nested_outer_array_size = 0; - - while (lex_accept(T_asterisk)) { - nested_ptr_count++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - if (!nested_ptr_count) - error_at("sizeof abstract declarator needs a pointer", - cur_token_loc()); - if (lex_peek(T_open_bracket, NULL)) { - nested_function_ptr_count = - read_sizeof_nested_function_pointer_suffix( - parent, &nested_outer_array_size); - if (nested_outer_array_size) { - array_size = nested_outer_array_size; - array_element_size = PTR_SIZE; - } else - ptr_cnt += nested_ptr_count + nested_function_ptr_count; - } else - while (lex_accept(T_open_square)) { - int bound = read_const_expr(parent); - - lex_expect(T_close_square); - if (bound <= 0) - error_at( - "sizeof array type needs a positive constant bound", - cur_token_loc()); - if (bound > 0 && nested_array_size && - nested_array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - nested_array_size = - nested_array_size ? nested_array_size * bound : bound; - } - if (!nested_function_ptr_count) { - lex_expect(T_close_bracket); - if (nested_array_size) { - array_size = nested_array_size; - array_element_size = PTR_SIZE; - - /* The grouped array declarator in `int (*[2][3])(int)` leaves - * its pointed-to function suffix outside the group. sizeof only - * needs pointer-sized elements, but still must parse that valid - * C99 prototype completely. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } else { - ptr_cnt += nested_ptr_count; - while (lex_accept(T_open_square)) { - int bound = read_const_expr(parent); - - lex_expect(T_close_square); - if (bound <= 0) - error_at( - "sizeof array type needs a positive constant bound", - cur_token_loc()); - } - - /* A pointer declarator followed by a parameter list names a - * function pointer. sizeof observes the pointer object, not the - * function type, so no expression lowering or function ABI is - * needed. Reuse the declaration parser for prototype syntax. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } - } - } - - if (!type) { - /* sizeof(expression) - parse the expression and get its type */ - basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; - unevaluated_expression_depth++; - if (!read_assignment_expression(parent, &unevaluated_bb)) { - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); - } - unevaluated_expression_depth--; - se_idx = saved_side_effects; - 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; - vd = require_var(parent); - vd->init_val = type->size; - if (array_size > 0) - vd->init_val = - array_size * (array_element_size ? array_element_size : type->size); - if (ptr_cnt) - vd->init_val = PTR_SIZE; - - /* VLA is intentionally outside shecc's C99 scope, so every admitted sizeof - * result is an integer constant expression. - */ - vd->is_const = true; - 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); -} - -/* 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(void) -{ - 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 || !(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_scalar_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(void) -{ - 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 || - !(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_scalar_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); -} - -void read_expr_operand(block_t *parent, basic_block_t **bb) -{ - var_t *vd, *rs1; - var_t *compound_object = NULL; - bool is_neg = false; - - if (grouped_scalar_pointee_row_postfix_starts()) { - 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()) { - 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) { - 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) { - 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); - if (object->compound_literal_bitfield) - write_bitfield_value(parent, bb, - object->compound_literal_address, vd, - object->compound_literal_bitfield); - else - add_insn(parent, *bb, OP_write, NULL, - object->compound_literal_address, vd, - object->ptr_level ? PTR_SIZE : object->type->size, - NULL); - opstack_push(vd); - return; - } - if (!object->is_compound_literal || object->array_size || - is_record_type(object->type)) - 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); - 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(); - 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)) { - 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()); - } - } - - if (lex_peek(T_string, NULL)) - read_literal_param(parent, *bb); - else if (lex_peek(T_wstring, NULL)) - read_wstring_param(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)) - read_numeric_param(parent, *bb, is_neg); - else if (lex_accept(T_log_not)) { - read_expr_operand(parent, bb); - - rs1 = opstack_pop(); - 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(); - 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; - 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)) { - /* 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; - int cast_array_size = 0; - int cast_array_dim2 = 0; - int cast_array_dim3 = 0; - int cast_array_dim4 = 0; - int cast_array_dims = 0; - int cast_array_element_ptr_level = 0; - int cast_pointee_array_size = 0; - int cast_pointee_array_dim2 = 0; - int cast_pointee_array_dim3 = 0; - int cast_pointee_array_dim4 = 0; - bool cast_array_outer_unsized = false; - bool cast_parenthesized_array = false; - bool cast_const_qualified = false; - bool cast_const_pointer = false; - unsigned int cast_pointer_const_mask = 0; - bool cast_volatile_qualified = false; - - 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 typename followed by ) */ - token_t *type_start = cur_token; - bool has_const_type = false; - bool has_signed_type = false; - bool has_unsigned_type = false; - int long_type_count = 0; - type_t *leading_scalar_type = NULL; - - if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short") || - !strcmp(lookahead_token, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && (after_base->kind == T_signed || - after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(lookahead_token, true); - } - } - while (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)) { - if (lex_accept(T_const)) - has_const_type = true; - else if (lex_accept(T_volatile)) - cast_volatile_qualified = true; - else if (lex_accept(T_signed)) { - if (has_signed_type) - error_at("duplicate signed type specifier", - cur_token_loc()); - has_signed_type = true; - } else if (lex_accept(T_unsigned)) { - if (has_unsigned_type) - error_at("duplicate unsigned type specifier", - cur_token_loc()); - has_unsigned_type = true; - } else { - lex_expect(T_long); - long_type_count++; - } - } - if (long_type_count > 2) - error_at("too many long type specifiers", cur_token_loc()); - if (has_signed_type && has_unsigned_type) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && - lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - bool has_long_type = long_type_count > 0; - bool has_enum_type = lex_accept(T_enum); - if (has_enum_type && - (has_signed_type || has_unsigned_type || has_long_type)) - error_at("enum type cannot be combined with integer specifiers", - cur_token_loc()); - bool has_type_identifier = lex_peek(T_identifier, lookahead_token); - if (has_const_type || has_signed_type || has_unsigned_type || - has_long_type || has_enum_type || has_type_identifier || - lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { - /* Check if it's a basic type or typedef */ - token_t *saved_token = type_start; - bool is_record = lex_accept(T_struct); - if (!is_record) - is_record = lex_accept(T_union); - if (is_record) - lex_ident(T_identifier, lookahead_token); - - type_t *type; - if (has_enum_type) { - lex_ident(T_identifier, lookahead_token); - type = find_type_tag(lookahead_token, parent); - } else if (has_unsigned_type && !is_record) { - if (has_long_type) { - type = TY_ulong; - if (long_type_count > 1) { - type = TY_ulong_long; - } - if (lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - } else if (leading_scalar_type == TY_char) { - type = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - type = TY_ushort; - } else if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "int") || - !strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short"))) { - lex_expect(T_identifier); - if (!strcmp(lookahead_token, "char")) - type = TY_uchar; - else if (!strcmp(lookahead_token, "short")) - type = TY_ushort; - else - type = TY_uint; - } else - type = TY_uint; - } else if (has_long_type && !is_record) { - type = TY_long; - if (long_type_count > 1) { - type = TY_long_long; - } - lex_accept(T_signed); - lex_accept(T_const); - if (lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - } else if (has_signed_type && !is_record) { - if (leading_scalar_type == TY_char || - leading_scalar_type == TY_short) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - type = leading_scalar_type == TY_char ? TY_schar - : leading_scalar_type; - } else if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "int") || - !strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short"))) { - lex_expect(T_identifier); - type = !strcmp(lookahead_token, "char") - ? TY_schar - : find_type(lookahead_token, true); - } else { - type = TY_int; - } - } else if (leading_scalar_type) { - type = leading_scalar_type; - } else { - type = find_type(lookahead_token, is_record ? 2 : true); - } - - if (type) { - if (type->is_floating) - error_at("Floating point types are not yet supported", - cur_token_loc()); - - /* Save current position to backtrack if needed Try to parse as - * typename - */ - if (!is_record && !has_enum_type && !has_signed_type && - !has_unsigned_type && !has_long_type) - lex_expect(T_identifier); - - /* A qualifier may appear before or after the base type. */ - while (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL)) { - if (lex_accept(T_const)) - has_const_type = true; - else - cast_volatile_qualified = true; - } - - /* 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)) { - cast_const_pointer = true; - if (ptr_level <= 32) - cast_pointer_const_mask |= 1U - << (ptr_level - 1); - } else if (lex_accept(T_volatile)) - cast_volatile_qualified = true; - else - lex_expect(T_restrict); - } - } - - if (PTR_SIZE < 8 && type->base_type == TYPE_long_long && - !(ptr_level || type->ptr_level)) - error_at("long long value needs 64-bit target lowering", - cur_token_loc()); - - bool is_array = false; - - /* 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); - cast_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)) { - cast_array_size = read_const_expr(parent); - if (cast_array_size <= 0) - error_at( - "Array compound literal needs a positive bound", - cur_token_loc()); - } else { - cast_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) - cast_pointee_array_size = bound; - else { - if (pointee_dims == 1) - cast_pointee_array_dim2 = bound; - else if (pointee_dims == 2) - cast_pointee_array_dim3 = bound; - else - cast_pointee_array_dim4 = bound; - cast_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; - cast_array_dims = 1; - cast_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 (!cast_parenthesized_array && lex_accept(T_open_square)) { - int bound = 0; - - is_array = true; - if (cast_array_dims >= 4) - error_at( - "Array declarators support at most four dimensions", - cur_token_loc()); - if (lex_peek(T_numeric, NULL)) { - char bound_token[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, bound_token, MAX_TOKEN_LEN); - bound = parse_numeric_constant(bound_token); - if (bound <= 0) - error_at( - "Array compound literal needs a positive bound", - next_token_loc()); - } - if (!bound && cast_array_dims) - error_at("Only the outer array bound may be omitted", - cur_token_loc()); - if (!cast_array_dims) - cast_array_size = bound; - else { - if (cast_array_size) - cast_array_size *= bound; - else - cast_array_size = bound; - if (cast_array_dims == 1) - cast_array_dim2 = bound; - else if (cast_array_dims == 2) - cast_array_dim3 = bound; - else - cast_array_dim4 = bound; - } - if (!cast_array_dims && !bound) - cast_array_outer_unsized = true; - cast_array_dims++; - lex_expect(T_close_square); - } - if (!is_array && type->array_size) { - is_array = true; - cast_array_size = type->array_size; - cast_array_dim2 = type->array_dim2; - cast_array_dim3 = type->array_dim3; - cast_array_dim4 = type->array_dim4; - cast_array_dims = 1 + !!cast_array_dim2 + - !!cast_array_dim3 + !!cast_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; - cast_or_literal_type = type; - cast_ptr_level = ptr_level; - cast_const_qualified = - has_const_type || type->is_const_qualified; - - /* 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 { - if (is_array) - error_at("Cast cannot specify an array type", - cur_token_loc()); - /* (type)expr - cast expression */ - is_cast = true; - cast_or_literal_type = type; - cast_ptr_level = ptr_level; - cast_const_qualified = - has_const_type || type->is_const_qualified; - } - } 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(); - - /* 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 (cast_or_literal_type->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, cast_or_literal_type, cast_ptr_level); - cast_var->var_name = gen_name(); - cast_var->is_const_qualified = cast_const_qualified; - cast_var->is_const_pointer = cast_const_pointer; - cast_var->pointer_const_mask = cast_pointer_const_mask; - cast_var->is_volatile = cast_volatile_qualified; - if (cast_or_literal_type->func_signature) { - cast_var->ptr_level = 1; - cast_var->func_signature = cast_or_literal_type->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 (!cast_ptr_level && expr_var->is_const && - !is_pointer_like_value(expr_var) && !expr_var->is_func && - cast_var->type->base_type != TYPE_void) { - unsigned int lo = (unsigned int) expr_var->init_val; - unsigned int hi = (unsigned int) expr_var->init_val_hi; - - cast_var->is_const = true; - - /* 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. - */ - if (cast_var->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. - */ - cast_var->init_val = lo || hi; - cast_var->init_val_hi = 0; - } else if (cast_var->type->size < TY_int->size) { - unsigned int bits = cast_var->type->size * 8; - unsigned int mask = (1U << bits) - 1; - - lo &= mask; - if (!cast_var->type->is_unsigned && - (lo & (1U << (bits - 1)))) - lo |= ~mask; - cast_var->init_val = (int) lo; - cast_var->init_val_hi = cast_var->init_val < 0 ? -1 : 0; - } else if (cast_var->type->size == TY_int->size) { - cast_var->init_val = (int) lo; - cast_var->init_val_hi = - cast_var->type->is_unsigned - ? 0 - : (cast_var->init_val < 0 ? -1 : 0); - } else { - cast_var->init_val = (int) lo; - cast_var->init_val_hi = (int) 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"); - - /* 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) { - /* 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; - compound_var->is_const_qualified = cast_const_qualified; - compound_var->is_const_pointer = cast_const_pointer; - compound_var->pointer_const_mask = cast_pointer_const_mask; - - /* 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; - - /* parse_array_init() consumes its own opening brace. The older - * one-dimensional literal helper expects it already consumed. - */ - if (!is_array_literal || - (cast_array_dims <= 1 && !cast_array_element_ptr_level)) - lex_expect(T_open_curly); - if (!is_array_literal || - (cast_array_dims <= 1 && !cast_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 = cast_array_size; - compound_var->array_dim2 = cast_array_dim2; - compound_var->array_dim3 = cast_array_dim3; - compound_var->array_dim4 = cast_array_dim4; - compound_var->has_unsized_array = cast_array_outer_unsized; - compound_var->ptr_level = cast_array_element_ptr_level; - compound_var->pointee_array_size = cast_pointee_array_size; - compound_var->pointee_array_dim2 = cast_pointee_array_dim2; - compound_var->pointee_array_dim3 = cast_pointee_array_dim3; - compound_var->pointee_array_dim4 = cast_pointee_array_dim4; - add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, - NULL); - if (cast_array_dims > 1 || cast_array_element_ptr_level) - parse_array_init(compound_var, parent, bb, true); - 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 (cast_ptr_level > 0) { - /* Pointer compound literal: (int*){&x} */ - var_t *initializer; - - compound_var->ptr_level = cast_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()); - } else { - /* Empty pointer compound literal: (int*){} */ - initializer = - require_typed_var(parent, cast_or_literal_type); - initializer->ptr_level = cast_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(cast_or_literal_type)) { - /* Record compound literals use the same aggregate initializer - * path for structs, unions, and their typedef aliases. - */ - type_t *struct_type = cast_or_literal_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, - true); - compound_object = compound_var; - opstack_push(compound_var); - } 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; - add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, - 0, NULL); - var_t *zero = - require_typed_var(parent, cast_or_literal_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 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); - - /* 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 * 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 */ - var_t *initializer = opstack_pop(); - 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)) { - 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 < cast_array_dims) { - /* A partially selected row decays to its first element. It - * remains a pointer value, not a scalar read. - */ - opstack_push(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); - if (!is_pointer_operation(compound_op, current, - value)) { - 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; - } - 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); - } - } - } - - /* 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(cast_or_literal_type) && - lex_accept(T_dot)) { - 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 = cast_or_literal_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); - } - if (!is_pointer_operation(compound_op, current, - value)) { - 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 - 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 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(cast_or_literal_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); - } - } else { - /* 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. - */ - opstack_pop(); - perform_side_effect(parent, *bb); - if (!read_assignment_expression(parent, bb)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - } - } - lex_expect(T_close_bracket); - - bool lowered_call_result_postfix = false; - if (lex_peek(T_open_square, NULL)) { - var_t *grouped = operand_stack[operand_stack_idx - 1]; - - if (grouped->pointee_array_size) { - lower_call_result_array_postfix(&grouped, parent, bb); - lowered_call_result_postfix = true; - } - } - - /* A parenthesized pointer/string expression is still a postfix - * operand. Unlike identifier lvalues it has no declaration to feed - * read_lvalue(), so lower its first subscript directly while - * retaining the same explicit element-size scaling. - */ - if (!lowered_call_result_postfix && lex_accept(T_open_square)) { - var_t *base = opstack_pop(); - var_t *index; - var_t *address; - int element_size; - - 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; - - /* A grouping around an array compound literal preserves its - * array type. Keep lowering each following index against - * the original shape, just as the direct compound-literal - * postfix path does. - */ - address = base; - element_size = base->type->size; - 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, 1); - indexed->var_name = gen_name(); - add_insn(parent, *bb, OP_add, indexed, address, index, - 0, NULL); - address = indexed; - subscript_depth++; - } while (lex_accept(T_open_square)); - - if (subscript_depth < array_dims) { - opstack_push(address); - } else { - vd = require_typed_var(parent, base->type); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, address, NULL, - element_size, NULL); - } - } else { - if (!base->ptr_level && !base->type->ptr_level) - error_at("Cannot apply square operator to non-pointer", - cur_token_loc()); - read_expr(parent, bb); - read_ternary_operation(parent, bb); - index = opstack_pop(); - lex_expect(T_close_square); - element_size = base->type->size; - if (element_size != 1) { - var_t *scale = require_var(parent); - scale->var_name = gen_name(); - scale->init_val = element_size; - 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, base->type, 1); - address->var_name = gen_name(); - add_insn(parent, *bb, OP_add, address, base, index, 0, - NULL); - vd = require_typed_var(parent, base->type); - vd->ptr_level = - base->ptr_level > 1 ? base->ptr_level - 1 : 0; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, address, NULL, - vd->ptr_level ? PTR_SIZE : element_size, NULL); - } - } - - /* Grouping does not stop a postfix member chain. The expression - * result is an aggregate value rather than an identifier lvalue, so - * materialize its address and lower the selected member here. This - * covers the ordinary C99 spellings `(*p).field` and - * `(*record.member).field`. - */ - while (lex_accept(T_dot)) { - char token[MAX_ID_LEN]; - var_t *base = opstack_pop(); - var_t *field; - var_t *base_address; - var_t *address; - var_t *offset; - - if (!base->type || !is_record_type(base->type) || - effective_pointer_depth(base)) - error_at("Cannot apply dot operator to non-record", - cur_token_loc()); - lex_ident(T_identifier, token); - field = find_member(token, base->type); - if (!field) - error_at("Unknown struct or union member", - next_token_loc()); - - base_address = require_typed_ptr_var(parent, base->type, 1); - base_address->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, base_address, base, NULL, - 0, NULL); - 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); - address = require_typed_ptr_var(parent, field->type, 1); - address->var_name = gen_name(); - add_insn(parent, *bb, OP_add, address, base_address, offset, 0, - NULL); - - vd = require_typed_var(parent, field->type); - vd->ptr_level = field->ptr_level; - vd->func_signature = field->func_signature; - vd->is_const_qualified = field->is_const_qualified; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, address, NULL, - field->ptr_level || field->type->ptr_level - ? PTR_SIZE - : get_size(field), - NULL); - } - - /* Function calls are postfix expressions, so a parenthesized - * function designator remains callable: `(fn)(...)` and - * `(*fp)(...)` have the same meaning as `fn(...)` and `fp(...)`. - */ - if (lex_peek(T_open_bracket, NULL)) { - var_t *callee = operand_stack[operand_stack_idx - 1]; - func_t *signature = get_func_signature(callee); - - if (signature) { - if (!callee->ptr_level) { - opstack_pop(); - opstack_push( - load_function_pointer_object(parent, bb, callee)); - } - vd = emit_indirect_call_result(callee, signature, true, - parent, bb); - } else if (callee->is_func) { - signature = find_func(callee->var_name); - if (!signature) - error_at("Called object is not a function", - cur_token_loc()); - opstack_pop(); - vd = emit_direct_call_result(signature, true, parent, bb); - } else { - error_at("Called object is not a function pointer", - cur_token_loc()); - } - lower_call_result_array_postfix(&vd, 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) { - 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; - 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, true); - - /* 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; - - 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_call_result_array_postfix(&vd, 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)) { - func_t *returned_signature = - func->return_def.type->func_signature; - vd = emit_direct_call_result(func, true, parent, bb); - lower_call_result_array_postfix(&vd, parent, bb); - lower_call_result_prefix_update(&vd, prefix_op, parent, bb); - if (returned_signature && lex_peek(T_open_bracket, NULL)) { - vd = emit_indirect_call_result(vd, returned_signature, true, - parent, bb); - } - } else { - /* indirective function pointer assignment */ - vd = require_func_symbol_var(parent); - vd->is_func = true; - vd->var_name = intern_string(token); - vd->func_target = func; - 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(); - 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); - - /* 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->type = rs1->type; - vd->is_const = true; - vd->init_val = -rs1->init_val; - vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); - 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_negate, vd, rs1, NULL, 0, NULL); - } - } - } -} - -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); - - /* 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)); -} - -/* 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; - - /* 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); - int left_depth = orig_rs1->ptr_level + orig_rs1->type->ptr_level; - int right_depth = orig_rs2->ptr_level + orig_rs2->type->ptr_level; - bool left_scalar_row = is_scalar_pointee_array_pointer(orig_rs1); - bool right_scalar_row = is_scalar_pointee_array_pointer(orig_rs2); - - if ((left_scalar_row || right_scalar_row) && - !scalar_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_scalar_row - ? scalar_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_scalar_pointee_array_pointer(rs1) - ? scalar_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_scalar_pointee_array_pointer(rs2) - ? scalar_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; - if (is_scalar_pointee_array_pointer(ptr_var)) - 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) -{ - if (!var || var->ptr_level || !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); -} - -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(); - - /* 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(); - - /* 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); - } - - if ((top_op == OP_lt || top_op == OP_leq || top_op == OP_gt || - top_op == OP_geq) && - ((rs1 && (rs1->is_func || get_func_signature(rs1))) || - (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); - 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; - 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->type = result_type; - vd->is_const = true; - 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(); - 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--; -} - - -/* Return the address that an expression points to, or evaluate its value. - * x =; - * x[] =; - * x[expr].field =; - * x[expr]->field =; - * - * @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. - */ -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) -{ - 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; - - /* 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 (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)) { - int indexed_ptr_level; - 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. - */ - if (lvalue->is_reference && - (lvalue->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). - */ - lvalue->size = - lvalue->value_ptr_level > 1 ? PTR_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); - 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; - - /* `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; - } - } - - 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; - - /* 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 && lvalue->value_ptr_level > 0; - subscript_depth++; - lvalue->is_reference = !indexes_pointee_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`. - */ - lvalue->is_const_qualified = - var->is_const_qualified || - (lvalue->pointee_func_signature && - var->type->array_element_is_const_pointer); - } 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->decl = 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->size = get_size(var); - lvalue->is_const_qualified |= var->is_const_qualified; - subscript_depth = 0; - - /* if it is an array, get the address of first element instead of - * its value. - */ - if (is_array_declarator(var)) - 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; - } - } - - 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()); - - /* 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. - */ - bool scalar_pointee_row = is_scalar_pointee_array_pointer(var); - if (allow_ptr_arith && lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var) || scalar_pointee_row) && - !lvalue->is_reference) { - if (!is_array_declarator(var) && - is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) - error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); - while (lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var) || - scalar_pointee_row)) { - 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 (scalar_pointee_row) { - lvalue->size = scalar_pointee_array_row_stride(var); - } else if (is_array_declarator(var) && var->array_dim2 && - !has_effective_pointer(var) && !var->is_func) { - lvalue->size = fixed_array_decay_stride(var); - } else if (var->ptr_level > 1 || - (is_array_declarator(var) && var->ptr_level)) - 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. Array expressions - * first decay to a pointer, adding one level to the declaration's - * element indirection. - */ - if (var->ptr_level || is_array_declarator(var) || - scalar_pointee_row) { - vd->type = lvalue->type; - vd->ptr_level = var->ptr_level + !!is_array_declarator(var); - if (scalar_pointee_row) - copy_pointee_array_shape(vd, var); - else if (is_array_declarator(var) && var->array_dim2 && - !has_effective_pointer(var) && !var->is_func) { - fixed_array_shape_t shape = fixed_array_shape_from_var(var); - - fixed_array_shape_drop_outer(&shape); - fixed_array_shape_to_pointee_var(vd, &shape); - } - } - vd->var_name = gen_name(); - vd->is_const_qualified = var->is_const_qualified; - vd->pointer_const_mask = var->pointer_const_mask; - vd->is_const_pointer = - vd->ptr_level > 0 && vd->ptr_level <= 32 && - (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); - 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(); - 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; - - /* 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; - 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; - } - opstack_push(t); - if (is_bitfield(lvalue->decl)) { - /* Bit-fields are values, never independently addressable - * storage: extract their slice from the containing unit. - */ - opstack_pop(); - t = read_bitfield_value(parent, bb, rs1, lvalue->decl); - opstack_push(t); - } else { - add_insn(parent, *bb, OP_read, t, rs1, NULL, lvalue->size, - NULL); - } - } - 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(); - - /* For pointer arithmetic, increment by the size of pointed-to type - */ - if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) - vd->init_val = scalar_pointee_array_row_stride(var); - else if (lvalue->ptr_level > 1) - vd->init_val = PTR_SIZE; - else if (lvalue->ptr_level) - vd->init_val = lvalue->type->size; - else if (lvalue->type && lvalue->type->ptr_level) - vd->init_val = get_pointer_element_size(var); - 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]; - 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)) { - opcode_t postfix_op = lex_accept(T_increment) ? OP_add : OP_sub; - 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; - } - - /* 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->is_reference && is_scalar_pointee_array_pointer(var)) { - increment_size = scalar_pointee_array_row_stride(var); - } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { - increment_size = PTR_SIZE; - } else 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(); - if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) { - vd->type = lvalue->type; - vd->ptr_level = var->ptr_level; - copy_pointee_array_shape(vd, var); - } - 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); - } - } - } -} - -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(); - 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(); - 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)) { - opstack_pop(); - perform_side_effect(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); - opstack_pop(); - perform_side_effect(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(void) -{ - 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 || !(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_scalar_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); - } - 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)) { - opstack_pop(); - perform_side_effect(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(); - 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 = require_ref_var(parent, true_val->type, 0); - false_addr = require_ref_var(parent, false_val->type, 0); - selected = require_ref_var(parent, true_val->type, 0); - true_addr->var_name = gen_name(); - false_addr->var_name = gen_name(); - selected->var_name = gen_name(); - add_insn(parent, then_, OP_address_of, true_addr, true_val, NULL, 0, - NULL); - add_insn(parent, else_, OP_address_of, false_addr, false_val, NULL, 0, - NULL); - 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); - } - 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; -} - -bool read_body_assignment(char *token, - block_t *parent, - opcode_t prefix_op, - basic_block_t **bb, - var_t **assignment_result, - int lvalue_paren_depth) -{ - var_t *var = find_local_var(token, parent), *vd, *rs1, *rs2, *t; - if (!var) - var = find_global_var(token); - 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, true); - while (lvalue_paren_depth-- > 0) - lex_expect(T_close_bracket); - size = lvalue.size; - - 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()); - - /* if we have a pointer, shift it by element size But not if we are - * operating on a dereferenced value (array indexing) - */ - if (!one && (op == OP_add || op == OP_sub) && - !lvalue.is_reference && is_scalar_pointee_array_pointer(var)) - increment_size = scalar_pointee_array_row_stride(var); - else if (lvalue.ptr_level > 1 && !lvalue.is_reference) - increment_size = PTR_SIZE; - else 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) { - /* Keep typedef-hidden depth: a void ** alias advances over - * pointer objects, whereas a direct void * was rejected above - * and never reaches this scaling path. - */ - increment_size = get_pointer_element_size(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 - 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 = - read_bitfield_value(parent, bb, t, lvalue.decl); - stored->is_bitfield = false; - 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(); - 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(); - 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); - 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 - 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 = - read_bitfield_value(parent, bb, t, lvalue.decl); - stored->is_bitfield = false; - 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(); - - /* 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(); - vd->func_target = rs2->func_target; - 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); - - /* Only a named function-pointer object owns this metadata. A - * record member or array element shares lvalue.decl with every - * object of that type, so storing provenance there would let - * one object's assignment authorize another's call. Those - * reference forms intentionally remain unknown until SSA - * provenance is available per storage location. - */ - if (!lvalue.is_reference && !lvalue.decl->func_target_invalid) { - func_t *assigned_target = rs2->func_target; - - if (!assigned_target || !assigned_target->bbs) { - lvalue.decl->func_target = NULL; - lvalue.decl->func_target_invalid = true; - } else - lvalue.decl->func_target = assigned_target; - } - if (assignment_result) - assignment_result[0] = rs2; - } else if (lvalue.is_reference) { - rs2 = opstack_pop(); - rs1 = opstack_pop(); - 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()); - } - if (is_bitfield(lvalue.decl)) - write_bitfield_value(parent, bb, rs1, rs2, lvalue.decl); - else - add_insn(parent, *bb, OP_write, NULL, rs1, rs2, size, NULL); - 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 = - read_bitfield_value(parent, bb, rs1, lvalue.decl); - stored->is_bitfield = false; - 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(); - 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)) { - 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 if (incompatible_const_pointer_conversion(rs1, vd)) { - diagnose_const_pointer_conversion(rs1, vd); - } else { - 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; -} - -bool read_assignment_expression(block_t *parent, basic_block_t **bb) -{ - char token[MAX_ID_LEN]; - var_t *result; - int lvalue_paren_depth = 0; - - if (!assignment_expression_follows()) - return false; - if (compound_literal_starts_expression()) - return false; - - /* 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; - int grouped_fixed_array_dimensions = 0; - 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. - */ - lex_expect(T_asterisk); - read_expr(parent, bb); - read_ternary_operation(parent, bb); - address = opstack_pop(); - object_address = address; - while (lvalue_paren_depth-- > 0) - lex_expect(T_close_bracket); - - /* The ordinary `(*pointer)` assignment path historically stopped at the - * grouping close. Admit only exact scalar fixed-array suffixes here, - * retaining its common store/result lowering below. - */ - if (lex_peek(T_open_square, NULL)) { - var_t shape = {0}; - fixed_array_shape_t array_shape; - int depth = 0; - int dimensions; - - if (!address->pointee_array_size || - address->pointee_array_element_ptr_level || - effective_pointer_depth(address) != 1) - error_at( - "Grouped subscript requires a scalar fixed array pointer", - cur_token_loc()); - shape.type = address->type->pointee_array_element_type - ? address->type->pointee_array_element_type - : address->type; - array_shape = fixed_array_shape_from_pointee_var(address); - fixed_array_shape_to_var(&shape, &array_shape); - dimensions = array_shape.rank; - grouped_fixed_array_dimensions = dimensions; - if (dimensions > 4) - error_at( - "Grouped fixed-array assignment supports at most four " - "dimensions", - cur_token_loc()); - while (lex_accept(T_open_square)) { - var_t *index; - var_t *scale; - var_t *offset; - var_t *next; - 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_subscript_stride(&shape, depth, - shape.type->size); - offset = index; - if (stride != 1) { - 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); - offset = require_var(parent); - offset->var_name = gen_name(); - add_insn(parent, *bb, OP_mul, offset, index, scale, 0, - NULL); - } - next = require_typed_ptr_var(parent, shape.type, 1); - next->var_name = gen_name(); - next->is_const_qualified = object_address->is_const_qualified || - shape.type->is_const_qualified; - add_insn(parent, *bb, OP_add, next, address, offset, 0, NULL); - address = next; - depth++; - } - if (depth != dimensions) - error_at("Grouped fixed-array assignment needs all subscripts", - cur_token_loc()); - } - - 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. - */ - 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()); - 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; - } - - int address_depth = effective_pointer_depth(object_address); - unsigned int address_mask = - effective_pointer_const_mask(object_address); - if ((field && field->is_const_qualified) || - address->is_const_qualified || - (address_depth > 1 && address_depth <= 32 && - (address_mask & (1U << (address_depth - 2))))) - 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 (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && - compound_op != OP_add && compound_op != OP_sub && - compound_op != OP_mul && compound_op != OP_div && - compound_op != OP_mod && compound_op != OP_lshift && - compound_op != OP_rshift && compound_op != OP_bit_and && - compound_op != OP_bit_xor && compound_op != OP_bit_or) - error_at("Unsupported rank-three/four grouped compound assignment", - cur_token_loc()); - - if (!read_assignment_expression(parent, bb)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - } - value = opstack_pop(); - - if (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && - (value_ptr_level != 0 || is_record_type(value_type) || - value_type->is_floating || value->is_func || - is_pointer_like_value(value) || is_record_type(value->type) || - value->type->is_floating)) - error_at( - "Invalid rank-three/four grouped compound assignment operand", - cur_token_loc()); - - 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); - } - if (!is_pointer_operation(compound_op, current, value)) { - 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 - 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 = read_bitfield_value(parent, bb, address, field); - result->is_bitfield = false; - } else { - result = require_typed_var(parent, value_type); - result->ptr_level = value_ptr_level; - result->var_name = gen_name(); - add_insn(parent, *bb, OP_read, result, address, NULL, store_size, - NULL); - } - if (result->type == TY_bool && !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 (!read_body_assignment(token, 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; -} - -/* Return the extent left after selecting leading array dimensions. The global - * constant evaluator retains dimensions in var_t instead of constructing an - * expression IR, so every sizeof object path uses this one calculation. - */ -static int sizeof_subscripted_array(var_t *object, int subscript_depth) -{ - int res; - - if (!subscript_depth) - return size_var(object); - - res = object->type->size; - if (subscript_depth == 1 && object->array_dim2 > 0) { - res *= object->array_dim2; - if (object->array_dim3 > 0) - res *= object->array_dim3; - if (object->array_dim4 > 0) - res *= object->array_dim4; - } else if (subscript_depth == 2 && object->array_dim3 > 0) { - res *= object->array_dim3; - if (object->array_dim4 > 0) - res *= object->array_dim4; - } else if (subscript_depth == 3 && object->array_dim4 > 0) { - res *= object->array_dim4; - } - return res; -} - -static void validate_global_sizeof_object(var_t *object) -{ - if (object->is_func) - error_at("sizeof(function) is invalid", cur_token_loc()); - if (is_bitfield(object)) - error_at("sizeof cannot be applied to a bit-field", cur_token_loc()); - if (object->is_flexible_array_member) - error_at("sizeof cannot be applied to a flexible array member", - cur_token_loc()); -} - -/* Resolve unevaluated record postfixes while preserving an array member's - * declared dimensions for the global sizeof constant evaluator. - */ -static var_t *read_const_object_members(var_t *object) -{ - while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { - bool through_pointer = lex_accept(T_arrow); - char field_name[MAX_ID_LEN]; - var_t *field; - int pointer_depth = effective_pointer_depth(object); - - if ((through_pointer && pointer_depth != 1) || - (!through_pointer && pointer_depth != 0)) - error_at("Invalid record member access", cur_token_loc()); - if (!through_pointer) - lex_expect(T_dot); - lex_ident(T_identifier, field_name); - field = find_member(field_name, object->type); - if (!field) - error_at("Unknown record member", cur_token_loc()); - object = field; - } - return object; -} - -/* Parse the object portion of a constant address expression. sizeof only needs - * its pointer type, but still applies the ordinary addressability constraints. - */ -static var_t *read_const_addressed_object(block_t *scope, int *subscript_depth) -{ - char buffer[MAX_TOKEN_LEN]; - var_t *object; - - lex_expect(T_ampersand); - if (subscript_depth) - *subscript_depth = 0; - lex_ident(T_identifier, buffer); - object = find_var(buffer, scope); - if (!object) - error_at("sizeof address requires a declared object", cur_token_loc()); - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - if (subscript_depth) - *subscript_depth = *subscript_depth + 1; - } - if (is_bitfield(object)) - error_at("Cannot take address of bit-field", cur_token_loc()); - return object; -} - -/* 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); - int saved_side_effects = se_idx; - var_t *result; - - handle_sizeof_operator(scope, &unevaluated_bb); - result = opstack_pop(); - se_idx = saved_side_effects; - return result->init_val; -} - -static int read_const_string_size(void) -{ - char buffer[MAX_STRING_LEN]; - char unescaped[MAX_STRING_LEN]; - int res = 0; - - do { - int length; - - lex_ident(T_string, buffer); - length = unescape_string(buffer, unescaped, sizeof(unescaped)); - if (length < 0) - error_at("Invalid escape sequence", cur_token_loc()); - res += length; - } while (lex_peek(T_string, buffer)); - return res + 1; -} - -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; -} - -int read_primary_constant(block_t *scope) -{ - /* return signed constant */ - int isneg = 0, res; - - /* This recurses once per nesting level of a constant expression, so the - * buffer holds only what is copied into it: a number, a character constant - * or an identifier, each at most MAX_TOKEN_LEN. A string is only tested for - * and never copied. - */ - char buffer[MAX_TOKEN_LEN]; - if (lex_accept(T_minus)) - isneg = 1; - if (lex_accept(T_sizeof)) { - /* The common scalar expression form needs no IR in a global constant: - * integer and character literals have type int. Type names continue - * through the declaration-only evaluator below. - */ - token_t *inside = - lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; - var_t *nested_array = NULL; - token_t *nested_root = NULL; - token_t *nested_after = NULL; - int nested_groups = 0; - - if (inside && inside->kind == T_open_bracket) { - token_t *token = inside; - - while (token && token->kind == T_open_bracket) { - nested_groups++; - token = token->next; - } - nested_root = token; - if (token && token->kind == T_identifier) - nested_array = find_var(token->literal, scope); - nested_after = token ? token->next : NULL; - for (int i = 0; i < nested_groups && nested_after && - nested_after->kind == T_close_bracket; - i++) - nested_after = nested_after->next; - } - if (can_scan_sizeof_postfix_extent( - scope, - lex_peek(T_open_bracket, NULL) ? cur_token->next->next - : cur_token->next, - lex_peek(T_open_bracket, NULL), false)) { - /* The shared postfix walker has already proved this is an - * identifier-rooted fixed array extent, so the detached parser can - * preserve it without a global-prefix spelling table. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_string) { - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - res = read_const_string_size(); - lex_expect(T_close_bracket); - lex_expect(T_close_bracket); - } else if (inside && inside->kind == T_wstring) { - res = read_const_wstring_size(); - } else if (nested_array && nested_array->array_size > 0 && - nested_groups > 0 && nested_after && - nested_after->kind == T_close_bracket) { - lex_expect(T_open_bracket); - for (int i = 0; i < nested_groups; i++) - lex_expect(T_open_bracket); - lex_expect(T_identifier); - for (int i = 0; i < nested_groups; i++) - lex_expect(T_close_bracket); - lex_expect(T_close_bracket); - res = size_var(nested_array); - } else if (nested_array && nested_groups > 0 && nested_root && - nested_root->next && - (nested_root->next->kind == T_dot || - nested_root->next->kind == T_arrow)) { - /* The local sizeof helper preserves a member-array lvalue through - * arbitrary grouping before a postfix subscript. Reuse it in a - * detached block for a global constant instead of duplicating that - * type-only traversal here. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_open_bracket && inside->next->next && - inside->next->next->kind == T_identifier) { - var_t *grouped_object = - find_var(inside->next->next->literal, scope); - - if (grouped_object && !grouped_object->array_size && - !grouped_object->has_unsized_array) - res = read_global_sizeof_expression(scope); - else - res = read_const_sizeof_type(scope); - } else if (inside && - (inside->kind == T_increment || - inside->kind == T_decrement || inside->kind == T_plus || - inside->kind == T_minus || inside->kind == T_bit_not || - inside->kind == T_log_not)) { - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - ((inside->next->kind == T_identifier && - find_type(inside->next->literal, true)) || - inside->next->kind == T_signed || - inside->next->kind == T_unsigned || - inside->next->kind == T_long || - inside->next->kind == T_const || - inside->next->kind == T_volatile || - inside->next->kind == T_struct || - inside->next->kind == T_union || - inside->next->kind == T_enum)) { - /* A cast begins with an extra parenthesis and cannot be evaluated - * as an integer constant when its operand is a declared object. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_identifier && - (find_var(inside->literal, scope) || - find_func(inside->literal)) && - inside->next && inside->next->kind != T_close_bracket && - inside->next->kind != T_dot && - inside->next->kind != T_arrow && - inside->next->kind != T_open_square) { - /* Parenthesized non-postfix operands need type derivation rather - * than constant evaluation: sizeof(global + 1), sizeof(global = 1), - * and calls are all unevaluated but need their expression type. - * Reuse the detached expression path used for local sizeof. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_asterisk && inside->next->next && - inside->next->next->kind == T_identifier && - find_var(inside->next->next->literal, scope)) { - var_t *pointer = find_var(inside->next->next->literal, scope); - var_t dereferenced; - var_t *object; - int subscript_depth = 0; - - if (effective_pointer_depth(pointer) != 1) - error_at("Cannot dereference non-pointer in sizeof", - cur_token_loc()); - - /* This is compile-time descriptor manipulation, not a source - * aggregate expression. A direct `dereferenced = *pointer` becomes - * one wide OP_read while self-hosting; ARM's scalar load backend - * correctly admits only 1/2/4-byte reads. Copy through libc - * instead, which keeps the generated compiler IR word-sized and - * preserves every descriptor field. - */ - memcpy(&dereferenced, pointer, sizeof(dereferenced)); - dereferenced.type = - pointee_type_from_pointer_typedef(pointer->type); - dereferenced.ptr_level = 0; - object = &dereferenced; - - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - lex_expect(T_identifier); - lex_expect(T_close_bracket); - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_asterisk && inside->next && - inside->next->kind == T_identifier && inside->next->next && - inside->next->next->kind == T_open_square && - is_direct_fixed_array_pointer_slot( - find_var(inside->next->literal, scope))) { - /* The shared direct-pointer helper also owns the supported global - * pointer-array slot form. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_asterisk && inside->next && - inside->next->kind == T_ampersand) { - var_t *object; - int subscript_depth; - - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - object = read_const_addressed_object(scope, &subscript_depth); - lex_expect(T_close_bracket); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_ampersand) { - lex_expect(T_open_bracket); - read_const_addressed_object(scope, NULL); - lex_expect(T_close_bracket); - res = PTR_SIZE; - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_identifier && - find_var(inside->next->literal, scope) && - inside->next->next && - inside->next->next->kind != T_close_bracket && - inside->next->next->kind != T_dot && - inside->next->next->kind != T_arrow && - inside->next->next->kind != T_open_square) { - /* A grouped object followed by an operator is an ordinary - * parenthesized expression, not the array/member postfix form - * handled below. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_identifier && - find_var(inside->next->literal, scope)) { - /* Preserve the array object type through an explicitly grouped - * object expression, e.g. sizeof((record.items)[0]). - */ - var_t *object = find_var(inside->next->literal, scope); - int subscript_depth = 0; - - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - lex_expect(T_identifier); - object = read_const_object_members(object); - lex_expect(T_close_bracket); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_open_bracket) { - lex_expect(T_open_bracket); - read_const_expr(scope); - lex_expect(T_close_bracket); - res = TY_int->size; - } else if (inside && - (inside->kind == T_numeric || inside->kind == T_char || - inside->kind == T_wchar)) { - lex_expect(T_open_bracket); - read_const_expr(scope); - lex_expect(T_close_bracket); - res = TY_int->size; - } else if (inside && inside->kind == T_identifier) { - var_t *object = find_var(inside->literal, scope); - constant_t *constant = find_scoped_constant(inside->literal, scope); - - if (object || constant) { - lex_expect(T_open_bracket); - lex_expect(T_identifier); - if (object) { - int subscript_depth = 0; - - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at( - "Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else { - lex_expect(T_close_bracket); - res = TY_int->size; - } - } else { - res = read_const_sizeof_type(scope); - } - } else if (lex_peek(T_numeric, buffer) || lex_peek(T_char, buffer) || - lex_peek(T_wchar, buffer)) { - read_primary_constant(scope); - res = TY_int->size; - } else if (lex_peek(T_string, NULL)) { - res = read_const_string_size(); - } else if (lex_peek(T_wstring, NULL)) { - res = read_const_wstring_size(); - } else if (lex_peek(T_ampersand, NULL)) { - read_const_addressed_object(scope, NULL); - res = PTR_SIZE; - } else if (lex_accept(T_asterisk)) { - var_t *object; - int subscript_depth; - - object = read_const_addressed_object(scope, &subscript_depth); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (lex_peek(T_minus, NULL) || lex_peek(T_plus, NULL) || - lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL)) { - res = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, buffer)) { - var_t *object = find_var(buffer, scope); - constant_t *constant = find_scoped_constant(buffer, scope); - - lex_expect(T_identifier); - if (object) { - int subscript_depth = 0; - - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (constant) { - res = TY_int->size; - } else { - error_at("sizeof requires a type or declared object", - cur_token_loc()); - res = 0; - } - } else { - res = read_const_sizeof_type(scope); - } - } else if (lex_accept(T_open_bracket)) { - res = read_primary_constant(scope); - 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) || lex_peek(T_wchar, buffer)) { - char unescaped[MAX_TOKEN_LEN]; - unescape_string(buffer, unescaped, MAX_TOKEN_LEN); - res = lex_peek(T_wchar, NULL) ? parse_wide_character_constant(buffer) - : parse_character_constant(buffer); - lex_expect(lex_peek(T_wchar, NULL) ? T_wchar : T_char); - } else if (lex_peek(T_identifier, buffer)) { - constant_t *con; - - if (!strcmp(buffer, "__builtin_offsetof")) { - res = read_const_expr_operand(scope); - if (isneg) - return (-1) * res; - return res; - } - lex_expect(T_identifier); - con = find_scoped_constant(buffer, scope); - if (!con) - error_at("Identifier is not an integer constant", next_token_loc()); - res = con->value; - } else - error_at("Invalid value after assignment", next_token_loc()); - if (isneg) - return (-1) * res; - return res; -} - -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()); - res = op1 << op2; - break; - case OP_rshift: - 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); - -void emit_global_scalar_assignment(block_t *parent, - basic_block_t *bb, - var_t *dest, - var_t *src) -{ - if (!dest->ptr_level && dest->type == TY_bool) - src->init_val = src->init_val != 0; - add_insn(parent, bb, OP_assign, dest, src, NULL, 0, NULL); -} - -/* 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) -{ - int bracket_depth = 0; - - for (; 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)) - 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; -} - -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; - -/* 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; - } - - /* 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; - - /* Materialize the usual arithmetic conversion in word form. A narrow - * signed source sign-extends to either signed long long or unsigned - * long long; a narrow unsigned source zero-extends in both cases. - */ - if (left->type->size <= TY_int->size) - hi = left->type->is_unsigned || !(lo & 0x80000000U) ? 0 - : 0xffffffffU; - if (right->type->size <= TY_int->size) - rhs_hi = right->type->is_unsigned || !(rhs_lo & 0x80000000U) - ? 0 - : 0xffffffffU; - - 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; - } - result->init_val = comparison; - result->init_val_hi = 0; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; - } - - if (op == OP_log_and || op == OP_log_or) { - bool left_true = lo || hi; - bool right_true = rhs_lo || rhs_hi; - - result->init_val = op == OP_log_and ? left_true && right_true - : left_true || right_true; - result->init_val_hi = 0; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; - } - - if (op == OP_div || op == OP_mod) { - unsigned int rem_lo = 0, rem_hi = 0, quo_lo = 0, quo_hi = 0; - bool is_unsigned = left->type->is_unsigned || right->type->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()); - 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); - } - } - result->init_val = out_lo; - result->init_val_hi = out_hi; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); - 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; - } - result->init_val = out_lo; - result->init_val_hi = out_hi; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); - return true; -} - -/* 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 = 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 = 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 = !(value->init_val || value->init_val_hi); - 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; - } - if (lex_accept(T_sizeof)) { - char sizeof_name[MAX_ID_LEN]; - - value = require_typed_var(parent, find_type("size_t", true)); - value->var_name = gen_name(); - if (lex_peek(T_string, NULL)) - value->init_val = read_const_string_size(); - else if (lex_peek(T_wstring, NULL)) - value->init_val = read_const_wstring_size(); - else if (lex_peek(T_numeric, NULL)) - value->init_val = read_global_sizeof_expression(scope); - else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_string) { - lex_expect(T_open_bracket); - value->init_val = read_const_string_size(); - lex_expect(T_close_bracket); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_wstring) { - lex_expect(T_open_bracket); - value->init_val = read_const_wstring_size(); - lex_expect(T_close_bracket); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_numeric) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_open_bracket) { - /* A nested group is an expression operand, e.g. sizeof((void)1) or - * sizeof((*incomplete_pointer)). - */ - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_asterisk && - cur_token->next->next->next && - cur_token->next->next->next->kind == T_identifier && - cur_token->next->next->next->next && - cur_token->next->next->next->next->kind == T_open_square && - is_direct_fixed_array_pointer_slot( - find_var(cur_token->next->next->next->literal, scope))) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_identifier && - cur_token->next->next->next && - cur_token->next->next->next->kind == T_close_bracket && - find_var(cur_token->next->next->literal, scope)) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, sizeof_name) && - find_var(sizeof_name, scope)) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_identifier && - cur_token->next->next->next && - cur_token->next->next->next->kind == T_close_bracket && - find_func(cur_token->next->next->literal)) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, sizeof_name) && - find_func(sizeof_name)) { - value->init_val = read_global_sizeof_expression(scope); - } else - value->init_val = read_const_sizeof_type(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 (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(); - value->init_val = constant->value; - value->is_const = true; - add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); - 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++] = - 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 = condition->init_val || condition->init_val_hi; - - /* 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--; - 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"); - protected_rhs[op_stack_index] = - is_logical(op) && - ((op == OP_log_and && - !(val_stack[val_stack_index - 1]->init_val || - val_stack[val_stack_index - 1]->init_val_hi)) || - (op == OP_log_or && - (val_stack[val_stack_index - 1]->init_val || - val_stack[val_stack_index - 1]->init_val_hi))); - op_stack[op_stack_index++] = op; - if (protected_rhs[op_stack_index - 1]) { - wide_global_unevaluated_depth++; - } - val_stack[val_stack_index++] = - 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]; -} - -void eval_ternary_imm(int cond, var_t *var) -{ - if (cond == 0) { - while (!lex_peek(T_colon, NULL)) { - lex_next(); - } - lex_accept(T_colon); - read_global_assignment_var(var); - } else { - read_global_assignment_var(var); - lex_expect(T_colon); - while (!lex_peek(T_semicolon, NULL)) { - lex_next(); - } - } -} - -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; - } - - /* global initialization must be constant */ - { - /* 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); - 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); - 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_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 ? 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 = 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; - 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); - if (incompatible_pointee_callback_conversion(object_addr, var)) - error_at("incompatible callback slot types in initializer", - cur_token_loc()); - add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, - NULL); - return true; - } - } - if (explicit_address) - error_at("Expected a global object or function after '&'", - cur_token_loc()); - - /* 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 (typed_global_literal_appears_before_initializer_end( - cur_token->next)) { - rs1 = read_wide_global_literal_expression(parent, bb, scope); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); - return true; - } - if (lex_peek(T_string, NULL) || lex_peek(T_wstring, 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";) - */ - 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_primary_constant(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_primary_constant(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; - 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_primary_constant(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; -} - -bool read_global_assignment(char *token) -{ - var_t *var = find_global_var(token); - return var && read_global_assignment_var(var); -} - -void perform_side_effect(block_t *parent, basic_block_t *bb) -{ - 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; -} - -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; - -void reject_label_followed_by_declaration_in_strict_c99(void) -{ - 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_type(name, true))) - error_at("a C99 label must precede a statement, not a declaration", - cur_token_loc()); -} - -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 { - int case_val; - var_t *constant; - var_t *comparison; - basic_block_t *next_dispatch; - - lex_expect(T_case); - case_val = read_const_expr(parent); - if (strict_c99) { - switch_case_value_t *seen; - - for (seen = context->seen_cases; seen; seen = seen->next) - if (seen->value == case_val) - error_at("duplicate case value in C99 switch", - cur_token_loc()); - seen = arena_alloc(GENERAL_ARENA, sizeof(*seen)); - seen->value = case_val; - seen->next = context->seen_cases; - context->seen_cases = seen; - } - - constant = require_var(context->dispatch_parent); - constant->var_name = gen_name(); - constant->init_val = case_val; - 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(); - 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. - */ - body = bb_create(parent); - - lex_expect(T_open_curly); - while (!lex_accept(T_close_curly)) { - body = read_body_statement(parent, body); - perform_side_effect(parent, 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; -} - -/* 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; - var_t *rs1; - var_t *var; - 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); - - 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_type(token, 1) - : NULL; - } - if (!type && saw_decl_specifier) - error_at("declaration specifier requires a type", cur_token_loc()); - if (type) { - /* 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()); - var = require_typed_var(blk, type); - var->is_static = is_static; - var->is_register = is_register; - var->is_global = is_static; - var->is_const_qualified = is_const; - var->is_volatile = is_volatile; - parsing_for_initializer_declaration = true; - read_full_var_decl(var, false, false, false); - parsing_for_initializer_declaration = false; - if (strict_c99 && var->is_func) - error_at("C99 for initializer cannot declare a function", - cur_token_loc()); - if (is_extern) { - if (var->is_func || lex_peek(T_open_bracket, NULL)) { - /* This helper consumes the declaration's semicolon. */ - blk->locals.size--; - var->is_block_scope_function_declaration = true; - GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = - var; - read_global_function_declarator(GLOBAL_BLOCK, var, false); - var_t *alias = require_var(blk); - alias->var_name = var->var_name; - alias->is_extern_function_alias = true; - for_decl_semicolon_consumed = true; - } else { - for (;;) { - var = bind_block_extern_object(blk, var); - if (lex_peek(T_assign, NULL)) - error_at( - "extern declaration cannot have an initializer", - next_token_loc()); - if (!lex_accept(T_comma)) - break; - var = require_typed_var(blk, type); - var->is_const_qualified = is_const; - var->is_volatile = is_volatile; - read_partial_var_decl(var, NULL); - } - } - } else { - if (is_incomplete_record_object(var)) - error_at( - "Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : blk, - is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, var, - NULL, NULL, 0, NULL); - add_symbol(setup, var); - if (lex_accept(T_assign)) { - validate_string_array_initializer(var); - if (is_static) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) - parse_array_init(var, GLOBAL_BLOCK, - &GLOBAL_FUNC->bbs, true); - 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, blk, &setup); - } else if ((var->has_unsized_array || - var->array_size > 0) && - is_wchar_array(var) && - lex_peek(T_wstring, NULL)) { - parse_wstring_array_init(var, blk, &setup); - } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, blk, &setup, true); - } else { - 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); - nv->is_static = is_static; - nv->is_register = is_register; - nv->is_global = is_static; - nv->is_const_qualified = is_const; - nv->is_volatile = is_volatile; - read_partial_var_decl(nv, var); /* partial */ - if (is_incomplete_record_object(nv)) - error_at( - "Incomplete struct/union type cannot define an " - "object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : blk, - is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, - nv, NULL, NULL, 0, NULL); - add_symbol(setup, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (is_static) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) - parse_array_init(nv, GLOBAL_BLOCK, - &GLOBAL_FUNC->bbs, true); - else if (lex_peek(T_open_curly, NULL)) - parse_global_record_init(nv, GLOBAL_BLOCK); - else - read_global_assignment_var(nv); - } else if ((nv->has_unsized_array || - nv->array_size > 0) && - is_char_array(nv) && - lex_peek(T_string, NULL)) { - parse_string_array_init(nv, blk, &setup); - } else if ((nv->has_unsized_array || - nv->array_size > 0) && - is_wchar_array(nv) && - lex_peek(T_wstring, NULL)) { - parse_wstring_array_init(nv, blk, &setup); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || - nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, blk, &setup, true); - } else { - 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_control_expression(blk, &setup); - opstack_pop(); - perform_side_effect(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(); - 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_); - opstack_pop(); - perform_side_effect(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(); - 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, - bool is_const, - bool is_static) -{ - char token[MAX_ID_LEN]; - type_t *type; - var_t *var; - - bool is_union = false; - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - is_union = true; - } - lex_ident(T_identifier, token); - type = find_type(token, find_type_flag); - if (lex_peek(T_open_curly, NULL)) { - int i = 0; - int size = 0; - int alignment = 1; - int max_size = 0; - bitfield_layout_t bits = {0}; - bool has_flexible_array_member = false; - - if (!type) - type = add_type(); - set_type_name(type, token); - type->base_type = is_union ? TYPE_union : TYPE_struct; - - lex_expect(T_open_curly); - do { - var_t *v = type_add_field(type, &i); - var_t *last = v; - - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, parent); - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, is_union); - if (is_union) { - v->offset = 0; - int field_size = is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - } else { - size = is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), v, - &alignment); - } - - while (lex_accept(T_comma)) { - if (!is_union && last->is_flexible_array_member) - error_at( - "Flexible array member must be the final struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, parent); - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, is_union); - last = nv; - if (is_union) { - nv->offset = 0; - int field_size = - is_bitfield(nv) - ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(nv) > alignment) - alignment = alignment_var(nv); - } else { - size = - is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); - } - } - - 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->has_flexible_array_member = has_flexible_array_member; - - if (lex_peek(T_semicolon, NULL)) { - lex_expect(T_semicolon); - return bb; - } - } - if (!type && lex_peek(T_semicolon, NULL)) { - /* A block-scope `struct tag;` or `union tag;` introduces an incomplete - * tag. Its pointer declarators become valid immediately; the later - * complete definition reuses this type record. - */ - type = add_type(); - type->base_type = is_union ? TYPE_union : TYPE_struct; - set_type_name(type, token); - lex_expect(T_semicolon); - return bb; - } - if (type) { - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - var->is_static = is_static; - var->is_global = is_static; - read_partial_var_decl(var, NULL); - if (is_incomplete_record_object(var)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - 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 (is_static && lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); - } else if (is_static && lex_peek(T_open_curly, NULL)) { - parse_global_record_init(var, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - 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_union || - 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; - - 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, - true); - lex_expect(T_close_curly); - } else { - if (!read_assignment_expression(parent, &bb)) { - 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, - !has_effective_pointer(var) && var->array_size == 0); - - emit_object_assignment(parent, &bb, var, rhs); - } - } - 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); - nv->is_static = is_static; - nv->is_global = is_static; - nv->is_const_qualified = is_const; - read_inner_var_decl(nv, false, false, false); - if (is_incomplete_record_object(nv)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, - NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (is_static && lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); - } else if (is_static && lex_peek(T_open_curly, NULL)) { - parse_global_record_init(nv, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - 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_union || - nv->type->base_type == TYPE_typedef)) { - type_t *struct_type = nv->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, nv, NULL, - 0, NULL); - lex_expect(T_open_curly); - parse_struct_field_init(parent, &bb, struct_type, - struct_addr, true); - 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, - !has_effective_pointer(nv) && nv->array_size == 0); - - emit_object_assignment(parent, &bb, nv, rhs); - } - } - } - lex_expect(T_semicolon); - return bb; - } - error_at("Unknown struct/union type", next_token_loc()); -} - -basic_block_t *handle_enum_declarators(block_t *parent, - basic_block_t *bb, - type_t *type, - bool is_const, - bool is_static) -{ - for (;;) { - var_t *var = require_typed_var(parent, type); - - var->is_const_qualified = is_const; - var->is_static = is_static; - var->is_global = is_static; - read_partial_var_decl(var, NULL); - add_insn(is_static ? GLOBAL_BLOCK : parent, - 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 (is_static) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, - true); - } 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)) { - parse_array_init(var, parent, &bb, true); - } 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, - !has_effective_pointer(var) && var->array_size == 0); - emit_object_assignment(parent, &bb, var, rhs); - } - } - if (is_static) - discard_global_declarator_operand(var); - - if (!lex_accept(T_comma)) - break; - perform_side_effect(parent, bb); - } - lex_expect(T_semicolon); - return bb; -} - -/* 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; -} - -/* A block-scope enum definition contributes integer constants to the current - * expression parser just as a file-scope definition does. Like a record - * definition, it may introduce declarators after the closing brace. - */ -basic_block_t *handle_enum_statement(block_t *parent, - basic_block_t *bb, - bool is_const, - bool is_static) -{ - char token[MAX_ID_LEN]; - int val = 0; - type_t *type = NULL; - bool has_tag = false; - - lex_expect(T_enum); - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - type = find_local_type_tag(token, parent); - has_tag = true; - } - if (!lex_peek(T_open_curly, NULL)) { - if (!has_tag) - error_at("Unknown enum type", next_token_loc()); - if (!type) - type = find_type_tag(token, parent); - if (!type) - error_at("Unknown enum type", next_token_loc()); - return handle_enum_declarators(parent, bb, type, is_const, is_static); - } - if (!type) - type = add_type(); - initialize_enum_type(type); - if (has_tag) { - set_type_name(type, token); - if (!find_local_type_tag(token, parent)) - add_type_tag(parent, 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(parent); - } - add_scoped_constant(parent, token, val); - first = false; - } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); - lex_expect(T_close_curly); - - if (lex_accept(T_semicolon)) - return bb; - return handle_enum_declarators(parent, bb, type, is_const, is_static); -} - -/* 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; - - 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; -} - -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()); -} - -/* 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; - var_t *var; - opcode_t prefix_op = OP_generic; - bool is_const = false; - bool is_static = false; - bool is_extern = false; - bool is_register = false; - bool is_auto = false; - bool is_volatile = false; - - 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 (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()); - - 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, is_const, is_static); - - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { - if (is_extern || is_register) - error_at("Unsupported storage class on record definition", - next_token_loc()); - return handle_record_statement(parent, bb, is_const, is_static); - } - - /* 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 (!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 */ - token_t *type_token = cur_token; - if (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_long, NULL)) { - type = TY_int; - } else { - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) - find_type_flag = 2; - if (find_type_flag == 2) - lex_peek(T_identifier, token); - type = find_type_flag == 2 ? find_type(token, find_type_flag) - : find_visible_type(token, parent); - if (find_type_flag == 2) - cur_token = type_token; - } - } - - if ((is_static || is_extern) && !type) - error_at("Expected declaration after storage class specifier", - next_token_loc()); - - if (type) { - var = require_typed_var(parent, type); - var->is_static = is_static; - var->is_register = is_register; - var->is_global = is_static; - var->is_const_qualified = is_const; - var->is_volatile = is_volatile; - read_full_var_decl(var, false, false, false); - reject_ordinary_typedef_collision(parent, var); - - /* 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 (is_static || is_register || is_auto) - error_at( - "invalid storage class for block function declaration", - cur_token_loc()); - if (is_const || is_volatile || var->is_const_qualified || - var->is_volatile) - error_at("function type cannot be qualified", - cur_token_loc()); - parent->locals.size--; - var->is_block_scope_function_declaration = true; - GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = - var; - read_global_function_declarator(GLOBAL_BLOCK, var, false); - var_t *alias = require_var(parent); - - alias->var_name = var->var_name; - alias->is_extern_function_alias = true; - if (!lex_accept(T_comma)) - return bb; - - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - var->is_volatile = 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 (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()); - if (is_extern) { - if (var->is_func || lex_peek(T_open_bracket, NULL)) { - /* The global helper owns function redeclaration compatibility - * and parameter parsing. Unlike an object declaration it - * consumes the trailing semicolon itself. - */ - parent->locals.size--; - var->is_block_scope_function_declaration = true; - GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = - var; - read_global_function_declarator(GLOBAL_BLOCK, var, false); - - /* Keep a lexical marker so this declaration hides an outer - * automatic object of the same name. - */ - var_t *alias = require_var(parent); - alias->var_name = var->var_name; - alias->is_extern_function_alias = true; - return bb; - } - for (;;) { - var = bind_block_extern_object(parent, var); - if (lex_peek(T_assign, NULL)) - error_at("extern declaration cannot have an initializer", - next_token_loc()); - if (!lex_accept(T_comma)) - break; - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - var->is_volatile = is_volatile; - read_partial_var_decl(var, NULL); - } - lex_expect(T_semicolon); - return bb; - } - if (is_incomplete_record_object(var)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - 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 (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, - true); - } 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, 1); - } 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, - true); - lex_expect(T_close_curly); - } else { - if (!read_assignment_expression(parent, &bb)) { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - } - - var_t *expr_result = opstack_pop(); - - /* 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; - } - - if (incompatible_pointee_callback_conversion(expr_result, var)) - error_at("incompatible callback slot types in initializer", - cur_token_loc()); - diagnose_const_pointer_conversion(expr_result, var); - emit_object_assignment(parent, &bb, var, expr_result); - } - } - if (is_static) - discard_global_declarator_operand(var); - 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); - nv->is_static = is_static; - nv->is_register = is_register; - nv->is_global = is_static; - nv->is_const_qualified = var->is_const_qualified; - nv->is_volatile = var->is_volatile; - read_partial_var_decl(nv, var); /* partial */ - reject_ordinary_typedef_collision(parent, nv); - if (is_incomplete_record_object(nv)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, - NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (is_static) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, - true); - } else if (global_compound_literal_starts_here() && - !(nv->ptr_level || nv->type->ptr_level) && - is_record_type(nv->type)) { - parse_global_compound_record_init(nv, GLOBAL_BLOCK); - } else if (global_compound_literal_starts_here() && - (nv->ptr_level || nv->type->ptr_level)) { - parse_global_compound_array_init(nv, GLOBAL_BLOCK); - } else if (global_compound_literal_starts_here()) { - parse_global_compound_scalar_init(nv, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL)) { - parse_global_record_init(nv, GLOBAL_BLOCK); - } else { - read_global_assignment_var(nv); - } - } else if ((nv->has_unsized_array || nv->array_size > 0) && - is_char_array(nv) && lex_peek(T_string, NULL)) { - parse_string_array_init(nv, parent, &bb); - } else if ((nv->has_unsized_array || nv->array_size > 0) && - is_wchar_array(nv) && lex_peek(T_wstring, NULL)) { - parse_wstring_array_init(nv, parent, &bb); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - /* Emit code for locals */ - parse_array_init(nv, parent, &bb, 1); - } else if (lex_peek(T_open_curly, NULL) && - is_record_type(nv->type)) { - type_t *struct_type = nv->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, nv, NULL, - 0, NULL); - lex_expect(T_open_curly); - parse_struct_field_init(parent, &bb, struct_type, - struct_addr, true); - lex_expect(T_close_curly); - } else { - if (!read_assignment_expression(parent, &bb)) { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - } - - emit_object_assignment(parent, &bb, nv, opstack_pop()); - } - } - if (is_static) - discard_global_declarator_operand(nv); - } - lex_expect(T_semicolon); - return bb; - } - - /* 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); - perform_side_effect(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(); - 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); - - /* 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(); - - int addr_depth = effective_pointer_depth(addr); - unsigned int addr_mask = effective_pointer_const_mask(addr); - if (addr->is_const_qualified || - (addr_depth > 1 && addr_depth <= 32 && - (addr_mask & (1U << (addr_depth - 2))))) - error_at("assignment of read-only location", next_token_loc()); - - /* 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, NULL, 0)) { - perform_side_effect(parent, bb); - while (lex_accept(T_comma)) { - prefix_op = OP_generic; - lex_peek(T_identifier, token); - if (!read_body_assignment(token, parent, prefix_op, &bb, NULL, 0)) - error_at("Expected assignment after comma", next_token_loc()); - 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; -} - -/* 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}; - - lex_expect(T_typedef); - decl.scope = parent; - parsing_block_typedef_declarator = true; - 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; - 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.ptr_level && !decl.array_size && - !decl.pointee_array_size && !base->ptr_level && - !is_record_type(base) && !base->is_floating && - !direct_function_signature->va_args && - !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, and no deeper pointer - * composition. - */ - bool callback_slot_alias = - callback_slot_signature && - decl.parenthesized_function_pointer_level == 2 && - decl.ptr_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_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)); - alias->base_type = TYPE_typedef; - alias->base_struct = is_record_type(base) ? base : base->base_struct; - alias->ptr_level = base->ptr_level + decl.ptr_level; - alias->pointer_const_mask = - base->pointer_const_mask | - (decl.pointer_const_mask << base->ptr_level); - 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) { - 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 (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; - - /* 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); - - 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()) - 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); - - /* struct/union variable declaration */ - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) - return handle_record_statement(parent, bb, false, false); - - if (lex_peek(T_enum, NULL)) - return handle_enum_statement(parent, bb, false, false); - - /* Handle const qualifier for local variable declarations */ - 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); - perform_side_effect(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; - 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; -} - -void print_ptr_level(int level) -{ - while (level > 0) { - printf("*"); - level--; - } -} - -void print_func_decl(func_t *func, const char *prefix, bool newline) -{ - 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 (i != func->num_params - 1) - printf(", "); - } - - if (func->va_args) - printf(", ..."); - printf(")"); - - if (newline) - printf("\n"); -} - -/* 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, true); - 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); -} - -/* 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. - */ -void read_global_function_declarator(block_t *block, var_t *var, bool is_static) -{ - /* 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 (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 (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_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)) { - 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; - } - if (inherited_direct_function_type && lex_peek(T_comma, NULL)) - return; - if (!lex_accept(T_semicolon)) - error_at("Syntax error in global declaration", next_token_loc()); -} - -/* 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; - - 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->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) - 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()); - - /* 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 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)) { - read_global_function_declarator(block, nv, is_static); - return true; - } - 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, true); - 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]; - int find_type_flag = 1; - type_t *compound_type, *target_type; - - lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { - find_type_flag = 2; - lex_ident(T_identifier, type_name); - } else { - lex_ident(T_identifier, type_name); - } - lex_expect(T_close_bracket); - - compound_type = find_type(type_name, find_type_flag); - 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) -{ - char type_name[MAX_ID_LEN]; - type_t *compound_type; - - UNUSED(block); - - lex_expect(T_open_bracket); - lex_ident(T_identifier, type_name); - compound_type = find_type(type_name, true); - 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) -{ - char type_name[MAX_ID_LEN]; - type_t *element_type; - var_t *array; - int find_type_flag = 1; - int element_ptr_level = 0; - - lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { - find_type_flag = 2; - lex_ident(T_identifier, type_name); - } else { - lex_ident(T_identifier, type_name); - } - element_type = find_type(type_name, find_type_flag); - 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, true); - 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, true); - 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_extern) -{ - 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; - 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)) { - read_global_function_declarator(block, var, is_static); - return true; - } - 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, true); - } 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; -} - -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); - - if (lex_peek(T_open_bracket, NULL)) { - read_global_function_declarator(block, var, is_static); - 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;". 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, var->is_const_qualified, - is_static, var->is_volatile, is_extern); - - lex_expect(T_semicolon); - return; -} - -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; -} - -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. */ - 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()); - - 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_accept(T_struct)) { - int i = 0, size = 0, alignment = 1; - bitfield_layout_t bits = {0}; - bool has_flexible_array_member = false; - - 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 && lex_peek(T_semicolon, NULL)) { - decl_type = add_type(); - decl_type->base_type = TYPE_struct; - set_type_name(decl_type, token); - lex_expect(T_semicolon); - return; - } - if (!decl_type) - error_at("Unknown struct type", &id_tk->location); - - if (read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern); - 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); - var_t *last = v; - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, block); - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, false); - size = is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field(flush_bitfield_layout(size, &bits), - v, &alignment); - - /* Handle multiple variable declarations with same base type */ - while (lex_accept(T_comma)) { - if (last->is_flexible_array_member) - error_at( - "Flexible array member must be the final struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, block); - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, false); - last = nv; - size = is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); - } - - lex_expect(T_semicolon); - if (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)); - - size = flush_bitfield_layout(size, &bits); - type->alignment = alignment; - type->size = ALIGN_UP(size, alignment); - type->num_fields = i; - type->has_flexible_array_member = has_flexible_array_member; - - /* A record definition may be followed by its declarators, as in "struct - * pair { int x, y; } first, *second;". - */ - if (!lex_peek(T_semicolon, NULL)) { - if (read_global_record_declarator(block, type, is_const, is_static, - is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static, - is_extern); - } - lex_expect(T_semicolon); - } else if (lex_accept(T_union)) { - int i = 0, max_size = 0, alignment = 1; - bool has_flexible_array_member = false; - - lex_ident(T_identifier, token); - token_t *id_tk = cur_token; - - /* A tagged union declaration may name an already-complete tag, just - * like `struct tag object;`. Do not require a second definition body - * before routing its declarators through the shared record path. - */ - if (!lex_peek(T_open_curly, NULL)) { - type_t *decl_type = find_type(token, 2); - if (!decl_type && lex_peek(T_semicolon, NULL)) { - decl_type = add_type(); - decl_type->base_type = TYPE_union; - set_type_name(decl_type, token); - lex_expect(T_semicolon); - return; - } - if (!decl_type) - error_at("Unknown union type", &id_tk->location); - - if (read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern); - lex_expect(T_semicolon); - return; - } - - /* 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, false, true); - read_bitfield_width(v, block); - has_flexible_array_member |= is_flexible_array_member_container(v); - mark_flexible_array_member(v, true); - v->offset = 0; /* All union fields start at offset 0 */ - int field_size = is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - - /* 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, false, true); - read_bitfield_width(nv, block); - has_flexible_array_member |= - is_flexible_array_member_container(nv); - mark_flexible_array_member(nv, true); - field_size = is_bitfield(nv) - ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(nv) > alignment) - alignment = alignment_var(nv); - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - - type->alignment = alignment; - type->size = ALIGN_UP(max_size, alignment); - type->num_fields = i; - type->has_flexible_array_member = has_flexible_array_member; - - if (!lex_peek(T_semicolon, NULL)) { - if (read_global_record_declarator(block, type, is_const, is_static, - is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static, - is_extern); - } - lex_expect(T_semicolon); - } else if (lex_accept(T_enum)) { - /* An enum definition is a declaration in its own right; it need not - * introduce a typedef. Its enumerators are integer constants and may - * use the same integer constant expressions accepted for array bounds - * and case labels. - */ - int val = 0; - bool has_tag = false; - type_t *type; - - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - has_tag = true; - } - if (!lex_peek(T_open_curly, NULL)) { - if (!has_tag) - error_at("Expected enum tag or definition", cur_token_loc()); - type = find_type(token, true); - if (!type) - error_at("Unknown enum type", cur_token_loc()); - if (read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern)) - return; - while (lex_accept(T_comma)) - read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern); - lex_expect(T_semicolon); - return; - } - type = has_tag ? find_type(token, true) : NULL; - if (!type) - type = add_type(); - - initialize_enum_type(type); - if (has_tag) - set_type_name(type, token); - 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(block); - add_constant(token, val); - first = false; - } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); - lex_expect(T_close_curly); - if (!lex_peek(T_semicolon, NULL)) { - if (read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern)) - return; - while (lex_accept(T_comma)) - read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern); - } - lex_expect(T_semicolon); - } else if (lex_accept(T_typedef)) { - if (lex_accept(T_enum)) { - int val = 0; - type_t *type = add_type(); - - initialize_enum_type(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(block); - add_constant(token, val); - first = false; - } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); - 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, alignment = 1; - bitfield_layout_t bits = {0}; - bool has_struct_def = false; - bool has_flexible_array_member = 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); - var_t *last = v; - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, block); - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, false); - size = - is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), v, - &alignment); - - /* Handle multiple variable declarations with same base type - */ - while (lex_accept(T_comma)) { - if (last->is_flexible_array_member) - error_at( - "Flexible array member must be the final " - "struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, block); - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, false); - last = nv; - size = is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, - &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); - } - - lex_expect(T_semicolon); - if (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)); - } - - while (lex_accept(T_asterisk)) { - type->ptr_level++; - type->size = PTR_SIZE; - } - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - size = flush_bitfield_layout(size, &bits); - type->alignment = type->ptr_level ? PTR_SIZE : alignment; - type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(size, alignment); - type->num_fields = i; - type->base_type = TYPE_typedef; - type->has_flexible_array_member = has_flexible_array_member; - - 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, alignment = 1; - bool has_union_def = false; - bool has_flexible_array_member = 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, false, true); - read_bitfield_width(v, block); - has_flexible_array_member |= - is_flexible_array_member_container(v); - mark_flexible_array_member(v, true); - v->offset = 0; /* All union fields start at offset 0 */ - int field_size = - is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - - /* 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, false, true); - read_bitfield_width(nv, block); - has_flexible_array_member |= - is_flexible_array_member_container(nv); - mark_flexible_array_member(nv, true); - field_size = - is_bitfield(nv) - ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(nv) > alignment) - alignment = alignment_var(nv); - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - } - - while (lex_accept(T_asterisk)) { - type->ptr_level++; - type->size = PTR_SIZE; - } - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - type->alignment = type->ptr_level ? PTR_SIZE : alignment; - type->size = - type->ptr_level ? PTR_SIZE : ALIGN_UP(max_size, alignment); - type->num_fields = i; - type->base_type = TYPE_typedef; - type->is_union = true; - type->has_flexible_array_member = has_flexible_array_member; - - 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(); - bool typedef_const = false; - bool is_signed = false; - bool is_unsigned = false; - bool is_long = false; - bool is_long_long = false; - type_t *leading_scalar_type = NULL; - - if (lex_peek(T_identifier, base_type) && - (!strcmp(base_type, "char") || !strcmp(base_type, "short") || - !strcmp(base_type, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && (after_base->kind == T_signed || - after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(base_type, true); - } - } - - /* Typedef declarations use the same freely ordered scalar specifier - * set as object declarations. Keeping this in a loop admits C99 - * spellings such as `long unsigned long` rather than treating the - * second specifier as the typedef name. - */ - while (lex_peek(T_const, NULL) || lex_peek(T_signed, NULL) || - lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { - if (lex_accept(T_const)) - typedef_const = true; - else 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 { - lex_expect(T_long); - if (is_long_long) - error_at("too many long type specifiers", - cur_token_loc()); - if (is_long) - is_long_long = true; - else - is_long = true; - } - } - if (is_signed && is_unsigned) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && - lex_peek(T_identifier, base_type) && !strcmp(base_type, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - - bool is_float = lex_accept(T_float); - bool is_double = lex_accept(T_double); - - if (is_float) { - if (is_signed || is_unsigned || is_long) - error_at("invalid float type specifiers", cur_token_loc()); - base = TY_float; - } else if (is_double) { - if (is_signed || is_unsigned || is_long_long) - error_at("invalid double type specifiers", cur_token_loc()); - base = is_long ? TY_long_double : TY_double; - } else if (is_long) { - if (lex_peek(T_identifier, base_type) && - !strcmp(base_type, "int")) - lex_expect(T_identifier); - if (is_long_long) - base = is_unsigned ? TY_ulong_long : TY_long_long; - else - base = is_unsigned ? TY_ulong : TY_long; - } else if (is_unsigned) { - if (leading_scalar_type == TY_char) { - base = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, base_type) && - !strcmp(base_type, "int")) - lex_expect(T_identifier); - base = TY_ushort; - } else { - if (lex_peek(T_identifier, base_type) && - (!strcmp(base_type, "int") || - !strcmp(base_type, "char") || - !strcmp(base_type, "short"))) { - lex_expect(T_identifier); - if (!strcmp(base_type, "char")) - base = TY_uchar; - else if (!strcmp(base_type, "short")) - base = TY_ushort; - else - base = TY_uint; - } else { - base = TY_uint; - } - } - } else if (is_signed && lex_peek(T_identifier, base_type) && - (!strcmp(base_type, "char") || - !strcmp(base_type, "short") || - !strcmp(base_type, "int"))) { - lex_expect(T_identifier); - base = !strcmp(base_type, "char") - ? TY_schar - : (!strcmp(base_type, "short") ? TY_short : TY_int); - } else if (is_signed && (leading_scalar_type == TY_char || - leading_scalar_type == TY_short)) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, base_type) && - !strcmp(base_type, "int")) - lex_expect(T_identifier); - base = leading_scalar_type == TY_char ? TY_schar - : leading_scalar_type; - } else if (is_signed && (!lex_peek(T_identifier, base_type) || - (strcmp(base_type, "int") && - strcmp(base_type, "char") && - strcmp(base_type, "short")))) { - base = TY_int; - } else if (leading_scalar_type) { - base = leading_scalar_type; - } else { - 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; - - /* 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_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; - - /* Handle pointer types in typedef: typedef char *string; */ - 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); - } else if (lex_accept(T_volatile) || - lex_accept(T_restrict)) { - ; - } else - break; - } - } - - /* 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; - if (!declarator.is_func) - error_at( - "Typedef parenthesized declarator must be a function " - "pointer", - cur_token_loc()); - 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; - lex_expect(T_semicolon); - return; - } - - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - - /* 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); - - 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); - } - lex_expect(T_semicolon); - } - } 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(); -} +/* 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 */ From 5f5b7c0c30cb7c858909b6f331c132eb078d6f41 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 08:34:16 +0800 Subject: [PATCH 12/40] Fix -E keywords and libc assertion headers Preprocessing a source that used volatile, static, extern, register, auto, restrict, inline, signed, unsigned or long stopped with "Unknown token kind", because token_to_string() had no spelling for them. A dynamic build could not compile shecc, whose elf_write_all() passes a const buffer to the fwrite declared in lib/c.h without const. assert called __assert_fail with three arguments, but glibc's handler takes the function name as a fourth. Spell those keywords in -E output, declare the fwrite buffer const, and pass __func__ to __assert_fail, with the driver expecting the wording of whichever libc reports the failure. --- lib/c.c | 11 +++++++++-- lib/c.h | 11 +++++++++-- src/preprocessor.c | 23 +++++++++++++++++++++++ tests/driver.sh | 18 +++++++++++++++--- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/lib/c.c b/lib/c.c index d97676ca..21a7aa11 100644 --- a/lib/c.c +++ b/lib/c.c @@ -644,9 +644,16 @@ void abort(void) exit(-1); } -void __assert_fail(const char *expr, const char *file, int line) +/* 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) { - printf("Assertion failed: %s, file %s, line %d\n", expr, file, line); + printf("Assertion failed: %s, function %s, file %s, line %d\n", expr, + function, file, line); abort(); } diff --git a/lib/c.h b/lib/c.h index 1f0fa0c3..f5ba2086 100644 --- a/lib/c.h +++ b/lib/c.h @@ -126,7 +126,7 @@ 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 char *ptr, int size, int nmemb, FILE *stream); int fseek(FILE *stream, int offset, int whence); int ftell(FILE *stream); @@ -153,7 +153,14 @@ int fflush(FILE *stream); /* Terminating program */ void exit(int exit_code); void abort(void); -void __assert_fail(const char *expr, const char *file, int line); + +/* 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); diff --git a/src/preprocessor.c b/src/preprocessor.c index 528c088a..cd04b86f 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -471,6 +471,9 @@ void install_assert_header(void) 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, @@ -3056,6 +3059,26 @@ 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: diff --git a/tests/driver.sh b/tests/driver.sh index 09d99476..71eb485e 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -18549,6 +18549,18 @@ EOF 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 +} + try_ 0 << EOF #include int main(void) { int calls = 0; assert(++calls == 1); return calls - 1; } @@ -18563,7 +18575,7 @@ try_ 0 << EOF #include int main(void) { int calls = 0; (assert(++calls), calls); return calls; } EOF -try_failure_output "Assertion failed: 0" << EOF +try_failure_output "$(assertion_message "0")" << EOF #define NDEBUG #include #undef NDEBUG @@ -18576,11 +18588,11 @@ try_ 0 << EOF #include int main(void) { int calls = 0; assert(++calls); return calls; } EOF -try_failure_output "Assertion failed: 2 + 2 == 5" << 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 failed: SUM(1, 1) == 3" << 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; } From 7417de0ab0210fbe9571fc9ada19a9d7b5803961 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:42 +0800 Subject: [PATCH 13/40] Move AArch64 instruction encoding to src/arm64.c The AArch64 backend spelled every instruction as a hexadecimal constant ORed with register and immediate fields, inline in the code generator, so reading what it emitted meant decoding each word by hand. The Arm, RISC-V and x86-64 backends already keep their encoding in a file of their own. src/arm64.c now holds one helper per instruction form, each returning its 32-bit word, with the register and condition-code names. The code generator composes them and keeps everything that reads the IR, plus the range checks. The size field of a load or store is computed as an unsigned int, so it does not rely on -fwrapv. Generated code is unchanged. --- src/arm64-codegen.c | 237 +++++++++++++------------------ src/arm64.c | 330 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 427 insertions(+), 140 deletions(-) create mode 100644 src/arm64.c diff --git a/src/arm64-codegen.c b/src/arm64-codegen.c index 762d3a01..8c913c13 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,10 +46,10 @@ 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 @@ -63,8 +60,8 @@ void a64_mov_imm_unsigned(int d, int v) { unsigned int value = v; - emit(0xd2800000 | ((value & 0xffff) << 5) | d); - emit(0xf2800000 | (((value >> 16) & 0xffff) << 5) | d | (1 << 21)); + 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 @@ -75,48 +72,52 @@ void a64_mov_imm_wide(int d, int lo, int hi) { unsigned int low = lo, high = hi; - emit(0xd2800000 | ((low & 0xffff) << 5) | d); - emit(0xf2800000 | (((low >> 16) & 0xffff) << 5) | d | (1 << 21)); - emit(0xf2800000 | ((high & 0xffff) << 5) | d | (2 << 21)); - emit(0xf2800000 | (((high >> 16) & 0xffff) << 5) | d | (3 << 21)); + 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)); } -int a64_div_opcode(ph2_ir_t *p) +/* Rd = Rn / Rm, unsigned when either operand is. */ +int a64_div_insn(ph2_ir_t *p, int d, int n, int m) { - if (p->size_bytes == 8) { - if (p->src0_is_unsigned || p->src1_is_unsigned) - return 0x9ac00800; /* UDIV Xd, Xn, Xm */ - return 0x9ac00c00; /* SDIV Xd, Xn, Xm */ - } + bool sf = p->size_bytes == 8; + if (p->src0_is_unsigned || p->src1_is_unsigned) - return 0x1ac00800; /* UDIV Wd, Wn, Wm */ - return 0x1ac00c00; /* SDIV Wd, Wn, Wm */ + return a64_udiv_insn(sf, d, n, m); + return a64_sdiv_insn(sf, d, n, m); } -int a64_rshift_opcode(ph2_ir_t *p) +/* Rd = Rn >> Rm, logical for an unsigned left operand. */ +int a64_rshift_insn(ph2_ir_t *p, int d, int n, int m) { - if (p->size_bytes == 8) { - if (p->src0_is_unsigned) - return 0x9ac02400; /* LSRV Xd, Xn, Xm */ - return 0x9ac02800; /* ASRV Xd, Xn, Xm */ - } + bool sf = p->size_bytes == 8; + if (p->src0_is_unsigned) - return 0x1ac02400; /* LSRV Wd, Wn, Wm */ - return 0x1ac02800; /* ASRV Wd, Wn, Wm */ + 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 @@ -125,11 +126,11 @@ 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 @@ -138,12 +139,12 @@ void a64_sub_sxtw(int d, int n, int m) void a64_extend(int d, int n, int size, bool is_unsigned) { if (size == 1) - emit((is_unsigned ? 0xd3401c00 : 0x93401c00) | (n << 5) | d); + emit(is_unsigned ? a64_zext_insn(d, n, 8) : a64_sext_insn(d, n, 8)); else if (size == 2) - emit((is_unsigned ? 0xd3403c00 : 0x93403c00) | (n << 5) | d); + emit(is_unsigned ? a64_zext_insn(d, n, 16) : a64_sext_insn(d, n, 16)); else if (size == 4) { if (is_unsigned) - emit(0xd3407c00 | (n << 5) | d); + emit(a64_zext_insn(d, n, 32)); else a64_sxtw(d, n); } else @@ -156,46 +157,12 @@ void a64_extend(int d, int n, int size, bool is_unsigned) * 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) -{ - 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 { - 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 (size != 1 && size != 2 && size != 4 && size != 8) + fatal("unsupported arm64 access width"); if (ofs >= -256 && ofs <= 255 && (ofs < 0 || ofs % size)) { - emit(op | ((ofs & 0x1ff) << 12) | (rn << 5) | rt); /* LDUR/STUR */ + emit(a64_mem_unscaled_insn(load, size, rt, rn, ofs)); return; } if (ofs < 0 || ofs > 4095 * size || ofs % size) { @@ -204,7 +171,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(load, size, rt, rn, ofs)); } /* Masking an out-of-range displacement silently branches somewhere else. The @@ -227,26 +194,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 @@ -270,21 +236,21 @@ bool a64_cmp_wide(ph2_ir_t *p) return p->size_bytes == 8 || p->src0_is_pointer || p->src1_is_pointer; } -int a64_cond(opcode_t op, bool is_unsigned) +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 is_unsigned ? 2 : 10; /* HS / GE */ + return is_unsigned ? A64_HS : A64_GE; case OP_lt: - return is_unsigned ? 3 : 11; /* LO / LT */ + return is_unsigned ? A64_LO : A64_LT; case OP_gt: - return is_unsigned ? 8 : 12; /* HI / GT */ + return is_unsigned ? A64_HI : A64_GT; default: - return is_unsigned ? 9 : 13; /* LS / LE */ + return is_unsigned ? A64_LS : A64_LE; } } @@ -433,14 +399,15 @@ void emit_ph2_ir(ph2_ir_t *p) bool reload_global_base = dynlink && !strcmp(p->func_name, "main"); fatal_function_context = p->func_name; - emit(0xa9bf7bfd); /* stp x29, x30, [sp, #-16]! */ - emit(0x910003fd); /* mov x29, sp */ - emit(0xa9bf57f4); /* stp x20, x21, [sp, #-16]! */ + 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); @@ -523,44 +490,38 @@ void emit_ph2_ir(ph2_ir_t *p) a64_sub(d, n, m); return; case OP_mul: - emit((p->size_bytes == 8 ? 0x9b007c00 : 0x1b007c00) | (m << 16) | - (n << 5) | d); + emit(a64_mul_insn(p->size_bytes == 8, d, n, m)); return; case OP_div: - emit(a64_div_opcode(p) | (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(a64_div_opcode(p) | (m << 16) | (n << 5) | A64_IP0); - emit((p->size_bytes == 8 ? 0x9b008000 : 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((p->size_bytes == 8 ? 0x9ac02000 : 0x1ac02000) | (m << 16) | - (n << 5) | d); + emit(a64_lslv_insn(p->size_bytes == 8, d, n, m)); return; case OP_rshift: - emit(a64_rshift_opcode(p) | (m << 16) | (n << 5) | d); + emit(a64_rshift_insn(p, d, n, m)); return; case OP_bit_and: - emit((p->size_bytes == 8 ? 0x8a000000 : 0x0a000000) | (m << 16) | - (n << 5) | d); + emit(a64_and_reg_insn(p->size_bytes == 8, d, n, m)); return; case OP_bit_or: - emit((p->size_bytes == 8 ? 0xaa000000 : 0x2a000000) | (m << 16) | - (n << 5) | d); + emit(a64_orr_reg_insn(p->size_bytes == 8, d, n, m)); return; case OP_bit_xor: - emit((p->size_bytes == 8 ? 0xca000000 : 0x4a000000) | (m << 16) | - (n << 5) | d); + emit(a64_eor_reg_insn(p->size_bytes == 8, d, n, m)); return; case OP_negate: - emit((p->size_bytes == 8 ? 0xcb0003e0 : 0x4b0003e0) | (n << 16) | d); + emit(a64_neg_insn(p->size_bytes == 8, d, n)); return; case OP_bit_not: - emit((p->size_bytes == 8 ? 0xaa2003e0 : 0x2a2003e0) | (n << 16) | d); + emit(a64_mvn_insn(p->size_bytes == 8, d, n)); return; case OP_eq: case OP_neq: @@ -568,12 +529,10 @@ 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, p->src0_is_unsigned || p->src1_is_unsigned) ^ 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 @@ -583,8 +542,8 @@ void emit_ph2_ir(ph2_ir_t *p) * 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, 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 @@ -625,8 +584,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: @@ -658,17 +616,17 @@ void emit_ph2_ir(ph2_ir_t *p) 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"); @@ -690,10 +648,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(true, 8, A64_IP1, A64_IP0, 0)); + elf_write_int(dynamic_sections.elf_plt, a64_br_insn(A64_IP1)); } } @@ -709,7 +667,7 @@ void code_generate(void) if (dynlink) a64_mov_sp(25); a64_mem(1, 8, 20, A64_SP, 0); - emit(0x910023f5); + emit(a64_add_imm_insn(true, 21, A64_SP, 8)); a64_mov(23, 20); a64_mov(24, 21); if (dynlink) { @@ -753,8 +711,8 @@ 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); @@ -770,14 +728,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..d3e36e3d --- /dev/null +++ b/src/arm64.c @@ -0,0 +1,330 @@ +/* + * 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; +} + +/* 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. @size must be 1, 2, 4 + * or 8. + */ +int a64_mem_op(bool load, 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 (!load) + return 0x38000000 | (sz << 30); + return 0x38000000 | (sz << 30) | ((size == 8 ? 1 : 2) << 22); +} + +/* LDUR/STUR: a signed 9-bit byte offset. */ +int a64_mem_unscaled_insn(bool load, int size, int rt, int rn, int ofs) +{ + return a64_mem_op(load, 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(bool load, int size, int rt, int rn, int ofs) +{ + return a64_mem_op(load, 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(bool load, int size, int rt, int rn, int ofs) +{ + return a64_mem_op(load, 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; +} From 3c1c3b7fd7bb5259a43663087e7bd5f1a82bac11 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:40 +0800 Subject: [PATCH 14/40] Fix backend miscompiles found by self-hosting Several backend paths produced wrong code once shecc compiled itself under dynamic linking or on 32-bit targets. A dynamic program's global frame began with the loader's leftovers. x86-64 casts between types of one width and different signedness did not re-extend the register. An eight-byte record could be placed in a register pair. Arm wide equality took the ordering sequence and emitted more instructions than the size estimate charged, wide slots at the top of the 12-bit range and halfword accesses past 255 bytes used unreachable immediates, a variadic function saved its parameters by source parameter rather than by ABI word, and the ELF32 section header offset ignored the static page padding. Clear the global frame with an inline loop, re-extend casts that change signedness, keep eight-byte records out of pairs, give Arm equality its own four-instruction sequence and choose one offset form for the emitter and the estimator alike, save variadic parameters word by word, and count the padding in e_shoff. --- src/arm-codegen.c | 80 +++++++++++++++++++++++++-------- src/arm64-codegen.c | 20 +++++---- src/elf.c | 9 +++- src/reg-alloc.c | 28 ++++++++---- src/riscv-codegen.c | 21 +++++++-- src/x64-codegen.c | 11 ++++- tests/driver.sh | 107 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 235 insertions(+), 41 deletions(-) diff --git a/src/arm-codegen.c b/src/arm-codegen.c index 6b4251ad..339fd491 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; @@ -57,12 +75,13 @@ void update_elf_offset(ph2_ir_t *ph2_ir) return; case OP_load: case OP_global_load: - /* LDR/LDRB use a 12-bit offset, but LDRSB uses eight bits. A larger - * signed-byte offset is materialized with MOVW/MOVT/ADD before the - * load, exactly as an offset beyond the general load range is. + /* 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 || (ph2_ir->size_bytes == 1 && - !ph2_ir->is_unsigned && ph2_ir->src0 > 255)) + if (arm_offset_needs_movw(ph2_ir)) if (ph2_ir->dest_hi >= 0) elf_offset += 20; else @@ -78,9 +97,10 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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) + if (arm_offset_needs_movw(ph2_ir)) if (ph2_ir->src0_hi >= 0) elf_offset += 20; else @@ -247,8 +267,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 */ @@ -434,7 +455,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_global_load: interm = ph2_ir->op == OP_load ? __sp : __r12; if (ph2_ir->dest_hi >= 0) { - if (ph2_ir->src0 > 4095) { + 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)); @@ -447,12 +468,10 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; } - /* LDRSB's immediate field is only eight bits, unlike LDRB's 12-bit - * field. Materialize a larger signed-byte offset before loading. + /* LDRSB, LDRH and LDRSH have only eight immediate bits, unlike LDRB's + * 12-bit field. Materialize a larger offset before loading. */ - if (ph2_ir->src0 > 4095 || - (ph2_ir->size_bytes == 1 && !ph2_ir->is_unsigned && - ph2_ir->src0 > 255)) { + 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)); @@ -478,7 +497,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) interm = ph2_ir->op == OP_store ? __sp : __r12; store_offset = ph2_ir->src1; if (ph2_ir->src0_hi >= 0) { - if (store_offset > 4095) { + 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)); @@ -494,7 +513,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) /* STRH has an 8-bit split immediate while STR/STRB accept 12 bits. * Materialize larger halfword offsets before selecting the width. */ - if (store_offset > (ph2_ir->size_bytes == 2 ? 255 : 4095)) { + 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)); @@ -945,7 +964,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_lt: case OP_geq: case OP_leq: - if (ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { + /* Wide equality compares the low words and, only when they match, the + * high words, below. The ordering sequence here would leave a stale + * result when the high words differ. + */ + 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; arm_cond_t true_cond = arm_get_cond(ph2_ir->op, unsigned_cmp); @@ -1169,6 +1193,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/arm64-codegen.c b/src/arm64-codegen.c index 8c913c13..d5a8846f 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -317,9 +317,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) { @@ -329,7 +329,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; @@ -693,13 +693,17 @@ void code_generate(void) a64_mov_imm(A64_IP0, elf_data_start); a64_mem(0, 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(false, 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. */ diff --git a/src/elf.c b/src/elf.c index 76ed9f69..91688bb3 100644 --- a/src/elf.c +++ b/src/elf.c @@ -294,8 +294,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; } diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 56d1006f..d14fe782 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -91,7 +91,7 @@ int var_slot_size(var_t *v) /* A direct wide scalar needs both words even when its address never * escapes: spilling an SSA temporary is still a memory round trip. */ - if (v->type->base_type != TYPE_struct && v->type->size == 8) + if (!is_record_type(v->type) && v->type->size == 8) return 8; if (!v->address_taken) return PTR_SIZE; @@ -155,8 +155,8 @@ int vreg_get_phys_hi(var_t *var) 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->base_type != TYPE_struct && var->type->size == 8; + !var->array_size && var->type && !is_record_type(var->type) && + var->type->size == 8; } /* ABI argument locations are measured in machine words, not source parameters. @@ -3470,8 +3470,20 @@ void reg_alloc(void) src0 = MAX_ARGS_IN_REG; } - 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 < args_in_reg; 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; } @@ -3480,10 +3492,10 @@ void reg_alloc(void) ir->src0 = src0; ir->src1 = func->stack_size; func->stack_size += PTR_SIZE; - if (i < args_in_reg) { - var_t *param = var_subscript0(&func->param_defs[i]); + if (param_idx >= 0) { + var_t *param = var_subscript0(&func->param_defs[param_idx]); - if (i + 1 == func->num_params && + if (param_idx + 1 == func->num_params && param->is_aggregate_param) { int footprint = ALIGN_UP(param->type->size, PTR_SIZE); diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 05b02a4c..67403c5b 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -269,13 +269,14 @@ void cfg_flatten(void) func_t *func; if (dynlink) { - /* When using dynamic linking, 20 instructions are generated at the + /* When using dynamic linking, 24 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 = 96; } else { /* Under static linking, "__syscall" must be generated to allow the * program to invoke system calls. @@ -1154,6 +1155,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/x64-codegen.c b/src/x64-codegen.c index 03cb0193..82d08345 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -2944,11 +2944,18 @@ void emit_logic_cast(ph2_ir_t *ph2_ir, int rd, int rs1) return; case OP_cast: { - /* Equal-width signed-to-unsigned casts still need to discard the source - * register's sign extension on LP64. + /* 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); } diff --git a/tests/driver.sh b/tests/driver.sh index 71eb485e..7fb4d9e0 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -665,6 +665,34 @@ 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" @@ -8196,6 +8224,29 @@ int main(void) { } 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; @@ -11653,6 +11704,32 @@ int main(void) { } 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 @@ -11677,6 +11754,14 @@ 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 int main(void) { return table[1499] + table[3]; } 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 + # Category: Const Qualifiers begin_category "Const Qualifiers" "Testing const qualifier support for variables and parameters" @@ -18683,6 +18768,28 @@ int main(void) { } EOF +# 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 + # 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 From 13e1d6dcf5bf2dcbffbdb3fca1c85b78da9b1603 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:41 +0800 Subject: [PATCH 15/40] Fix declarator, initializer and sizeof edge cases A block declaration such as "unsigned int a, b;" gave b the type int. A parameter list matched void by spelling rather than as the keyword. An automatic char array initialized by a string kept stack garbage after it, and every string literal lost the bytes after an embedded NUL. The long long global initializer folder converted operands inconsistently and rejected character constants. sizeof("abc") was refused in a global initializer, and several sizeof paths left their result non-constant. handle_declaration also carried a block of unreachable code. Give every block declarator the resolved type, match only the void keyword, zero the tail of automatic char arrays, track decoded literal lengths, convert wide initializer operands by the usual arithmetic conversions, accept grouped string literals in sizeof and mark every sizeof result constant, and remove the dead code. --- src/elf.c | 9 +-- src/parser-call.c | 49 ------------ src/parser-const.c | 179 ++++++++++++++++++++++++++++++-------------- src/parser-decl.c | 22 +++--- src/parser-global.c | 6 +- src/parser-init.c | 18 ++--- src/parser-sizeof.c | 108 +++++++++++--------------- src/parser-stmt.c | 103 +++---------------------- src/parser.c | 15 +++- tests/driver.sh | 153 +++++++++++++++++++++++++++++++++++++ 10 files changed, 369 insertions(+), 293 deletions(-) diff --git a/src/elf.c b/src/elf.c index 91688bb3..9483fc56 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, diff --git a/src/parser-call.c b/src/parser-call.c index d43d9717..851e5ebf 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -1638,55 +1638,6 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) } } -/* 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. - */ -bool stmt_starts_assignment(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: - 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; -} - void handle_multiple_dereference(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1; diff --git a/src/parser-const.c b/src/parser-const.c index 419425ec..1f44d009 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -122,24 +122,6 @@ static int read_global_sizeof_expression(block_t *scope) return result->init_val; } -static int read_const_string_size(void) -{ - char buffer[MAX_STRING_LEN]; - char unescaped[MAX_STRING_LEN]; - int res = 0; - - do { - int length; - - lex_ident(T_string, buffer); - length = unescape_string(buffer, unescaped, sizeof(unescaped)); - if (length < 0) - error_at("Invalid escape sequence", cur_token_loc()); - res += length; - } while (lex_peek(T_string, buffer)); - return res + 1; -} - int read_const_wstring_size(void) { int values[MAX_STRING_LEN]; @@ -147,6 +129,33 @@ int read_const_wstring_size(void) return (read_wstring_units(values, MAX_STRING_LEN) + 1) * wide_type->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. + */ +static 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; +} + int read_primary_constant(block_t *scope) { /* return signed constant */ @@ -171,6 +180,8 @@ int read_primary_constant(block_t *scope) token_t *nested_root = NULL; token_t *nested_after = NULL; int nested_groups = 0; + int string_groups = sizeof_grouped_literal_depth(inside, T_string); + int wstring_groups = sizeof_grouped_literal_depth(inside, T_wstring); if (inside && inside->kind == T_open_bracket) { token_t *token = inside; @@ -198,15 +209,22 @@ int read_primary_constant(block_t *scope) * preserve it without a global-prefix spelling table. */ res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_string) { - lex_expect(T_open_bracket); + } else if (string_groups >= 0 || wstring_groups >= 0) { + /* sizeof's own parenthesis and any grouping inside it enclose the + * literal array; none of them is part of its extent. + */ + int groups = string_groups >= 0 ? string_groups : wstring_groups; + lex_expect(T_open_bracket); - res = read_const_string_size(); - lex_expect(T_close_bracket); + for (int i = 0; i < groups; i++) + lex_expect(T_open_bracket); + if (string_groups >= 0) + res = read_sizeof_string_literal(); + else + res = read_const_wstring_size(); + for (int i = 0; i < groups; i++) + lex_expect(T_close_bracket); lex_expect(T_close_bracket); - } else if (inside && inside->kind == T_wstring) { - res = read_const_wstring_size(); } else if (nested_array && nested_array->array_size > 0 && nested_groups > 0 && nested_after && nested_after->kind == T_close_bracket) { @@ -425,7 +443,7 @@ int read_primary_constant(block_t *scope) read_primary_constant(scope); res = TY_int->size; } else if (lex_peek(T_string, NULL)) { - res = read_const_string_size(); + res = read_sizeof_string_literal(); } else if (lex_peek(T_wstring, NULL)) { res = read_const_wstring_size(); } else if (lex_peek(T_ampersand, NULL)) { @@ -697,6 +715,32 @@ int read_global_address_offset(block_t *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; +} + +/* 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. @@ -721,6 +765,28 @@ bool emit_wide_global_word_arithmetic(block_t *parent, return true; } + /* 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 @@ -734,18 +800,6 @@ bool emit_wide_global_word_arithmetic(block_t *parent, bool less; bool comparison; - /* Materialize the usual arithmetic conversion in word form. A narrow - * signed source sign-extends to either signed long long or unsigned - * long long; a narrow unsigned source zero-extends in both cases. - */ - if (left->type->size <= TY_int->size) - hi = left->type->is_unsigned || !(lo & 0x80000000U) ? 0 - : 0xffffffffU; - if (right->type->size <= TY_int->size) - rhs_hi = right->type->is_unsigned || !(rhs_lo & 0x80000000U) - ? 0 - : 0xffffffffU; - if (common->size <= TY_int->size) { equal = lo == rhs_lo; if (common->is_unsigned) @@ -784,10 +838,7 @@ bool emit_wide_global_word_arithmetic(block_t *parent, comparison = !less; break; } - result->init_val = comparison; - result->init_val_hi = 0; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + store_wide_global_word_result(parent, bb, result, comparison, 0); return true; } @@ -795,17 +846,22 @@ bool emit_wide_global_word_arithmetic(block_t *parent, bool left_true = lo || hi; bool right_true = rhs_lo || rhs_hi; - result->init_val = op == OP_log_and ? left_true && right_true - : left_true || right_true; - result->init_val_hi = 0; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + 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; - bool is_unsigned = left->type->is_unsigned || right->type->is_unsigned; + + /* 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); @@ -852,10 +908,7 @@ bool emit_wide_global_word_arithmetic(block_t *parent, out_hi = ~out_hi + (out_lo == 0); } } - result->init_val = out_lo; - result->init_val_hi = out_hi; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + store_wide_global_word_result(parent, bb, result, out_lo, out_hi); return true; } @@ -933,10 +986,7 @@ bool emit_wide_global_word_arithmetic(block_t *parent, default: return false; } - result->init_val = out_lo; - result->init_val_hi = out_hi; - result->is_const = true; - add_insn(parent, bb, OP_load_constant, result, NULL, NULL, 0, NULL); + store_wide_global_word_result(parent, bb, result, out_lo, out_hi); return true; } @@ -1014,7 +1064,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, value = require_typed_var(parent, find_type("size_t", true)); value->var_name = gen_name(); if (lex_peek(T_string, NULL)) - value->init_val = read_const_string_size(); + value->init_val = read_sizeof_string_literal(); else if (lex_peek(T_wstring, NULL)) value->init_val = read_const_wstring_size(); else if (lex_peek(T_numeric, NULL)) @@ -1023,7 +1073,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, cur_token->next->next && cur_token->next->next->kind == T_string) { lex_expect(T_open_bracket); - value->init_val = read_const_string_size(); + value->init_val = read_sizeof_string_literal(); lex_expect(T_close_bracket); } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && cur_token->next->next && @@ -1098,6 +1148,19 @@ var_t *read_wide_global_literal_primary(block_t *parent, 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()); diff --git a/src/parser-decl.c b/src/parser-decl.c index 764dc5e5..49cedc4c 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -1295,7 +1295,7 @@ void read_parameter_list_decl(func_t *func, bool anon) /* C99's empty parameter list is deliberately not a prototype. */ if (lex_accept(T_close_bracket)) return; - if (lex_peek(T_identifier, token) && !strncmp(token, "void", 4)) { + if (lex_peek(T_identifier, token) && !strcmp(token, "void")) { lex_next(); if (lex_accept(T_close_bracket)) { func->has_prototype = true; @@ -1368,10 +1368,8 @@ void read_parameter_list_decl(func_t *func, bool anon) void read_literal_param(block_t *parent, basic_block_t *bb) { char combined[MAX_STRING_LEN]; - - read_concatenated_string(combined); - - const int index = write_symbol(combined); + 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(); @@ -1392,10 +1390,9 @@ void parse_string_array_init(var_t *var, block_t *parent, basic_block_t **bb) { char combined[MAX_STRING_LEN]; int len; + int count; - read_concatenated_string(combined); - - len = strlen(combined) + 1; + len = read_concatenated_string(combined) + 1; if (var->has_unsized_array) { var->array_size = len; var->has_unsized_array = false; @@ -1403,12 +1400,17 @@ void parse_string_array_init(var_t *var, block_t *parent, basic_block_t **bb) error_at("String initializer is too long for character array", cur_token_loc()); - for (int i = 0; i < len; i++) { + /* 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 = (unsigned char) combined[i]; + 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); diff --git a/src/parser-global.c b/src/parser-global.c index b2ea3732..2e792c25 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -1013,8 +1013,10 @@ static void read_global_typedef_declarators(block_t *block) lex_expect(T_semicolon); } -/* A file-scope typedef, after its keyword: record, enum, function and scalar - * aliases, with every comma-separated declarator. +/* A file-scope typedef, after its keyword: record, untagged enum, callback + * pointer and scalar aliases. Each form reads a single declarator; a + * comma-separated declarator list is only parsed by the block-scope typedef + * reader. */ static void read_global_typedef(block_t *block) { diff --git a/src/parser-init.c b/src/parser-init.c index 6021c30b..a8386326 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -634,7 +634,8 @@ void parse_array_field_hyperplane_init(block_t *parent, * 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. + * 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) { @@ -642,15 +643,16 @@ int read_concatenated_string(char *combined) int used; lex_ident(T_string, literal); - unescape_string(literal, combined, MAX_STRING_LEN); - used = strlen(combined); + 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); - unescape_string(literal, combined + used, MAX_STRING_LEN - used); - added = strlen(combined + used); - if (used + added >= MAX_STRING_LEN - 1) + 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; } @@ -671,9 +673,7 @@ void parse_string_field_init(block_t *parent, char combined[MAX_STRING_LEN]; int len; - read_concatenated_string(combined); - - len = strlen(combined) + 1; + len = read_concatenated_string(combined) + 1; if (len > field->array_size) error_at("String initializer is too long for character array", cur_token_loc()); diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index b7de80c2..546f6ef0 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -25,19 +25,35 @@ int read_sizeof_string_literal(void) 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); - unescape_string(literal, unescaped, MAX_STRING_LEN); - size += strlen(unescaped); + 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; } -int read_sizeof_wstring_literal(void) +/* 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. + */ +static void push_sizeof_result(block_t *parent, basic_block_t *bb, int size) { - 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; + var_t *result = require_var(parent); + + 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) @@ -297,7 +313,6 @@ static bool read_sizeof_postfix_operand(block_t *parent, { bool open_consumed = false; var_t object; - var_t *result; int size; if (!walk_local_sizeof_operand(parent, cur_token->next, parenthesized, @@ -326,11 +341,7 @@ static bool read_sizeof_postfix_operand(block_t *parent, size = PTR_SIZE; else size = object.type->size; - result = require_var(parent); - result->init_val = size; - result->var_name = gen_name(); - opstack_push(result); - add_insn(parent, *bb, OP_load_constant, result, NULL, NULL, 0, NULL); + push_sizeof_result(parent, *bb, size); return true; } @@ -353,7 +364,6 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) token_t *sizeof_tk = cur_token; type_t *type = NULL; bool is_function = false; - var_t *vd; bool parenthesized = lex_accept(T_open_bracket); @@ -368,13 +378,11 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (lex_peek(T_string, NULL) && (!parenthesized || (after_string && after_string->kind == T_close_bracket))) { - vd = require_var(parent); - vd->init_val = read_sizeof_string_literal(); - vd->var_name = gen_name(); + int size = read_sizeof_string_literal(); + if (parenthesized) lex_expect(T_close_bracket); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + push_sizeof_result(parent, *bb, size); return; } @@ -384,13 +392,11 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (lex_peek(T_wstring, NULL) && (!parenthesized || (after_wstring && after_wstring->kind == T_close_bracket))) { - vd = require_var(parent); - vd->init_val = read_sizeof_wstring_literal(); - vd->var_name = gen_name(); + int size = read_const_wstring_size(); + if (parenthesized) lex_expect(T_close_bracket); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + push_sizeof_result(parent, *bb, size); return; } @@ -405,11 +411,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (parent->func && lex_peek(T_identifier, token) && !strcmp(token, "__func__")) { lex_expect(T_identifier); - vd = require_var(parent); - vd->init_val = strlen(parent->func->return_def.var_name) + 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + push_sizeof_result(parent, *bb, + strlen(parent->func->return_def.var_name) + 1); return; } @@ -418,12 +421,7 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (array && array->array_size > 0) { lex_expect(T_identifier); - vd = require_var(parent); - vd->init_val = sizeof_array_object(array); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, - NULL); + push_sizeof_result(parent, *bb, sizeof_array_object(array)); return; } } @@ -445,15 +443,13 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (is_function && ptr_cnt == 0) error_at("sizeof(function) is invalid", &sizeof_tk->location); - vd = require_var(parent); - vd->init_val = type->size; + int size = type->size; + if (array_size > 0) - vd->init_val = array_size * type->size; + size = array_size * type->size; if (ptr_cnt) - vd->init_val = PTR_SIZE; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + size = PTR_SIZE; + push_sizeof_result(parent, *bb, size); return; } @@ -468,11 +464,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) cur_token->next->next->kind == T_close_bracket) { lex_expect(T_identifier); lex_expect(T_close_bracket); - vd = require_var(parent); - vd->init_val = strlen(parent->func->return_def.var_name) + 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + push_sizeof_result(parent, *bb, + strlen(parent->func->return_def.var_name) + 1); return; } @@ -487,12 +480,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (array && array->array_size > 0) { lex_expect(T_identifier); - vd = require_var(parent); - vd->init_val = sizeof_array_object(array); - 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); + push_sizeof_result(parent, *bb, sizeof_array_object(array)); return; } } @@ -806,20 +795,13 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (!array_size && !ptr_cnt && type->array_size) array_size = type->array_size; - vd = require_var(parent); - vd->init_val = type->size; + int size = type->size; + if (array_size > 0) - vd->init_val = + size = array_size * (array_element_size ? array_element_size : type->size); if (ptr_cnt) - vd->init_val = PTR_SIZE; - - /* VLA is intentionally outside shecc's C99 scope, so every admitted sizeof - * result is an integer constant expression. - */ - vd->is_const = true; - vd->var_name = gen_name(); - opstack_push(vd); + size = PTR_SIZE; lex_expect(T_close_bracket); - add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + push_sizeof_result(parent, *bb, size); } diff --git a/src/parser-stmt.c b/src/parser-stmt.c index f6d8ac44..41cc9878 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -1056,6 +1056,15 @@ static basic_block_t *read_block_declarators( read_full_var_decl(var, false, false, false); 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 @@ -1345,7 +1354,6 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) char token[MAX_ID_LEN]; func_t *func; type_t *type; - opcode_t prefix_op = OP_generic; block_decl_specifiers_t spec = {0}; while (lex_peek(T_static, NULL) || lex_peek(T_extern, NULL) || @@ -1522,99 +1530,6 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) return n; } return read_full_expression_statement(parent, 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(); - - int addr_depth = effective_pointer_depth(addr); - unsigned int addr_mask = effective_pointer_const_mask(addr); - if (addr->is_const_qualified || - (addr_depth > 1 && addr_depth <= 32 && - (addr_mask & (1U << (addr_depth - 2))))) - error_at("assignment of read-only location", next_token_loc()); - - /* 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, NULL, 0)) { - perform_side_effect(parent, bb); - while (lex_accept(T_comma)) { - prefix_op = OP_generic; - lex_peek(T_identifier, token); - if (!read_body_assignment(token, parent, prefix_op, &bb, NULL, 0)) - error_at("Expected assignment after comma", next_token_loc()); - 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; } /* Lexical typedef aliases are declaration-only bindings on the current block, diff --git a/src/parser.c b/src/parser.c index 31885755..ffb1f4b4 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1134,15 +1134,24 @@ void discard_global_declarator_operand(var_t *var) void read_expr(block_t *parent, basic_block_t **bb); -int write_symbol(const char *data) +/* 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) { - /* Write string literals to .rodata section */ const int start_len = elf_rodata->size; - elf_write_str(elf_rodata, data); + + elf_write_blk(elf_rodata, data, length); elf_write_byte(elf_rodata, 0); return start_len; } +/* Write the null-terminated string @data to .rodata. */ +int write_symbol(const char *data) +{ + return write_string_symbol(data, strlen(data)); +} + int write_wide_symbol(const int *data, int length) { int start_len = elf_rodata->size; diff --git a/tests/driver.sh b/tests/driver.sh index 7fb4d9e0..3286f649 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1214,6 +1214,24 @@ int main(void) { } EOF +# 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 + # C99 _Bool conversions store the truth value, not a truncated source byte. try_ 4 << EOF _Bool echo_bool(_Bool value) { return value; } @@ -2338,6 +2356,58 @@ int main(void) { ((-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 fi try_ 1 << EOF @@ -8353,6 +8423,20 @@ try_compile_error << EOF int main(void, int i) {} 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 @@ -10974,6 +11058,24 @@ int writable_static_string(void) 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"; @@ -11754,6 +11856,17 @@ 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 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 @@ -13002,6 +13115,46 @@ int main(void) { } 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 + # Category: Switch Statements begin_category "Switch Statements" "Testing switch-case control flow" From 30bdaa5e45e87191ec083aaaae94adc84be506c5 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:42 +0800 Subject: [PATCH 16/40] Harden literal lexing and preprocessing A floating literal filling the token buffer wrote past it, and '' and a bare 0x compiled as zero. An #include operand naming a function-like macro with no arguments was expanded, and an include path too long for its buffer was silently truncated. Adjacent string literals were joined before their escapes were decoded, so "\x1" "2" became 0x12; a narrow and a wide literal could not be joined; an out-of-range hexadecimal escape was accepted; and a literal ending in an escaped backslash did not end. The translation epoch was converted with GNU date only. Bound every append to a numeric literal, reject the empty character constant and the digitless hex literal, diagnose both include operands, join string literals as C99 phase 6 requires with escapes kept apart, range-check narrow hex escapes, end a literal after an escaped backslash, and fall back to BSD date. --- COMPLIANCE.md | 2 +- Makefile | 12 +++- src/globals.c | 36 +++++++++- src/lexer.c | 116 ++++++++++++++++---------------- src/preprocessor.c | 103 ++++++++++++++++++++++++----- tests/driver.sh | 160 +++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 343 insertions(+), 86 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index b7c75ef1..80a79c6c 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -82,7 +82,7 @@ This document tracks compliance gaps and non-standard behaviors. | 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 before narrowing. | +| 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 diff --git a/Makefile b/Makefile index d3863431..4d291a68 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,16 @@ BUILTIN_LIBC_HEADER := c.h # 1, and 2, which keeps bootstrap byte-for-byte reproducible. Rebuilders can # supply SOURCE_DATE_EPOCH for a stable timestamp across separate invocations. SOURCE_DATE_EPOCH ?= $(shell date -u +%s) -TRANSLATION_DATE := $(shell LC_ALL=C TZ=UTC date -u -d "@$(SOURCE_DATE_EPOCH)" '+%b %e %Y') -TRANSLATION_TIME := $(shell LC_ALL=C TZ=UTC date -u -d "@$(SOURCE_DATE_EPOCH)" '+%H:%M:%S') +# 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 diff --git a/src/globals.c b/src/globals.c index 01802c23..367a7f50 100644 --- a/src/globals.c +++ b/src/globals.c @@ -756,6 +756,33 @@ static int append_utf8(char *output, int out, int limit, unsigned int value) 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) @@ -839,9 +866,14 @@ int unescape_string(const char *input, char *output, int output_size) return -1; } - int value = 0; + /* 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(). + */ + unsigned int value = 0; while (isxdigit(input[i])) { - value = (value << 4) + hex_digit_value(input[i]); + value = ((value << 4) + hex_digit_value(input[i])) & 0xff; i++; } diff --git a/src/lexer.c b/src/lexer.c index 6f338e20..2f73ab1e 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -573,6 +573,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 @@ -593,11 +609,7 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) token_buffer[sz++] = ch; ch = read_char(buf); while (isdigit(ch)) { - 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 { @@ -608,11 +620,7 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) if (!is_floating && token_buffer[0] == '0' && ((ch | 32) == 'x')) { /* Hexadecimal: starts with 0x or 0X */ is_hex = true; - 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); @@ -621,11 +629,7 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) * until the floating spelling has been recognized. */ while (isxdigit(ch)) { - 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); has_hex_significand = true; ch = read_char(buf); } @@ -635,11 +639,7 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) /* Binary literal: 0b or 0B */ if (strict_c99) error_at("binary literals are a GNU extension in C99", 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); if (ch != '0' && ch != '1') { @@ -648,33 +648,21 @@ 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 (!is_floating && token_buffer[0] == '0') { /* Octal: starts with 0 but not followed by 'x' or 'b' */ while (isdigit(ch)) { - 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 if (!is_floating) { /* Decimal */ while (isdigit(ch)) { - 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); } } @@ -685,25 +673,17 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) */ if (ch == '.') { is_floating = true; - token_buffer[sz++] = ch; + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); if (is_hex) { while (isxdigit(ch)) { - 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); has_hex_significand = true; ch = read_char(buf); } } else { while (isdigit(ch)) { - 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); } } @@ -712,20 +692,16 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) is_floating = true; if (is_hex) has_hex_exponent = true; - token_buffer[sz++] = ch; + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); if (ch == '+' || ch == '-') { - token_buffer[sz++] = 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 { - 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 (isdigit(ch)); } @@ -737,7 +713,7 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) error_at("Hexadecimal floating literal needs a significand", loc); if ((ch | 32) == 'f' || (ch | 32) == 'l') { - token_buffer[sz++] = ch; + sz = number_append(token_buffer, sz, ch, loc); ch = read_char(buf); } token_buffer[sz] = '\0'; @@ -746,6 +722,14 @@ token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) 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') { @@ -823,10 +807,11 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) } 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); } @@ -882,6 +867,10 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char 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) @@ -1545,7 +1534,7 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) 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 phase-6 literal so it cannot concatenate with bytes. + * distinct literal so phase 6 can make a join with it wide. */ read_char(buf); token = lex_literal(buf, loc, '"'); @@ -1555,8 +1544,15 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) RETURN_LEX_TOKEN(token); } token = lex_literal(buf, loc, ch); - if (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_LEX_TOKEN(token); diff --git a/src/preprocessor.c b/src/preprocessor.c index cd04b86f..d2de5697 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -632,7 +632,13 @@ bool resolve_angle_include(token_t *open, for (int i = 0; i < include_dirs_idx; i++) { FILE *file; - snprintf(resolved, resolved_size, "%s/%s", include_dirs[i], name); + + /* 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); @@ -2426,8 +2432,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) tk = pp_lex_next_token(tk, true); macro = hashmap_get(MACROS, tk->literal); - while (macro && !macro->is_disabled && !macro->param_num && - macro->replacement && !macro->replacement->next && + 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) @@ -2439,7 +2446,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) aliases[alias_count++] = macro; macro = hashmap_get(MACROS, macro->replacement->literal); } - if (!macro || macro->is_disabled || macro->param_num || + if (!macro || macro->is_disabled || macro->is_function_like || !macro->replacement) error_at("#include macro must expand to a header name", &tk->location); @@ -2758,6 +2765,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. * @@ -2783,30 +2833,53 @@ token_t *pp_strip_layout(token_t *tk) /* 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. + * 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 && tk->kind == T_string) || - (cur->kind == T_wstring && tk->kind == T_wstring))) { + 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); - - if (left_len + right_len >= MAX_STRING_LEN) + 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); - memcpy(combined + left_len, tk->literal, right_len + 1); + 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 && tk->kind == T_wstring) || - (cur->kind == T_wstring && tk->kind == T_string))) - error_at("Cannot concatenate narrow and wide string literals", - &tk->location); + 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; } diff --git a/tests/driver.sh b/tests/driver.sh index 3286f649..d2658b0b 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -723,6 +723,31 @@ EOF try_compile_error << EOF int main(void) { return 0x.p1; } EOF + +# 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 + +# 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_compile_error << EOF int main(void) { return 08.5; } EOF @@ -1075,6 +1100,19 @@ EOF # 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 + # The current execution wide-character representation is int. Wide character # constants therefore share ordinary scalar expression and ICE lowering. try_ 3 << EOF @@ -1168,6 +1206,26 @@ int main(void) { } EOF +# 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 + try_compile_error << EOF int main(void) { char values[] = "a" L"b"; return 0; } EOF @@ -2568,7 +2626,10 @@ int main(void) { } EOF try_ 1 << EOF -int main(void) { return '\x123' == 0x23; } +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 @@ -13490,6 +13551,20 @@ try_ 7 << EOF 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 + # 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 @@ -13739,6 +13814,17 @@ fi 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 @@ -13972,8 +14058,14 @@ try_ 14 << EOF int main(void) { return CHARACTER_RESULT; } EOF -try_ 1 << 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 @@ -15869,12 +15961,27 @@ int main() { } EOF -# C99 hexadecimal escapes consume every following hex digit; assigning to a char -# produces the low byte. +# 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 = "\\x123"; - return s[0] == 0x23; + char *s = "\\x0041"; + return s[0] == 'A' && s[1] == 0; +} +EOF +try_compile_error_message "Hexadecimal escape sequence out of range" << 'EOF' +int main(void) { char *s = "\x100"; return s[0]; } +EOF +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 @@ -15964,6 +16071,47 @@ int main(void) { } EOF +# 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 + +# 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 + # va_list and variadic function tests Note: These tests demonstrate both direct # pointer arithmetic and va_list typedef forwarding between functions, now fully # supported. From fb75f4c68e0f11c5f51c89b34bdc41e44ca6982c Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:42 +0800 Subject: [PATCH 17/40] Scope record tags and check their kinds Struct and union tags from every scope shared one type table, and most lookups searched it by name alone. A later function therefore resolved a tag to a record another function had declared, a block definition overwrote a file-scope record instead of shadowing it, and sizeof, casts, offsetof and va_arg ignored local tags. A tag of the other kind was accepted wherever it was found: struct U after union U, or after enum U, silently named the union or the enumeration. A repeated file-scope declaration such as a second forward declaration failed with an unexpected token, and a struct naming an undeclared tag was rejected although it declares one. Register every tag with the block that declares it, file-scope ones with GLOBAL_BLOCK, and resolve uses through the scope chain and then file scope only. Check the kind at every declaration and use, typedefs included. As C99 6.7.2.3 requires, a specifier naming no visible tag declares a new incomplete one in the current scope, so a pointer to it works and an object of it is rejected, and a repeated declaration names the same type. Members of a block-scope record see the tags of that block. --- src/defs.h | 4 +- src/globals.c | 77 ++++++++++++--- src/parser-call.c | 10 +- src/parser-decl.c | 33 +++---- src/parser-expr.c | 9 +- src/parser-global.c | 112 +++++++-------------- src/parser-sizeof.c | 12 +-- src/parser-stmt.c | 230 +++++++++++++++++++++----------------------- src/parser.c | 12 +++ tests/driver.sh | 146 +++++++++++++++++++++++++++- 10 files changed, 395 insertions(+), 250 deletions(-) diff --git a/src/defs.h b/src/defs.h index 051b0742..d0f8759a 100644 --- a/src/defs.h +++ b/src/defs.h @@ -860,7 +860,9 @@ int read_const_wstring_size(void); 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); +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); typedef struct basic_block basic_block_t; /* Definition of a growable buffer for a mutable null-terminated string diff --git a/src/globals.c b/src/globals.c index 367a7f50..f2803287 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1139,20 +1139,6 @@ type_t *find_type_tag(char name[], block_t *block) return find_type(name, 1); } -/* Struct and union tags use a namespace distinct from ordinary typedef names. - * Keep this narrower lookup separate from find_type_tag(), which also serves - * enum-tag parsing. - */ -type_t *find_record_tag(char name[], block_t *block) -{ - for (; block; block = block->parent) { - for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) - if (!strcmp(tag->name, name)) - return tag->type; - } - return find_type(name, 2); -} - type_t *find_local_type_tag(char name[], block_t *block) { for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) @@ -1161,6 +1147,69 @@ type_t *find_local_type_tag(char name[], block_t *block) return NULL; } +/* 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 the + * other keyword is an error rather than a miss. + */ +static type_t *check_record_tag(type_t *type, base_type_t kind) +{ + if (type && type->base_type != kind) + error_at("tag was previously declared as a different kind of record", + cur_token_loc()); + return type; +} + +/* 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. 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. + */ +type_t *find_record_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_record_tag(type, 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_record_tag(find_local_type_tag(name, block), kind); + + return type ? type : declare_record_tag(name, block, kind); +} + bool find_block_typedef(block_t *block, const char *name) { for (typedef_binding_t *binding = block->typedefs; binding; diff --git a/src/parser-call.c b/src/parser-call.c index 851e5ebf..12906642 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -391,9 +391,10 @@ void read_builtin_offsetof(block_t *parent, basic_block_t **bb) lex_expect(T_identifier); lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { + base_type_t record_kind = accept_record_keyword(); + if (record_kind) { lex_ident(T_identifier, name); - record = find_type(name, 2); + record = find_record_tag(name, parent, record_kind); } else { lex_ident(T_identifier, name); record = find_type(name, true); @@ -509,12 +510,13 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) if (long_count > 2 || (is_short && long_count)) error_at("invalid va_arg integer type", cur_token_loc()); - if (lex_accept(T_struct) || lex_accept(T_union)) { + base_type_t record_kind = accept_record_keyword(); + if (record_kind) { if (is_signed || is_unsigned || long_count || is_short) error_at("record type cannot have integer specifiers", cur_token_loc()); lex_ident(T_identifier, name); - type = find_type(name, 2); + type = find_record_tag(name, parent, record_kind); } else if (lex_accept(T_enum)) { if (is_signed || is_unsigned || long_count || is_short || saw_base) error_at("enum type cannot have integer specifiers", diff --git a/src/parser-decl.c b/src/parser-decl.c index 49cedc4c..4bef1c4d 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -137,9 +137,10 @@ int read_const_sizeof_type(block_t *scope) int long_count = 0; lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { + base_type_t record_kind = accept_record_keyword(); + if (record_kind) { lex_ident(T_identifier, token); - type = find_type(token, 2); + type = find_record_tag(token, scope, record_kind); } else if (lex_accept(T_enum)) { lex_ident(T_identifier, token); type = find_type_tag(token, scope); @@ -411,9 +412,10 @@ int read_const_expr_operand(block_t *scope) lex_expect(T_identifier); lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { + base_type_t record_kind = accept_record_keyword(); + if (record_kind) { lex_ident(T_identifier, type_name); - record = find_type(type_name, 2); + record = find_record_tag(type_name, scope, record_kind); } else { lex_ident(T_identifier, type_name); record = find_type(type_name, true); @@ -1094,11 +1096,7 @@ void read_full_var_decl(var_t *vd, if (is_enum_type && (is_signed || is_unsigned || is_long)) error_at("enum type cannot be combined with integer specifiers", cur_token_loc()); - bool is_record_type = lex_peek(T_struct, NULL) || lex_peek(T_union, NULL); - bool is_union_type = lex_accept(T_union); - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (is_union_type) - find_type_flag = 2; + base_type_t record_kind = accept_record_keyword(); type_t *type; /* `signed` has the existing signed scalar semantics. C permits its `int` @@ -1160,7 +1158,7 @@ void read_full_var_decl(var_t *vd, type = !strcmp(type_name, "char") ? TY_schar : (!strcmp(type_name, "short") ? TY_short : TY_int); - } else if (is_signed && find_type_flag == 1 && + } else if (is_signed && !record_kind && (!lex_peek(T_identifier, type_name) || (strcmp(type_name, "int") && strcmp(type_name, "char") && strcmp(type_name, "short")))) { @@ -1169,22 +1167,15 @@ void read_full_var_decl(var_t *vd, type = leading_scalar_type; } else { lex_ident(T_identifier, type_name); - type = find_type_flag == 2 ? find_record_tag(type_name, vd->scope) - : find_visible_type(type_name, vd->scope); - if (!type && find_type_flag == 2) - type = find_type(type_name, 2); - if (find_type_flag == 2 && - (!type || !is_record_type || - (is_union_type ? type->base_type != TYPE_union - : type->base_type != TYPE_struct))) - error_at("Unknown struct/union type", cur_token_loc()); + 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%s", - find_type_flag == 2 ? "struct/union " : "", type_name); + snprintf(message, MAX_LINE_LEN, "Could not find type %s", type_name); error_at(message, cur_token_loc()); } diff --git a/src/parser-expr.c b/src/parser-expr.c index 306eb1b1..fd57953c 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1349,9 +1349,8 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { /* Check if it's a basic type or typedef */ token_t *saved_token = type_start; - bool is_record = lex_accept(T_struct); - if (!is_record) - is_record = lex_accept(T_union); + base_type_t record_kind = accept_record_keyword(); + bool is_record = record_kind != TYPE_void; if (is_record) lex_ident(T_identifier, lookahead_token); @@ -1421,7 +1420,9 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) } else if (leading_scalar_type) { type = leading_scalar_type; } else { - type = find_type(lookahead_token, is_record ? 2 : true); + type = is_record + ? find_record_tag(lookahead_token, parent, record_kind) + : find_type(lookahead_token, true); } if (type) { diff --git a/src/parser-global.c b/src/parser-global.c index 2e792c25..a970415e 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -339,19 +339,16 @@ void parse_global_record_init(var_t *var, block_t *block) void parse_global_compound_record_init(var_t *var, block_t *block) { char type_name[MAX_ID_LEN]; - int find_type_flag = 1; type_t *compound_type, *target_type; lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { - find_type_flag = 2; - lex_ident(T_identifier, type_name); - } else { - lex_ident(T_identifier, type_name); - } + base_type_t record_kind = accept_record_keyword(); + lex_ident(T_identifier, type_name); lex_expect(T_close_bracket); - compound_type = find_type(type_name, find_type_flag); + 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; @@ -408,17 +405,14 @@ void parse_global_compound_array_init(var_t *var, block_t *block) char type_name[MAX_ID_LEN]; type_t *element_type; var_t *array; - int find_type_flag = 1; int element_ptr_level = 0; lex_expect(T_open_bracket); - if (lex_accept(T_struct) || lex_accept(T_union)) { - find_type_flag = 2; - lex_ident(T_identifier, type_name); - } else { - lex_ident(T_identifier, type_name); - } - element_type = find_type(type_name, find_type_flag); + base_type_t record_kind = accept_record_keyword(); + lex_ident(T_identifier, type_name); + element_type = record_kind + ? find_record_tag(type_name, var->scope, record_kind) + : find_type(type_name, 1); while (lex_accept(T_asterisk)) { element_ptr_level++; while (lex_accept(T_const) || lex_accept(T_volatile) || @@ -1053,13 +1047,7 @@ static void read_global_typedef(block_t *block) 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); - } + tag = local_record_tag(token, GLOBAL_BLOCK, TYPE_struct); } /* typedef with struct definition */ @@ -1153,13 +1141,7 @@ static void read_global_typedef(block_t *block) 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); - } + tag = local_record_tag(token, GLOBAL_BLOCK, TYPE_union); } /* typedef with union definition */ @@ -1296,39 +1278,27 @@ void read_global_statement(void) bool has_flexible_array_member = false; lex_ident(T_identifier, token); - token_t *id_tk = cur_token; + type_t *type = local_record_tag(token, GLOBAL_BLOCK, TYPE_struct); /* variable declaration using existing struct tag? */ if (!lex_peek(T_open_curly, NULL)) { - type_t *decl_type = find_type(token, 2); - if (!decl_type && lex_peek(T_semicolon, NULL)) { - decl_type = add_type(); - decl_type->base_type = TYPE_struct; - set_type_name(decl_type, token); - lex_expect(T_semicolon); + /* 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; - } - if (!decl_type) - error_at("Unknown struct type", &id_tk->location); - if (read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern)) + if (read_global_record_declarator(block, type, is_const, is_static, + is_extern)) return; while (lex_accept(T_comma)) - read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern); + read_global_record_declarator(block, type, is_const, is_static, + is_extern); 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); @@ -1400,42 +1370,27 @@ void read_global_statement(void) bool has_flexible_array_member = false; lex_ident(T_identifier, token); - token_t *id_tk = cur_token; + type_t *type = local_record_tag(token, GLOBAL_BLOCK, TYPE_union); /* A tagged union declaration may name an already-complete tag, just * like `struct tag object;`. Do not require a second definition body * before routing its declarators through the shared record path. */ if (!lex_peek(T_open_curly, NULL)) { - type_t *decl_type = find_type(token, 2); - if (!decl_type && lex_peek(T_semicolon, NULL)) { - decl_type = add_type(); - decl_type->base_type = TYPE_union; - set_type_name(decl_type, token); - lex_expect(T_semicolon); + /* As for struct, a bare tag declaration may repeat a known tag. */ + if (lex_accept(T_semicolon)) return; - } - if (!decl_type) - error_at("Unknown union type", &id_tk->location); - if (read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern)) + if (read_global_record_declarator(block, type, is_const, is_static, + is_extern)) return; while (lex_accept(T_comma)) - read_global_record_declarator(block, decl_type, is_const, - is_static, is_extern); + read_global_record_declarator(block, type, is_const, is_static, + is_extern); lex_expect(T_semicolon); return; } - /* 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); @@ -1522,8 +1477,15 @@ void read_global_statement(void) type = add_type(); initialize_enum_type(type); - if (has_tag) + + /* Register the tag at file scope as well, so that a struct or union + * specifier reusing the name, in any scope, sees an enum tag. + */ + if (has_tag) { set_type_name(type, token); + if (!find_local_type_tag(token, GLOBAL_BLOCK)) + add_type_tag(GLOBAL_BLOCK, token, type); + } lex_expect(T_open_curly); bool first = true; do { diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 546f6ef0..df973d1a 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -536,9 +536,7 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) (has_signed_type || has_unsigned_type || has_long_type)) error_at("enum type cannot be combined with integer specifiers", cur_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; + base_type_t record_kind = accept_record_keyword(); /* `sizeof` only consumes object representation metadata, so it can admit * the C99 floating type names before value arithmetic and ABI lowering @@ -546,12 +544,12 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) */ if (lex_accept(T_float)) { if (has_signed_type || has_unsigned_type || has_long_type || - has_enum_type || find_type_flag != 1) + has_enum_type || record_kind) error_at("invalid float type specifiers", cur_token_loc()); type = TY_float; } else if (lex_accept(T_double)) { if (has_signed_type || has_unsigned_type || has_enum_type || - find_type_flag != 1 || long_type_count > 1) + record_kind || long_type_count > 1) error_at("invalid double type specifiers", cur_token_loc()); type = has_long_type ? TY_long_double : TY_double; } else if (has_enum_type) { @@ -643,8 +641,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) } } else if (lex_peek(T_identifier, token)) { /* Try to parse as a type first */ - type = find_type_flag == 2 ? find_type(token, find_type_flag) - : find_visible_type(token, parent); + type = record_kind ? find_record_tag(token, parent, record_kind) + : find_visible_type(token, parent); if (type) { /* sizeof(type) */ lex_expect(T_identifier); diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 41cc9878..59f8fd87 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -580,17 +580,13 @@ basic_block_t *handle_record_statement(block_t *parent, bool is_static) { char token[MAX_ID_LEN]; - type_t *type; + type_t *type = NULL; var_t *var; - bool is_union = false; - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - is_union = true; - } + /* Both callers have seen struct or union ahead. */ + base_type_t kind = accept_record_keyword(); + bool is_union = kind == TYPE_union; lex_ident(T_identifier, token); - type = find_type(token, find_type_flag); if (lex_peek(T_open_curly, NULL)) { int i = 0; int size = 0; @@ -599,16 +595,15 @@ basic_block_t *handle_record_statement(block_t *parent, bitfield_layout_t bits = {0}; bool has_flexible_array_member = false; - if (!type) - type = add_type(); - set_type_name(type, token); - type->base_type = is_union ? TYPE_union : TYPE_struct; + type = local_record_tag(token, parent, 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 block. */ + v->scope = parent; read_full_var_decl(v, false, false, true); read_bitfield_width(v, parent); reject_flexible_array_member_container(v); @@ -689,137 +684,134 @@ basic_block_t *handle_record_statement(block_t *parent, return bb; } } - if (!type && lex_peek(T_semicolon, NULL)) { + if (!type && lex_accept(T_semicolon)) { /* A block-scope `struct tag;` or `union tag;` introduces an incomplete - * tag. Its pointer declarators become valid immediately; the later - * complete definition reuses this type record. + * tag in this block, or repeats one it declared. Its pointer + * declarators become valid immediately; the later complete definition + * reuses this type record. */ - type = add_type(); - type->base_type = is_union ? TYPE_union : TYPE_struct; - set_type_name(type, token); - lex_expect(T_semicolon); + local_record_tag(token, parent, kind); return bb; } - if (type) { - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - var->is_static = is_static; - var->is_global = is_static; - read_partial_var_decl(var, NULL); - if (is_incomplete_record_object(var)) + if (!type) + type = reference_record_tag(token, parent, kind); + var = require_typed_var(parent, type); + var->is_const_qualified = is_const; + var->is_static = is_static; + var->is_global = is_static; + read_partial_var_decl(var, NULL); + if (is_incomplete_record_object(var)) + error_at("Incomplete struct/union type cannot define an object", + cur_token_loc()); + add_insn(is_static ? GLOBAL_BLOCK : parent, + 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 (is_static && lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + var->ptr_level > 0)) { + parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + } else if (is_static && lex_peek(T_open_curly, NULL)) { + parse_global_record_init(var, GLOBAL_BLOCK); + } else if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->has_unsized_array || + 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_union || + 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; + + 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, + true); + lex_expect(T_close_curly); + } else { + if (!read_assignment_expression(parent, &bb)) { + 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, + !has_effective_pointer(var) && var->array_size == 0); + + emit_object_assignment(parent, &bb, var, rhs); + } + } + 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); + nv->is_static = is_static; + nv->is_global = is_static; + nv->is_const_qualified = is_const; + read_inner_var_decl(nv, false, false, false); + if (is_incomplete_record_object(nv)) error_at("Incomplete struct/union type cannot define an object", cur_token_loc()); add_insn(is_static ? GLOBAL_BLOCK : parent, - is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, var, NULL, NULL, + is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(bb, var); + add_symbol(bb, nv); if (lex_accept(T_assign)) { - validate_string_array_initializer(var); + validate_string_array_initializer(nv); if (is_static && lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); } else if (is_static && lex_peek(T_open_curly, NULL)) { - parse_global_record_init(var, GLOBAL_BLOCK); + parse_global_record_init(nv, GLOBAL_BLOCK); } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, parent, &bb, 1); /* Always emit code */ + (nv->array_size > 0 || nv->has_unsized_array || + nv->ptr_level > 0)) { + parse_array_init(nv, parent, &bb, true); } else if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_union || - var->type->base_type == TYPE_typedef)) { - type_t *struct_type = var->type; + (nv->type->base_type == TYPE_struct || + nv->type->base_type == TYPE_union || + nv->type->base_type == TYPE_typedef)) { + type_t *struct_type = nv->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, + add_insn(parent, bb, OP_address_of, struct_addr, nv, NULL, 0, NULL); lex_expect(T_open_curly); parse_struct_field_init(parent, &bb, struct_type, struct_addr, true); lex_expect(T_close_curly); } else { - if (!read_assignment_expression(parent, &bb)) { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - } - + 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, - !has_effective_pointer(var) && var->array_size == 0); - - emit_object_assignment(parent, &bb, var, rhs); - } - } - 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); - nv->is_static = is_static; - nv->is_global = is_static; - nv->is_const_qualified = is_const; - read_inner_var_decl(nv, false, false, false); - if (is_incomplete_record_object(nv)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, - NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (is_static && lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); - } else if (is_static && lex_peek(T_open_curly, NULL)) { - parse_global_record_init(nv, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - 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_union || - nv->type->base_type == TYPE_typedef)) { - type_t *struct_type = nv->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, nv, NULL, - 0, NULL); - lex_expect(T_open_curly); - parse_struct_field_init(parent, &bb, struct_type, - struct_addr, true); - 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, - !has_effective_pointer(nv) && nv->array_size == 0); + parent, &bb, rhs, nv->type, + !has_effective_pointer(nv) && nv->array_size == 0); - emit_object_assignment(parent, &bb, nv, rhs); - } + emit_object_assignment(parent, &bb, nv, rhs); } } - lex_expect(T_semicolon); - return bb; } - error_at("Unknown struct/union type", next_token_loc()); + lex_expect(T_semicolon); + return bb; } basic_block_t *handle_enum_declarators(block_t *parent, @@ -1448,20 +1440,14 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) type = NULL; } else { /* Normal type checking without asterisk */ - token_t *type_token = cur_token; if (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { type = TY_int; } else { - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) - find_type_flag = 2; - if (find_type_flag == 2) - lex_peek(T_identifier, token); - type = find_type_flag == 2 ? find_type(token, find_type_flag) - : find_visible_type(token, parent); - if (find_type_flag == 2) - cur_token = type_token; + /* struct and union declarations were handed to + * handle_record_statement() above. + */ + type = find_visible_type(token, parent); } } diff --git a/src/parser.c b/src/parser.c index ffb1f4b4..611fe6b3 100644 --- a/src/parser.c +++ b/src/parser.c @@ -305,6 +305,18 @@ bool floating_type_starts_here(void) 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) diff --git a/tests/driver.sh b/tests/driver.sh index d2658b0b..59823a8b 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -3121,8 +3121,22 @@ int main(void) { return 0; } EOF -try_compile_error << EOF -int main(void) { typedef struct missing_tag missing_alias; return 0; } + +# 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; }; @@ -3136,6 +3150,134 @@ int main(void) { 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 record. +try_compile_error_message "tag was previously declared as a different kind of record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 record" << EOF +struct offsetof_kind { int a; }; +int main(void) { return __builtin_offsetof(union offsetof_kind, a); } +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) { From 11e80636dbc97ce6a078eee2f8caed479a301b53 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:42 +0800 Subject: [PATCH 18/40] Fix va_arg, address-taken and peephole miscompiles A pointer-to-array va_arg type read its element at pointer width. A variable whose address is taken was renamed by SSA, matched by CSE and reused across stores, and a global's register copy survived a write through a pointer, so values written through a pointer were lost, as issues 337 and 340 report. Several peephole rules rewrote a register that was still read, as issue 336 reports. Read the va_arg element at its own width. Give an address-taken variable one SSA name, keep CSE and array read reuse off anything a store may reach, and spill globals on a write through a pointer. Rewrite in the peephole pass only when the register losing its value is dead, using operand tables and block liveness shared with the x86-64 backend. --- src/parser-call.c | 16 +- src/peephole.c | 356 +++++++++++++----- src/reg-alloc.c | 49 ++- src/ssa.c | 123 ++++--- src/x64-codegen.c | 149 -------- tests/driver.sh | 905 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1317 insertions(+), 281 deletions(-) diff --git a/src/parser-call.c b/src/parser-call.c index 12906642..9308e6b2 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -808,6 +808,18 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) 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; @@ -848,7 +860,7 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) index = scaled; } address = require_typed_ptr_var( - parent, requested.type, + 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); @@ -869,7 +881,7 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) base = value; } else if (depth == dimension_count + 1) { var_t *value = require_typed_ptr_var( - parent, requested.type, + parent, element_type, requested.pointee_element_ptr_level); value->var_name = gen_name(); add_insn(parent, *bb, OP_read, value, address, NULL, diff --git a/src/peephole.c b/src/peephole.c index f450dbed..3b6ec9ef 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,112 @@ 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]); + 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. + */ +bool fold_loses_dest(basic_block_t *bb, ph2_ir_t *ir, ph2_ir_t *last) +{ + if (ir->dest != last->dest && ir->dest != last->dest_hi && + reg_read_after(bb, last, ir->dest)) + return true; + return ir->dest_hi != last->dest && ir->dest_hi != last->dest_hi && + reg_read_after(bb, last, ir->dest_hi); +} + +/* 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. None of the rewrites carries a high half, so both + * instructions have to be single registers. + */ +bool const_fold_ok(basic_block_t *bb, ph2_ir_t *li, ph2_ir_t *op) +{ + if (li->dest_hi >= 0 || op->dest_hi >= 0 || op->src0 == op->src1) + return false; + return !fold_loses_dest(bb, li, op); +} + bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) { ph2_ir_t *next = ph2_ir->next; @@ -68,9 +288,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->dest_hi = next->dest_hi; @@ -83,7 +308,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) 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} @@ -98,7 +324,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} @@ -123,7 +349,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 @@ -140,7 +367,8 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) 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 @@ -157,7 +385,7 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) /* Bitwise identity operations */ if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == -1 && ph2_ir->src1 == 0 && next->op == OP_bit_and && - ph2_ir->dest == next->src1) { + 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 @@ -171,7 +399,7 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && ph2_ir->src1 == 0 && (next->op == OP_lshift || next->op == OP_rshift) && - ph2_ir->dest == next->src1) { + 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 @@ -185,7 +413,7 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && ph2_ir->src1 == 0 && next->op == OP_bit_or && - ph2_ir->dest == next->src1) { + 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) @@ -203,7 +431,7 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) if (ph2_ir->op == OP_load_constant && ph2_ir->src0 > 0 && 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} @@ -217,7 +445,7 @@ 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 && ph2_ir->src1 == 0 && next->op == OP_bit_xor && - ph2_ir->dest == next->src1) { + 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 @@ -233,7 +461,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 && - ph2_ir->src1 == 0 && 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 @@ -493,7 +722,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; @@ -504,11 +733,21 @@ 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) return false; + if (!const_fold_ok(bb, ph2_ir, next)) + return false; /* Calculate shift amount for power of 2 */ int shift = 0; @@ -572,9 +811,12 @@ 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 && ph2_ir->dest_hi < 0 && + next->dest_hi < 0 && !fold_loses_dest(bb, ph2_ir, next)) { /* Replace with simple assignment */ ph2_ir->op = OP_assign; ph2_ir->dest = next->dest; @@ -582,74 +824,17 @@ 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) + /* 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. 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 == -1 && - ph2_ir->src1 == 0 && 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 && - ph2_ir->src1 == 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 && - ph2_ir->src1 == 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 && - ph2_ir->src1 == 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 && - ph2_ir->src1 == 0 && 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 - */ - if (ph2_ir->op == OP_load_constant && ph2_ir->src0 == 0 && - ph2_ir->src1 == 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->src1 == 0 && + ((ph2_ir->src0 == 0 && next->op == OP_bit_and) || + (ph2_ir->src0 == -1 && 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; } @@ -678,8 +863,11 @@ bool triple_pattern_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) 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) && + !reg_read_after(bb, second, second->dest_hi)) { /* The load result is not used, can eliminate it */ ph2_ir->next = third; return true; @@ -792,7 +980,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/reg-alloc.c b/src/reg-alloc.c index d14fe782..b064aeec 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -1164,9 +1164,17 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) 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. + */ int i = find_in_regs(var); - if (i > -1 && !var->address_taken) { + if (i > -1 && (!var->address_taken || REGS[i].polluted)) { vreg_map_to_phys(var, i); return i; } @@ -1483,13 +1491,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; @@ -2691,6 +2702,34 @@ 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. */ @@ -3348,6 +3387,8 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) fflush(stdout); /* see fatal() */ abort(); } + + store_addressed_def(bb, insn); } if (bb->next) { diff --git a/src/ssa.c b/src/ssa.c index aac9e360..9afb62b2 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -22,6 +22,15 @@ /* 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; +} + void var_list_ensure_capacity(var_list_t *list, int min_capacity) { if (list->capacity >= min_capacity) @@ -803,7 +812,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)) { @@ -877,6 +889,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; @@ -884,6 +912,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++; @@ -926,13 +958,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 @@ -1308,7 +1344,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; } @@ -1403,7 +1439,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) @@ -1823,7 +1859,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 @@ -2341,7 +2377,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; @@ -2514,7 +2550,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; @@ -3313,44 +3349,36 @@ 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) + /* 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; } @@ -3366,6 +3394,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) @@ -3711,8 +3747,11 @@ int dce_init_mark(insn_t *insn, insn_t *work_list[], int work_list_idx) default: 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); diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 82d08345..3b0bc408 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -90,12 +90,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. */ @@ -554,116 +548,6 @@ int cmp_mem_slot; 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 @@ -708,38 +592,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. */ @@ -3799,7 +3651,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; diff --git a/tests/driver.sh b/tests/driver.sh index 59823a8b..e99d5eaf 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -2873,6 +2873,156 @@ 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" @@ -8705,6 +8855,197 @@ 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);" +# 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);" @@ -9434,6 +9775,283 @@ int main() { } 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" @@ -15016,6 +15634,228 @@ int main() } 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 + +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 + +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 + +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 + +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 + +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 s; +} +EOF + +# 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 + +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 + +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 + +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 + +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 + +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 + +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 + +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 + # constant folding try_ 20 << EOF int main() @@ -19409,6 +20249,71 @@ int main(void) { } EOF +# 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 +#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(void) { + int pair[2] = { 8, 5 }; + row value = { &pair[1], &pair[0] }; + return typedef_pointer_element_width(1, &value); +} +EOF + +# 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 +#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(void) { + words value = { "ab", "cd" }; + return typedef_char_pointer_element(1, &value); +} +EOF + +try_ 0 << EOF +#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(void) { + int pair[2] = { 8, 5 }; + pointer value[2] = { &pair[1], &pair[0] }; + return parenthesized_pointer_element_width(1, &value); +} +EOF +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(void) { + text value[2] = { "ab", "cd" }; + return typedef_pointer_declarator_element(1, &value); +} +EOF + try_compile_error << EOF #include int unsupported_void_pointer_to_array(int count, ...) { From 1686e616f63ea6cc50f7afcf6aba8fba141f8c81 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:42 +0800 Subject: [PATCH 19/40] Lower long long values on 32-bit targets Arm and RISC-V already lowered 64-bit integers to register pairs, but the parser refused long long there, and every path that had never been exercised dropped the high word: increments, inlined and folded constants, reads and writes through pointers, address-taken and phi stores, spills, globals, aggregate initializers, operations with a one-word operand, overlapping pair moves, truth tests, Arm ordering and pair shifts. Admit long long on 32-bit targets and carry both words through each of those paths: fold constants as whole values and keep peephole rules off pairs, store and reload both words everywhere, widen one-word operands after the optimizer, move overlapping pairs in order, keep six registers free for pair operations, OR both words for a truth test, order Arm pairs with a full subtraction, and keep pair shifts in range. --- src/arm-codegen.c | 126 +++++++++++----- src/arm.c | 5 + src/arm64-codegen.c | 13 +- src/main.c | 3 + src/parser-decl.c | 17 --- src/parser-expr.c | 28 +++- src/parser-init.c | 39 ++++- src/peephole.c | 86 ++++++++--- src/reg-alloc.c | 283 ++++++++++++++++++---------------- src/riscv-codegen.c | 85 ++++++++++- src/ssa.c | 245 +++++++++++++++++++++++++++++- tests/driver.sh | 359 ++++++++++++++++++++++++++++++++++++++++---- 12 files changed, 1016 insertions(+), 273 deletions(-) diff --git a/src/arm-codegen.c b/src/arm-codegen.c index 339fd491..56ee5f24 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -72,6 +72,10 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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: @@ -114,7 +118,11 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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_jump: case OP_load_func: case OP_indirect: @@ -202,16 +210,16 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 8; return; case OP_address_of_func: - case OP_log_not: elf_offset += 12; return; + case OP_log_not: + elf_offset += ph2_ir->src0_hi >= 0 ? 16 : 12; + return; case OP_gt: case OP_lt: - elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 24 : 12; - return; case OP_geq: case OP_leq: - elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 28 : 12; + elf_offset += ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0 ? 16 : 12; return; case OP_eq: case OP_neq: @@ -222,6 +230,8 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 12; else elf_offset += 8; + if (ph2_ir->src0_hi >= 0) + elf_offset += 4; return; case OP_return: elf_offset += 24; @@ -445,6 +455,23 @@ 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 (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; + } if (rd != rn) emit(__mov_r(__AL, rd, rn)); if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && @@ -534,6 +561,22 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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(ph2_ir->is_unsigned ? __lb(__AL, rd, rn, 0) : __lsb(__AL, rd, rn, 0)); @@ -546,6 +589,13 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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) @@ -556,7 +606,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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) { /* 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 @@ -935,8 +990,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__cmp_i(__AL, rm, 32)); emit(__b(__CS, 32)); emit(__mov_r(__AL, __r8, ph2_ir->src0_hi)); - emit(shift_kind == logic_rs ? __srl(__AL, rd, rn, rm) - : __sra(__AL, rd, rn, rm)); + + /* 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)); @@ -964,42 +1023,29 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_lt: case OP_geq: case OP_leq: - /* Wide equality compares the low words and, only when they match, the - * high words, below. The ordering sequence here would leave a stale - * result when the high words differ. + /* 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; - arm_cond_t true_cond = arm_get_cond(ph2_ir->op, unsigned_cmp); - arm_cond_t false_cond; + 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); - switch (ph2_ir->op) { - case OP_lt: - false_cond = unsigned_cmp ? __CS : __GE; - emit(__zero(rd)); - break; - case OP_gt: - false_cond = unsigned_cmp ? __LS : __LE; - emit(__zero(rd)); - break; - case OP_geq: - false_cond = unsigned_cmp ? __CC : __LT; - emit(__mov_i(__AL, rd, 1)); - break; - default: /* OP_leq */ - false_cond = unsigned_cmp ? __HI : __GT; - emit(__mov_i(__AL, rd, 1)); - break; - } - emit(__cmp_r(__AL, ph2_ir->src0_hi, ph2_ir->src1_hi)); - emit(__mov_i(true_cond, rd, 1)); - emit(__mov_i(false_cond, rd, 0)); - emit(__cmp_r(__EQ, rn, rm)); - if (ph2_ir->op == OP_geq || ph2_ir->op == OP_leq) - emit(__mov_i(__EQ, rd, 0)); - emit(__mov_i(true_cond, rd, 1)); + 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)); @@ -1045,7 +1091,11 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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; diff --git a/src/arm.c b/src/arm.c index cdcd43d7..ee101317 100644 --- a/src/arm.c +++ b/src/arm.c @@ -292,6 +292,11 @@ 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); diff --git a/src/arm64-codegen.c b/src/arm64-codegen.c index d5a8846f..cbe3d731 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -536,13 +536,13 @@ void emit_ph2_ir(ph2_ir_t *p) 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(a64_cmp_imm_insn(p->src0_is_pointer, n, 0)); + emit(a64_cmp_imm_insn(p->src0_is_pointer || p->size_bytes == 8, n, 0)); emit(a64_cset_insn(false, d, A64_EQ)); return; @@ -569,7 +569,8 @@ void emit_ph2_ir(ph2_ir_t *p) 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: diff --git a/src/main.c b/src/main.c index ddf4646d..4b3aa5d6 100644 --- a/src/main.c +++ b/src/main.c @@ -263,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/parser-decl.c b/src/parser-decl.c index 4bef1c4d..87f68ab6 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -1257,17 +1257,6 @@ void read_full_var_decl(var_t *vd, */ if (vd->type && vd->type->is_floating && !parsing_sizeof_function_signature) error_at("Floating point types are not yet supported", cur_token_loc()); - - /* A 32-bit target can carry a pointer to an eight-byte object without a - * paired-value register representation. Keep direct wide objects, arrays, - * parameters, returns, and function-pointer returns rejected until that - * lowering exists, but admit declarations such as `long long *p` and - * typedef aliases used exclusively behind a pointer. - */ - if (PTR_SIZE < 8 && vd->type && vd->type->base_type == TYPE_long_long && - (!(vd->ptr_level || vd->type->ptr_level) || vd->is_func)) - error_at("long long value needs 64-bit target lowering", - cur_token_loc()); } /* starting next_token, need to check the type */ @@ -1544,9 +1533,6 @@ void force_wide_global_literal_type(var_t *value, const char *token) return; if (value->type->size >= 8) return; - if (PTR_SIZE < 8) - error_at("long long literal needs 64-bit target lowering", - cur_token_loc()); value->type = numeric_has_unsigned_suffix(token) || (unsigned int) value->init_val_hi > 0x7fffffffU ? TY_ulong_long @@ -1737,9 +1723,6 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) (is_decimal && !has_unsigned_suffix && numeric_literal_needs_wide_path(token)) || (is_decimal && !has_unsigned_suffix && value > 0x80000000U)) { - if (PTR_SIZE < 8) - error_at("long long literal needs 64-bit target lowering", - cur_token_loc()); if (has_unsigned_suffix || (!is_decimal && value_hi > 0x7fffffffU)) vd->type = TY_ulong_long; else diff --git a/src/parser-expr.c b/src/parser-expr.c index fd57953c..535ed199 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1462,11 +1462,6 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) } } - if (PTR_SIZE < 8 && type->base_type == TYPE_long_long && - !(ptr_level || type->ptr_level)) - error_at("long long value needs 64-bit target lowering", - cur_token_loc()); - bool is_array = false; /* A parenthesized abstract declarator, such as `(int @@ -2824,6 +2819,20 @@ void read_expr(block_t *parent, basic_block_t **bb) } +/* 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; +} + /* What follows a completed lvalue: pointer arithmetic on it, or an update or * read of the object it names. read_lvalue() finishes with this. */ @@ -2996,6 +3005,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, vd->init_val = get_pointer_element_size(var); else vd->init_val = 1; + 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); @@ -3006,6 +3017,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, 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) { @@ -3094,6 +3107,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, } } vd->init_val = increment_size; + if (lvalue_is_wide_integer(lvalue)) + vd->type = lvalue->type; side_effect[se_idx].rd = vd; side_effect[se_idx].rs1 = NULL; @@ -3123,7 +3138,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, 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; side_effect[se_idx].rd = vd; se_idx++; diff --git a/src/parser-init.c b/src/parser-init.c index a8386326..94ef32d9 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -24,6 +24,9 @@ void read_global_function_declarator(block_t *block, bool is_static); 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); /* Forward declaration for ternary handling used by initializers */ void read_ternary_operation(block_t *parent, basic_block_t **bb); @@ -312,6 +315,14 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) cur_token_loc()); error_at("Global aggregate initializer requires a constant value", cur_token_loc()); + } else if (typed_global_literal_appears_before_initializer_end( + cur_token->next)) { + /* The word-sized reader below parses a literal into an int, so a member + * initialized with 0x100000000LL kept only its low word. Take the + * two-word reader a scalar global initializer uses whenever the value + * has a wide or unsigned literal in it. + */ + val = read_wide_global_literal_expression(parent, *bb, scope); } else if (lex_peek(T_numeric, NULL) || lex_peek(T_minus, NULL)) { bool is_neg = false; if (lex_accept(T_minus)) @@ -1747,12 +1758,21 @@ void parse_array_init(var_t *var, * enumerators before emitting the global setup-store * value. */ - 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); + if (typed_global_literal_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]; @@ -1888,7 +1908,12 @@ void parse_array_init(var_t *var, var_t *elem_addr = compute_element_address( parent, bb, base_addr, count, elem_size); - if (elem_size <= PTR_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 { diff --git a/src/peephole.c b/src/peephole.c index 3b6ec9ef..54d76ba7 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -253,28 +253,54 @@ bool reg_read_after(basic_block_t *bb, ph2_ir_t *ir, int 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) { - if (ir->dest != last->dest && ir->dest != last->dest_hi && - reg_read_after(bb, last, ir->dest)) - return true; - return ir->dest_hi != last->dest && ir->dest_hi != last->dest_hi && - reg_read_after(bb, last, ir->dest_hi); + 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. None of the rewrites carries a high half, so both - * instructions have to be single registers. + * reads the other one. */ bool const_fold_ok(basic_block_t *bb, ph2_ir_t *li, ph2_ir_t *op) { - if (li->dest_hi >= 0 || op->dest_hi >= 0 || op->src0 == op->src1) + 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; - return !fold_loses_dest(bb, li, op); + 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; + 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) @@ -298,7 +324,6 @@ bool insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) * instruction no longer writes it. */ ph2_ir->dest = next->dest; - ph2_ir->dest_hi = next->dest_hi; ph2_ir_drop_after(bb, ph2_ir, next); return true; } @@ -382,10 +407,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 && - ph2_ir->src1 == 0 && next->op == OP_bit_and && - ph2_ir->dest == next->src1 && const_fold_ok(bb, ph2_ir, next)) { + 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 @@ -743,8 +771,10 @@ bool strength_reduction(basic_block_t *bb, ph2_ir_t *ph2_ir) 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; @@ -815,8 +845,7 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * 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 && ph2_ir->dest_hi < 0 && - next->dest_hi < 0 && !fold_loses_dest(bb, ph2_ir, next)) { + 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; @@ -826,12 +855,15 @@ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) /* 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. The identities x & -1, x | 0, x ^ 0 - * and x << 0 belong to insn_fusion(), which tries them first. + * 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->src1 == 0 && - ((ph2_ir->src0 == 0 && next->op == OP_bit_and) || - (ph2_ir->src0 == -1 && next->op == OP_bit_or)) && + 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; @@ -866,8 +898,7 @@ bool triple_pattern_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) /* 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) && - !reg_read_after(bb, second, second->dest_hi)) { + if (!reg_read_after(bb, second, second->dest)) { /* The load result is not used, can eliminate it */ ph2_ir->next = third; return true; @@ -969,6 +1000,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) */ diff --git a/src/reg-alloc.c b/src/reg-alloc.c index b064aeec..00c1c8d2 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -152,13 +152,6 @@ int vreg_get_phys_hi(var_t *var) return -1; } -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 && !is_record_type(var->type) && - var->type->size == 8; -} - /* 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, @@ -636,10 +629,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 @@ -699,7 +700,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; } @@ -724,16 +725,21 @@ 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. @@ -875,6 +881,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; @@ -887,12 +894,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. @@ -1095,6 +1113,63 @@ void load_var(basic_block_t *bb, var_t *var, int idx) 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 @@ -1116,48 +1191,9 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) return phys_reg; if (var_needs_register_pair(var)) { - int 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; - break; - } - } - - /* Loading a spilled pair needs the same two-register recovery as - * producing one. Without this, several live wide locals could be stored - * safely in stack slots yet a later reload aborted before the ordinary - * spill chooser got a chance to free a complete pair. - */ - if (high < 0 && !is_pushing_args) { - for (int r = 0; r < REG_CNT; r++) { - var_t *owner = REGS[r].var; + int low, high; - if (!owner || r == operand_0 || pinned_base[r] || - !var_needs_register_pair(owner)) - continue; - spill_var(bb, owner, r); - low = -1; - high = -1; - for (int q = 0; q < REG_CNT; q++) { - if (!reg_is_free(q)) - continue; - if (low < 0) - low = q; - else { - high = q; - break; - } - } - if (high >= 0) - break; - } - } - if (high < 0) + 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); @@ -1203,9 +1239,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); @@ -1330,81 +1366,9 @@ int prepare_dest(basic_block_t *bb, REGS[mapped_low].polluted = 1; return mapped_low; } - int 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; - break; - } - } - - /* Unlike a word destination, a pair cannot use the ordinary single - * register spill chooser. Before argument staging begins, spill whole - * non-source pairs and retry; their slot preserves both words. Once - * OP_push has installed ABI arguments, those registers are live until - * the call and must never be reclaimed here. - */ - if (high < 0 && !is_pushing_args) { - 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] || !var_needs_register_pair(owner)) - continue; - spill_var(bb, owner, r); - low = -1; - high = -1; - for (int q = 0; q < REG_CNT; q++) { - if (!reg_is_free(q)) - continue; - if (low < 0) - low = q; - else { - high = q; - break; - } - } - if (high >= 0) - break; - } - } - - /* A pair result may need two registers while otherwise-dead scalar - * values still occupy the file. Whole-pair spilling above cannot help - * in that case, but scalar values already have an ordinary stack-backed - * spill path. Do not touch either source, pinned state, or a reserved - * high half. - */ - if (high < 0 && !is_pushing_args) { - for (int r = 0; r < REG_CNT; r++) { - var_t *owner = REGS[r].var; + int low, high; - if (!owner || r == operand_0 || r == operand_1 || - pinned_base[r] || pair_high_owner[r] || reg_is_locked(r) || - var_needs_register_pair(owner)) - continue; - spill_var(bb, owner, r); - low = -1; - high = -1; - for (int q = 0; q < REG_CNT; q++) { - if (!reg_is_free(q)) - continue; - if (low < 0) - low = q; - else { - high = q; - break; - } - } - if (high >= 0) - break; - } - } - if (high < 0) + 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; @@ -1478,9 +1442,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; @@ -2464,9 +2428,20 @@ void reg_alloc_global(insn_t *global_insn) } 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); @@ -2640,6 +2615,7 @@ void reg_alloc_global(insn_t *global_insn) 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). @@ -2677,6 +2653,7 @@ 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; @@ -2694,6 +2671,15 @@ 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); break; default: printf("Unsupported global operation: %d\n", global_insn->opcode); @@ -2805,6 +2791,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); @@ -2875,6 +2866,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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; REGS[dest].polluted = 0; } @@ -2982,11 +2974,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); } } @@ -3121,6 +3119,7 @@ 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); set_ptr_flags(ir, insn); break; case OP_write: @@ -3145,6 +3144,7 @@ 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); } @@ -3152,6 +3152,11 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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. * @@ -3164,12 +3169,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; @@ -3365,6 +3377,11 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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; break; case OP_trunc: case OP_sign_ext: diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 67403c5b..07fd16dc 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -107,6 +107,10 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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: @@ -133,7 +137,11 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 4; 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_jump: case OP_call: case OP_load_func: @@ -209,7 +217,7 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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: elf_offset += 12; @@ -221,7 +229,7 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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; @@ -421,6 +429,22 @@ 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) @@ -497,6 +521,22 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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(ph2_ir->is_unsigned ? __lbu(rd, rs1, 0) : __lb(rd, rs1, 0)); else if (ph2_ir->src1 == 2) @@ -507,6 +547,13 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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) @@ -520,6 +567,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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)); @@ -851,8 +904,14 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__sll(rd_hi, rs1, __t1)); emit(__addi(rd, __zero, 0)); emit(__jal(__zero, 32)); - emit(__addi(__t1, __zero, 32)); - emit(__addi(__t2, rs1, 0)); + + /* 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)); @@ -874,9 +933,17 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(is_unsigned ? __addi(rd_hi, __zero, 0) : __srai(rd_hi, rs1_hi, 31)); emit(__jal(__zero, 32)); - emit(__addi(__t1, __zero, 32)); - emit(__addi(__t2, rs1_hi, 0)); - emit(is_unsigned ? __srl(rd, rs1, rs2) : __sra(rd, rs1, rs2)); + + /* 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)); @@ -978,6 +1045,10 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) 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; diff --git a/src/ssa.c b/src/ssa.c index 9afb62b2..196f851f 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -1400,6 +1400,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: @@ -1642,8 +1652,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); @@ -1743,6 +1757,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++; @@ -2663,6 +2678,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) { @@ -3493,6 +3696,21 @@ 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) @@ -4064,6 +4282,18 @@ void optimize(void) !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 || @@ -4104,7 +4334,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; @@ -4114,6 +4344,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; } @@ -4225,9 +4457,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; diff --git a/tests/driver.sh b/tests/driver.sh index e99d5eaf..7fdf6fb3 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1782,38 +1782,27 @@ int main(void) { } EOF fi -if [ "$PTR_SZ" -lt 8 ]; then - try_compile_error << EOF -int main(void) { return 4294967296U; } -EOF -else +if [ "$PTR_SZ" -ge 8 ]; then try_ 1 << EOF int main(void) { return 4294967296U >> 32; } EOF fi -if [ "$PTR_SZ" -lt 8 ]; then - try_compile_error << EOF -int main(void) { return 1LL; } -EOF - try_compile_error << EOF -long long unsupported_value; -int main(void) { return 0; } -EOF - try_compile_error << EOF -long long unsupported_return(void) { return 0; } -int main(void) { return 0; } -EOF - try_compile_error << EOF -long long (*unsupported_callback)(void); -int main(void) { return 0; } + +# 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 +try_ 2 << EOF int main(void) { return (sizeof(long long) == 8) + (sizeof(unsigned long long) == 8); } EOF - try_ 1 << EOF +try_ 1 << EOF typedef long long *wide_pointer; long long *identity_wide_pointer(long long *value) { return value; } int main(void) { @@ -1822,7 +1811,7 @@ int main(void) { return identity_wide_pointer(alias) == alias; } EOF -else +if [ "$PTR_SZ" -ge 8 ]; then try_ 3 << EOF int main(void) { return (sizeof(1LL) == 8) + ((1LL << 32) != 0) + @@ -2468,6 +2457,323 @@ int main(void) { } EOF fi + +# 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 + +# 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 + +# ++ 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 + +# 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 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 try_ 1 << EOF unsigned int identity(unsigned int value) { return value; } int main(void) { return identity(4294967295U) >> 31; } @@ -7065,13 +7371,6 @@ int main(void) { } EOF -if [ "$PTR_SZ" -lt 8 ]; then - try_compile_error << EOF -long long value; -int main(void) { return 0; } -EOF -fi - # Compound literal support - C90/C99 compliant implementation Basic struct # compound literals (verified working) try_compile_error << EOF From 1ea08c3eec43dcd4ce94eb3c1b6b9a95bda28218 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 17:57:43 +0800 Subject: [PATCH 20/40] Pass long long arguments per the 32-bit ABIs Long long arguments did not follow AAPCS32 or the RV32 calling convention: the outgoing area was sized per source parameter, va_arg stepped one word at a time, stack-passed named parameters of variadic functions were never homed, and every long long started at an even word, which RV32 does only for variadic and stack arguments. Size the outgoing area from the largest call, align variadic long long words to eight bytes, home every named parameter, and choose each argument's start by its target's rule, splitting a named RV32 long long across a7 and the stack. The ABI suites check both conventions, the driver runs its long long cases on every target, and COMPLIANCE.md records that eight-byte values now work everywhere, with some wide expressions still miscompiled by the x64 and AArch64 backends. --- COMPLIANCE.md | 4 +- src/parser-call.c | 39 ++++- src/peephole.c | 2 +- src/reg-alloc.c | 220 +++++++++++++++++++++++++--- tests/arm-abi.sh | 47 ++++++ tests/driver.sh | 354 +++++++++++++++++++++++++++------------------ tests/riscv-abi.sh | 74 ++++++++++ 7 files changed, 574 insertions(+), 166 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 80a79c6c..448e32d4 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -65,7 +65,7 @@ This document tracks compliance gaps and non-standard behaviors. | Feature | Status | Notes | |---------|--------|-------| | `long` | Partial | Distinct rank with the current 32-bit representation. | -| `long long` | Partial | Eight-byte values work on x64/AArch64; 32-bit target admission remains gated. | +| `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 | @@ -77,7 +77,7 @@ This document tracks compliance gaps and non-standard behaviors. | Feature | Status | Current Behavior | |---------|--------|-----------------| -| Integer suffixes (`u`, `l`, `ll`) | Partial | Common suffix spellings and x64 wide literals are parsed; full candidate-type selection remains incomplete. | +| 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. | diff --git a/src/parser-call.c b/src/parser-call.c index 9308e6b2..a27ee4eb 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -102,8 +102,13 @@ void read_func_parameters_with_sret(func_t *func, param->var_name = gen_name(); add_insn(parent, *bb, OP_address_of, param, copy, NULL, 0, NULL); - } else if (!is_pointer_like_value(param)) + } 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 @@ -761,10 +766,40 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) 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; + 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); diff --git a/src/peephole.c b/src/peephole.c index 54d76ba7..92f2bbaf 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -185,7 +185,7 @@ int call_arg_regs(ph2_ir_t *ir) 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]); + words = abi_arg_next(words, &callee->param_defs[i], false); return words < MAX_ARGS_IN_REG ? words : MAX_ARGS_IN_REG; } diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 00c1c8d2..7cb084f7 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -162,16 +162,34 @@ int abi_arg_words(const var_t *var) return var_needs_register_pair(var) ? 2 : 1; } -int abi_arg_start(int cursor, const var_t *var) +/* 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) + 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) +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 abi_arg_start(cursor, var) + abi_arg_words(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) @@ -179,8 +197,8 @@ 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]); - return abi_arg_start(cursor, &func->param_defs[param_idx]); + 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) @@ -1053,7 +1071,7 @@ void pin_registers(func_t *func) 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]); + arg_words_in_reg = abi_arg_next(word, &func->param_defs[i], false); } if (reg < arg_words_in_reg) @@ -1662,6 +1680,63 @@ void prepare_pair_argument(basic_block_t *bb, var_t *var, int low) 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->dest = reg; +} + /* Return whether extra arguments are pushed onto stack. */ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) { @@ -1669,16 +1744,33 @@ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) * MAX_PARAMS source arguments. */ insn_t *pushes[MAX_PARAMS + 1]; - int starts[MAX_PARAMS + 1]; + int *starts = call_arg_starts; int num_of_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) { + bool variadic; + if (num_of_args >= MAX_PARAMS + 1) fatal("Too many call arguments"); - starts[num_of_args] = abi_arg_start(cursor, insn->rs1); + 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); + cursor = abi_arg_next(cursor, insn->rs1, variadic); insn = insn->next; } @@ -1688,6 +1780,21 @@ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) 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; @@ -3215,17 +3322,23 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) handle_abi = true; } - args = abi_arg_start(args, insn->rs1); + args = call_arg_starts[call_arg_next++]; if (args_on_stack && args >= MAX_ARGS_IN_REG) { - args = abi_arg_next(args, insn->rs1); + args += abi_arg_words(insn->rs1); break; } if (abi_arg_words(insn->rs1) == 2) { - if (args + 2 > MAX_ARGS_IN_REG) - fatal("Wide argument crosses ABI register boundary"); + 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); + args += 2; + break; + } prepare_pair_argument(bb, insn->rs1, args); - args = abi_arg_next(args, insn->rs1); + args += 2; break; } @@ -3237,7 +3350,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) ir->src0_is_unsigned = is_unsigned_scalar(insn->rs1); REGS[ir->dest].var = insn->rs1; REGS[ir->dest].polluted = 0; - args = abi_arg_next(args, insn->rs1); + args++; break; case OP_call: callee_func = find_func(insn->str); @@ -3462,6 +3575,7 @@ void reg_alloc(void) } slot_var_count = 0; + reserve_outgoing_args(func); coalesce_phi_slots(func); pin_registers(func); @@ -3512,8 +3626,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; @@ -3522,10 +3655,10 @@ 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; } /* Slot i saves ABI word i. The parameter homed there is found @@ -3533,7 +3666,7 @@ void reg_alloc(void) * takes word 0 and shifts every named parameter up. */ int param_idx = -1; - for (int q = 0; q < args_in_reg; q++) { + for (int q = 0; q < func->num_params; q++) { if (abi_param_start(func, q) == i) { param_idx = q; break; @@ -3564,6 +3697,27 @@ void reg_alloc(void) } } } + + /* 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 * are directly placed in the caller's stack space instead of the @@ -3602,6 +3756,30 @@ void reg_alloc(void) var_t *param = var_subscript0(&func->param_defs[i]); 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; diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index 1fa990a6..0171be9f 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -360,6 +360,51 @@ int main(void) { ' "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() { + void *raw = words; + call_t call = raw; + 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() { + void *raw = words; + call_t call = raw; + 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() @@ -669,6 +714,8 @@ 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}" diff --git a/tests/driver.sh b/tests/driver.sh index 7fdf6fb3..e533d092 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -813,15 +813,10 @@ int main(void) { } EOF -# A U-suffixed value above the one-word range selects unsigned long long. Keep -# that capability-specific assertion apart from the one-word unsigned tests -# above: Armv7 and RV32 deliberately reject direct wide values until -# paired-value parser admission is complete. -if [ "$PTR_SZ" -ge 8 ]; then - try_ 0 << EOF +# A U-suffixed value above the one-word range selects unsigned long long. +try_ 0 << EOF int main(void) { return sizeof(4294967296U) != 8; } EOF -fi try_ 0 << EOF enum { global_unsigned_shift_count = 1 }; @@ -1548,8 +1543,7 @@ int main(void) { return (byte == 0U) + (half == 0U); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 3 << EOF +try_ 3 << EOF long long identity(long long value) { return value; } unsigned long long uidentity(unsigned long long value) { return value; } int main(void) { @@ -1560,7 +1554,7 @@ int main(void) { (uidentity(unsigned_value) == 2000U); } EOF - try_ 4 << 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; @@ -1578,7 +1572,7 @@ int main(void) { (sizeof(unsigned long long int) == 8); } EOF - try_ 2 << EOF +try_ 2 << EOF typedef long long signed_wide; typedef unsigned long long unsigned_wide; int main(void) { @@ -1587,7 +1581,7 @@ int main(void) { return (signed_value < 0LL) + ((unsigned_value >> 32) == 1ULL); } EOF - try_ 2 << EOF +try_ 2 << EOF typedef long unsigned long reordered_unsigned_wide; typedef const long long signed_wide_const; int main(void) { @@ -1596,7 +1590,6 @@ int main(void) { return ((value >> 32) == 1ULL) + (negative < 0LL); } EOF -fi try_compile_error << EOF unsigned unsigned int invalid; int main(void) { return invalid; } @@ -1723,13 +1716,10 @@ int main(void) { (sizeof(typedef_half) == 2 && typedef_half == 65535U); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 1 << EOF +try_ 1 << EOF int main(void) { return (unsigned long long) 1 == 1ULL; } EOF -fi -if [ "$PTR_SZ" -ge 8 ]; then - try_ 4 << EOF +try_ 4 << EOF int main(void) { long long decimal = 4294967296; long long hexadecimal = 0x100000000; @@ -1740,13 +1730,13 @@ int main(void) { (all_bits > 0ULL); } EOF - try_ 1 << EOF +try_ 1 << EOF int main(void) { unsigned long long maximum = 0xffffffffffffffffULL; return maximum + 1ULL == 0ULL; } EOF - try_ 4 << EOF +try_ 4 << EOF int main(void) { return (sizeof(2147483647) == 4) + (sizeof(2147483648) == 8) + @@ -1754,13 +1744,16 @@ int main(void) { (sizeof(0x100000000) == 8); } EOF - try_ 2 << 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 +try_flags 6 "--no-libc" << EOF #include #include int main(void) { @@ -1769,7 +1762,7 @@ int main(void) { (sizeof(LLONG_MIN) == 8) + (sizeof(INTMAX_MIN) == 8); } EOF - try_ 6 << 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. */ @@ -1781,12 +1774,9 @@ int main(void) { (sizeof(4294967296U) == 8); } EOF -fi -if [ "$PTR_SZ" -ge 8 ]; then - try_ 1 << EOF +try_ 1 << EOF int main(void) { return 4294967296U >> 32; } EOF -fi # A 32-bit target admits long long literals, objects, returns and callbacks as # well as sizeof and pointers. @@ -1811,14 +1801,13 @@ int main(void) { return identity_wide_pointer(alias) == alias; } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 3 << EOF +try_ 3 << EOF int main(void) { return (sizeof(1LL) == 8) + ((1LL << 32) != 0) + (((1ULL << 32) >> 32) == 1U); } EOF - try_ 3 << EOF +try_ 3 << EOF int main(void) { unsigned long long value = 0x100000000ULL; unsigned long long pattern = 0x123456789abcdef0ULL; @@ -1827,7 +1816,7 @@ int main(void) { ((value + 7ULL) == 0x100000007ULL); } EOF - try_ 3 << EOF +try_ 3 << EOF int main(void) { unsigned long long value = 4294967296ULL; unsigned long long pattern = 1311768467463790320ULL; @@ -1836,7 +1825,7 @@ int main(void) { ((value + 7ULL) == 4294967303ULL); } EOF - try_ 3 << EOF +try_ 3 << EOF int main(void) { unsigned long long octal = 040000000000ULL; unsigned long long binary = 0b100000000000000000000000000000000ULL; @@ -1845,7 +1834,7 @@ int main(void) { ((octal + binary) == 0x200000000ULL); } EOF - try_ 4 << EOF +try_ 4 << EOF int main(void) { unsigned long long all = 18446744073709551615ULL; long long min = -9223372036854775808LL; @@ -1855,27 +1844,27 @@ int main(void) { ((min >> 63) == -1LL); } EOF - try_ 2 << 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 +try_ 2 << EOF long long global_min = -9223372036854775808LL; int main(void) { return (global_min < 0LL) + ((global_min >> 63) == -1LL); } EOF - try_ 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 +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 = @@ -1897,7 +1886,7 @@ int main(void) { ((ternary_signed_word >> 32) == 1ULL); } EOF - try_ 8 << EOF +try_ 8 << EOF unsigned long long logical_and_true = 0x100000000ULL && 1 ? 0x100000000ULL : 1U; unsigned long long logical_and_false = @@ -1925,7 +1914,7 @@ int main(void) { (logical_nested == 0x100000000ULL); } EOF - try_ 7 << EOF +try_ 7 << EOF unsigned long long logical_protected_and = 0 && (1 / 0) ? 0x100000000ULL : 1U; unsigned long long logical_protected_or = @@ -1950,7 +1939,7 @@ int main(void) { (logical_nested_protected_or == 0x100000000ULL); } EOF - try_ 4 << EOF +try_ 4 << EOF unsigned long long ternary_protected_true = 0 ? 1 / 0 : 1U; unsigned long long ternary_protected_false = @@ -1966,24 +1955,24 @@ int main(void) { (ternary_protected_logical_condition == 0x100000000ULL); } EOF - try_compile_error << EOF +try_compile_error << EOF unsigned long long invalid_wide_active_ternary_true = 1 ? 1 / 0 : 0x100000000ULL; EOF - try_compile_error << EOF +try_compile_error << EOF unsigned long long invalid_wide_active_ternary_false = 0 ? 0x100000000ULL : 1 / 0; EOF - try_compile_error << EOF +try_compile_error << EOF unsigned long long invalid_wide_ternary_condition = 1 / 0 ? 0x100000000ULL : 1U; EOF - try_compile_error << 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 - try_ 18 << EOF +try_ 18 << EOF unsigned long long global_ternary_true = (1 ? 0x100000000ULL : 0ULL) + 1ULL; unsigned long long global_ternary_false = @@ -2043,7 +2032,7 @@ int main(void) { (global_ternary_sizeof_array == 7ULL); } EOF - try_ 6 << EOF +try_ 6 << EOF int postfix_object; typedef char cast_byte; struct wide_inc_rec; @@ -2067,63 +2056,63 @@ int main(void) { (incomplete_typedef_pointer == 1ULL); } EOF - try_compile_error << 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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +try_compile_error << EOF struct bad_expr_trec; typedef struct bad_expr_trec bad_expr_t; bad_expr_t *bad_expr_tptr; @@ -2131,13 +2120,13 @@ 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 +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 +try_ 2 << EOF unsigned long long global_parenthesized = (0x100000000ULL + 7ULL); int main(void) { return ((global_parenthesized >> 32) == 1ULL) + @@ -2145,7 +2134,7 @@ int main(void) { } EOF - try_ 2 << EOF +try_ 2 << EOF unsigned long long global_nested_parenthesized = ((0x100000000ULL + 7ULL)); int main(void) { return ((global_nested_parenthesized >> 32) == 1ULL) + @@ -2153,7 +2142,7 @@ int main(void) { } EOF - try_ 2 << EOF +try_ 2 << EOF unsigned long long global_grouped_outer = (0x100000000ULL + 7ULL) * 2ULL; int main(void) { return ((global_grouped_outer >> 32) == 2ULL) + @@ -2161,7 +2150,7 @@ int main(void) { } EOF - try_ 2 << EOF +try_ 2 << EOF unsigned long long global_wide_late = (3 + 0x100000000ULL) * 2ULL; int main(void) { return ((global_wide_late >> 32) == 2ULL) + @@ -2169,7 +2158,7 @@ int main(void) { } EOF - try_ 2 << EOF +try_ 2 << EOF unsigned long long global_nested_grouped = (0x100000000ULL + (3ULL * 4ULL)) - 5ULL; int main(void) { @@ -2178,7 +2167,7 @@ int main(void) { } EOF - try_ 2 << EOF +try_ 2 << EOF long long global_negated_grouped = -(0x100000000LL + 7LL); int main(void) { return ((global_negated_grouped >> 32) == -2LL) + @@ -2186,7 +2175,7 @@ int main(void) { } EOF - try_ 4 << 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; @@ -2199,14 +2188,14 @@ int main(void) { } EOF - try_ 2 << EOF +try_ 2 << EOF int main(void) { unsigned int all = 0xffffffffU; return (all == 0xffffffffU) + (all > 1U); } EOF - try_ 2 << EOF +try_ 2 << EOF long long global_negative_wide = -0x100000000LL; int main(void) { return ((global_negative_wide >> 32) == -1LL) + @@ -2214,14 +2203,14 @@ int main(void) { } EOF - try_ 2 << 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 +try_ 3 << EOF unsigned long long global_unsuffixed = 0x100000000; unsigned long long global_all_bits = 0xffffffffffffffff; long long global_decimal = 2147483648; @@ -2231,7 +2220,7 @@ int main(void) { (global_decimal == 2147483648LL); } EOF - try_ 2 << EOF +try_ 2 << EOF unsigned int global_octal_max = 037777777777; unsigned long long global_octal_wide = 040000000000; int main(void) { @@ -2239,7 +2228,7 @@ int main(void) { ((global_octal_wide >> 32) == 1ULL); } EOF - try_ 3 << EOF +try_ 3 << EOF unsigned long long global_quotient = 0x123456789abcdef0ULL / 0x100000000ULL; unsigned long long global_remainder = @@ -2250,7 +2239,7 @@ int main(void) { ((global_quotient << 32) == 0x1234567800000000ULL); } EOF - try_ 2 << EOF +try_ 2 << EOF unsigned long long global_precedence = 0x100000000ULL + 3ULL * 4ULL - 5ULL; int main(void) { @@ -2258,7 +2247,7 @@ int main(void) { ((unsigned int)global_precedence == 7U); } EOF - try_ 2 << 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); @@ -2266,7 +2255,7 @@ int main(void) { ((value - 0x1234567800000000ULL) == 0x9abcdef0ULL); } EOF - try_ 3 << EOF +try_ 3 << EOF int main(void) { unsigned long long high = 0x100000000ULL; unsigned long long mask = 0xffffffffffffffffULL; @@ -2275,7 +2264,7 @@ int main(void) { ((high ^ high) == 0ULL); } EOF - try_ 3 << EOF +try_ 3 << EOF int main(void) { unsigned long long value = 0x123456789abcdef0ULL; return ((value / 0x100000000ULL) == 0x12345678ULL) + @@ -2283,50 +2272,50 @@ int main(void) { ((0x100000000ULL / 3ULL) == 1431655765ULL); } EOF - try_compile_error << EOF +try_compile_error << EOF int main(void) { return 18446744073709551616ULL != 0ULL; } EOF - try_compile_error << EOF +try_compile_error << EOF int main(void) { return 9223372036854775808LL != 0LL; } EOF - try_compile_error << EOF +try_compile_error << EOF int main(void) { return 9223372036854775808 != 0LL; } EOF - try_ 2 << 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 +try_ 2 << EOF int main(void) { unsigned long long all_bits = ~0ULL; return ((all_bits >> 63) == 1ULL) + (!0x100000000ULL == 0); } EOF - try_compile_error << EOF +try_compile_error << EOF int main(void) { return 1UU; } EOF - try_compile_error << EOF +try_compile_error << EOF int main(void) { return 1LLL; } EOF - try_compile_error << EOF +try_compile_error << EOF int main(void) { return 1LUL; } EOF - try_ 2 << EOF +try_ 2 << EOF int main(void) { int value = -6; return (value / 4 == -1) + (value % 4 == -2); } EOF - try_ 2 << EOF +try_ 2 << EOF int main(void) { return (sizeof(long long) == 8) + (sizeof(unsigned long long) == 8); } EOF - try_ 5 << EOF +try_ 5 << EOF int main(void) { unsigned int high = 0xffffffffU; unsigned long long widened_unsigned = (unsigned long long) high; @@ -2339,7 +2328,7 @@ int main(void) { ((unsigned int) shifted == 0U); } EOF - try_ 2 << 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) { @@ -2349,7 +2338,7 @@ int main(void) { ((twice(unsigned_value) >> 33) == 1ULL); } EOF - try_ 4 << EOF +try_ 4 << EOF int main(void) { long long signed_value = 1LL << 33; unsigned long long unsigned_value = 1ULL << 33; @@ -2359,7 +2348,7 @@ int main(void) { ((unsigned_value % 3ULL) == 2ULL); } EOF - try_ 2 << 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; @@ -2377,14 +2366,14 @@ int main(void) { ((ueighth(1ULL, 2ULL, 3ULL, 4ULL, 5ULL, 6ULL, 7ULL, unsigned_value) >> 32) == 1ULL); } EOF - try_ 1 << EOF +try_ 1 << EOF int main(void) { unsigned long long value = 0xffffffffU; value = value * 16 + 0; return (value >> 32) == 15ULL; } EOF - try_ 1 << EOF +try_ 1 << EOF unsigned long long scale(unsigned long long value, int factor) { unsigned long long product = value * factor; return product; @@ -2394,7 +2383,7 @@ int main(void) { return (scale(value, 16) >> 32) == 15ULL; } EOF - try_ 4 << EOF +try_ 4 << EOF int main(void) { unsigned int high = 0xffffffffU; long long one = 1LL; @@ -2405,10 +2394,10 @@ int main(void) { } 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 +# 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; @@ -2428,9 +2417,9 @@ int main(void) { } 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 +# 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; @@ -2443,9 +2432,9 @@ int main(void) { } EOF - # Character constants are integer constant expression operands, so a wide - # global initializer accepts them next to a long long literal. - try_ 4 << 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; @@ -2456,7 +2445,6 @@ int main(void) { (wide_char_condition == 0x100000000LL); } EOF -fi # A constant copied into its caller by inlining keeps its high word: ~0ULL # returned from a helper is not 0xffffffff. @@ -2774,6 +2762,113 @@ int main(void) { 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; } @@ -8814,14 +8909,12 @@ int main(void) { return (1 ? narrow_nullable_increment : (unsigned char)256)(4); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 5 << 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 -fi try_compile_error << EOF int void_cast_increment(int value) { return value + 1; } int main(void) { return (1 ? void_cast_increment : (void)0)(4); } @@ -10771,11 +10864,9 @@ int main(void) } EOF -# The LP64 ABIs return an eight-byte callback result in the normal integer -# return register pair/value. Keep this separate from the still-gated 32-bit -# long-long ABI work. -if [ "$PTR_SZ" -eq 8 ]; then - try_ 42 << 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) { @@ -10790,7 +10881,6 @@ int main(void) : 1; } EOF -fi # C99 permits neither addition nor subtraction on function pointers: only # pointers to complete object types have elements to scale or subtract. @@ -12821,11 +12911,9 @@ int main(void) { } EOF -# This evaluates a direct unsigned long long global expression. Keep the -# address-offset lowering check on targets that currently admit wide values; the -# surrounding one-word enum-offset regression remains portable. -if [ "$PTR_SZ" -ge 8 ]; then - try_ 0 << 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); @@ -12843,7 +12931,6 @@ int main(void) { *aggregate_value != 23; } EOF -fi try_ 0 << EOF struct pointer_member_holder { int *value; }; typedef char *pointer_member_char_ptr; @@ -13720,15 +13807,14 @@ int main(void) { return (quotient == 2147483647U) + (negative == 1U); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 1 << EOF +try_ 1 << EOF int main(void) { unsigned long long value = 0ULL; value += -1; return value == 0xffffffffffffffffULL; } EOF - try_ 2 << EOF +try_ 2 << EOF int main(void) { unsigned long long quotient = 0x100000000ULL; unsigned long long remainder = 0x100000001ULL; @@ -13738,7 +13824,6 @@ int main(void) { return (quotient == 0x80000000ULL) + (remainder == 2ULL); } EOF -fi # Category: Sizeof Operator begin_category "Sizeof Operator" "Testing sizeof operator on various types" @@ -14676,12 +14761,10 @@ int main(void) { (LONG_MIN < 0) + (ULONG_MAX > LONG_MAX); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_flags 2 "--no-libc" << EOF +try_flags 2 "--no-libc" << EOF #include int main(void) { return (LLONG_MIN < 0) + (ULLONG_MAX > LLONG_MAX); } EOF -fi try_flags "$((8 + 2 * PTR_SZ))" "--no-libc" << EOF #include #include @@ -14803,8 +14886,8 @@ int main(void) { (INT32_MIN < 0) + (INT32_MAX > 0) + (UINT32_MAX > INT32_MAX); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_flags 9 "--no-libc" << 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) + @@ -14812,7 +14895,6 @@ int main(void) { (INTPTR_MIN < 0) + (INTPTR_MAX > INT32_MAX) + (UINTPTR_MAX > INTPTR_MAX); } EOF -fi try_flags 10 "--no-libc" << EOF #include #include @@ -14839,15 +14921,13 @@ int main(void) { (INT_FAST32_MIN < 0) + (UINT_FAST32_MAX > INT_FAST32_MAX); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_flags 4 "--no-libc" << 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 -fi try_flags 12 "--no-libc" << EOF #include int main(void) { @@ -14859,8 +14939,7 @@ int main(void) { (sizeof(INT32_C(12)) == 4) + (sizeof(UINT32_C(12)) == 4); } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_flags 8 "--no-libc" << EOF +try_flags 8 "--no-libc" << EOF #include int main(void) { return (INT64_C(12) == 12LL) + (UINT64_C(12) == 12ULL) + @@ -14869,7 +14948,6 @@ int main(void) { (sizeof(INTMAX_C(12)) == 8) + (sizeof(UINTMAX_C(12)) == 8); } EOF -fi try_compile_error_message "Angle header not found in -I search paths" << EOF #include EOF @@ -20310,8 +20388,7 @@ int main(void) { return after_char(0, 2) != 2 || after_pointer(&value, 3) != 3; } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 0 << EOF +try_ 0 << EOF #include int after_wide(long long last, ...) { va_list ap; @@ -20320,7 +20397,6 @@ int after_wide(long long last, ...) { } int main(void) { return after_wide(0, 4) != 4; } EOF -fi try_ 0 << EOF #include @@ -20389,8 +20465,7 @@ int main(void) { } EOF -if [ "$PTR_SZ" -ge 8 ]; then - try_ 0 << EOF +try_ 0 << EOF #include long long wide_argument(int count, ...) { va_list ap; @@ -20399,7 +20474,6 @@ long long wide_argument(int count, ...) { } int main(void) { return wide_argument(1, 1234567890123LL) != 1234567890123LL; } EOF -fi try_ 0 << EOF #include diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index 2ec4b338..a7b264d7 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -293,6 +293,77 @@ 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() { + void *raw = words; + call_t call = raw; + 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() { + void *raw = words; + call_t call = raw; + 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() { + void *raw = words; + call_t call = raw; + 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 +617,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}" From a6a6f0e1f65056d06f009fae3dfff99a891d01f5 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 19:56:18 +0800 Subject: [PATCH 21/40] Match C99 in the preprocessor, lexer and libc Several preprocessing and lexing rules differed from C99 and gcc. A directive after a spliced line was missed or misread, a nested conditional inside a skipped group was counted twice, white space between # and the directive name and the null directive were rejected, and _Pragma("once") was dropped. __DATE__ reached through a macro alias expanded as __TIME__, trailing white space in a replacement list broke #include of a macro, and __VA_ARGS__ was unbound when a variadic call supplied no extra argument. A literal could contain an unescaped newline, and wide character escapes kept only their low byte. The Makefile read the translation epoch twice, so __DATE__ and __TIME__ could disagree. In the library, memcmp compared signed bytes, a failed assert printed to standard output, and fwrite took a char buffer. COMPLIANCE.md also claimed restrict was retained, which it is not. --- COMPLIANCE.md | 2 +- Makefile | 6 +- lib/c.c | 7 +- lib/c.h | 2 +- src/defs.h | 1 + src/globals.c | 141 ++++++++++++++++++++- src/lexer.c | 117 +++++++++++------ src/parser.c | 144 +-------------------- src/preprocessor.c | 154 +++++++++++++---------- tests/driver.sh | 305 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 628 insertions(+), 251 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 448e32d4..c5400bd1 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -57,7 +57,7 @@ This document tracks compliance gaps and non-standard behaviors. | `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 | Parsed and retained as an aliasing qualifier; does not yet drive optimization. | +| `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 diff --git a/Makefile b/Makefile index 4d291a68..a48d5c36 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,11 @@ BUILTIN_LIBC_HEADER := c.h # 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. -SOURCE_DATE_EPOCH ?= $(shell date -u +%s) +# 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 # 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. diff --git a/lib/c.c b/lib/c.c index 21a7aa11..9e0c2a20 100644 --- a/lib/c.c +++ b/lib/c.c @@ -188,7 +188,8 @@ char *memcpy(char *dest, const char *src, int count) int memcmp(const void *s1, const void *s2, int n) { - const char *p1 = s1, *p2 = 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]) @@ -652,8 +653,8 @@ void __assert_fail(const char *expr, unsigned int line, const char *function) { - printf("Assertion failed: %s, function %s, file %s, line %d\n", expr, - function, file, line); + fprintf(stderr, "Assertion failed: %s, function %s, file %s, line %d\n", + expr, function, file, line); abort(); } diff --git a/lib/c.h b/lib/c.h index f5ba2086..30ec7090 100644 --- a/lib/c.h +++ b/lib/c.h @@ -126,7 +126,7 @@ int fputc(int c, FILE *stream); * through '__syscall' instead. */ int fread(char *ptr, int size, int nmemb, FILE *stream); -int fwrite(const 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); diff --git a/src/defs.h b/src/defs.h index d0f8759a..869d64be 100644 --- a/src/defs.h +++ b/src/defs.h @@ -395,6 +395,7 @@ typedef enum { 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. diff --git a/src/globals.c b/src/globals.c index f2803287..ee5ab140 100644 --- a/src/globals.c +++ b/src/globals.c @@ -869,7 +869,8 @@ int unescape_string(const char *input, char *output, int output_size) /* 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(). + * hex_escape_exceeds_byte(); a wide one takes its value from + * decode_wstring_units() instead. */ unsigned int value = 0; while (isxdigit(input[i])) { @@ -964,6 +965,144 @@ int parse_character_constant(const char *literal) 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; +} + int parse_numeric_constant(const char *buffer) { int i = 0; diff --git a/src/lexer.c b/src/lexer.c index 2f73ab1e..3a1fc434 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -371,6 +371,13 @@ 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. */ @@ -381,24 +388,28 @@ char read_layout_char(strbuf_t *buf, source_location_t *loc); * 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; \ - return tk; \ +#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. @@ -434,7 +445,7 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) return token; } - if (loc->column != 1) { + if (!lex_at_line_start) { int hash_chars = is_digraph_hash ? 2 : 1; for (int i = 0; i < hash_chars; i++) @@ -451,6 +462,36 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) 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; @@ -462,11 +503,13 @@ token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) } 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, source_len); loc->column += source_len; @@ -791,15 +834,15 @@ 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 == '\\' && peek_char(buf, 1) == '\n') { - read_char(buf); - ch = read_char(buf); - loc->line++; - loc->column = 1; - special = false; - continue; + if (ch == '\n' || !ch) { + loc->len = 1; + error_at("Unenclosed string literal", loc); } if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; @@ -835,15 +878,9 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) if (ch == '\'') { int sz = 0; + /* As in a string literal, a newline is never part of the constant. */ ch = read_char(buf); - while (ch && ch != '\'') { - if (ch == '\\' && peek_char(buf, 1) == '\n') { - read_char(buf); - ch = read_char(buf); - loc->line++; - loc->column = 1; - continue; - } + while (ch && ch != '\'' && ch != '\n') { if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; error_at("Character literal too long", loc); @@ -851,7 +888,7 @@ token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) token_buffer[sz++] = ch; if (ch == '\\') { ch = read_char(buf); - if (!ch) + if (!ch || ch == '\n') break; if (sz >= MAX_TOKEN_LEN - 1) { loc->len = sz + 1; @@ -1676,6 +1713,7 @@ token_stream_t *gen_file_token_stream(char *filename) /* 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); @@ -1736,6 +1774,7 @@ token_stream_t *gen_libc_token_stream(void) /* 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); diff --git a/src/parser.c b/src/parser.c index 611fe6b3..09cdec1b 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1174,148 +1174,16 @@ int write_wide_symbol(const int *data, int length) return start_len; } -/* 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 single wide character constant denotes one execution-wide unit. Keep the - * existing implementation-defined packing for multi-character constants, but do - * not route a UCN such as L'\u00e9' through the narrow UTF-8 decoder. +/* 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 units[MAX_TOKEN_LEN]; - int length; - - bool has_ucn = false; - for (int i = 0; literal[i]; i++) - if (literal[i] == '\\' && - (literal[i + 1] == 'u' || literal[i + 1] == 'U')) { - has_ucn = true; - break; - } - if (!has_ucn) - return parse_character_constant(literal); - length = decode_wstring_units(literal, units, MAX_TOKEN_LEN); - if (length < 0) + int value; + + if (!wide_character_constant(literal, &value)) error_at("Invalid wide character escape sequence", cur_token_loc()); - if (length == 1) - return units[0]; - return parse_character_constant(literal); + return value; } int read_wstring_units(int *units, int capacity) diff --git a/src/preprocessor.c b/src/preprocessor.c index d2de5697..1ee39541 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -64,28 +64,6 @@ token_t *pp_lex_expect_token(token_t *tk, token_kind_t kind, bool skip_space) return tk; } -/* A single wide UCN character constant represents its execution-wide unit in a - * preprocessing integer constant expression. - */ -int pp_parse_wide_character_constant(const char *literal) -{ - unsigned int value = 0; - int digits; - - if (literal[0] != '\\' || (literal[1] != 'u' && literal[1] != 'U')) - return parse_character_constant(literal); - digits = literal[1] == 'u' ? 4 : 8; - for (int i = 0; i < digits; i++) { - if (!isxdigit(literal[i + 2])) - return parse_character_constant(literal); - value = (value << 4) + hex_digit_value(literal[i + 2]); - } - if (literal[digits + 2] || value > 0x10ffff || - (value >= 0xd800 && value <= 0xdfff)) - return parse_character_constant(literal); - return (int) value; -} - /* Copies and isolate the given copied token */ token_t *copy_token(const token_t *tk) { @@ -709,18 +687,31 @@ token_t *line_macro_handler(token_t *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_macro_handler(token_t *tk) +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 = !strcmp(tk->literal, "__DATE__") ? SHECC_TRANSLATION_DATE - : SHECC_TRANSLATION_TIME; + 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. @@ -1459,9 +1450,12 @@ token_t *pp_read_constant_expr_operand(token_t *tk, 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 = tk->kind == T_wchar - ? pp_parse_wide_character_constant(tk->literal) - : parse_character_constant(tk->literal); + 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); val->lo = character; val->hi = character < 0 ? ~0U : 0; @@ -1784,52 +1778,32 @@ token_t *pp_read_constant_expr(token_t *tk, pp_integer_t *val) 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; } @@ -1921,13 +1895,30 @@ __noreturn void pp_error_directive(token_t *directive) /* 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; consume the operator syntax so no tokens reach the - * parser. + * 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_ignore_pragma_operator(token_t *tk) +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); } @@ -1964,6 +1955,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); @@ -2186,7 +2179,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } if (!strcmp(tk->literal, "_Pragma")) { - tk = pp_ignore_pragma_operator(tk); + tk = pp_pragma_operator(tk, expansion_ctx.expanded_from); tk = pp_lex_next_token(tk, false); continue; } @@ -2366,6 +2359,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 */ @@ -2543,6 +2547,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); @@ -2612,8 +2617,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; @@ -2730,6 +2746,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) 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 @@ -2914,12 +2933,12 @@ token_t *preprocess(token_t *tk) macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = "__DATE__"; - macro->handler = translation_timestamp_macro_handler; + macro->handler = date_macro_handler; hashmap_put(MACROS, "__DATE__", macro); macro = arena_calloc(TOKEN_ARENA, 1, sizeof(macro_t)); macro->name = "__TIME__"; - macro->handler = translation_timestamp_macro_handler; + macro->handler = time_macro_handler; hashmap_put(MACROS, "__TIME__", macro); /* C99-required implementation macros. shecc supplies its own small runtime @@ -3192,6 +3211,7 @@ char *token_to_string(token_t *tk, char *dest) 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/tests/driver.sh b/tests/driver.sh index e533d092..5eba19f9 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1108,6 +1108,31 @@ try_compile_error_message "Empty character constant" << EOF int main(void) { return 0; } EOF +# 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 + # The current execution wide-character representation is int. Wide character # constants therefore share ordinary scalar expression and ICE lowering. try_ 3 << EOF @@ -1172,6 +1197,40 @@ try_flags 0 "--no-libc" << EOF #endif int main(void) { return 0; } EOF + +# 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'}; @@ -14709,6 +14768,21 @@ try_compile_error_message "#include macro must expand to a header name" << EOF 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 @@ -14717,6 +14791,21 @@ 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 @@ -15185,6 +15274,157 @@ try_compile_error << EOF #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 @@ -15722,6 +15962,17 @@ int main(void) } 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" @@ -15780,6 +16031,24 @@ int main() } 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 @@ -15906,6 +16175,19 @@ int main() } 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 + # macro parameter substitution works in expression contexts try_ 15 << EOF #define ADD_PARAMS(a, b) ((a) + (b)) @@ -17080,6 +17362,15 @@ int main() } EOF +# 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 + # 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 @@ -20343,6 +20634,20 @@ try_failure_output "$(assertion_message "SUM(1, 1) == 3")" << EOF int main(void) { assert(SUM(1, 1) == 3); return 0; } EOF +# 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 + begin_category "C99 stdarg.h" "Testing variadic argument macros" try_ 0 << EOF From 0f8b8cdbf50307d6c1bc1f7be2d814364f7cac06 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 20:48:42 +0800 Subject: [PATCH 22/40] Fix Arm, AArch64 and peephole code generation AArch64 loaded unsigned narrow objects with sign extension, so an unsigned char of 200 indexed an array at -56. The Arm wide multiply read its operands after UMULL could overwrite them if the result pair shared an operand register, which the allocator does not produce today. Redundant move elimination kept the dropped instruction's width, so an address could be cut to 32 bits on x86-64, and a cast widening to long long moved the register without extending it, which the RISC-V size estimate had also got wrong for the two byte forms. Load unsigned narrow values with the zero-extending forms, sum the Arm cross terms before UMULL, keep the surviving instruction's flags, and extend integers cast to long long by their own signedness. --- src/arm-codegen.c | 9 +++- src/arm64-codegen.c | 55 +++++++++++--------- src/arm64.c | 43 +++++++++------- src/parser-expr.c | 18 +++++-- src/peephole.c | 121 ++++++++++---------------------------------- src/riscv-codegen.c | 12 +++-- tests/driver.sh | 87 +++++++++++++++++++++++++++++++ 7 files changed, 202 insertions(+), 143 deletions(-) diff --git a/src/arm-codegen.c b/src/arm-codegen.c index 56ee5f24..f0fdda95 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -737,11 +737,16 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) case OP_mul: if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 && ph2_ir->src1_hi >= 0) { - emit(__umull(__AL, rd, ph2_ir->dest_hi, rn, rm)); + /* 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)); - emit(__add_r(__AL, ph2_ir->dest_hi, ph2_ir->dest_hi, __r9)); } else emit(__mul(__AL, rd, rn, rm)); return; diff --git a/src/arm64-codegen.c b/src/arm64-codegen.c index cbe3d731..a66246d7 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -151,18 +151,27 @@ void a64_extend(int d, int n, int size, bool is_unsigned) 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. */ -void a64_mem(int load, int size, int rt, int rn, int ofs) +void a64_mem(int access, int size, int rt, int rn, int ofs) { if (size != 1 && size != 2 && size != 4 && size != 8) fatal("unsupported arm64 access width"); if (ofs >= -256 && ofs <= 255 && (ofs < 0 || ofs % size)) { - emit(a64_mem_unscaled_insn(load, size, rt, rn, ofs)); + emit(a64_mem_unscaled_insn(access, size, rt, rn, ofs)); return; } if (ofs < 0 || ofs > 4095 * size || ofs % size) { @@ -171,7 +180,7 @@ void a64_mem(int load, int size, int rt, int rn, int ofs) rn = A64_IP0; ofs = 0; } - emit(a64_mem_scaled_insn(load, size, rt, rn, ofs)); + emit(a64_mem_scaled_insn(access, size, rt, rn, ofs)); } /* Masking an out-of-range displacement silently branches somewhere else. The @@ -413,14 +422,14 @@ void emit_ph2_ir(ph2_ir_t *p) /* 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; } @@ -445,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 @@ -610,7 +619,7 @@ 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: @@ -651,7 +660,7 @@ void plt_generate(void) elf_write_int(dynamic_sections.elf_plt, a64_add_imm_insn(true, A64_IP0, A64_IP0, got & 0xfff)); elf_write_int(dynamic_sections.elf_plt, - a64_mem_scaled_insn(true, 8, A64_IP1, A64_IP0, 0)); + a64_mem_scaled_insn(A64_LOAD, 8, A64_IP1, A64_IP0, 0)); elf_write_int(dynamic_sections.elf_plt, a64_br_insn(A64_IP1)); } } @@ -667,15 +676,15 @@ void code_generate(void) */ if (dynlink) a64_mov_sp(25); - a64_mem(1, 8, 20, A64_SP, 0); + 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 @@ -692,7 +701,7 @@ 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) { /* Clear the frame inline rather than through libc's memset, which a * --no-libc translation unit never declares. The frame is a multiple of @@ -702,7 +711,7 @@ void code_generate(void) a64_mov(0, A64_GP); a64_mov_imm(2, global_frame); emit(a64_cbz_insn(true, 2, 4)); - emit(a64_mem_post_insn(false, 8, A64_ZR, 0, 8)); + 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)); } @@ -723,8 +732,8 @@ void code_generate(void) 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); diff --git a/src/arm64.c b/src/arm64.c index d3e36e3d..1fca24b7 100644 --- a/src/arm64.c +++ b/src/arm64.c @@ -208,44 +208,53 @@ int a64_cset_insn(bool sf, int rd, a64_cond_t cond) return a64_sf(sf) | 0x1a9f07e0 | ((cond ^ 1) << 12) | rd; } -/* 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. @size must be 1, 2, 4 - * or 8. +/* 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. */ -int a64_mem_op(bool load, int size) +#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 (!load) + if (access == A64_STORE) return 0x38000000 | (sz << 30); - return 0x38000000 | (sz << 30) | ((size == 8 ? 1 : 2) << 22); + 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(bool load, int size, int rt, int rn, int ofs) +int a64_mem_unscaled_insn(int access, int size, int rt, int rn, int ofs) { - return a64_mem_op(load, size) | ((ofs & 0x1ff) << 12) | (rn << 5) | rt; + 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(bool load, int size, int rt, int rn, int ofs) +int a64_mem_scaled_insn(int access, int size, int rt, int rn, int ofs) { - return a64_mem_op(load, size) | (1 << 24) | ((ofs / size) << 10) | + 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(bool load, int size, int rt, int rn, int ofs) +int a64_mem_post_insn(int access, int size, int rt, int rn, int ofs) { - return a64_mem_op(load, size) | 0x400 | ((ofs & 0x1ff) << 12) | (rn << 5) | - rt; + 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 diff --git a/src/parser-expr.c b/src/parser-expr.c index 535ed199..899f9524 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1261,12 +1261,24 @@ static void read_cast_operand(block_t *parent, * 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. */ opcode_t cast_op = OP_cast; - if (get_size(cast_var) < get_size(expr_var)) + int cast_size = get_size(cast_var); + if (cast_size < get_size(expr_var)) cast_op = OP_trunc; - add_insn(parent, *bb, cast_op, cast_var, expr_var, NULL, get_size(cast_var), - NULL); + else if (cast_size > TY_int->size && get_size(expr_var) <= 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 |= get_size(expr_var) << 16; + } + add_insn(parent, *bb, cast_op, cast_var, expr_var, NULL, cast_size, NULL); /* Push the cast result */ opstack_push(cast_var); diff --git a/src/peephole.c b/src/peephole.c index 92f2bbaf..a731d8ae 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -505,8 +505,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) { @@ -514,99 +531,15 @@ 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 - */ - 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->dest != next->dest || + (ph2_ir->dest_hi >= 0 && ph2_ir->dest_hi != next->dest_hi) || + (next->op == OP_assign && next->src0 == ph2_ir->dest)) + 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 diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 07fd16dc..13877fc2 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -247,11 +247,15 @@ void update_elf_offset(ph2_ir_t *ph2_ir) /* 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 += - (ph2_ir->src0_is_unsigned || ph2_ir->src0_is_pointer) && - (source_size == 1 || source_size == 2) - ? 12 - : 8; + source_size == 2 || (source_size == 1 && !zero_ext) ? 12 : 8; return; } if (source_size == 2) diff --git a/tests/driver.sh b/tests/driver.sh index 5eba19f9..a01e0c6b 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -2387,6 +2387,38 @@ int main(void) { ((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; } @@ -2810,6 +2842,22 @@ int main(void) { } 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 @@ -3108,6 +3156,23 @@ int main(void) { 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 } }; @@ -13866,6 +13931,28 @@ int main(void) { return (quotient == 2147483647U) + (negative == 1U); } 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; From 679fe9a025884dad81fae5136042cd04e9a3f935 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 20:03:30 +0800 Subject: [PATCH 23/40] Fix initializer, declaration and sizeof gaps Designators and brace elision lost values: a member designator did not end a pending array sequence, array member braces refused designators, reordered rows were zeroed, an inferred outer bound ignored partial rows, and brace-elided record elements were rejected. Declarations mishandled long long case labels, volatile file-scope records, enum tags reusing record tags, extern and register records and untagged local records in blocks, assignment expressions in for declarations, several prototypes in one declaration, block prototypes without extern, and qualifiers after the type specifier. Constant expressions ran out-of-range shifts in the compiler, rejected sizeof of strings, dereferences and arrays of pointers, took sizeof 1LL as int, and could not fold casts in file-scope initializers. A call accepted a trailing comma. Fix each at its reader, sharing one abstract declarator and one sizeof evaluation between the constant and expression paths, and one block declarator lowering between records, for declarations and ordinary objects. --- src/defs.h | 3 + src/globals.c | 68 +++- src/parser-call.c | 15 +- src/parser-const.c | 739 +++++++++++++++++++++----------------- src/parser-decl.c | 332 ++++++++--------- src/parser-expr.c | 73 ++-- src/parser-global.c | 232 +++++++----- src/parser-init.c | 560 +++++++++++++++++++++-------- src/parser-sizeof.c | 267 +++++--------- src/parser-stmt.c | 640 ++++++++++++--------------------- src/parser.c | 38 +- src/preprocessor.c | 6 +- tests/driver.sh | 854 +++++++++++++++++++++++++++++++++++++++++++- 13 files changed, 2422 insertions(+), 1405 deletions(-) diff --git a/src/defs.h b/src/defs.h index 869d64be..f62e9925 100644 --- a/src/defs.h +++ b/src/defs.h @@ -858,12 +858,15 @@ 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); 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); +type_t *find_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 diff --git a/src/globals.c b/src/globals.c index ee5ab140..9e1f6f7c 100644 --- a/src/globals.c +++ b/src/globals.c @@ -608,10 +608,29 @@ type_t *find_type(const char *type_name, int flag) /* If it is a forwardly declared alias of a structure, return * the base structure type. */ - if (TYPES[i].base_type == TYPE_typedef && TYPES[i].size == 0 && - TYPES[i].ptr_level == 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) + 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 || !base->size || + (!alias->is_const_qualified && + !alias->is_volatile_qualified)) + return base; + 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; } } } @@ -1268,16 +1287,6 @@ void add_type_tag(block_t *block, char name[], type_t *type) block->type_tags = tag; } -type_t *find_type_tag(char name[], block_t *block) -{ - for (; block; block = block->parent) { - for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) - if (!strcmp(tag->name, name)) - return tag->type; - } - return find_type(name, 1); -} - type_t *find_local_type_tag(char name[], block_t *block) { for (type_tag_t *tag = block->type_tags; tag; tag = tag->next) @@ -1349,6 +1358,37 @@ type_t *local_record_tag(char name[], block_t *block, base_type_t kind) return type ? type : declare_record_tag(name, block, kind); } +/* An enum tag shares the struct and union name space, so an existing record tag + * of the same name is the wrong kind of tag rather than a miss. Looking in the + * tag table, not the type table, also keeps a typedef name out of it. + */ +static type_t *check_enum_tag(type_t *type) +{ + if (type && + (type->base_type == TYPE_struct || type->base_type == TYPE_union)) + error_at("tag was previously declared as a different kind of tag", + cur_token_loc()); + return type; +} + +/* The enum tag @name as seen from @block, or NULL when none is visible. */ +type_t *find_enum_tag(char name[], block_t *block) +{ + 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_enum_tag(type); +} + +/* The enum tag @name that @block itself declares, or NULL. */ +type_t *local_enum_tag(char name[], block_t *block) +{ + return check_enum_tag(find_local_type_tag(name, block)); +} + bool find_block_typedef(block_t *block, const char *name) { for (typedef_binding_t *binding = block->typedefs; binding; diff --git a/src/parser-call.c b/src/parser-call.c index a27ee4eb..13e891f3 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -123,9 +123,16 @@ void read_func_parameters_with_sret(func_t *func, } params[param_num++] = param; - if (lex_accept(T_comma) && strict_c99 && - lex_peek(T_close_bracket, NULL)) - error_at("trailing comma in function call is not permitted in C99", + + /* 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()); } @@ -527,7 +534,7 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) error_at("enum type cannot have integer specifiers", cur_token_loc()); lex_ident(T_identifier, name); - type = find_type_tag(name, parent); + type = find_enum_tag(name, parent); } else { if (!saw_base && !is_signed && !is_unsigned && !long_count && !is_short) { diff --git a/src/parser-const.c b/src/parser-const.c index 1f44d009..dcf972a7 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -129,339 +129,256 @@ int read_const_wstring_size(void) return (read_wstring_units(values, MAX_STRING_LEN) + 1) * wide_type->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. +/* 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. Declared objects keep their extents through a type-only + * walk and type names use the declaration-only evaluator; every other operand + * goes through the ordinary sizeof parser in a detached block, so its size + * follows from its real type. */ -static int sizeof_grouped_literal_depth(token_t *token, token_kind_t kind) +int read_sizeof_constant(block_t *scope) { - int depth = 0; + /* The buffer holds only a copied identifier, at most MAX_TOKEN_LEN. */ + char buffer[MAX_TOKEN_LEN]; + int res; - 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; + /* The detached expression parser needs a block to hold its values. */ + if (!scope) + scope = GLOBAL_BLOCK; + + token_t *inside = + lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; + var_t *nested_array = NULL; + token_t *nested_root = NULL; + token_t *nested_after = NULL; + int nested_groups = 0; + int string_groups = sizeof_grouped_literal_depth(inside, T_string); + int wstring_groups = sizeof_grouped_literal_depth(inside, T_wstring); + + if (inside && inside->kind == T_open_bracket) { + token_t *token = inside; + + while (token && token->kind == T_open_bracket) { + nested_groups++; + token = token->next; + } + nested_root = token; + if (token && token->kind == T_identifier) + nested_array = find_var(token->literal, scope); + nested_after = token ? token->next : NULL; + for (int i = 0; i < nested_groups && nested_after && + nested_after->kind == T_close_bracket; + i++) + nested_after = nested_after->next; } - return depth; -} - -int read_primary_constant(block_t *scope) -{ - /* return signed constant */ - int isneg = 0, res; - - /* This recurses once per nesting level of a constant expression, so the - * buffer holds only what is copied into it: a number, a character constant - * or an identifier, each at most MAX_TOKEN_LEN. A string is only tested for - * and never copied. - */ - char buffer[MAX_TOKEN_LEN]; - if (lex_accept(T_minus)) - isneg = 1; - if (lex_accept(T_sizeof)) { - /* The common scalar expression form needs no IR in a global constant: - * integer and character literals have type int. Type names continue - * through the declaration-only evaluator below. + if (can_scan_sizeof_postfix_extent(scope, + lex_peek(T_open_bracket, NULL) + ? cur_token->next->next + : cur_token->next, + lex_peek(T_open_bracket, NULL), false)) { + /* The shared postfix walker has already proved this is an + * identifier-rooted fixed array extent, so the detached parser can + * preserve it without a global-prefix spelling table. */ - token_t *inside = - lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; - var_t *nested_array = NULL; - token_t *nested_root = NULL; - token_t *nested_after = NULL; - int nested_groups = 0; - int string_groups = sizeof_grouped_literal_depth(inside, T_string); - int wstring_groups = sizeof_grouped_literal_depth(inside, T_wstring); - - if (inside && inside->kind == T_open_bracket) { - token_t *token = inside; - - while (token && token->kind == T_open_bracket) { - nested_groups++; - token = token->next; - } - nested_root = token; - if (token && token->kind == T_identifier) - nested_array = find_var(token->literal, scope); - nested_after = token ? token->next : NULL; - for (int i = 0; i < nested_groups && nested_after && - nested_after->kind == T_close_bracket; - i++) - nested_after = nested_after->next; - } - if (can_scan_sizeof_postfix_extent( - scope, - lex_peek(T_open_bracket, NULL) ? cur_token->next->next - : cur_token->next, - lex_peek(T_open_bracket, NULL), false)) { - /* The shared postfix walker has already proved this is an - * identifier-rooted fixed array extent, so the detached parser can - * preserve it without a global-prefix spelling table. - */ - res = read_global_sizeof_expression(scope); - } else if (string_groups >= 0 || wstring_groups >= 0) { - /* sizeof's own parenthesis and any grouping inside it enclose the - * literal array; none of them is part of its extent. - */ - int groups = string_groups >= 0 ? string_groups : wstring_groups; + res = read_global_sizeof_expression(scope); + } else if (string_groups >= 0 || wstring_groups >= 0) { + /* sizeof's own parenthesis and any grouping inside it enclose the + * literal array; none of them is part of its extent. + */ + int groups = string_groups >= 0 ? string_groups : wstring_groups; + lex_expect(T_open_bracket); + for (int i = 0; i < groups; i++) lex_expect(T_open_bracket); - for (int i = 0; i < groups; i++) - lex_expect(T_open_bracket); - if (string_groups >= 0) - res = read_sizeof_string_literal(); - else - res = read_const_wstring_size(); - for (int i = 0; i < groups; i++) - lex_expect(T_close_bracket); + if (string_groups >= 0) + res = read_sizeof_string_literal(); + else + res = read_const_wstring_size(); + for (int i = 0; i < groups; i++) lex_expect(T_close_bracket); - } else if (nested_array && nested_array->array_size > 0 && - nested_groups > 0 && nested_after && - nested_after->kind == T_close_bracket) { + lex_expect(T_close_bracket); + } else if (nested_array && nested_array->array_size > 0 && + nested_groups > 0 && nested_after && + nested_after->kind == T_close_bracket) { + lex_expect(T_open_bracket); + for (int i = 0; i < nested_groups; i++) lex_expect(T_open_bracket); - for (int i = 0; i < nested_groups; i++) - lex_expect(T_open_bracket); - lex_expect(T_identifier); - for (int i = 0; i < nested_groups; i++) - lex_expect(T_close_bracket); + lex_expect(T_identifier); + for (int i = 0; i < nested_groups; i++) lex_expect(T_close_bracket); - res = size_var(nested_array); - } else if (nested_array && nested_groups > 0 && nested_root && - nested_root->next && - (nested_root->next->kind == T_dot || - nested_root->next->kind == T_arrow)) { - /* The local sizeof helper preserves a member-array lvalue through - * arbitrary grouping before a postfix subscript. Reuse it in a - * detached block for a global constant instead of duplicating that - * type-only traversal here. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_open_bracket && inside->next->next && - inside->next->next->kind == T_identifier) { - var_t *grouped_object = - find_var(inside->next->next->literal, scope); - - if (grouped_object && !grouped_object->array_size && - !grouped_object->has_unsized_array) - res = read_global_sizeof_expression(scope); - else - res = read_const_sizeof_type(scope); - } else if (inside && - (inside->kind == T_increment || - inside->kind == T_decrement || inside->kind == T_plus || - inside->kind == T_minus || inside->kind == T_bit_not || - inside->kind == T_log_not)) { - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - ((inside->next->kind == T_identifier && - find_type(inside->next->literal, true)) || - inside->next->kind == T_signed || - inside->next->kind == T_unsigned || - inside->next->kind == T_long || - inside->next->kind == T_const || - inside->next->kind == T_volatile || - inside->next->kind == T_struct || - inside->next->kind == T_union || - inside->next->kind == T_enum)) { - /* A cast begins with an extra parenthesis and cannot be evaluated - * as an integer constant when its operand is a declared object. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_identifier && - (find_var(inside->literal, scope) || - find_func(inside->literal)) && - inside->next && inside->next->kind != T_close_bracket && - inside->next->kind != T_dot && - inside->next->kind != T_arrow && - inside->next->kind != T_open_square) { - /* Parenthesized non-postfix operands need type derivation rather - * than constant evaluation: sizeof(global + 1), sizeof(global = 1), - * and calls are all unevaluated but need their expression type. - * Reuse the detached expression path used for local sizeof. - */ + lex_expect(T_close_bracket); + res = size_var(nested_array); + } else if (nested_array && nested_groups > 0 && nested_root && + nested_root->next && + (nested_root->next->kind == T_dot || + nested_root->next->kind == T_arrow)) { + /* The local sizeof helper preserves a member-array lvalue through + * arbitrary grouping before a postfix subscript. Reuse it in a detached + * block for a global constant instead of duplicating that type-only + * traversal here. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_open_bracket && inside->next->next && + inside->next->next->kind == T_identifier) { + var_t *grouped_object = find_var(inside->next->next->literal, scope); + + if (grouped_object && !grouped_object->array_size && + !grouped_object->has_unsized_array) res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_asterisk && inside->next->next && - inside->next->next->kind == T_identifier && - find_var(inside->next->next->literal, scope)) { - var_t *pointer = find_var(inside->next->next->literal, scope); - var_t dereferenced; - var_t *object; - int subscript_depth = 0; + else + res = read_const_sizeof_type(scope); + } else if (inside && + (inside->kind == T_increment || inside->kind == T_decrement || + inside->kind == T_plus || inside->kind == T_minus || + inside->kind == T_bit_not || inside->kind == T_log_not)) { + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + ((inside->next->kind == T_identifier && + find_type(inside->next->literal, true)) || + inside->next->kind == T_signed || + inside->next->kind == T_unsigned || + inside->next->kind == T_long || inside->next->kind == T_const || + inside->next->kind == T_volatile || + inside->next->kind == T_struct || + inside->next->kind == T_union || + inside->next->kind == T_enum)) { + /* A cast begins with an extra parenthesis and cannot be evaluated as an + * integer constant when its operand is a declared object. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_identifier && + (find_var(inside->literal, scope) || + find_func(inside->literal)) && + inside->next && inside->next->kind != T_close_bracket && + inside->next->kind != T_dot && inside->next->kind != T_arrow && + inside->next->kind != T_open_square) { + /* Parenthesized non-postfix operands need type derivation rather than + * constant evaluation: sizeof(global + 1), sizeof(global = 1), and + * calls are all unevaluated but need their expression type. Reuse the + * detached expression path used for local sizeof. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_asterisk && inside->next->next && + inside->next->next->kind == T_identifier && + find_var(inside->next->next->literal, scope)) { + var_t *pointer = find_var(inside->next->next->literal, scope); + var_t dereferenced; + var_t *object; + int subscript_depth = 0; + + if (effective_pointer_depth(pointer) != 1) + error_at("Cannot dereference non-pointer in sizeof", + cur_token_loc()); - if (effective_pointer_depth(pointer) != 1) - error_at("Cannot dereference non-pointer in sizeof", + /* This is compile-time descriptor manipulation, not a source aggregate + * expression. A direct `dereferenced = *pointer` becomes one wide + * OP_read while self-hosting; ARM's scalar load backend correctly + * admits only 1/2/4-byte reads. Copy through libc instead, which keeps + * the generated compiler IR word-sized and preserves every descriptor + * field. + */ + memcpy(&dereferenced, pointer, sizeof(dereferenced)); + dereferenced.type = pointee_type_from_pointer_typedef(pointer->type); + dereferenced.ptr_level = 0; + object = &dereferenced; + + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + lex_expect(T_identifier); + lex_expect(T_close_bracket); + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", cur_token_loc()); - - /* This is compile-time descriptor manipulation, not a source - * aggregate expression. A direct `dereferenced = *pointer` becomes - * one wide OP_read while self-hosting; ARM's scalar load backend - * correctly admits only 1/2/4-byte reads. Copy through libc - * instead, which keeps the generated compiler IR word-sized and - * preserves every descriptor field. - */ - memcpy(&dereferenced, pointer, sizeof(dereferenced)); - dereferenced.type = - pointee_type_from_pointer_typedef(pointer->type); - dereferenced.ptr_level = 0; - object = &dereferenced; - - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - lex_expect(T_identifier); - lex_expect(T_close_bracket); - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_asterisk && inside->next && - inside->next->kind == T_identifier && inside->next->next && - inside->next->next->kind == T_open_square && - is_direct_fixed_array_pointer_slot( - find_var(inside->next->literal, scope))) { - /* The shared direct-pointer helper also owns the supported global - * pointer-array slot form. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_asterisk && inside->next && - inside->next->kind == T_ampersand) { - var_t *object; - int subscript_depth; - - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - object = read_const_addressed_object(scope, &subscript_depth); - lex_expect(T_close_bracket); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_ampersand) { - lex_expect(T_open_bracket); - read_const_addressed_object(scope, NULL); - lex_expect(T_close_bracket); - res = PTR_SIZE; - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_identifier && - find_var(inside->next->literal, scope) && - inside->next->next && - inside->next->next->kind != T_close_bracket && - inside->next->next->kind != T_dot && - inside->next->next->kind != T_arrow && - inside->next->next->kind != T_open_square) { - /* A grouped object followed by an operator is an ordinary - * parenthesized expression, not the array/member postfix form - * handled below. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_identifier && - find_var(inside->next->literal, scope)) { - /* Preserve the array object type through an explicitly grouped - * object expression, e.g. sizeof((record.items)[0]). - */ - var_t *object = find_var(inside->next->literal, scope); - int subscript_depth = 0; - - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - lex_expect(T_identifier); - object = read_const_object_members(object); - lex_expect(T_close_bracket); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_open_bracket) { - lex_expect(T_open_bracket); - read_const_expr(scope); - lex_expect(T_close_bracket); - res = TY_int->size; - } else if (inside && - (inside->kind == T_numeric || inside->kind == T_char || - inside->kind == T_wchar)) { - lex_expect(T_open_bracket); read_const_expr(scope); - lex_expect(T_close_bracket); - res = TY_int->size; - } else if (inside && inside->kind == T_identifier) { - var_t *object = find_var(inside->literal, scope); - constant_t *constant = find_scoped_constant(inside->literal, scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_asterisk && inside->next && + inside->next->kind == T_identifier && inside->next->next && + inside->next->next->kind == T_open_square && + is_direct_fixed_array_pointer_slot( + find_var(inside->next->literal, scope))) { + /* The shared direct-pointer helper also owns the supported global + * pointer-array slot form. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_asterisk && inside->next && + inside->next->kind == T_ampersand) { + var_t *object; + int subscript_depth; + + lex_expect(T_open_bracket); + lex_expect(T_asterisk); + object = read_const_addressed_object(scope, &subscript_depth); + lex_expect(T_close_bracket); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && inside->kind == T_asterisk) { + /* Only "*&object" keeps a declared extent through the address walk; any + * other dereference, such as *pointer, needs the pointee type. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_ampersand) { + lex_expect(T_open_bracket); + read_const_addressed_object(scope, NULL); + lex_expect(T_close_bracket); + res = PTR_SIZE; + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_identifier && + find_var(inside->next->literal, scope) && inside->next->next && + inside->next->next->kind != T_close_bracket && + inside->next->next->kind != T_dot && + inside->next->next->kind != T_arrow && + inside->next->next->kind != T_open_square) { + /* A grouped object followed by an operator is an ordinary parenthesized + * expression, not the array/member postfix form handled below. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_open_bracket && inside->next && + inside->next->kind == T_identifier && + find_var(inside->next->literal, scope)) { + /* Preserve the array object type through an explicitly grouped object + * expression, e.g. sizeof((record.items)[0]). + */ + var_t *object = find_var(inside->next->literal, scope); + int subscript_depth = 0; - if (object || constant) { - lex_expect(T_open_bracket); - lex_expect(T_identifier); - if (object) { - int subscript_depth = 0; - - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at( - "Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else { - lex_expect(T_close_bracket); - res = TY_int->size; - } - } else { - res = read_const_sizeof_type(scope); - } - } else if (lex_peek(T_numeric, buffer) || lex_peek(T_char, buffer) || - lex_peek(T_wchar, buffer)) { - read_primary_constant(scope); - res = TY_int->size; - } else if (lex_peek(T_string, NULL)) { - res = read_sizeof_string_literal(); - } else if (lex_peek(T_wstring, NULL)) { - res = read_const_wstring_size(); - } else if (lex_peek(T_ampersand, NULL)) { - read_const_addressed_object(scope, NULL); - res = PTR_SIZE; - } else if (lex_accept(T_asterisk)) { - var_t *object; - int subscript_depth; - - object = read_const_addressed_object(scope, &subscript_depth); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (lex_peek(T_minus, NULL) || lex_peek(T_plus, NULL) || - lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL)) { - res = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, buffer)) { - var_t *object = find_var(buffer, scope); - constant_t *constant = find_scoped_constant(buffer, scope); + lex_expect(T_open_bracket); + lex_expect(T_open_bracket); + lex_expect(T_identifier); + object = read_const_object_members(object); + lex_expect(T_close_bracket); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + lex_expect(T_close_bracket); + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (inside && + (inside->kind == T_open_bracket || inside->kind == T_numeric || + inside->kind == T_char || inside->kind == T_wchar)) { + /* A literal has the type its suffix and value select, such as long long + * for 1LL, so only the ordinary expression parser knows its size. + */ + res = read_global_sizeof_expression(scope); + } else if (inside && inside->kind == T_identifier) { + var_t *object = find_var(inside->literal, scope); + constant_t *constant = find_scoped_constant(inside->literal, scope); + if (object || constant) { + lex_expect(T_open_bracket); lex_expect(T_identifier); if (object) { int subscript_depth = 0; @@ -475,18 +392,87 @@ int read_primary_constant(block_t *scope) lex_expect(T_close_square); subscript_depth++; } + lex_expect(T_close_bracket); validate_global_sizeof_object(object); res = sizeof_subscripted_array(object, subscript_depth); - } else if (constant) { - res = TY_int->size; } else { - error_at("sizeof requires a type or declared object", - cur_token_loc()); - res = 0; + lex_expect(T_close_bracket); + res = TY_int->size; } } else { res = read_const_sizeof_type(scope); } + } else if (lex_peek(T_numeric, NULL) || lex_peek(T_char, NULL) || + lex_peek(T_wchar, NULL)) { + res = read_global_sizeof_expression(scope); + } else if (lex_peek(T_string, NULL)) { + res = read_sizeof_string_literal(); + } else if (lex_peek(T_wstring, NULL)) { + res = read_const_wstring_size(); + } else if (lex_peek(T_ampersand, NULL)) { + read_const_addressed_object(scope, NULL); + res = PTR_SIZE; + } else if (lex_peek(T_asterisk, NULL) && cur_token->next->next && + cur_token->next->next->kind == T_ampersand) { + var_t *object; + int subscript_depth; + + lex_expect(T_asterisk); + object = read_const_addressed_object(scope, &subscript_depth); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (lex_peek(T_asterisk, NULL)) { + /* As above, a dereference other than "*&object" needs its type. */ + res = read_global_sizeof_expression(scope); + } else if (lex_peek(T_minus, NULL) || lex_peek(T_plus, NULL) || + lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL)) { + res = read_global_sizeof_expression(scope); + } else if (lex_peek(T_identifier, buffer)) { + var_t *object = find_var(buffer, scope); + constant_t *constant = find_scoped_constant(buffer, scope); + + lex_expect(T_identifier); + if (object) { + int subscript_depth = 0; + + object = read_const_object_members(object); + while (lex_accept(T_open_square)) { + if (!object->array_size && !object->has_unsized_array) + error_at("Cannot apply square operator to non-array", + cur_token_loc()); + read_const_expr(scope); + lex_expect(T_close_square); + subscript_depth++; + } + validate_global_sizeof_object(object); + res = sizeof_subscripted_array(object, subscript_depth); + } else if (constant) { + res = TY_int->size; + } else { + error_at("sizeof requires a type or declared object", + cur_token_loc()); + res = 0; + } + } else { + res = read_const_sizeof_type(scope); + } + return res; +} + +int read_primary_constant(block_t *scope) +{ + /* return signed constant */ + int isneg = 0, res; + + /* This recurses once per nesting level of a constant expression, so the + * buffer holds only what is copied into it: a number, a character constant + * or an identifier, each at most MAX_TOKEN_LEN. A string is only tested for + * and never copied. + */ + char buffer[MAX_TOKEN_LEN]; + if (lex_accept(T_minus)) + isneg = 1; + if (lex_accept(T_sizeof)) { + res = read_sizeof_constant(scope); } else if (lex_accept(T_open_bracket)) { res = read_primary_constant(scope); lex_expect(T_close_bracket); @@ -584,9 +570,20 @@ int eval_expression_imm(opcode_t op, int op1, int op2) if (checking_enum_constant && (op2 < 0 || op2 >= 32 || op1 < 0 || op1 > (INT_MAX >> op2))) error_at("Enumerator value exceeds int range", cur_token_loc()); - res = op1 << op2; + + /* 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: @@ -668,6 +665,61 @@ bool typed_global_literal_appears_before_initializer_end(token_t *token) return false; } +/* 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; +} + +/* A scalar initializer containing an integer cast needs the two-word reader + * too: the word-sized evaluator has no notion of a type name. + */ +static bool global_integer_cast_appears_before_initializer_end(token_t *token, + block_t *scope) +{ + int bracket_depth = 0; + + for (; token; token = token->next) { + if (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; +} + var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb, block_t *scope); @@ -1123,12 +1175,47 @@ var_t *read_wide_global_literal_primary(block_t *parent, find_func(sizeof_name)) { value->init_val = read_global_sizeof_expression(scope); } else - value->init_val = read_const_sizeof_type(scope); + 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); + int saved_side_effects = se_idx; + 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); + se_idx = saved_side_effects; + 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); @@ -1480,7 +1567,9 @@ bool read_global_assignment_var(var_t *var) * narrow the expression through that evaluator. */ if (typed_global_literal_appears_before_initializer_end( - cur_token->next)) { + cur_token->next) || + global_integer_cast_appears_before_initializer_end(cur_token->next, + scope)) { rs1 = read_wide_global_literal_expression(parent, bb, scope); add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); return true; diff --git a/src/parser-decl.c b/src/parser-decl.c index 87f68ab6..2a802dd5 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -37,87 +37,136 @@ void read_sizeof_function_prototype(void) parsing_sizeof_function_signature = saved_sizeof_signature; } -/* Consume a nested direct-abstract-declarator in forms such as `int - * (*(*)(int))[2]` and `int (*(*(*)(int))(int))(int)`. The surrounding - * declarator already consumed its leading `(` and pointer chain. Each recursive - * invocation consumes its own parenthesized pointer declarator and function - * suffix, then the enclosing `)` and that declarator's suffix. The complete - * type remains pointer-sized, but its bounds and prototypes must still obey C99 - * syntax and the project's fixed-array-only policy. +/* 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. */ -int read_sizeof_nested_function_pointer_suffix(block_t *scope, - int *outer_array_size) +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 inner_ptr_count = 0; + 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; - lex_expect(T_open_bracket); while (lex_accept(T_asterisk)) { - inner_ptr_count++; + derivation = SIZEOF_DERIVED_POINTER; while (lex_accept(T_const) || lex_accept(T_volatile) || lex_accept(T_restrict)) ; } - if (!inner_ptr_count) - error_at("sizeof nested abstract declarator needs a pointer", - cur_token_loc()); - - if (lex_peek(T_open_bracket, NULL)) - inner_ptr_count += - read_sizeof_nested_function_pointer_suffix(scope, outer_array_size); - else { - int direct_array_size = 0; - - while (lex_accept(T_open_square)) { - 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 (bound > 0 && direct_array_size && - direct_array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - direct_array_size = - direct_array_size ? direct_array_size * bound : bound; - } - if (direct_array_size && outer_array_size) - *outer_array_size = direct_array_size; + /* 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); - if (lex_peek(T_open_bracket, NULL)) { + 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(); - } else if (lex_peek(T_open_square, NULL)) { - do { - int bound; + derivation = SIZEOF_DERIVED_FUNCTION; + } else + break; + } - lex_expect(T_open_square); - 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()); - } while (lex_peek(T_open_square, NULL)); - } else { - error_at( - "sizeof nested abstract declarator needs a function or " - "array suffix", - cur_token_loc()); - } + 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; +} - lex_expect(T_close_bracket); - while (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - while (lex_accept(T_open_square)) { - int bound = read_const_expr(scope); +/* 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; - lex_expect(T_close_square); - if (bound <= 0) - error_at("sizeof array type needs a positive constant bound", + 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; + } } - return inner_ptr_count; + if (size > INT_MAX / elements) + error_at("sizeof array type is too large", cur_token_loc()); + return size * elements; } /* Integer constant expressions may contain sizeof(type-name). This parser only @@ -129,9 +178,8 @@ int read_const_sizeof_type(block_t *scope) { char token[MAX_ID_LEN]; type_t *type = NULL; - int ptr_level = 0; - int array_size = 0; - int array_element_size = 0; + sizeof_derivation_t derivation; + int elements; bool is_unsigned = false; bool is_signed = false; int long_count = 0; @@ -143,7 +191,7 @@ int read_const_sizeof_type(block_t *scope) type = find_record_tag(token, scope, record_kind); } else if (lex_accept(T_enum)) { lex_ident(T_identifier, token); - type = find_type_tag(token, scope); + type = find_enum_tag(token, scope); } else { /* Declaration specifiers may appear in any order: all of "unsigned long * int", "long unsigned int", and "int unsigned" name the same type. @@ -209,119 +257,9 @@ int read_const_sizeof_type(block_t *scope) error_at( "sizeof in an integer constant expression requires a type name", cur_token_loc()); - while (lex_accept(T_asterisk)) { - ptr_level++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - while (ptr_level == 0 && lex_accept(T_open_square)) { - 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 (bound > 0 && array_size && array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - array_size = array_size ? array_size * bound : bound; - } - if (lex_accept(T_open_bracket)) { - int nested_array_size = 0; - int nested_ptr_count = 0; - int nested_function_ptr_count = 0; - int nested_outer_array_size = 0; - - while (lex_accept(T_asterisk)) { - nested_ptr_count++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - if (!nested_ptr_count) - error_at("sizeof abstract declarator needs a pointer", - cur_token_loc()); - if (lex_peek(T_open_bracket, NULL)) { - nested_function_ptr_count = - read_sizeof_nested_function_pointer_suffix( - scope, &nested_outer_array_size); - if (nested_outer_array_size) { - array_size = nested_outer_array_size; - array_element_size = PTR_SIZE; - } else - ptr_level += nested_ptr_count + nested_function_ptr_count; - } else - while (lex_accept(T_open_square)) { - 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 (bound > 0 && nested_array_size && - nested_array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - nested_array_size = - nested_array_size ? nested_array_size * bound : bound; - } - if (!nested_function_ptr_count) { - lex_expect(T_close_bracket); - if (nested_array_size) { - array_size = nested_array_size; - array_element_size = PTR_SIZE; - - /* `int (*[2][3])(int)` is an array of function pointers: the - * parenthesized declarator owns the array bounds, while the - * following parameter list belongs to each pointed-to function. - * Consume that suffix before the enclosing sizeof parenthesis. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } else { - ptr_level += nested_ptr_count; - while (lex_accept(T_open_square)) { - 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()); - } - - /* A pointer declarator followed by a parameter list names a - * function pointer. Its function type has no object - * representation, but the pointer itself is an object and - * sizeof is pointer-sized. Reuse the declaration parser for - * complete prototype syntax, then discard the otherwise-unused - * signature. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } - } - } + derivation = read_sizeof_abstract_declarator(scope, &elements); lex_expect(T_close_bracket); - if (ptr_level) - return PTR_SIZE; - if (type == TY_void) - error_at("sizeof cannot be applied to void", cur_token_loc()); - if (type->is_direct_function_type) - error_at("sizeof(function) is invalid", cur_token_loc()); - if (!type->size && (!type->base_struct || !type->base_struct->size)) - error_at("sizeof cannot be applied to an incomplete type", - cur_token_loc()); - if (!array_size && !ptr_level && type->array_size) - array_size = type->array_size; - if (array_size) - return array_size * - (array_element_size ? array_element_size : type->size); - if (type->size) - return type->size; - return type->base_struct->size; + return sizeof_type_name_size(type, derivation, elements); } /* Return the row-major offset contributed by a fixed terminal array member in @@ -377,11 +315,8 @@ int read_const_expr_operand(block_t *scope) return ~read_const_expr_operand(scope); if (lex_accept(T_log_not)) return !read_const_expr_operand(scope); - if (lex_accept(T_sizeof)) { - if (lex_peek(T_wstring, NULL)) - return read_const_wstring_size(); - return read_const_sizeof_type(scope); - } + if (lex_accept(T_sizeof)) + return read_sizeof_constant(scope); if (lex_accept(T_open_bracket)) { int res = read_const_expr(scope); @@ -1009,6 +944,31 @@ void read_inner_var_decl(var_t *vd, error_at("void type cannot define an object", cur_token_loc()); } +/* 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; + } +} + /* starting next_token, need to check the type */ void read_full_var_decl(var_t *vd, bool anon, @@ -1113,7 +1073,7 @@ void read_full_var_decl(var_t *vd, type = is_long ? TY_long_double : TY_double; } else if (is_enum_type) { lex_ident(T_identifier, type_name); - type = find_type_tag(type_name, vd->scope); + type = find_enum_tag(type_name, vd->scope); } else if (is_unsigned) { if (is_long) { if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) diff --git a/src/parser-expr.c b/src/parser-expr.c index 899f9524..059be4c3 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1172,6 +1172,41 @@ static void read_compound_literal_operand(block_t *parent, } } +/* 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. */ @@ -1212,41 +1247,9 @@ static void read_cast_operand(block_t *parent, 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) { - unsigned int lo = (unsigned int) expr_var->init_val; - unsigned int hi = (unsigned int) expr_var->init_val_hi; - cast_var->is_const = true; - - /* 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. - */ - if (cast_var->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. - */ - cast_var->init_val = lo || hi; - cast_var->init_val_hi = 0; - } else if (cast_var->type->size < TY_int->size) { - unsigned int bits = cast_var->type->size * 8; - unsigned int mask = (1U << bits) - 1; - - lo &= mask; - if (!cast_var->type->is_unsigned && (lo & (1U << (bits - 1)))) - lo |= ~mask; - cast_var->init_val = (int) lo; - cast_var->init_val_hi = cast_var->init_val < 0 ? -1 : 0; - } else if (cast_var->type->size == TY_int->size) { - cast_var->init_val = (int) lo; - cast_var->init_val_hi = cast_var->type->is_unsigned - ? 0 - : (cast_var->init_val < 0 ? -1 : 0); - } else { - cast_var->init_val = (int) lo; - cast_var->init_val_hi = (int) hi; - } + 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 @@ -1369,7 +1372,7 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) type_t *type; if (has_enum_type) { lex_ident(T_identifier, lookahead_token); - type = find_type_tag(lookahead_token, parent); + type = find_enum_tag(lookahead_token, parent); } else if (has_unsigned_type && !is_record) { if (has_long_type) { type = TY_ulong; diff --git a/src/parser-global.c b/src/parser-global.c index a970415e..4a0d85e9 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -44,7 +44,10 @@ void read_global_init_var(var_t *var, block_t *block) * completion independent of how that type was spelled lets enum, record, and * ordinary scalar declarations share linkage and redeclaration checks. */ -void read_global_function_declarator(block_t *block, var_t *var, bool is_static) +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 @@ -184,6 +187,15 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) } 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 (check_decl && func_tmp.bbs) error_at("redefinition of function", next_token_loc()); if (is_incomplete_record_object(&func->return_def)) @@ -200,12 +212,17 @@ void read_global_function_declarator(block_t *block, var_t *var, bool is_static) error_at("Incomplete struct/union type cannot define an object", next_token_loc()); read_func_body(func); - return; + return true; } - if (inherited_direct_function_type && lex_peek(T_comma, NULL)) - return; + + /* 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 @@ -257,7 +274,8 @@ var_t *resolve_global_declarator(block_t *block, 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_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", @@ -283,7 +301,8 @@ bool read_global_declarator(block_t *block, bool is_const, bool is_static, bool is_volatile, - bool is_extern) + bool is_extern, + bool allow_definition) { bool is_redeclaration; var_t *nv = require_typed_var(block, decl_type); @@ -293,10 +312,9 @@ bool read_global_declarator(block_t *block, 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)) { - read_global_function_declarator(block, nv, is_static); - return true; - } + if (lex_peek(T_open_bracket, NULL)) + 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", @@ -575,19 +593,21 @@ bool read_global_record_declarator(block_t *block, type_t *decl_type, bool is_const, bool is_static, - bool is_extern) + 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)) { - read_global_function_declarator(block, var, is_static); - return true; - } + if (lex_peek(T_open_bracket, NULL)) + 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", @@ -622,6 +642,40 @@ bool read_global_record_declarator(block_t *block, 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, @@ -642,8 +696,8 @@ void read_global_decl(block_t *block, var->is_extern = is_extern && !lex_peek(T_assign, NULL); if (lex_peek(T_open_bracket, NULL)) { - read_global_function_declarator(block, var, is_static); - return; + if (read_global_function_declarator(block, var, is_static, true)) + return; } else { bool is_definition = !var->is_extern; if (var->is_inline) @@ -659,27 +713,28 @@ void read_global_decl(block_t *block, 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()); + /* 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); } - discard_global_declarator_operand(var); - /* 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. + /* 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)) - read_global_declarator(block, var->type, var->is_const_qualified, - is_static, var->is_volatile, is_extern); + if (read_global_declarator(block, var->type, var->is_const_qualified, + is_static, var->is_volatile, is_extern, + false)) + return; lex_expect(T_semicolon); - return; } void consume_global_compound_literal(void) @@ -737,12 +792,13 @@ void initialize_struct_field(var_t *nv, var_t *v, int offset) /* The declarators of a file-scope typedef whose base type is not a record or * enum definition: scalar, pointer, array and function aliases. */ -static void read_global_typedef_declarators(block_t *block) +static void read_global_typedef_declarators(block_t *block, + bool typedef_const, + bool typedef_volatile) { char base_type[MAX_ID_LEN]; const type_t *base; type_t *type = add_type(); - bool typedef_const = false; bool is_signed = false; bool is_unsigned = false; bool is_long = false; @@ -768,10 +824,13 @@ static void read_global_typedef_declarators(block_t *block) * `long unsigned long` rather than treating the second specifier as the * typedef name. */ - while (lex_peek(T_const, NULL) || lex_peek(T_signed, NULL) || - lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { + while (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)) { if (lex_accept(T_const)) typedef_const = true; + else if (lex_accept(T_volatile)) + typedef_volatile = true; else if (lex_accept(T_signed)) { if (is_signed) error_at("duplicate signed type specifier", cur_token_loc()); @@ -862,6 +921,12 @@ static void read_global_typedef_declarators(block_t *block) } if (!base) error_at("Unable to find base type", cur_token_loc()); + + /* `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); type->base_type = base->base_type; type->size = base->size; @@ -882,6 +947,8 @@ static void read_global_typedef_declarators(block_t *block) 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; type->is_unsigned = base->is_unsigned; type->is_floating = base->is_floating; type->is_signed_char = base->is_signed_char; @@ -902,7 +969,12 @@ static void read_global_typedef_declarators(block_t *block) if (lex_accept(T_const)) { if (type->ptr_level <= 32) type->pointer_const_mask |= 1U << (type->ptr_level - 1); - } else if (lex_accept(T_volatile) || lex_accept(T_restrict)) { + } 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; @@ -1015,7 +1087,14 @@ static void read_global_typedef_declarators(block_t *block) 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_accept(T_enum)) { int val = 0; type_t *type = add_type(); @@ -1033,6 +1112,9 @@ static void read_global_typedef(block_t *block) first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); + read_type_qualifiers(&typedef_const, &typedef_volatile, false); + type->is_const_qualified = typedef_const; + type->is_volatile_qualified = typedef_volatile; lex_ident(T_identifier, token); set_type_name(type, token); lex_expect(T_semicolon); @@ -1105,6 +1187,7 @@ static void read_global_typedef(block_t *block) } while (!lex_accept(T_close_curly)); } + read_type_qualifiers(&typedef_const, &typedef_volatile, false); while (lex_accept(T_asterisk)) { type->ptr_level++; type->size = PTR_SIZE; @@ -1130,6 +1213,10 @@ static void read_global_typedef(block_t *block) type->base_struct = tag; } + /* Only the alias is qualified; the tag copy above must stay plain. */ + type->is_const_qualified = typedef_const; + type->is_volatile_qualified = typedef_volatile; + lex_expect(T_semicolon); } else if (lex_accept(T_union)) { int i = 0, max_size = 0, alignment = 1; @@ -1187,6 +1274,7 @@ static void read_global_typedef(block_t *block) } while (!lex_accept(T_close_curly)); } + read_type_qualifiers(&typedef_const, &typedef_volatile, false); while (lex_accept(T_asterisk)) { type->ptr_level++; type->size = PTR_SIZE; @@ -1212,9 +1300,12 @@ static void read_global_typedef(block_t *block) type->base_struct = tag; } + type->is_const_qualified = typedef_const; + type->is_volatile_qualified = typedef_volatile; + lex_expect(T_semicolon); } else { - read_global_typedef_declarators(block); + read_global_typedef_declarators(block, typedef_const, typedef_volatile); } } @@ -1289,13 +1380,8 @@ void read_global_statement(void) if (lex_accept(T_semicolon)) return; - if (read_global_record_declarator(block, type, is_const, is_static, - is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static, - is_extern); - lex_expect(T_semicolon); + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, true); return; } @@ -1356,15 +1442,9 @@ void read_global_statement(void) /* A record definition may be followed by its declarators, as in "struct * pair { int x, y; } first, *second;". */ - if (!lex_peek(T_semicolon, NULL)) { - if (read_global_record_declarator(block, type, is_const, is_static, - is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static, - is_extern); - } - lex_expect(T_semicolon); + if (!lex_accept(T_semicolon)) + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, true); } else if (lex_accept(T_union)) { int i = 0, max_size = 0, alignment = 1; bool has_flexible_array_member = false; @@ -1381,13 +1461,8 @@ void read_global_statement(void) if (lex_accept(T_semicolon)) return; - if (read_global_record_declarator(block, type, is_const, is_static, - is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static, - is_extern); - lex_expect(T_semicolon); + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, true); return; } @@ -1434,15 +1509,9 @@ void read_global_statement(void) type->num_fields = i; type->has_flexible_array_member = has_flexible_array_member; - if (!lex_peek(T_semicolon, NULL)) { - if (read_global_record_declarator(block, type, is_const, is_static, - is_extern)) - return; - while (lex_accept(T_comma)) - read_global_record_declarator(block, type, is_const, is_static, - is_extern); - } - lex_expect(T_semicolon); + if (!lex_accept(T_semicolon)) + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, true); } else if (lex_accept(T_enum)) { /* An enum definition is a declaration in its own right; it need not * introduce a typedef. Its enumerators are integer constants and may @@ -1460,19 +1529,14 @@ void read_global_statement(void) if (!lex_peek(T_open_curly, NULL)) { if (!has_tag) error_at("Expected enum tag or definition", cur_token_loc()); - type = find_type(token, true); + type = find_enum_tag(token, GLOBAL_BLOCK); if (!type) error_at("Unknown enum type", cur_token_loc()); - if (read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern)) - return; - while (lex_accept(T_comma)) - read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern); - lex_expect(T_semicolon); + read_global_declarator_list(block, type, is_const, is_static, + is_volatile, is_extern, false); return; } - type = has_tag ? find_type(token, true) : NULL; + type = has_tag ? local_enum_tag(token, GLOBAL_BLOCK) : NULL; if (!type) type = add_type(); @@ -1498,15 +1562,9 @@ void read_global_statement(void) first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); - if (!lex_peek(T_semicolon, NULL)) { - if (read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern)) - return; - while (lex_accept(T_comma)) - read_global_declarator(block, type, is_const, is_static, - is_volatile, is_extern); - } - lex_expect(T_semicolon); + if (!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) || diff --git a/src/parser-init.c b/src/parser-init.c index 94ef32d9..f0472be6 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -19,9 +19,10 @@ var_t *resolve_global_declarator(block_t *block, var_t *var, bool is_static, bool *is_redeclaration); -void read_global_function_declarator(block_t *block, +bool read_global_function_declarator(block_t *block, var_t *var, - bool is_static); + 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, @@ -392,8 +393,72 @@ void parse_struct_field_init(block_t *parent, type_t *struct_type, var_t *target_addr, bool emit_code); +bool parse_struct_field_values(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr, + bool emit_code, + bool elided, + var_t *first_value); +bool parse_unbraced_record_init(block_t *parent, + basic_block_t **bb, + type_t *record_type, + var_t *addr, + bool emit_code); +bool unbraced_record_starts_here(const var_t *elem); + +/* 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; -void parse_array_field_row_values(block_t *parent, + if (from >= to) + return; + 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); + for (int i = from; i < to; i++) { + var_t *elem_addr = + compute_element_address(parent, bb, base, i, elem_size); + for (int offset = 0; offset < elem_size; offset++) { + var_t *byte_addr = + compute_element_address(parent, bb, elem_addr, offset, 1); + add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); + } + } +} + +/* Read an `[index] =` designator for one of @bound slots, if one is next. */ +bool accept_slot_designator(block_t *scope, int bound, int *slot) +{ + int index; + + if (!lex_accept(T_open_square)) + return false; + index = read_const_expr(scope); + lex_expect(T_close_square); + if (index < 0 || index >= bound) + error_at("Array designator index is out of bounds", cur_token_loc()); + lex_expect(T_assign); + *slot = index; + 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, @@ -402,6 +467,8 @@ void parse_array_field_row_values(block_t *parent, bool braced) { int count = 0; + int filled = 0; + bool comma_consumed = false; int elem_size = (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; @@ -413,12 +480,19 @@ void parse_array_field_row_values(block_t *parent, var_t *value = NULL; var_t *elem_addr; + if (braced) + accept_slot_designator(field->scope ? field->scope : parent, + field->array_dim2, &count); if (count >= field->array_dim2) error_at("Too many elements in array initializer", next_token_loc()); + if (emit_code) + 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 = field->type; if (record_type->base_type == TYPE_typedef && @@ -428,6 +502,9 @@ void parse_array_field_row_values(block_t *parent, parse_struct_field_init(parent, bb, record_type, elem_addr, emit_code); lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(field)) { + comma_consumed = parse_unbraced_record_init(parent, bb, field->type, + elem_addr, emit_code); } else if (parent == GLOBAL_BLOCK) { if (emit_code) value = parse_global_constant_value(parent, bb); @@ -449,33 +526,31 @@ void parse_array_field_row_values(block_t *parent, } count++; + if (count > filled) + filled = count; if (count == field->array_dim2) { - if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + 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 (!lex_accept(T_comma)) + if (!comma_consumed && !lex_accept(T_comma)) break; + comma_consumed = true; } - if (braced) + if (braced) { lex_expect(T_close_curly); - - if (emit_code) { - 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); - for (; count < field->array_dim2; count++) { - var_t *elem_addr = compute_element_address( - parent, bb, target_addr, start + count, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } + comma_consumed = false; } + + if (emit_code) + 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, @@ -502,6 +577,7 @@ void parse_array_field_plane_values(block_t *parent, { var_t row; int rows = 0; + int filled = 0; int row_width = field->array_dim3; int elem_size = (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; @@ -514,53 +590,39 @@ void parse_array_field_plane_values(block_t *parent, reject_empty_initializer_in_strict_c99(); } while (!lex_peek(T_close_curly, NULL)) { - if (lex_accept(T_open_square)) { - int index = read_const_expr(field->scope ? field->scope : parent); - - lex_expect(T_close_square); - if (index < 0 || index >= field->array_dim2) - error_at("Array designator index is out of bounds", - cur_token_loc()); - rows = index; - lex_expect(T_assign); - } + accept_slot_designator(field->scope ? field->scope : parent, + field->array_dim2, &rows); if (rows >= field->array_dim2) error_at("Too many rows in array initializer", next_token_loc()); - parse_array_field_row_values(parent, bb, &row, target_addr, - start + rows * row_width, emit_code, - lex_peek(T_open_curly, NULL)); + if (emit_code) + 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, emit_code, + lex_peek(T_open_curly, NULL)); rows++; + if (rows > filled) + filled = rows; if (rows == field->array_dim2) { - if (braced && lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)) + 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 (!lex_accept(T_comma)) + if (!row_comma && !lex_accept(T_comma)) break; } if (braced) lex_expect(T_close_curly); - if (emit_code) { - 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); - for (; rows < field->array_dim2; rows++) { - for (int col = 0; col < row_width; col++) { - var_t *addr = compute_element_address( - parent, bb, target_addr, start + rows * row_width + col, - elem_size); - for (int byte = 0; byte < elem_size; byte++) { - var_t *byte_addr = - compute_element_address(parent, bb, addr, byte, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, - NULL); - } - } - } - } + if (emit_code) + 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, @@ -587,6 +649,7 @@ void parse_array_field_hyperplane_init(block_t *parent, { var_t plane; int planes = 0; + int filled = 0; int plane_size = field->array_dim3 * field->array_dim4; int elem_size = (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; @@ -599,46 +662,28 @@ void parse_array_field_hyperplane_init(block_t *parent, lex_expect(T_open_curly); reject_empty_initializer_in_strict_c99(); while (!lex_peek(T_close_curly, NULL)) { - if (lex_accept(T_open_square)) { - int index = read_const_expr(field->scope ? field->scope : parent); - - lex_expect(T_close_square); - if (index < 0 || index >= field->array_dim2) - error_at("Array designator index is out of bounds", - cur_token_loc()); - planes = index; - lex_expect(T_assign); - } + accept_slot_designator(field->scope ? field->scope : parent, + field->array_dim2, &planes); if (planes >= field->array_dim2) error_at("Too many planes in array initializer", next_token_loc()); + if (emit_code) + 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, emit_code, lex_peek(T_open_curly, NULL)); planes++; + if (planes > filled) + filled = planes; if (!lex_accept(T_comma)) break; } lex_expect(T_close_curly); - if (emit_code) { - 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); - for (; planes < field->array_dim2; planes++) { - for (int element = 0; element < plane_size; element++) { - var_t *addr = compute_element_address( - parent, bb, target_addr, - start + planes * plane_size + element, elem_size); - for (int byte = 0; byte < elem_size; byte++) { - var_t *byte_addr = - compute_element_address(parent, bb, addr, byte, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, - NULL); - } - } - } - } + if (emit_code) + 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 @@ -774,6 +819,12 @@ void parse_array_field_init(block_t *parent, bool emit_code) { 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 = (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; @@ -781,86 +832,172 @@ void parse_array_field_init(block_t *parent, 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 (lex_accept(T_open_square)) { + var_t element; + + memcpy(&element, field, sizeof(element)); + count = 0; + do { + int stride = fixed_array_inner_count(&element); + int index = + read_const_expr(field->scope ? field->scope : parent); + + lex_expect(T_close_square); + if (index < 0 || index >= element.array_size / stride) + error_at("Array designator index is out of bounds", + cur_token_loc()); + count += index * stride; + memcpy(&slice, &element, sizeof(slice)); + fixed_array_var_drop_outer(&element); + } while (element.array_size && lex_accept(T_open_square)); + lex_expect(T_assign); + } if (count >= field->array_size) error_at("Too many elements in array initializer", next_token_loc()); + if (emit_code) + 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 (field->array_dim4 && lex_peek(T_open_curly, NULL)) { - parse_array_field_hyperplane_init(parent, bb, field, target_addr, + if (slice.array_dim4 && lex_peek(T_open_curly, NULL)) { + parse_array_field_hyperplane_init(parent, bb, &slice, target_addr, count, emit_code); - count += fixed_array_inner_count(field); - if (!lex_accept(T_comma)) - break; - continue; - } else if (field->array_dim3 && lex_peek(T_open_curly, NULL)) { - parse_array_field_plane_init(parent, bb, field, 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, emit_code); - count += fixed_array_inner_count(field); - if (!lex_accept(T_comma)) - break; - continue; - } else if (field->array_dim2 && lex_peek(T_open_curly, NULL)) { - parse_array_field_row_init(parent, bb, field, 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, emit_code); - count += fixed_array_inner_count(field); - if (!lex_accept(T_comma)) - break; - continue; - } else if (lex_peek(T_open_curly, NULL) && - is_record_type(field->type)) { - type_t *record_type = field->type; - 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(parent, bb, record_type, elem_addr, - emit_code); - lex_expect(T_close_curly); - } else if (parent == GLOBAL_BLOCK) { - if (emit_code) - value = parse_global_constant_value(parent, bb); - else - consume_global_constant_syntax(); + count += fixed_array_inner_count(&slice); } else { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - value = opstack_pop(); - } + if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { + type_t *record_type = field->type; + 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(parent, bb, record_type, elem_addr, + emit_code); + lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(field)) { + comma_consumed = parse_unbraced_record_init( + parent, bb, field->type, elem_addr, emit_code); + } else if (parent == GLOBAL_BLOCK) { + if (emit_code) + value = parse_global_constant_value(parent, bb); + else + consume_global_constant_syntax(); + } else { + read_expr(parent, bb); + read_ternary_operation(parent, bb); + value = opstack_pop(); + } - if (value && emit_code) { - 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); + if (value && emit_code) { + 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++; } - count++; - if (!lex_accept(T_comma)) + if (count > filled) + filled = count; + if (!comma_consumed && !lex_accept(T_comma)) break; } lex_expect(T_close_curly); - if (emit_code) { - 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); + if (emit_code) + emit_zero_elements(parent, bb, target_addr, filled, field->array_size, + elem_size); +} - for (; count < field->array_size; count++) { - var_t *elem_addr = compute_element_address(parent, bb, target_addr, - count, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } +type_t *resolve_record_type(type_t *type) +{ + if (type->base_type == TYPE_typedef && type->base_struct) + return type->base_struct; + return type; +} + +/* 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). + * + * 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, + bool emit_code, + var_t *value) +{ + record_type = resolve_record_type(record_type); + if (is_record_object(value) && + resolve_record_type(value->type) == record_type) { + if (emit_code) + emit_record_copy_to_address(parent, bb, addr, value); + return false; + } + return parse_struct_field_values(parent, bb, record_type, addr, emit_code, + true, 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 emit_code) +{ + 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, emit_code, + value); +} + +/* 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); } void parse_struct_field_init(block_t *parent, @@ -868,11 +1005,33 @@ void parse_struct_field_init(block_t *parent, type_t *struct_type, var_t *target_addr, bool emit_code) +{ + parse_struct_field_values(parent, bb, struct_type, target_addr, emit_code, + false, NULL); +} + +/* 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. + */ +bool parse_struct_field_values(block_t *parent, + basic_block_t **bb, + type_t *struct_type, + var_t *target_addr, + bool emit_code, + bool elided, + 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; - reject_empty_initializer_in_strict_c99(); + 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 @@ -921,19 +1080,27 @@ void parse_struct_field_init(block_t *parent, } } - if (!lex_peek(T_close_curly, NULL)) { + if (first_value || !lex_peek(T_close_curly, NULL)) { for (;;) { var_t *field_val_raw = NULL; var_t *field = NULL; var_t *designated_field_addr = NULL; bool consumed_pending_array_element = false; + bool nested_comma_consumed = false; - if (lex_accept(T_dot)) { + if (!first_value && lex_accept(T_dot)) { char field_name[MAX_ID_LEN]; type_t *designator_type = struct_type; var_t *designator_base = target_addr; bool first = true; + /* 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; + for (;;) { bool found = false; @@ -1073,9 +1240,11 @@ void parse_struct_field_init(block_t *parent, */ if (field && !designated_field_addr && !has_pending_array_element && field->array_size && - (field->ptr_level || !is_record_type(field->type)) && - !lex_peek(T_open_curly, NULL) && !lex_peek(T_string, NULL) && - !lex_peek(T_wstring, NULL)) { + (first_value || + (!lex_peek(T_open_curly, NULL) && + ((!field->ptr_level && is_record_type(field->type)) || + (!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 = (field->ptr_level || field->is_func) @@ -1095,7 +1264,8 @@ void parse_struct_field_init(block_t *parent, consumed_pending_array_element = true; } - if (field && field->array_size && !field->ptr_level && + if (!first_value && field && field->array_size && + !field->ptr_level && (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)))) @@ -1103,8 +1273,25 @@ void parse_struct_field_init(block_t *parent, "String literal initializer has incompatible array element " "type", cur_token_loc()); - if (field && field->array_size && is_char_array(field) && - lex_peek(T_string, NULL)) { + 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(). + */ + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + + if (!field->ptr_level && is_record_type(field->type)) + nested_comma_consumed = parse_record_from_value( + parent, bb, field->type, field_addr, emit_code, + first_value); + else + field_val_raw = first_value; + first_value = NULL; + } else if (field && field->array_size && is_char_array(field) && + lex_peek(T_string, NULL)) { var_t *field_addr = designated_field_addr ? designated_field_addr @@ -1144,6 +1331,15 @@ void parse_struct_field_init(block_t *parent, parse_struct_field_init(parent, bb, nested_type, field_addr, emit_code); lex_expect(T_close_curly); + } else if (field && !field->array_size && + unbraced_record_starts_here(field)) { + var_t *field_addr = + designated_field_addr + ? designated_field_addr + : compute_field_address(parent, bb, target_addr, field); + + nested_comma_consumed = parse_unbraced_record_init( + parent, bb, field->type, field_addr, emit_code); } else if (parent == GLOBAL_BLOCK) { if (emit_code) { field_val_raw = parse_global_constant_value(parent, bb); @@ -1222,10 +1418,29 @@ void parse_struct_field_init(block_t *parent, field_idx++; } initializer_count++; - if (!lex_accept(T_comma)) + + /* An elided record ends with its last member and leaves the comma + * that follows to the enclosing list. + */ + if (elided && !has_pending_array_element) { + while (field_idx < struct_type->num_fields && + is_bitfield(&struct_type->fields[field_idx]) && + !struct_type->fields[field_idx].var_name[0]) + 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; } } @@ -1240,6 +1455,7 @@ void parse_struct_field_init(block_t *parent, unit->bit_storage_size, NULL); } } + return comma_consumed; } void parse_array_literal_expr(block_t *parent, basic_block_t **bb) @@ -1581,6 +1797,7 @@ void parse_array_init(var_t *var, var_t designated_array; var_t *initializer_var = var; int prior_count = count; + bool elided_comma = false; if (lex_accept(T_open_square)) { int index; @@ -1724,6 +1941,20 @@ void parse_array_init(var_t *var, lex_expect(T_close_curly); val = NULL; } + } else if (emit_code && 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); + + if (is_implicit && parent != GLOBAL_BLOCK) + emit_zero_elements(parent, bb, base_addr, inferred_size, + count, elem_size); + elided_comma = parse_unbraced_record_init(parent, bb, var->type, + elem_addr, emit_code); } else { /* A global initializer is restricted to simple constants, but * it still has to be stored. Consuming the tokens and dropping @@ -1924,7 +2155,7 @@ void parse_array_init(var_t *var, count++; if (is_implicit && count > inferred_size) inferred_size = count; - if (!lex_accept(T_comma)) + if (!elided_comma && !lex_accept(T_comma)) break; if (lex_peek(T_close_curly, NULL)) break; @@ -1934,6 +2165,18 @@ void parse_array_init(var_t *var, 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 (emit_code && 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; } @@ -1981,6 +2224,8 @@ void parse_array_compound_literal(var_t *var, if (!lex_peek(T_close_curly, NULL)) { for (;;) { + bool elided_comma = false; + if (lex_accept(T_open_square)) { count = read_const_expr(parent); lex_expect(T_close_square); @@ -2036,6 +2281,9 @@ void parse_array_compound_literal(var_t *var, parse_struct_field_init(parent, bb, record_type, elem_addr, true); lex_expect(T_close_curly); + } else if (unbraced_record_starts_here(var)) { + elided_comma = parse_unbraced_record_init(parent, bb, var->type, + elem_addr, true); } else { read_expr(parent, bb); read_ternary_operation(parent, bb); @@ -2053,7 +2301,7 @@ void parse_array_compound_literal(var_t *var, inferred_size = count + 1; } count++; - if (!lex_accept(T_comma)) + if (!elided_comma && !lex_accept(T_comma)) break; if (lex_peek(T_close_curly, NULL)) break; diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index df973d1a..654e3923 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -40,6 +40,33 @@ int read_sizeof_string_literal(void) 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 @@ -360,7 +387,6 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) char token[MAX_ID_LEN]; int ptr_cnt = 0; int array_size = 0; - int array_element_size = 0; token_t *sizeof_tk = cur_token; type_t *type = NULL; bool is_function = false; @@ -369,31 +395,40 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) /* 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 is the - * complete operand. + * 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. */ - token_t *after_string = cur_token->next; - while (after_string && after_string->kind == T_string) - after_string = after_string->next; - if (lex_peek(T_string, NULL) && - (!parenthesized || - (after_string && after_string->kind == T_close_bracket))) { - int size = read_sizeof_string_literal(); + 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); + } + 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; } - - token_t *after_wstring = cur_token->next; - while (after_wstring && after_wstring->kind == T_wstring) - after_wstring = after_wstring->next; - if (lex_peek(T_wstring, NULL) && - (!parenthesized || - (after_wstring && after_wstring->kind == T_close_bracket))) { - int size = read_const_wstring_size(); - + 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); @@ -554,15 +589,9 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = has_long_type ? TY_long_double : TY_double; } else if (has_enum_type) { lex_ident(T_identifier, token); - type = find_type_tag(token, parent); + type = find_enum_tag(token, parent); if (!type) error_at("Unknown enum type", cur_token_loc()); - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } } else if (has_long_type) { type = has_unsigned_type ? TY_ulong : TY_long; if (long_type_count > 1) { @@ -580,12 +609,6 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) lex_accept(T_const); if (lex_peek(T_identifier, token) && !strcmp(token, "int")) lex_expect(T_identifier); - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } } else if (has_unsigned_type) { if (leading_scalar_type == TY_char) { type = TY_uchar; @@ -605,12 +628,6 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = TY_uint; } else type = TY_uint; - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } } else if (has_signed_type) { if (leading_scalar_type == TY_char || leading_scalar_type == TY_short) { if (leading_scalar_type == TY_short && @@ -625,20 +642,8 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = !strcmp(token, "char") ? TY_schar : find_type(token, true); } else type = TY_int; - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } } else if (leading_scalar_type) { type = leading_scalar_type; - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } } else if (lex_peek(T_identifier, token)) { /* Try to parse as a type first */ type = record_kind ? find_record_tag(token, parent, record_kind) @@ -646,143 +651,44 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) if (type) { /* sizeof(type) */ lex_expect(T_identifier); - while (lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } } } - /* The integer branches above consume their own abstract pointer declarators - * for historical reasons. Float/double type names select a type before that - * legacy code, so finish the common suffix here. - */ - while (type && lex_accept(T_asterisk)) { - ptr_cnt++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - - /* VLA support is intentionally outside this compiler's C99 scope, but a - * type name in sizeof may still carry fixed abstract array bounds. Keep the - * simple direct form here rather than treating '[' as an expression token - * after the scalar type name. + /* A type name continues with its abstract declarator; only the derivation + * it applies last decides the object size. */ - while (type && ptr_cnt == 0 && lex_accept(T_open_square)) { - int bound = read_const_expr(parent); - - lex_expect(T_close_square); - if (bound <= 0) - error_at("sizeof array type needs a positive constant bound", - cur_token_loc()); - if (bound > 0 && array_size && array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - array_size = array_size ? array_size * bound : bound; - } - - if (type && lex_accept(T_open_bracket)) { - int nested_array_size = 0; - int nested_ptr_count = 0; - int nested_function_ptr_count = 0; - int nested_outer_array_size = 0; - - while (lex_accept(T_asterisk)) { - nested_ptr_count++; - while (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) - ; - } - if (!nested_ptr_count) - error_at("sizeof abstract declarator needs a pointer", - cur_token_loc()); - if (lex_peek(T_open_bracket, NULL)) { - nested_function_ptr_count = - read_sizeof_nested_function_pointer_suffix( - parent, &nested_outer_array_size); - if (nested_outer_array_size) { - array_size = nested_outer_array_size; - array_element_size = PTR_SIZE; - } else - ptr_cnt += nested_ptr_count + nested_function_ptr_count; - } else - while (lex_accept(T_open_square)) { - int bound = read_const_expr(parent); - - lex_expect(T_close_square); - if (bound <= 0) - error_at( - "sizeof array type needs a positive constant bound", - cur_token_loc()); - if (bound > 0 && nested_array_size && - nested_array_size > INT_MAX / bound) - error_at("sizeof array type is too large", cur_token_loc()); - if (bound > 0) - nested_array_size = - nested_array_size ? nested_array_size * bound : bound; - } - if (!nested_function_ptr_count) { - lex_expect(T_close_bracket); - if (nested_array_size) { - array_size = nested_array_size; - array_element_size = PTR_SIZE; - - /* The grouped array declarator in `int (*[2][3])(int)` leaves - * its pointed-to function suffix outside the group. sizeof only - * needs pointer-sized elements, but still must parse that valid - * C99 prototype completely. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } else { - ptr_cnt += nested_ptr_count; - while (lex_accept(T_open_square)) { - int bound = read_const_expr(parent); - - lex_expect(T_close_square); - if (bound <= 0) - error_at( - "sizeof array type needs a positive constant bound", - cur_token_loc()); - } + if (type) { + int elements; + sizeof_derivation_t derivation = + read_sizeof_abstract_declarator(parent, &elements); + int size = sizeof_type_name_size(type, derivation, elements); - /* A pointer declarator followed by a parameter list names a - * function pointer. sizeof observes the pointer object, not the - * function type, so no expression lowering or function ABI is - * needed. Reuse the declaration parser for prototype syntax. - */ - if (lex_peek(T_open_bracket, NULL)) - read_sizeof_function_prototype(); - } - } + lex_expect(T_close_bracket); + push_sizeof_result(parent, *bb, size); + return; } - if (!type) { - /* sizeof(expression) - parse the expression and get its type */ - basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; - unevaluated_expression_depth++; - if (!read_assignment_expression(parent, &unevaluated_bb)) { - read_expr(parent, &unevaluated_bb); - read_ternary_operation(parent, &unevaluated_bb); - } - unevaluated_expression_depth--; - se_idx = saved_side_effects; - 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); + /* sizeof(expression) - parse the expression and get its type */ + basic_block_t *unevaluated_bb = bb_create(parent); + int saved_side_effects = se_idx; + unevaluated_expression_depth++; + if (!read_assignment_expression(parent, &unevaluated_bb)) { + read_expr(parent, &unevaluated_bb); + read_ternary_operation(parent, &unevaluated_bb); } + unevaluated_expression_depth--; + se_idx = saved_side_effects; + 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); @@ -796,8 +702,7 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) int size = type->size; if (array_size > 0) - size = - array_size * (array_element_size ? array_element_size : type->size); + size = array_size * type->size; if (ptr_cnt) size = PTR_SIZE; lex_expect(T_close_bracket); diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 59f8fd87..7cee94a2 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -62,6 +62,40 @@ void reject_label_followed_by_declaration_in_strict_c99(void) cur_token_loc()); } +/* The word-sized constant evaluator narrows a case expression to int. Select + * the typed two-word evaluator when a literal needing a wider or unsigned type + * appears before the label's colon; a colon closing a nested '?' is not it. + */ +bool case_label_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) + bracket_depth++; + else if (token->kind == T_close_bracket) { + 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) + 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; @@ -80,29 +114,57 @@ basic_block_t *read_switch_label_statement(block_t *parent, basic_block_t *body) context->has_default = true; context->default_body = label_body; } else { - int case_val; + unsigned int case_lo; + unsigned int case_hi; var_t *constant; var_t *comparison; basic_block_t *next_dispatch; lex_expect(T_case); - case_val = read_const_expr(parent); + if (case_label_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_val) + 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_val; + seen->value = case_lo; + seen->value_hi = case_hi; seen->next = context->seen_cases; context->seen_cases = seen; } - constant = require_var(context->dispatch_parent); + constant = + require_typed_var(context->dispatch_parent, context->control->type); constant->var_name = gen_name(); - constant->init_val = case_val; + 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); @@ -200,14 +262,38 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) 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()); +} + +/* 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; - var_t *rs1; - var_t *var; bool is_const = false; bool is_static = false; bool is_extern = false; @@ -299,6 +385,8 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) 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. @@ -307,148 +395,19 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) error_at( "C99 for initializer permits only auto or register objects", cur_token_loc()); - var = require_typed_var(blk, type); - var->is_static = is_static; - var->is_register = is_register; - var->is_global = is_static; - var->is_const_qualified = is_const; - var->is_volatile = is_volatile; + 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; - read_full_var_decl(var, false, false, false); - parsing_for_initializer_declaration = false; - if (strict_c99 && var->is_func) - error_at("C99 for initializer cannot declare a function", - cur_token_loc()); - if (is_extern) { - if (var->is_func || lex_peek(T_open_bracket, NULL)) { - /* This helper consumes the declaration's semicolon. */ - blk->locals.size--; - var->is_block_scope_function_declaration = true; - GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = - var; - read_global_function_declarator(GLOBAL_BLOCK, var, false); - var_t *alias = require_var(blk); - alias->var_name = var->var_name; - alias->is_extern_function_alias = true; - for_decl_semicolon_consumed = true; - } else { - for (;;) { - var = bind_block_extern_object(blk, var); - if (lex_peek(T_assign, NULL)) - error_at( - "extern declaration cannot have an initializer", - next_token_loc()); - if (!lex_accept(T_comma)) - break; - var = require_typed_var(blk, type); - var->is_const_qualified = is_const; - var->is_volatile = is_volatile; - read_partial_var_decl(var, NULL); - } - } - } else { - if (is_incomplete_record_object(var)) - error_at( - "Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : blk, - is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, var, - NULL, NULL, 0, NULL); - add_symbol(setup, var); - if (lex_accept(T_assign)) { - validate_string_array_initializer(var); - if (is_static) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) - parse_array_init(var, GLOBAL_BLOCK, - &GLOBAL_FUNC->bbs, true); - 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, blk, &setup); - } else if ((var->has_unsized_array || - var->array_size > 0) && - is_wchar_array(var) && - lex_peek(T_wstring, NULL)) { - parse_wstring_array_init(var, blk, &setup); - } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, blk, &setup, true); - } else { - 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); - nv->is_static = is_static; - nv->is_register = is_register; - nv->is_global = is_static; - nv->is_const_qualified = is_const; - nv->is_volatile = is_volatile; - read_partial_var_decl(nv, var); /* partial */ - if (is_incomplete_record_object(nv)) - error_at( - "Incomplete struct/union type cannot define an " - "object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : blk, - is_static ? GLOBAL_FUNC->bbs : setup, OP_allocat, - nv, NULL, NULL, 0, NULL); - add_symbol(setup, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (is_static) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) - parse_array_init(nv, GLOBAL_BLOCK, - &GLOBAL_FUNC->bbs, true); - else if (lex_peek(T_open_curly, NULL)) - parse_global_record_init(nv, GLOBAL_BLOCK); - else - read_global_assignment_var(nv); - } else if ((nv->has_unsized_array || - nv->array_size > 0) && - is_char_array(nv) && - lex_peek(T_string, NULL)) { - parse_string_array_init(nv, blk, &setup); - } else if ((nv->has_unsized_array || - nv->array_size > 0) && - is_wchar_array(nv) && - lex_peek(T_wstring, NULL)) { - parse_wstring_array_init(nv, blk, &setup); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || - nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, blk, &setup, true); - } else { - read_expr(blk, &setup); - - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, - NULL); - } - } - } - } + setup = read_block_declarators(blk, setup, type, &spec, false); + for_decl_semicolon_consumed = true; } else { read_control_expression(blk, &setup); opstack_pop(); @@ -576,17 +535,21 @@ basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) /* A local struct or union declaration. */ basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb, - bool is_const, - bool is_static) + const block_decl_specifiers_t *spec) { char token[MAX_ID_LEN]; type_t *type = NULL; - var_t *var; - /* Both callers have seen struct or union ahead. */ + /* The caller has seen struct or union ahead. */ base_type_t kind = accept_record_keyword(); bool is_union = kind == TYPE_union; - lex_ident(T_identifier, token); + 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)) { int i = 0; int size = 0; @@ -595,7 +558,15 @@ basic_block_t *handle_record_statement(block_t *parent, bitfield_layout_t bits = {0}; bool has_flexible_array_member = false; - type = local_record_tag(token, parent, kind); + /* An untagged definition names a type no other declaration can reach, + * so it needs no tag table entry. + */ + if (has_tag) + type = local_record_tag(token, parent, kind); + else { + type = add_type(); + type->base_type = kind; + } lex_expect(T_open_curly); do { @@ -695,184 +666,11 @@ basic_block_t *handle_record_statement(block_t *parent, } if (!type) type = reference_record_tag(token, parent, kind); - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - var->is_static = is_static; - var->is_global = is_static; - read_partial_var_decl(var, NULL); - if (is_incomplete_record_object(var)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - 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 (is_static && lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); - } else if (is_static && lex_peek(T_open_curly, NULL)) { - parse_global_record_init(var, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - 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_union || - 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; - - 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, - true); - lex_expect(T_close_curly); - } else { - if (!read_assignment_expression(parent, &bb)) { - 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, - !has_effective_pointer(var) && var->array_size == 0); - - emit_object_assignment(parent, &bb, var, rhs); - } - } - 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); - nv->is_static = is_static; - nv->is_global = is_static; - nv->is_const_qualified = is_const; - read_inner_var_decl(nv, false, false, false); - if (is_incomplete_record_object(nv)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - add_insn(is_static ? GLOBAL_BLOCK : parent, - is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, NULL, - 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (is_static && lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); - } else if (is_static && lex_peek(T_open_curly, NULL)) { - parse_global_record_init(nv, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - 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_union || - nv->type->base_type == TYPE_typedef)) { - type_t *struct_type = nv->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, nv, NULL, 0, - NULL); - lex_expect(T_open_curly); - parse_struct_field_init(parent, &bb, struct_type, struct_addr, - true); - 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, - !has_effective_pointer(nv) && nv->array_size == 0); - - emit_object_assignment(parent, &bb, nv, rhs); - } - } - } - lex_expect(T_semicolon); - return bb; -} - -basic_block_t *handle_enum_declarators(block_t *parent, - basic_block_t *bb, - type_t *type, - bool is_const, - bool is_static) -{ - for (;;) { - var_t *var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - var->is_static = is_static; - var->is_global = is_static; - read_partial_var_decl(var, NULL); - add_insn(is_static ? GLOBAL_BLOCK : parent, - 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 (is_static) { - if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->has_unsized_array || - var->ptr_level > 0)) { - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, - true); - } 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)) { - parse_array_init(var, parent, &bb, true); - } 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, - !has_effective_pointer(var) && var->array_size == 0); - emit_object_assignment(parent, &bb, var, rhs); - } - } - if (is_static) - discard_global_declarator_operand(var); - - if (!lex_accept(T_comma)) - break; - perform_side_effect(parent, bb); - } - lex_expect(T_semicolon); - return bb; + /* 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 @@ -930,8 +728,7 @@ int read_enum_constant(block_t *scope) */ basic_block_t *handle_enum_statement(block_t *parent, basic_block_t *bb, - bool is_const, - bool is_static) + const block_decl_specifiers_t *spec) { char token[MAX_ID_LEN]; int val = 0; @@ -941,17 +738,17 @@ basic_block_t *handle_enum_statement(block_t *parent, lex_expect(T_enum); if (lex_peek(T_identifier, token)) { lex_expect(T_identifier); - type = find_local_type_tag(token, parent); + type = local_enum_tag(token, parent); has_tag = true; } if (!lex_peek(T_open_curly, NULL)) { if (!has_tag) error_at("Unknown enum type", next_token_loc()); if (!type) - type = find_type_tag(token, parent); + type = find_enum_tag(token, parent); if (!type) error_at("Unknown enum type", next_token_loc()); - return handle_enum_declarators(parent, bb, type, is_const, is_static); + return read_block_declarators(parent, bb, type, spec, true); } if (!type) type = add_type(); @@ -977,7 +774,7 @@ basic_block_t *handle_enum_statement(block_t *parent, if (lex_accept(T_semicolon)) return bb; - return handle_enum_declarators(parent, bb, type, is_const, is_static); + return read_block_declarators(parent, bb, type, spec, true); } /* Bind a block-scope extern declaration to the file-scope declaration table. @@ -1007,27 +804,35 @@ var_t *bind_block_extern_object(block_t *parent, var_t *var) return var; } -static void reject_ordinary_typedef_collision(block_t *block, var_t *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) { - if (find_block_typedef(block, var->var_name)) - error_at("ordinary identifier conflicts with typedef name", - cur_token_loc()); -} + bool ended; + var_t *alias; -/* 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; + 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; +} /* A block-scope declaration whose type handle_declaration() has resolved: every - * declarator, its initializer, and its storage. + * 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. */ @@ -1035,17 +840,44 @@ static basic_block_t *read_block_declarators( block_t *parent, basic_block_t *bb, type_t *type, - const block_decl_specifiers_t *spec) + const block_decl_specifiers_t *spec, + bool has_base_type) { + block_decl_specifiers_t qualified; var_t *var; + /* 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) { + memcpy(&qualified, spec, sizeof(qualified)); + 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; - read_full_var_decl(var, false, false, false); + 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 @@ -1070,16 +902,9 @@ static basic_block_t *read_block_declarators( if (spec->is_const || spec->is_volatile || var->is_const_qualified || var->is_volatile) error_at("function type cannot be qualified", cur_token_loc()); - parent->locals.size--; - var->is_block_scope_function_declaration = true; - GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; - read_global_function_declarator(GLOBAL_BLOCK, var, false); - var_t *alias = require_var(parent); - - alias->var_name = var->var_name; - alias->is_extern_function_alias = true; - if (!lex_accept(T_comma)) + 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; @@ -1111,39 +936,42 @@ static basic_block_t *read_block_declarators( !var->is_const_pointer) error_at("external inline definition cannot define static object", cur_token_loc()); - if (spec->is_extern) { - if (var->is_func || lex_peek(T_open_bracket, NULL)) { - /* The global helper owns function redeclaration compatibility and - * parameter parsing. Unlike an object declaration it consumes the - * trailing semicolon itself. - */ - parent->locals.size--; - var->is_block_scope_function_declaration = true; - GLOBAL_BLOCK->locals.elements[GLOBAL_BLOCK->locals.size++] = var; - read_global_function_declarator(GLOBAL_BLOCK, var, false); - /* Keep a lexical marker so this declaration hides an outer - * automatic object of the same name. - */ - var_t *alias = require_var(parent); - alias->var_name = var->var_name; - alias->is_extern_function_alias = true; - return bb; - } + /* 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 (;;) { - var = bind_block_extern_object(parent, var); - if (lex_peek(T_assign, NULL)) - error_at("extern declaration cannot have an initializer", - next_token_loc()); - if (!lex_accept(T_comma)) + 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); } - lex_expect(T_semicolon); - return bb; } if (is_incomplete_record_object(var)) error_at("Incomplete struct/union type cannot define an object", @@ -1268,6 +1096,14 @@ static basic_block_t *read_block_declarators( nv->is_volatile = var->is_volatile; read_partial_var_decl(nv, var); /* partial */ 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; + } if (is_incomplete_record_object(nv)) error_at("Incomplete struct/union type cannot define an object", cur_token_loc()); @@ -1388,15 +1224,10 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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.is_const, spec.is_static); + return handle_enum_statement(parent, bb, &spec); - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { - if (spec.is_extern || spec.is_register) - error_at("Unsupported storage class on record definition", - next_token_loc()); - return handle_record_statement(parent, bb, spec.is_const, - spec.is_static); - } + 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); @@ -1456,7 +1287,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) next_token_loc()); if (type) - return read_block_declarators(parent, bb, type, &spec); + 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 @@ -1898,14 +1729,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) lex_peek(T_bit_not, NULL)) return read_full_expression_statement(parent, bb); - /* struct/union variable declaration */ - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) - return handle_record_statement(parent, bb, false, false); - - if (lex_peek(T_enum, NULL)) - return handle_enum_statement(parent, bb, false, false); - - /* Handle const qualifier for local variable declarations */ + /* Declarations, including struct, union and enum ones */ return handle_declaration(parent, bb); } diff --git a/src/parser.c b/src/parser.c index 09cdec1b..a2284f18 100644 --- a/src/parser.c +++ b/src/parser.c @@ -340,9 +340,21 @@ bool global_compound_literal_starts_here(void) if (!lex_peek(T_open_bracket, NULL)) return false; next = cur_token->next->next; - return next && - ((next->kind == T_identifier && find_type(next->literal, true)) || - next->kind == T_struct || next->kind == T_union); + if (!next || + !((next->kind == T_identifier && find_type(next->literal, true)) || + next->kind == T_struct || next->kind == T_union)) + 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 @@ -712,8 +724,25 @@ static int callback_pointer_indirection(const var_t *var) return 0; } +/* 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) +{ + 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; +} + bool compatible_decl_type(const type_t *left, const type_t *right) { + /* 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. + */ + 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; @@ -953,7 +982,8 @@ typedef struct { * translation unit merely to check duplicate converted values. */ typedef struct switch_case_value { - int value; + unsigned int value; + unsigned int value_hi; struct switch_case_value *next; } switch_case_value_t; diff --git a/src/preprocessor.c b/src/preprocessor.c index 1ee39541..983c0bfd 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -1423,14 +1423,12 @@ token_t *pp_read_constant_expr_operand(token_t *tk, /* Parser-side enum evaluation shares this token walker, but unlike #if it * admits C's sizeof integer constant expressions. Reuse the parser's - * unevaluated type reader and leave both cursors at the consumed operand. + * 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 = lex_peek(T_wstring, NULL) - ? read_const_wstring_size() - : read_const_sizeof_type(pp_integer_constant_scope); + val->lo = read_sizeof_constant(pp_integer_constant_scope); val->hi = 0; val->is_unsigned = false; val->enum_width = 32; diff --git a/tests/driver.sh b/tests/driver.sh index a01e0c6b..6ec1fed8 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -940,6 +940,54 @@ int main(void) { } 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; + } + 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 + +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 + try_ 0 << EOF int global_nested_designated[2][2][2] = { [0] = { [1] = { 2 } }, 3, 4, 5 }; int nested_designated(void) { @@ -1418,6 +1466,166 @@ int main(void) { } EOF +# 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 +try_compile_error << EOF +struct S { int a; }; +volatile struct S s; +extern struct S s; +int main(void) { return 0; } +EOF +try_compile_error << EOF +volatile union U { int a; } u; +extern union U u; +int main(void) { return 0; } +EOF +try_compile_error << EOF +volatile int value; +extern int value; +int main(void) { return 0; } +EOF + +# 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 + # 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. @@ -1916,6 +2124,35 @@ 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 try_ 2 << EOF unsigned long long global_sum = 0x100000000ULL + 7ULL; int main(void) { @@ -3916,6 +4153,63 @@ try_compile_error_message "tag was previously declared as a different kind of re 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 record" << EOF +int main(void) { enum block_record { BLOCK_RECORD }; struct block_record { int a; }; 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. @@ -7289,6 +7583,89 @@ int main(void) { (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; }; @@ -8698,6 +9075,61 @@ 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 +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 + # Category: Loop Constructs begin_category "Loop Constructs" "Testing while, do-while, and for loops" @@ -8712,6 +9144,27 @@ 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;" @@ -11207,6 +11660,37 @@ int main(void) { 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) { @@ -12076,6 +12560,95 @@ int main(void) { } 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 @@ -12090,6 +12663,22 @@ int main(void) { } 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 @@ -14506,6 +15095,68 @@ int main(void) { } 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 + # Category: Switch Statements begin_category "Switch Statements" "Testing switch-case control flow" @@ -14535,6 +15186,36 @@ int main(void) { } 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" @@ -14640,6 +15321,32 @@ 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 @@ -19618,6 +20325,130 @@ EOF 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 + +# 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 + # 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. @@ -20437,6 +21268,19 @@ int main(void) { } EOF try_compile_error_flag --std=c99 << EOF +int main(void) { + switch (0) { case 0x100000000LL: return 1; case 0: return 2; } + return 0; +} +EOF +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 +try_compile_error_flag --std=c99 << EOF int main(void) { switch (1) { case 1: int value = 1; return value; } return 0; @@ -20665,10 +21509,18 @@ try_ 0 << EOF int variadic(...) { return 0; } int main(void) { return variadic(); } EOF -try_ 1 << 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 begin_category "C99 assert.h" "Testing freestanding assertion semantics" From cf4d60010e99d6b91dc06dca557b41642d5f85c1 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 14 Sep 2026 23:57:40 +0800 Subject: [PATCH 24/40] Fix expression, record and typedef lowering Many expressions lowered incorrectly. A row of a two-dimensional array was loaded instead of decaying, member access on a call result did not parse, compound assignment and casts converted operands wrongly, stores and ++ on _Bool did not keep 0 or 1, unary minus was dropped before a parenthesis, sizeof mistyped many operands, and records reached through pointers were copied as one word. Postfix operators, address-of and array subscripts accepted only a few operand shapes, casts bound looser than pointer arithmetic, arrays did not decay in pointer differences, and arrays and records were accepted as operands of scalar operators. Declarations mishandled trailing commas in parameters, redeclarations in one block, storage classes after the type, enum and array typedefs, block pointer typedefs, nested and parenthesized declarators and tag redefinitions, and initializers mishandled strings in character rows, static address constants, function designators and strings that fill their array. Lower each through shared paths: one lvalue tail for postfix operators, one conversion for stored values, one record copy through an address, and declarators that compose typedef and array shapes. --- lib/c.c | 4 +- src/defs.h | 29 +- src/globals.c | 57 +- src/parser-call.c | 766 +++++++++---- src/parser-const.c | 88 +- src/parser-decl.c | 391 ++++++- src/parser-expr.c | 1288 +++++++++++++++------ src/parser-global.c | 600 ++++++---- src/parser-init.c | 476 ++++++-- src/parser-sizeof.c | 426 +++++-- src/parser-stmt.c | 370 +++--- src/parser.c | 83 +- src/reg-alloc.c | 2 +- src/ssa.c | 26 +- src/x64-codegen.c | 9 + tests/driver.sh | 2615 ++++++++++++++++++++++++++++++++++++++++++- 16 files changed, 6024 insertions(+), 1206 deletions(-) diff --git a/lib/c.c b/lib/c.c index 9e0c2a20..2c78c241 100644 --- a/lib/c.c +++ b/lib/c.c @@ -791,8 +791,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; diff --git a/src/defs.h b/src/defs.h index f62e9925..3a73cd4f 100644 --- a/src/defs.h +++ b/src/defs.h @@ -779,9 +779,11 @@ struct var { */ bool is_compound_literal; - /* A scalar value read from a compound-literal aggregate. This points at its - * element/member address so a following prefix update can write back - * through the automatic aggregate rather than assign the temporary. + /* 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; @@ -813,16 +815,6 @@ struct var { */ void *func_signature; - /* When a function pointer was initialized from a visible function - * designator, retain that concrete target for internal-ABI validation. - */ - void *func_target; - - /* Once an assignment may have supplied an unknown/external target, parser - * order cannot prove provenance across later control-flow joins. - */ - bool func_target_invalid; - /* 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 @@ -866,6 +858,7 @@ 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); 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; @@ -1079,6 +1072,16 @@ typedef struct { 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; diff --git a/src/globals.c b/src/globals.c index 9e1f6f7c..4a82e956 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1263,14 +1263,45 @@ void add_scoped_constant(block_t *block, char alias[], int value) block->constants = constant; } +/* 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). + */ constant_t *find_scoped_constant(char alias[], block_t *block) { - for (; block; block = block->parent) { + func_t *func = block ? block->func : NULL; + char head = alias[0]; + + for (; block && block != GLOBAL_BLOCK; block = block->parent) { for (constant_t *constant = block->constants; constant; constant = constant->next) { if (!strcmp(constant->alias, alias)) return constant; } + + /* A block's locals include its IR temporaries; settle the first byte + * before the library call, as find_visible_type() does. + */ + for (int i = 0; i < block->locals.size; i++) { + var_t *var = block->locals.elements[i]; + + if (var && var->var_name && var->var_name[0] == head && + !strcmp(var->var_name, alias)) + return NULL; + } + for (typedef_binding_t *binding = block->typedefs; binding; + binding = binding->next) + if (binding->name[0] == head && !strcmp(binding->name, alias)) + return NULL; + } + if (func) { + for (int i = 0; i < func->num_params; i++) { + const char *name = func->param_defs[i].var_name; + + if (name && name[0] == head && !strcmp(name, alias)) + return NULL; + } } return find_constant(alias); } @@ -1383,6 +1414,21 @@ type_t *find_enum_tag(char name[], block_t *block) return check_enum_tag(type); } +/* 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) { @@ -1479,11 +1525,12 @@ type_t *find_visible_type(const char *name, block_t *block) 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; diff --git a/src/parser-call.c b/src/parser-call.c index 13e891f3..16b7c35c 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -57,8 +57,8 @@ void read_func_parameters_with_sret(func_t *func, "argument", cur_token_loc()); diagnose_const_pointer_conversion(param, target); - if (is_record_type(target->type) && - !has_effective_pointer(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 @@ -261,7 +261,13 @@ void lower_call_result_array_postfix(var_t **value, bool assignment; element->ptr_level = base->pointee_array_element_ptr_level; - element->func_signature = base->type->func_signature; + + /* 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(); @@ -357,36 +363,10 @@ void lower_call_result_prefix_update(var_t **value, block_t *parent, basic_block_t **bb) { - var_t *object; - var_t *one; - var_t *updated; - if (op == OP_generic) return; - object = opstack_pop(); - if (!object->is_compound_literal_reference) - 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); - 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); - add_insn(parent, *bb, OP_write, NULL, object->compound_literal_address, - updated, object->ptr_level ? PTR_SIZE : object->type->size, NULL); - *value = updated; - opstack_push(updated); + *value = lower_reference_update(opstack_pop(), op, parent, bb); + opstack_push(*value); } /* The freestanding maps offsetof(type, member-designator) here. Keep @@ -534,7 +514,7 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) error_at("enum type cannot have integer specifiers", cur_token_loc()); lex_ident(T_identifier, name); - type = find_enum_tag(name, parent); + type = reference_enum_tag(name, parent); } else { if (!saw_base && !is_signed && !is_unsigned && !long_count && !is_short) { @@ -737,6 +717,105 @@ void emit_record_copy_from_address(block_t *parent, 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, so it is copied into @value's own storage instead. + */ +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)) { + add_insn(parent, *bb, OP_allocat, value, NULL, NULL, 0, NULL); + emit_record_copy_from_address(parent, bb, value, address); + } 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()); + 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); + 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; @@ -992,8 +1071,6 @@ var_t *load_function_pointer_object(block_t *parent, add_insn(parent, *bb, OP_address_of, address, object, NULL, 0, NULL); target->var_name = gen_name(); target->func_signature = object->func_signature; - target->func_target = object->func_target; - target->func_target_invalid = object->func_target_invalid; add_insn(parent, *bb, OP_read, target, address, NULL, PTR_SIZE, NULL); return target; } @@ -1025,7 +1102,6 @@ var_t *materialize_function_designator(block_t *parent, object->var_name = gen_name(); object->is_func = true; object->func_signature = func; - object->func_target = 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); @@ -1126,14 +1202,14 @@ var_t *emit_indirect_call_result(var_t *callee, if (signature && signature->returns_aggregate) { var_t *sret; - func_t *target = callee ? callee->func_target : NULL; - if (callee->func_target_invalid || !target || !target->bbs || - !target->returns_aggregate) - error_at( - "aggregate-return indirect call requires a shecc-defined " - "target", - cur_token_loc()); + /* 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 { @@ -1274,6 +1350,111 @@ void read_lvalue(lvalue_t *lvalue, opcode_t op, bool allow_ptr_arith); +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; +} + +/* 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). + */ + reading_unary_operand = true; + 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) { + /* A function designator, possibly parenthesized, is its address. */ + 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; + if (operand->func_signature) + result->pointee_func_signature = operand->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. */ @@ -1283,8 +1464,12 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) lvalue_t lvalue; var_t *vd, *rs1; - if (!lex_peek(T_identifier, token)) - error_at("Expected an identifier", next_token_loc()); + if (!lex_peek(T_identifier, token) || + (find_var(token, parent) && + is_swapped_subscript_base(find_var(token, parent)))) { + 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", @@ -1328,6 +1513,33 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) 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); @@ -1345,34 +1557,71 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) : lvalue.decl->func_signature) : NULL; opstack_push(vd); - if (lvalue.decl && is_array_declarator(lvalue.decl)) { + + /* 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 + } 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; } } -/* A scalar pointer-to-array dereference designates the addressed row; it does - * not load a scalar object. Retain that row as an array descriptor so a - * following grouped postfix subscript can use the ordinary array path. +/* 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_scalar_pointee_array_dereference(var_t *source, - block_t *parent, - basic_block_t **bb) +static bool lower_pointee_array_dereference(var_t *source, + block_t *parent, + basic_block_t **bb) { type_t *element_type; var_t *row; - if (!source || !source->type || !source->pointee_array_size || - source->pointee_array_element_ptr_level || - effective_pointer_depth(source) != 1) + if (!is_pointee_array_pointer(source)) return false; element_type = source->type->pointee_array_element_type @@ -1383,6 +1632,7 @@ static bool lower_scalar_pointee_array_dereference(var_t *source, 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; row->var_name = gen_name(); @@ -1391,28 +1641,14 @@ static bool lower_scalar_pointee_array_dereference(var_t *source, return true; } -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; -} - -static bool is_scalar_pointee_array_pointer(const var_t *var) -{ - return var && var->type && var->pointee_array_size > 0 && - !var->pointee_array_element_ptr_level && - effective_pointer_depth(var) == 1; -} - -static int scalar_pointee_array_row_stride(const var_t *var) +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; } @@ -1465,14 +1701,13 @@ static int fixed_array_decay_stride(const var_t *var) return fixed_array_subscript_stride(var, 0, var->type->size); } -static bool scalar_pointee_array_shapes_compatible(const var_t *left, - const var_t *right) +static bool pointee_array_shapes_compatible(const var_t *left, + const var_t *right) { type_t *left_element; type_t *right_element; - if (!is_scalar_pointee_array_pointer(left) || - !is_scalar_pointee_array_pointer(right)) + 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 @@ -1489,101 +1724,80 @@ static bool scalar_pointee_array_shapes_compatible(const var_t *left, compatible_decl_type(left_element, right_element); } -void handle_single_dereference(block_t *parent, basic_block_t **bb) +/* 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, *rs1; + var_t *vd; 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(); - - 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 (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_scalar_pointee_array_dereference(rs1, parent, bb)) - return; + if (lower_pointee_array_dereference(rs1, parent, bb)) + 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; + /* 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->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 (deref_type && deref_type->func_signature && + effective_pointer_depth(rs1) == 1) { + vd->func_signature = deref_type->func_signature; + vd->ptr_level = 1; + sz = PTR_SIZE; + } + push_object_at(parent, bb, vd, rs1, sz); +} - /* 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(); - 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))); +void handle_single_dereference(block_t *parent, basic_block_t **bb) +{ + var_t *vd, *rs1; + int sz; - /* 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 (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } else if (lex_peek(T_increment, NULL) || lex_peek(T_decrement, NULL)) { - /* A prefix update is itself a unary expression, so `*++pointer` and - * `*--pointer` dereference its updated pointer result just like - * `*(pointer + 1)` dereferences a parenthesized operand. + if (lex_peek(T_open_bracket, NULL) || lex_peek(T_increment, NULL) || + lex_peek(T_decrement, NULL) || lex_peek(T_ampersand, NULL)) { + /* 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. */ read_expr_operand(parent, bb); - rs1 = opstack_pop(); - 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))); - if (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + push_dereference(parent, bb, opstack_pop()); } else { /* Handle simple identifier dereference: *var */ char token[MAX_VAR_LEN]; @@ -1613,8 +1827,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) vd->is_const_pointer = vd->ptr_level > 0 && vd->ptr_level <= 32 && (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + push_object_at(parent, bb, vd, rs1, sz); return; } var_t *var = find_var(token, parent); @@ -1643,7 +1856,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) opstack_push(load_function_pointer_object(parent, bb, rs1)); return; } - if (lower_scalar_pointee_array_dereference(rs1, parent, bb)) + if (lower_pointee_array_dereference(rs1, parent, bb)) return; /* A member function-pointer typedef was already loaded by @@ -1689,8 +1902,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) vd->ptr_level = 1; sz = PTR_SIZE; } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + push_object_at(parent, bb, vd, rs1, sz); } } @@ -1707,44 +1919,13 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) 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); + if (lex_peek(T_open_bracket, NULL) || lex_peek(T_ampersand, NULL)) { + /* 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++) { - 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(); - 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))); - - /* A parenthesized pointer-arithmetic result can designate a - * callback object just like `*slot`. Restore its prototype only - * after consuming exactly that final object-pointer indirection. - */ - if (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; - vd->ptr_level = 1; - sz = PTR_SIZE; - } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); - } + 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]; @@ -1793,8 +1974,183 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) vd->ptr_level = 1; sz = PTR_SIZE; } - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, rs1, NULL, sz, NULL); + push_object_at(parent, bb, vd, rs1, sz); } } } + +/* Lower a postfix member chain from the record at @address, of @record_type, + * and return 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 copied + * out whole, as a record element is. + */ +var_t *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; + + 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()); + if (arrow_first) + is_lvalue = true; + } else if (lex_accept(T_arrow)) { + /* Only a selected pointer-to-record member can be followed. */ + if (depth < shape.rank || field->ptr_level != 1 || + field->type->ptr_level || field->is_func || + !is_record_type(field->type)) + error_at("Invalid record member access", cur_token_loc()); + var_t *pointer = require_typed_ptr_var(parent, field->type, 1); + + pointer->var_name = gen_name(); + add_insn(parent, *bb, OP_read, pointer, address, NULL, PTR_SIZE, + NULL); + address = pointer; + record_type = field->type; + is_lvalue = true; + } else { + lex_expect(T_dot); + } + 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; + } + + if (depth < shape.rank && 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 (depth < shape.rank) { + 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 (!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()); + return result; +} + +/* A record returned by value is an rvalue, yet C99 6.5.2.3 still lets a postfix + * member chain select from it: `make().inner.value`, `make().items[1]` or + * `make().next->value`. Aggregate calls already write their result to + * caller-owned storage, so each member is lowered from that storage's address. + */ +void lower_call_result_member_postfix(var_t **value, + block_t *parent, + basic_block_t **bb) +{ + var_t *base; + var_t *address; + + if (!value || !(base = *value) || !lex_peek(T_dot, NULL) || + !is_record_type(base->type) || effective_pointer_depth(base) || + base->array_size) + return; + + opstack_pop(); + address = require_ref_var(parent, base->type, 0); + address->var_name = gen_name(); + add_insn(parent, *bb, OP_address_of, address, base, NULL, 0, NULL); + *value = + lower_member_postfix(address, base->type, false, false, parent, bb); +} diff --git a/src/parser-const.c b/src/parser-const.c index dcf972a7..41c1d6a0 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -458,54 +458,6 @@ int read_sizeof_constant(block_t *scope) return res; } -int read_primary_constant(block_t *scope) -{ - /* return signed constant */ - int isneg = 0, res; - - /* This recurses once per nesting level of a constant expression, so the - * buffer holds only what is copied into it: a number, a character constant - * or an identifier, each at most MAX_TOKEN_LEN. A string is only tested for - * and never copied. - */ - char buffer[MAX_TOKEN_LEN]; - if (lex_accept(T_minus)) - isneg = 1; - if (lex_accept(T_sizeof)) { - res = read_sizeof_constant(scope); - } else if (lex_accept(T_open_bracket)) { - res = read_primary_constant(scope); - 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) || lex_peek(T_wchar, buffer)) { - char unescaped[MAX_TOKEN_LEN]; - unescape_string(buffer, unescaped, MAX_TOKEN_LEN); - res = lex_peek(T_wchar, NULL) ? parse_wide_character_constant(buffer) - : parse_character_constant(buffer); - lex_expect(lex_peek(T_wchar, NULL) ? T_wchar : T_char); - } else if (lex_peek(T_identifier, buffer)) { - constant_t *con; - - if (!strcmp(buffer, "__builtin_offsetof")) { - res = read_const_expr_operand(scope); - if (isneg) - return (-1) * res; - return res; - } - lex_expect(T_identifier); - con = find_scoped_constant(buffer, scope); - if (!con) - error_at("Identifier is not an integer constant", next_token_loc()); - res = con->value; - } else - error_at("Invalid value after assignment", next_token_loc()); - if (isneg) - return (-1) * res; - return res; -} - int eval_expression_imm(opcode_t op, int op1, int op2) { /* return immediate result */ @@ -632,7 +584,7 @@ void emit_global_scalar_assignment(block_t *parent, var_t *dest, var_t *src) { - if (!dest->ptr_level && dest->type == TY_bool) + 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); } @@ -1435,6 +1387,23 @@ bool read_global_assignment_var(var_t *var) } /* global initialization must be constant */ + if (global_pointer_cast_starts_here(scope)) { + int saved_stride = global_pointer_cast_stride; + int stride = read_global_pointer_cast(scope); + + /* 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); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + 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 @@ -1526,8 +1495,13 @@ bool read_global_assignment_var(var_t *var) lex_expect(T_identifier); add_insn(parent, bb, OP_address_of, object_addr, object, NULL, 0, NULL); - if (!explicit_address && object->array_size && - (lex_peek(T_plus, NULL) || lex_peek(T_minus, 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 ? PTR_SIZE : object->type->size; @@ -1535,7 +1509,9 @@ bool read_global_assignment_var(var_t *var) var_t *offset_addr = require_ref_var(parent, object->type, object->ptr_level); - stride = fixed_array_shape_stride(&shape, 0, stride); + 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, @@ -1548,7 +1524,7 @@ bool read_global_assignment_var(var_t *var) } if (explicit_address) object_addr = read_global_address_designator( - scope, parent, &bb, &object, object_addr); + scope, parent, &bb, &object, object_addr, false); if (incompatible_pointee_callback_conversion(object_addr, var)) error_at("incompatible callback slot types in initializer", cur_token_loc()); @@ -1596,7 +1572,7 @@ bool read_global_assignment_var(var_t *var) int val_stack[MAX_OPERATOR_STACK_SIZE]; int op_stack_index = 0, val_stack_index = 0; int operand1, operand2; - operand1 = read_primary_constant(scope); + operand1 = read_const_expr_operand(scope); op = get_operator(); /* only one value after assignment */ if (op == OP_generic) { @@ -1614,7 +1590,7 @@ bool read_global_assignment_var(var_t *var) eval_ternary_imm(operand1, var); return true; } - operand2 = read_primary_constant(scope); + operand2 = read_const_expr_operand(scope); next_op = get_operator(); if (next_op == OP_generic) { /* only two operands, apply and return */ @@ -1661,7 +1637,7 @@ bool read_global_assignment_var(var_t *var) 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(scope); + val_stack[val_stack_index++] = read_const_expr_operand(scope); /* push operator on stack */ op_stack[op_stack_index++] = op; op = get_operator(); diff --git a/src/parser-decl.c b/src/parser-decl.c index 2a802dd5..3ae7f32f 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -169,29 +169,24 @@ int sizeof_type_name_size(const type_t *type, return size * elements; } -/* 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. +/* 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. */ -int read_const_sizeof_type(block_t *scope) +type_t *read_type_name_specifiers(block_t *scope) { char token[MAX_ID_LEN]; type_t *type = NULL; - sizeof_derivation_t derivation; - int elements; bool is_unsigned = false; bool is_signed = false; int long_count = 0; - lex_expect(T_open_bracket); base_type_t 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 = find_enum_tag(token, scope); + type = reference_enum_tag(token, scope); } else { /* Declaration specifiers may appear in any order: all of "unsigned long * int", "long unsigned int", and "int unsigned" name the same type. @@ -253,6 +248,22 @@ int read_const_sizeof_type(block_t *scope) type = type == TY_char ? TY_schar : (type ? type : TY_int); } } + 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", @@ -509,6 +520,7 @@ void read_inner_var_decl(var_t *vd, func_t *func = arena_alloc_func(); char temp_name[MAX_VAR_LEN]; int nested_ptr_level = 0; + bool inner_array = false; do { lex_expect(T_asterisk); @@ -561,6 +573,7 @@ void read_inner_var_decl(var_t *vd, 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()); @@ -670,6 +683,56 @@ void read_inner_var_decl(var_t *vd, 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. @@ -736,6 +799,26 @@ void read_inner_var_decl(var_t *vd, 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 @@ -835,6 +918,22 @@ void read_inner_var_decl(var_t *vd, 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 @@ -944,6 +1043,76 @@ void read_inner_var_decl(var_t *vd, 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. "int" may follow another type word, as in "short int", 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) { + if (saw_type_name && strcmp(tk->literal, "int")) + 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. @@ -969,6 +1138,138 @@ void read_type_qualifiers(bool *is_const, } } +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); + + /* C99 6.7.2.3p1: a scope defines the content of a tag only once. */ + if (type->num_fields) + error_at("redefinition of struct or union tag", cur_token_loc()); + } 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); + read_bitfield_width(v, 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(v); + else + reject_flexible_array_member_container(v); + mark_flexible_array_member(v, is_union); + if (is_union) { + v->offset = 0; + int field_size = is_bitfield(v) + ? (v->bit_width ? v->bit_storage_size : 0) + : size_var(v); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(v) > alignment) + alignment = alignment_var(v); + } else { + size = is_bitfield(v) + ? layout_bitfield_field(size, v, &alignment, &bits) + : layout_struct_field(flush_bitfield_layout(size, &bits), + v, &alignment); + } + + while (lex_accept(T_comma)) { + if (!is_union && last->is_flexible_array_member) + error_at( + "Flexible array member must be the final struct member", + cur_token_loc()); + var_t *nv = type_add_field(type, &i); + initialize_struct_field(nv, v, 0); + read_inner_var_decl(nv, false, false, true); + read_bitfield_width(nv, scope); + if (is_union) + has_flexible_array_member |= + is_flexible_array_member_container(nv); + else + reject_flexible_array_member_container(nv); + mark_flexible_array_member(nv, is_union); + last = nv; + if (is_union) { + nv->offset = 0; + int field_size = + is_bitfield(nv) ? (nv->bit_width ? nv->bit_storage_size : 0) + : size_var(nv); + if (field_size > max_size) + max_size = field_size; + if (alignment_var(nv) > alignment) + alignment = alignment_var(nv); + } else { + size = is_bitfield(nv) + ? layout_bitfield_field(size, nv, &alignment, &bits) + : layout_struct_field( + flush_bitfield_layout(size, &bits), nv, + &alignment); + } + } + + 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->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, @@ -1073,7 +1374,7 @@ void read_full_var_decl(var_t *vd, type = is_long ? TY_long_double : TY_double; } else if (is_enum_type) { lex_ident(T_identifier, type_name); - type = find_enum_tag(type_name, vd->scope); + type = reference_enum_tag(type_name, vd->scope); } else if (is_unsigned) { if (is_long) { if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) @@ -1125,6 +1426,16 @@ void read_full_var_decl(var_t *vd, type = TY_int; } else if (leading_scalar_type) { type = leading_scalar_type; + } else 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 @@ -1226,9 +1537,25 @@ void read_partial_var_decl(var_t *vd, var_t *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]; @@ -1249,25 +1576,24 @@ void read_parameter_list_decl(func_t *func, bool anon) error_at("'void' must be the only parameter and unnamed", cur_token_loc()); vn++; - if (lex_accept(T_comma) && strict_c99 && - lex_peek(T_close_bracket, NULL)) - error_at("trailing comma in parameter list is not permitted in C99", - cur_token_loc()); + 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 ((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)) { + 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)) @@ -1286,15 +1612,14 @@ void read_parameter_list_decl(func_t *func, bool anon) is_record_type(func->param_defs[vn].type) && !func->param_defs[vn].ptr_level; vn++; - if (lex_accept(T_comma) && strict_c99 && - lex_peek(T_close_bracket, NULL)) - error_at("trailing comma in parameter list is not permitted in C99", - cur_token_loc()); + expect_parameter = read_parameter_separator(); } func->num_params = vn; - /* Up to 'MAX_PARAMS' parameters are accepted for the variadic function. */ - if (lex_accept(T_elipsis)) { + /* 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()); @@ -1336,10 +1661,16 @@ void parse_string_array_init(var_t *var, block_t *parent, basic_block_t **bb) if (var->has_unsized_array) { var->array_size = len; var->has_unsized_array = false; - } else if (len > var->array_size) + } 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. @@ -1366,11 +1697,13 @@ void parse_wstring_array_init(var_t *var, block_t *parent, basic_block_t **bb) 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 (units > var->array_size) + } else if (length > var->array_size) error_at("Wide string initializer is too long for array", cur_token_loc()); diff --git a/src/parser-expr.c b/src/parser-expr.c index 059be4c3..c7e8fda7 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -12,18 +12,32 @@ * 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(void) +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 || !(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; @@ -68,7 +82,7 @@ static void handle_grouped_scalar_pointee_row_postfix(block_t *parent, source = find_var(name, parent); if (!source) error_at("Undeclared identifier", cur_token_loc()); - if (!lower_scalar_pointee_array_dereference(source, parent, bb)) + 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(); @@ -146,7 +160,7 @@ static void handle_grouped_scalar_pointee_row_postfix(block_t *parent, * 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(void) +static bool grouped_scalar_pointee_row_prefix_starts(block_t *parent) { token_t *token = cur_token ? cur_token->next : NULL; int suffixes = 0; @@ -155,8 +169,8 @@ static bool grouped_scalar_pointee_row_prefix_starts(void) !(token = token->next) || token->kind != T_open_bracket || !(token = token->next) || token->kind != T_asterisk || !(token = token->next) || token->kind != T_identifier || - !(token = token->next) || token->kind != T_close_bracket || - !(token = token->next)) + !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) { @@ -204,7 +218,7 @@ static void handle_grouped_scalar_pointee_row_prefix(block_t *parent, source = find_var(name, parent); if (!source) error_at("Undeclared identifier", cur_token_loc()); - if (!lower_scalar_pointee_array_dereference(source, parent, bb)) + 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(); @@ -297,221 +311,284 @@ typedef struct { bool volatile_qualified; } paren_type_name_t; -/* A parenthesized expression, possibly a comma expression, and the postfix - * operators applied to its value. +/* A subscript applied to the value on top of the operand stack, which has no + * declaration for read_lvalue() to follow. */ -static void read_grouped_operand(block_t *parent, basic_block_t **bb) +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; - /* 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. + 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; + int element_size = base->ptr_level ? 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. */ - opstack_pop(); - perform_side_effect(parent, *bb); - if (!read_assignment_expression(parent, bb)) { - read_expr(parent, bb); - read_ternary_operation(parent, bb); - } - } - lex_expect(T_close_bracket); - - bool lowered_call_result_postfix = false; - if (lex_peek(T_open_square, NULL)) { - var_t *grouped = operand_stack[operand_stack_idx - 1]; - - if (grouped->pointee_array_size) { - lower_call_result_array_postfix(&grouped, parent, bb); - lowered_call_result_postfix = true; - } - } - - /* A parenthesized pointer/string expression is still a postfix operand. - * Unlike identifier lvalues it has no declaration to feed read_lvalue(), so - * lower its first subscript directly while retaining the same explicit - * element-size scaling. - */ - if (!lowered_call_result_postfix && lex_accept(T_open_square)) { - var_t *base = opstack_pop(); - var_t *index; - var_t *address; - int element_size; - - 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; - - /* A grouping around an array compound literal preserves its array - * type. Keep lowering each following index against the original - * shape, just as the direct compound-literal postfix path does. - */ - address = base; - element_size = base->type->size; - 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); + address = base; + do { + int stride = element_size; - 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; - subscript_depth++; - } while (lex_accept(T_open_square)); - - if (subscript_depth < array_dims) { - opstack_push(address); - } else { - vd = require_typed_var(parent, base->type); - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, address, NULL, element_size, - NULL); + if (!read_assignment_expression(parent, bb)) { + read_expr(parent, bb); + read_ternary_operation(parent, bb); } - } else { - if (!base->ptr_level && !base->type->ptr_level) - error_at("Cannot apply square operator to non-pointer", - cur_token_loc()); - read_expr(parent, bb); - read_ternary_operation(parent, bb); index = opstack_pop(); lex_expect(T_close_square); - element_size = base->type->size; - if (element_size != 1) { + 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 = element_size; + 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; } - address = require_typed_ptr_var(parent, base->type, 1); - address->var_name = gen_name(); - add_insn(parent, *bb, OP_add, address, base, index, 0, NULL); - vd = require_typed_var(parent, base->type); - vd->ptr_level = base->ptr_level > 1 ? base->ptr_level - 1 : 0; + 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) { + opstack_push(address); + } else { + vd = require_typed_ptr_var(parent, base->type, base->ptr_level); vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, address, NULL, - vd->ptr_level ? PTR_SIZE : element_size, NULL); + vd->is_const_qualified = base->is_const_qualified; + push_object_at(parent, bb, vd, address, element_size); } + return; } - /* Grouping does not stop a postfix member chain. The expression result is - * an aggregate value rather than an identifier lvalue, so materialize its - * address and lower the selected member here. This covers the ordinary C99 - * spellings `(*p).field` and `(*record.member).field`. + /* E1[E2] is (*((E1)+(E2))): pointer arithmetic scales the index by the + * element size, and the dereference yields the element object. */ - while (lex_accept(T_dot)) { - char token[MAX_ID_LEN]; - var_t *base = opstack_pop(); - var_t *field; - var_t *base_address; - var_t *address; - var_t *offset; + 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()); +} - if (!base->type || !is_record_type(base->type) || - effective_pointer_depth(base)) - error_at("Cannot apply dot operator to non-record", +/* 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()); - lex_ident(T_identifier, token); - field = find_member(token, base->type); - if (!field) - error_at("Unknown struct or union member", next_token_loc()); - - base_address = require_typed_ptr_var(parent, base->type, 1); - base_address->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, base_address, base, NULL, 0, NULL); - 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); - address = require_typed_ptr_var(parent, field->type, 1); - address->var_name = gen_name(); - add_insn(parent, *bb, OP_add, address, base_address, offset, 0, NULL); + if (effective_pointer_depth(base) != 1) + record_type = base->type; + address = base; + lower_member_postfix(address, record_type, true, true, parent, bb); + return; + } - vd = require_typed_var(parent, field->type); - vd->ptr_level = field->ptr_level; - vd->func_signature = field->func_signature; - vd->is_const_qualified = field->is_const_qualified; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(parent, *bb, OP_read, vd, address, NULL, - field->ptr_level || field->type->ptr_level ? PTR_SIZE - : get_size(field), - NULL); + 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) { + /* A record loaded from an object: select from the object itself. */ + address = base->compound_literal_address; + } 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(); + add_insn(parent, *bb, OP_address_of, address, base, NULL, 0, NULL); } + lower_member_postfix(address, record_type, false, is_lvalue, parent, bb); +} - /* Function calls are postfix expressions, so a parenthesized function - * designator remains callable: `(fn)(...)` and `(*fp)(...)` have the same - * meaning as `fn(...)` and `fp(...)`. - */ - if (lex_peek(T_open_bracket, NULL)) { - var_t *callee = operand_stack[operand_stack_idx - 1]; - func_t *signature = get_func_signature(callee); +/* 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 (!callee->ptr_level) { + 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(); - opstack_push(load_function_pointer_object(parent, bb, callee)); + emit_direct_call_result(signature, true, parent, bb); + } else { + error_at("Called object is not a function pointer", + cur_token_loc()); } - vd = emit_indirect_call_result(callee, signature, true, parent, bb); - } else if (callee->is_func) { - signature = find_func(callee->var_name); - if (!signature) - error_at("Called object is not a function", cur_token_loc()); - opstack_pop(); - vd = 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_call_result_array_postfix(&vd, parent, bb); + 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. + */ + opstack_pop(); + perform_side_effect(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. */ @@ -587,20 +664,27 @@ static void lower_array_literal_subscripts(block_t *parent, current->var_name = gen_name(); add_insn(parent, *bb, OP_read, current, address, NULL, element_size, NULL); - if (!is_pointer_operation(compound_op, current, value)) { + /* 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; } - 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); } @@ -758,25 +842,33 @@ static void lower_record_literal_members(block_t *parent, add_insn(parent, *bb, OP_read, current, address, NULL, value_size, NULL); } - if (!is_pointer_operation(compound_op, current, value)) { + /* 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; } - 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 + 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)) { @@ -1215,11 +1307,21 @@ static void read_cast_operand(block_t *parent, paren_type_name_t *tn) { /* Process cast: (type)expr Parse the expression to be cast */ + reading_unary_operand = true; 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. @@ -1259,6 +1361,18 @@ static void read_cast_operand(block_t *parent, if (incompatible_const_pointer_conversion(expr_var, cast_var)) printf("Warning: discarding const qualifier in cast\n"); + /* 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; + } + /* 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 @@ -1372,7 +1486,7 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) type_t *type; if (has_enum_type) { lex_ident(T_identifier, lookahead_token); - type = find_enum_tag(lookahead_token, parent); + type = reference_enum_tag(lookahead_token, parent); } else if (has_unsigned_type && !is_record) { if (has_long_type) { type = TY_ulong; @@ -1643,14 +1757,17 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1; bool is_neg = false; + bool allow_ptr_arith = !reading_unary_operand; - if (grouped_scalar_pointee_row_postfix_starts()) { + reading_unary_operand = 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()) { + grouped_scalar_pointee_row_prefix_starts(parent)) { handle_grouped_scalar_pointee_row_prefix(parent, bb); return; } @@ -1658,7 +1775,13 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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_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; @@ -1670,36 +1793,16 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr_operand(parent, bb); object = opstack_pop(); if (object->is_compound_literal_reference) { - 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); - if (object->compound_literal_bitfield) - write_bitfield_value(parent, bb, - object->compound_literal_address, vd, - object->compound_literal_bitfield); - else - add_insn(parent, *bb, OP_write, NULL, - object->compound_literal_address, vd, - object->ptr_level ? PTR_SIZE : object->type->size, - NULL); - opstack_push(vd); + opstack_push(lower_reference_update(object, op, parent, bb)); return; } - if (!object->is_compound_literal || object->array_size || - is_record_type(object->type)) + 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) @@ -1718,14 +1821,17 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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)) { + reading_unary_operand = true; 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()); @@ -1760,12 +1866,23 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) error_at("Floating point literals are not yet supported", cur_token_loc()); - else if (lex_peek(T_numeric, NULL)) + 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)) { + is_neg = false; + } else if (lex_accept(T_log_not)) { + reading_unary_operand = true; read_expr_operand(parent, bb); rs1 = opstack_pop(); + reject_record_operand(rs1); rs1 = materialize_function_designator(parent, bb, rs1); /* Constant folding for logical NOT */ @@ -1792,9 +1909,11 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_log_not, vd, rs1, NULL, 0, NULL); } } else if (lex_accept(T_bit_not)) { + reading_unary_operand = true; 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()); @@ -1894,6 +2013,12 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) 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; @@ -1902,7 +2027,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) cur_token->next->next->kind == T_open_bracket; read_lvalue(&lvalue, var, parent, bb, true, - deferred_call_prefix ? OP_generic : prefix_op, true); + deferred_call_prefix ? OP_generic : prefix_op, + allow_ptr_arith && !is_neg); /* is it an indirect call with function pointer? */ if (lex_peek(T_open_bracket, NULL)) { @@ -1943,7 +2069,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } vd = emit_indirect_call_result(callee, signature, true, parent, bb); - lower_call_result_array_postfix(&vd, parent, bb); + lower_postfix_operators(parent, bb); lower_call_result_prefix_update( &vd, deferred_call_prefix ? prefix_op : OP_generic, parent, bb); @@ -1958,21 +2084,14 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_expect(T_identifier); if (lex_peek(T_open_bracket, NULL)) { - func_t *returned_signature = - func->return_def.type->func_signature; vd = emit_direct_call_result(func, true, parent, bb); - lower_call_result_array_postfix(&vd, parent, bb); + lower_postfix_operators(parent, bb); lower_call_result_prefix_update(&vd, prefix_op, parent, bb); - if (returned_signature && lex_peek(T_open_bracket, NULL)) { - vd = emit_indirect_call_result(vd, returned_signature, true, - parent, bb); - } } else { /* indirective function pointer assignment */ vd = require_func_symbol_var(parent); vd->is_func = true; vd->var_name = intern_string(token); - vd->func_target = func; opstack_push(vd); } } else if (lex_accept(T_open_curly)) { @@ -1981,32 +2100,36 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* unknown expression */ error_at("Unrecognized expression token", next_token_loc()); } + } - if (is_neg) { - rs1 = opstack_pop(); - 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); + /* A numeric literal takes its minus sign as part of its value; any other + * operand, grouped ones included, is negated here. Only the identifier path + * used to apply it, so "-(x)" evaluated to x. + */ + if (is_neg) { + rs1 = opstack_pop(); + 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); - /* 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->type = rs1->type; - vd->is_const = true; - vd->init_val = -rs1->init_val; - vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); - 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_negate, vd, rs1, NULL, 0, NULL); - } + /* 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->type = rs1->type; + vd->is_const = true; + vd->init_val = -rs1->init_val; + vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); + 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_negate, vd, rs1, NULL, 0, NULL); } } } @@ -2142,6 +2265,27 @@ bool is_direct_void_pointer_type(const type_t *type, int ptr_level) (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; +} + /* Helper function to handle pointer arithmetic (add/sub with scaling) */ void handle_pointer_arithmetic(block_t *parent, basic_block_t **bb, @@ -2209,13 +2353,26 @@ void handle_pointer_arithmetic(block_t *parent, pointee_type_from_pointer_typedef(orig_rs1->type); type_t *right_pointee = pointee_type_from_pointer_typedef(orig_rs2->type); - int left_depth = orig_rs1->ptr_level + orig_rs1->type->ptr_level; - int right_depth = orig_rs2->ptr_level + orig_rs2->type->ptr_level; - bool left_scalar_row = is_scalar_pointee_array_pointer(orig_rs1); - bool right_scalar_row = is_scalar_pointee_array_pointer(orig_rs2); - if ((left_scalar_row || right_scalar_row) && - !scalar_pointee_array_shapes_compatible(orig_rs1, orig_rs2)) + /* 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); + bool right_row = is_pointee_array_pointer(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()); @@ -2227,9 +2384,8 @@ void handle_pointer_arithmetic(block_t *parent, element_size = PTR_SIZE; /* Default */ - element_size = left_scalar_row - ? scalar_pointee_array_row_stride(orig_rs1) - : get_pointer_element_size(orig_rs1); + 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); @@ -2260,8 +2416,8 @@ void handle_pointer_arithmetic(block_t *parent, ptr_var = rs1; int_var = rs2; element_size = - is_scalar_pointee_array_pointer(rs1) - ? scalar_pointee_array_row_stride(rs1) + is_pointee_array_pointer(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) @@ -2272,8 +2428,8 @@ void handle_pointer_arithmetic(block_t *parent, ptr_var = rs2; int_var = rs1; element_size = - is_scalar_pointee_array_pointer(rs2) - ? scalar_pointee_array_row_stride(rs2) + is_pointee_array_pointer(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) @@ -2312,7 +2468,7 @@ void handle_pointer_arithmetic(block_t *parent, * typedefs cannot be mistaken for a direct callback value. */ vd->ptr_level = ptr_var->ptr_level + !!ptr_var->array_size; - if (is_scalar_pointee_array_pointer(ptr_var)) + if (is_pointee_array_pointer(ptr_var)) 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) { @@ -2458,6 +2614,12 @@ void normalize_integer_binary_operands(block_t *parent, right[0] = resize_to(parent, bb, right[0], common, 0); } +/* Whether the right operand of @op binds tighter than a following `+`. */ +static bool operand_excludes_addition(opcode_t op) +{ + return op == OP_sub || op == OP_mul || op == OP_div || op == OP_mod; +} + void read_expr_body(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1, *rs2; @@ -2493,6 +2655,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) fatal("Expression too complex: operator stack exhausted"); oper_stack[oper_stack_idx++] = op; } + reading_unary_operand = operand_excludes_addition(op); read_expr_operand(parent, bb); op = get_operator(); @@ -2504,6 +2667,8 @@ void read_expr_body(block_t *parent, basic_block_t **bb) if (get_operator_prio(top_op) >= get_operator_prio(op)) { rs2 = opstack_pop(); rs1 = opstack_pop(); + reject_record_operand(rs1); + reject_record_operand(rs2); /* Handle pointer arithmetic for addition and subtraction */ if (is_pointer_operation(top_op, rs1, rs2)) { @@ -2622,6 +2787,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) pprev_log_op = 0; } } + reading_unary_operand = operand_excludes_addition(op); read_expr_operand(parent, bb); if (!is_logical(op)) { if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) @@ -2635,6 +2801,8 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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 @@ -2751,11 +2919,25 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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: - result = rs1->init_val << rs2->init_val; + 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: - result = rs1->init_val >> rs2->init_val; + 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; @@ -2848,6 +3030,15 @@ static bool lvalue_is_wide_integer(const lvalue_t *lvalue) 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); +} + /* What follows a completed lvalue: pointer arithmetic on it, or an update or * read of the object it names. read_lvalue() finishes with this. */ @@ -2865,16 +3056,15 @@ static void lower_lvalue_tail(lvalue_t *lvalue, * been dereferenced. After array indexing like arr[0], we have a value, not * a pointer. */ - bool scalar_pointee_row = is_scalar_pointee_array_pointer(var); + bool pointee_row = is_pointee_array_pointer(var); if (allow_ptr_arith && lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var) || scalar_pointee_row) && + (var->ptr_level || is_array_declarator(var) || pointee_row) && !lvalue->is_reference) { if (!is_array_declarator(var) && is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); while (lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var) || - scalar_pointee_row)) { + (var->ptr_level || is_array_declarator(var) || pointee_row)) { lex_expect(T_plus); if (lvalue->is_reference) { rs1 = opstack_pop(); @@ -2891,8 +3081,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, * pointer that pointee is itself a pointer, so the stride is * PTR_SIZE rather than the base type's width. */ - if (scalar_pointee_row) { - lvalue->size = scalar_pointee_array_row_stride(var); + if (pointee_row) { + lvalue->size = pointee_array_row_stride(var); } else if (is_array_declarator(var) && var->array_dim2 && !has_effective_pointer(var) && !var->is_func) { lvalue->size = fixed_array_decay_stride(var); @@ -2926,11 +3116,10 @@ static void lower_lvalue_tail(lvalue_t *lvalue, * first decay to a pointer, adding one level to the declaration's * element indirection. */ - if (var->ptr_level || is_array_declarator(var) || - scalar_pointee_row) { + if (var->ptr_level || is_array_declarator(var) || pointee_row) { vd->type = lvalue->type; vd->ptr_level = var->ptr_level + !!is_array_declarator(var); - if (scalar_pointee_row) + if (pointee_row) copy_pointee_array_shape(vd, var); else if (is_array_declarator(var) && var->array_dim2 && !has_effective_pointer(var) && !var->is_func) { @@ -2987,17 +3176,26 @@ static void lower_lvalue_tail(lvalue_t *lvalue, t->pointer_const_mask = t->is_const_pointer ? 1U : 0; t->is_volatile = var->type->array_element_is_volatile; } - opstack_push(t); if (is_bitfield(lvalue->decl)) { /* Bit-fields are values, never independently addressable * storage: extract their slice from the containing unit. */ - opstack_pop(); t = read_bitfield_value(parent, bb, rs1, lvalue->decl); opstack_push(t); - } else { + 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 selected record is copied into storage of its own; a + * register holds no more than a pointer's worth of it. + */ + push_object_at(parent, bb, t, rs1, lvalue->size); } } if (prefix_op != OP_generic) { @@ -3010,8 +3208,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, /* For pointer arithmetic, increment by the size of pointed-to type */ - if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) - vd->init_val = scalar_pointee_array_row_stride(var); + if (!lvalue->is_reference && is_pointee_array_pointer(var)) + vd->init_val = pointee_array_row_stride(var); else if (lvalue->ptr_level > 1) vd->init_val = PTR_SIZE; else if (lvalue->ptr_level) @@ -3040,16 +3238,30 @@ static void lower_lvalue_tail(lvalue_t *lvalue, rs1 = vd; vd = opstack_pop(); - /* The column of arguments of the new insn of 'OP_write' is - * different from 'ph1_ir' + /* 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. */ - add_insn(parent, *bb, OP_write, NULL, vd, rs1, lvalue->size, - NULL); + 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); } @@ -3064,7 +3276,14 @@ static void lower_lvalue_tail(lvalue_t *lvalue, * extracted pre-update value as the expression result. */ if (lvalue->is_reference && is_bitfield(lvalue->decl)) { - opcode_t postfix_op = lex_accept(T_increment) ? OP_add : OP_sub; + /* 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); @@ -3081,10 +3300,10 @@ static void lower_lvalue_tail(lvalue_t *lvalue, return; } - /* This arm appends three entries, so check for room once before - * writing any of them. + /* This arm appends up to five entries, so check for room once + * before writing any of them. */ - if (se_idx + 3 > MAX_SIDE_EFFECT) + if (se_idx + 5 > MAX_SIDE_EFFECT) error_at("Too many postfix operators in one statement", next_token_loc()); @@ -3094,8 +3313,8 @@ static void lower_lvalue_tail(lvalue_t *lvalue, /* Calculate increment size based on pointer type */ int increment_size = 1; - if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) { - increment_size = scalar_pointee_array_row_stride(var); + if (!lvalue->is_reference && is_pointee_array_pointer(var)) { + increment_size = pointee_array_row_stride(var); } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { increment_size = PTR_SIZE; } else if (lvalue->ptr_level && !lvalue->is_reference) { @@ -3149,7 +3368,7 @@ static void lower_lvalue_tail(lvalue_t *lvalue, side_effect[se_idx].rs1 = operand_stack[operand_stack_idx - 1]; vd = require_var(parent); vd->var_name = gen_name(); - if (!lvalue->is_reference && is_scalar_pointee_array_pointer(var)) { + 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); @@ -3158,6 +3377,33 @@ static void lower_lvalue_tail(lvalue_t *lvalue, side_effect[se_idx].rd = vd; se_idx++; + /* 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). The deferred entries are raw instructions, so spell out + * the comparison normalize_bool() would emit. + */ + if (lvalue_is_bool(lvalue)) { + var_t *zero = require_typed_var(parent, TY_int); + var_t *truth = require_typed_var(parent, TY_bool); + + zero->var_name = gen_name(); + zero->init_val = 0; + zero->is_const = true; + side_effect[se_idx].opcode = OP_load_constant; + side_effect[se_idx].rd = zero; + side_effect[se_idx].rs1 = NULL; + side_effect[se_idx].rs2 = NULL; + se_idx++; + + truth->var_name = gen_name(); + side_effect[se_idx].opcode = OP_neq; + side_effect[se_idx].rd = truth; + side_effect[se_idx].rs1 = vd; + side_effect[se_idx].rs2 = zero; + se_idx++; + vd = truth; + } + if (lvalue->is_reference) { side_effect[se_idx].opcode = OP_write; side_effect[se_idx].rs2 = vd; @@ -3184,6 +3430,23 @@ static void lower_lvalue_tail(lvalue_t *lvalue, } } +/* 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[] =; @@ -3215,6 +3478,18 @@ void read_lvalue(lvalue_t *lvalue, 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. */ @@ -3264,8 +3539,10 @@ void read_lvalue(lvalue_t *lvalue, 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; @@ -3274,10 +3551,11 @@ void read_lvalue(lvalue_t *lvalue, /* 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. + * 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->ptr_level || pending_fixed_array_pointer_slot) && + (lvalue->value_ptr_level || pending_fixed_array_pointer_slot) && is_member) { rs1 = opstack_pop(); vd = require_var(parent); @@ -3307,10 +3585,23 @@ void read_lvalue(lvalue_t *lvalue, /* 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). + * (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. */ - lvalue->size = - lvalue->value_ptr_level > 1 ? PTR_SIZE : lvalue->type->size; + 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 @@ -3412,6 +3703,13 @@ void read_lvalue(lvalue_t *lvalue, } 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. + */ + if (lvalue->value_ptr_level > 0) + lvalue->size = PTR_SIZE; } read_expr(parent, bb); @@ -3481,15 +3779,36 @@ void read_lvalue(lvalue_t *lvalue, 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; + } + /* 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 && lvalue->value_ptr_level > 0; + is_member = !indexes_pointee_row && !array_row && + lvalue->value_ptr_level > 0; subscript_depth++; - lvalue->is_reference = !indexes_pointee_row; + 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) @@ -3523,11 +3842,17 @@ void read_lvalue(lvalue_t *lvalue, lvalue->is_const_qualified = var->is_const_qualified || (lvalue->pointee_func_signature && - var->type->array_element_is_const_pointer); + var->type->array_element_is_const_pointer) || + (record_is_const && is_array_declarator(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. */ @@ -3541,6 +3866,7 @@ void read_lvalue(lvalue_t *lvalue, } } else { lex_expect(T_dot); + record_is_const = lvalue->is_const_qualified; if (!is_address_got) { rs1 = opstack_pop(); @@ -3562,18 +3888,32 @@ void read_lvalue(lvalue_t *lvalue, 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); - lvalue->is_const_qualified |= var->is_const_qualified; + + /* 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. + * 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`. */ - if (is_array_declarator(var)) - lvalue->is_reference = false; + lvalue->is_reference = !is_array_declarator(var); /* move pointer to offset of structure */ vd = require_var(parent); @@ -3586,6 +3926,15 @@ void read_lvalue(lvalue_t *lvalue, rs1 = opstack_pop(); vd = require_var(parent); vd->var_name = gen_name(); + + /* 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 (is_array_declarator(var) && !var->array_dim2) { + vd->type = var->type; + vd->ptr_level = var->ptr_level + 1; + } opstack_push(vd); add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); @@ -3594,6 +3943,21 @@ void read_lvalue(lvalue_t *lvalue, } } + 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; @@ -3602,8 +3966,8 @@ void read_lvalue(lvalue_t *lvalue, lex_peek(T_decrement, NULL))) error_at("assignment of read-only location", next_token_loc()); - lower_lvalue_tail(lvalue, var, parent, bb, prefix_op, allow_ptr_arith, - pointer_row_element_type); + lower_lvalue_tail(lvalue, array_row ? array_row : var, parent, bb, + prefix_op, allow_ptr_arith, pointer_row_element_type); } void read_logical(opcode_t op, block_t *parent, basic_block_t **bb) @@ -3616,6 +3980,7 @@ void read_logical(opcode_t op, block_t *parent, basic_block_t **bb) /* 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); @@ -3716,6 +4081,7 @@ void finalize_logical(opcode_t op, /* 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); @@ -3808,14 +4174,15 @@ basic_block_t *read_full_expression_statement(block_t *parent, /* 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(void) +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 || !(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; @@ -3861,7 +4228,7 @@ static basic_block_t *handle_grouped_scalar_pointee_row_store(block_t *parent, source = find_var(name, parent); if (!source) error_at("Undeclared identifier", cur_token_loc()); - if (!lower_scalar_pointee_array_dereference(source, parent, &bb)) + 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(); @@ -3918,6 +4285,7 @@ static basic_block_t *handle_grouped_scalar_pointee_row_store(block_t *parent, 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; @@ -3964,6 +4332,7 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) /* 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); @@ -4170,6 +4539,9 @@ bool read_body_assignment(char *token, 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", @@ -4219,8 +4591,8 @@ bool read_body_assignment(char *token, * operating on a dereferenced value (array indexing) */ if (!one && (op == OP_add || op == OP_sub) && - !lvalue.is_reference && is_scalar_pointee_array_pointer(var)) - increment_size = scalar_pointee_array_row_stride(var); + !lvalue.is_reference && is_pointee_array_pointer(var)) + increment_size = pointee_array_row_stride(var); else if (lvalue.ptr_level > 1 && !lvalue.is_reference) increment_size = PTR_SIZE; else if (lvalue.ptr_level && !lvalue.is_reference) @@ -4273,9 +4645,12 @@ bool read_body_assignment(char *token, if (lvalue.is_reference) { if (is_bitfield(lvalue.decl)) write_bitfield_value(parent, bb, t, vd, lvalue.decl); - else + 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); @@ -4328,11 +4703,20 @@ bool read_body_assignment(char *token, } 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; @@ -4373,9 +4757,18 @@ bool read_body_assignment(char *token, if (lvalue.is_reference) { if (is_bitfield(lvalue.decl)) write_bitfield_value(parent, bb, t, vd, lvalue.decl); - else + 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); @@ -4418,7 +4811,6 @@ bool read_body_assignment(char *token, vd = require_var(parent); vd->var_name = gen_name(); - vd->func_target = rs2->func_target; add_insn(parent, *bb, OP_read, vd, t, NULL, PTR_SIZE, NULL); rs2 = vd; } @@ -4436,23 +4828,6 @@ bool read_body_assignment(char *token, } add_insn(parent, *bb, OP_write, NULL, rs1, rs2, PTR_SIZE, NULL); - - /* Only a named function-pointer object owns this metadata. A - * record member or array element shares lvalue.decl with every - * object of that type, so storing provenance there would let - * one object's assignment authorize another's call. Those - * reference forms intentionally remain unknown until SSA - * provenance is available per storage location. - */ - if (!lvalue.is_reference && !lvalue.decl->func_target_invalid) { - func_t *assigned_target = rs2->func_target; - - if (!assigned_target || !assigned_target->bbs) { - lvalue.decl->func_target = NULL; - lvalue.decl->func_target_invalid = true; - } else - lvalue.decl->func_target = assigned_target; - } if (assignment_result) assignment_result[0] = rs2; } else if (lvalue.is_reference) { @@ -4479,11 +4854,28 @@ bool read_body_assignment(char *token, "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 + 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) { + } + 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 @@ -4507,6 +4899,10 @@ bool read_body_assignment(char *token, 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); @@ -4619,6 +5015,143 @@ bool compound_literal_starts_expression(void) 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; +} + +/* 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(); + 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 = read_bitfield_value(parent, bb, address, + target->compound_literal_bitfield); + result->is_bitfield = false; + 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); +} + bool read_assignment_expression(block_t *parent, basic_block_t **bb) { char token[MAX_ID_LEN]; @@ -4629,6 +5162,14 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) return false; if (compound_literal_starts_expression()) return false; + if (grouped_postfix_lvalue_follows() || + (lex_peek(T_numeric, NULL) && cur_token->next->next && + cur_token->next->next->kind == T_open_square) || + (lex_peek(T_identifier, token) && find_var(token, parent) && + is_swapped_subscript_base(find_var(token, parent)))) { + 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 = @@ -4761,6 +5302,9 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) 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); @@ -4809,26 +5353,40 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_read, current, address, NULL, store_size, NULL); } - if (!is_pointer_operation(compound_op, current, value)) { + /* 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; } - 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 + 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. @@ -4843,7 +5401,7 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_read, result, address, NULL, store_size, NULL); } - if (result->type == TY_bool && !result->ptr_level) + if (is_bool_scalar(result->type, result->ptr_level)) result = normalize_bool(parent, bb, result); opstack_push(result); return true; diff --git a/src/parser-global.c b/src/parser-global.c index 4a0d85e9..c1d25047 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -789,16 +789,17 @@ void initialize_struct_field(var_t *nv, var_t *v, int offset) nv->is_compound_literal = false; } -/* The declarators of a file-scope typedef whose base type is not a record or - * enum definition: scalar, pointer, array and function aliases. +/* 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 void read_global_typedef_declarators(block_t *block, - bool typedef_const, - bool typedef_volatile) +static const type_t *read_global_typedef_base(bool *typedef_const, + bool *typedef_volatile) { char base_type[MAX_ID_LEN]; const type_t *base; - type_t *type = add_type(); bool is_signed = false; bool is_unsigned = false; bool is_long = false; @@ -828,9 +829,9 @@ static void read_global_typedef_declarators(block_t *block, lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || lex_peek(T_long, NULL)) { if (lex_accept(T_const)) - typedef_const = true; + *typedef_const = true; else if (lex_accept(T_volatile)) - typedef_volatile = true; + *typedef_volatile = true; else if (lex_accept(T_signed)) { if (is_signed) error_at("duplicate signed type specifier", cur_token_loc()); @@ -921,12 +922,20 @@ static void read_global_typedef_declarators(block_t *block, } 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(); - /* `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); type->base_type = base->base_type; type->size = base->size; @@ -949,6 +958,15 @@ static void read_global_typedef_declarators(block_t *block, 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; @@ -961,6 +979,16 @@ static void read_global_typedef_declarators(block_t *block, 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; */ while (lex_accept(T_asterisk)) { type->ptr_level++; @@ -1002,6 +1030,22 @@ static void read_global_typedef_declarators(block_t *block, 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; + return; + } if (!declarator.is_func) error_at( "Typedef parenthesized declarator must be a function " @@ -1032,7 +1076,6 @@ static void read_global_typedef_declarators(block_t *block, type->ptr_level = 0; type->pointer_const_mask = declarator.pointer_const_mask; type->is_volatile_qualified = declarator.is_volatile; - lex_expect(T_semicolon); return; } @@ -1062,6 +1105,11 @@ static void read_global_typedef_declarators(block_t *block, 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 @@ -1076,13 +1124,141 @@ static void read_global_typedef_declarators(block_t *block, ? pointee_type_from_pointer_typedef((type_t *) base) : (type_t *) base); } +} + +/* The declarator list of a file-scope typedef whose specifier resolved to + * @base, with the qualifiers read so far, 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) +{ + /* `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); + 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, 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; + + 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; +} + +/* A file-scope enum specifier after its keyword: a reference to a known 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 and may use the same integer constant expressions accepted for + * array bounds and case labels. + * + * Returns the enum type. + */ +static type_t *read_global_enum_specifier(block_t *block) +{ + char token[MAX_ID_LEN]; + int val = 0; + bool has_tag = false; + type_t *type; + + if (lex_peek(T_identifier, token)) { + lex_expect(T_identifier); + has_tag = true; + } + if (!lex_peek(T_open_curly, NULL)) { + if (!has_tag) + error_at("Expected enum tag or definition", cur_token_loc()); + return reference_enum_tag(token, GLOBAL_BLOCK); + } + + /* Only a definition creates an enum tag, so one already declared here would + * be defined twice (C99 6.7.2.3p1). + */ + if (has_tag && local_enum_tag(token, GLOBAL_BLOCK)) + error_at("redefinition of enum tag", cur_token_loc()); + type = add_type(); + + initialize_enum_type(type); + + /* Register the tag at file scope as well, so that a struct or union + * specifier reusing the name, in any scope, sees an enum tag. + */ + if (has_tag) { + set_type_name(type, token); + add_type_tag(GLOBAL_BLOCK, 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(block); + add_constant(token, val); + first = false; + } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); + lex_expect(T_close_curly); + return type; +} + +/* Whether the first declarator of a file-scope record typedef is only stars and + * a name, which the record branches below complete 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; +} + +/* Read the declarators of a file-scope record typedef whose first declarator is + * not plain, deriving each from @record, the completed or referenced record. + */ +static void read_global_record_typedef_declarators(block_t *block, + bool typedef_const, + bool typedef_volatile, + const type_t *record) +{ + do { + read_global_typedef_declarator(block, typedef_const, typedef_volatile, + record); + } while (lex_accept(T_comma)); lex_expect(T_semicolon); } -/* A file-scope typedef, after its keyword: record, untagged enum, callback - * pointer and scalar aliases. Each form reads a single declarator; a - * comma-separated declarator list is only parsed by the block-scope typedef - * reader. +/* 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) { @@ -1096,28 +1272,13 @@ static void read_global_typedef(block_t *block) */ read_type_qualifiers(&typedef_const, &typedef_volatile, false); if (lex_accept(T_enum)) { - int val = 0; - type_t *type = add_type(); + /* The alias may name an existing tag, define a tagged or untagged enum, + * and derive a pointer or array from it like any other base. + */ + type_t *type = read_global_enum_specifier(block); - initialize_enum_type(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(block); - add_constant(token, val); - first = false; - } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); - lex_expect(T_close_curly); - read_type_qualifiers(&typedef_const, &typedef_volatile, false); - type->is_const_qualified = typedef_const; - type->is_volatile_qualified = typedef_volatile; - lex_ident(T_identifier, token); - set_type_name(type, token); - lex_expect(T_semicolon); + read_global_typedef_declarators(block, typedef_const, typedef_volatile, + type); } else if (lex_accept(T_struct)) { int i = 0, size = 0, alignment = 1; bitfield_layout_t bits = {0}; @@ -1135,6 +1296,9 @@ static void read_global_typedef(block_t *block) /* typedef with struct definition */ if (lex_accept(T_open_curly)) { has_struct_def = true; + if (tag && tag->num_fields) + error_at("redefinition of struct or union tag", + cur_token_loc()); do { var_t *v = type_add_field(type, &i); var_t *last = v; @@ -1188,11 +1352,13 @@ static void read_global_typedef(block_t *block) } read_type_qualifiers(&typedef_const, &typedef_volatile, false); - while (lex_accept(T_asterisk)) { + bool is_plain = global_record_typedef_declarator_is_plain(); + while (is_plain && lex_accept(T_asterisk)) { type->ptr_level++; type->size = PTR_SIZE; } - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + if (is_plain) + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); size = flush_bitfield_layout(size, &bits); type->alignment = type->ptr_level ? PTR_SIZE : alignment; type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(size, alignment); @@ -1217,6 +1383,22 @@ static void read_global_typedef(block_t *block) type->is_const_qualified = typedef_const; type->is_volatile_qualified = typedef_volatile; + if (!is_plain) { + read_global_record_typedef_declarators( + block, typedef_const, typedef_volatile, tag ? tag : type); + return; + } + + /* Later declarators derive from the record as a typedef of it would. */ + if (lex_accept(T_comma)) { + const type_t *record = global_record_typedef_base( + type, ALIGN_UP(size, alignment), alignment); + + do { + read_global_typedef_declarator(block, typedef_const, + typedef_volatile, record); + } while (lex_accept(T_comma)); + } lex_expect(T_semicolon); } else if (lex_accept(T_union)) { int i = 0, max_size = 0, alignment = 1; @@ -1234,6 +1416,9 @@ static void read_global_typedef(block_t *block) /* typedef with union definition */ if (lex_accept(T_open_curly)) { has_union_def = true; + if (tag && tag->num_fields) + error_at("redefinition of struct or union tag", + cur_token_loc()); do { var_t *v = type_add_field(type, &i); read_full_var_decl(v, false, false, true); @@ -1275,11 +1460,13 @@ static void read_global_typedef(block_t *block) } read_type_qualifiers(&typedef_const, &typedef_volatile, false); - while (lex_accept(T_asterisk)) { + bool is_plain = global_record_typedef_declarator_is_plain(); + while (is_plain && lex_accept(T_asterisk)) { type->ptr_level++; type->size = PTR_SIZE; } - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); + if (is_plain) + lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); type->alignment = type->ptr_level ? PTR_SIZE : alignment; type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(max_size, alignment); type->num_fields = i; @@ -1303,9 +1490,28 @@ static void read_global_typedef(block_t *block) type->is_const_qualified = typedef_const; type->is_volatile_qualified = typedef_volatile; + if (!is_plain) { + read_global_record_typedef_declarators( + block, typedef_const, typedef_volatile, tag ? tag : type); + return; + } + + if (lex_accept(T_comma)) { + const type_t *record = global_record_typedef_base( + type, ALIGN_UP(max_size, alignment), alignment); + + do { + read_global_typedef_declarator(block, typedef_const, + typedef_volatile, record); + } while (lex_accept(T_comma)); + } lex_expect(T_semicolon); } else { - read_global_typedef_declarators(block, typedef_const, typedef_volatile); + const type_t *base = + read_global_typedef_base(&typedef_const, &typedef_volatile); + + read_global_typedef_declarators(block, typedef_const, typedef_volatile, + base); } } @@ -1319,7 +1525,10 @@ void read_global_statement(void) bool is_inline = false; bool is_volatile = false; - /* These specifiers may appear in either order. */ + /* 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)) { @@ -1349,6 +1558,13 @@ void read_global_statement(void) 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()); @@ -1363,16 +1579,21 @@ void read_global_statement(void) error_at("inline specifier requires a function declarator", cur_token_loc()); - if (lex_accept(T_struct)) { - int i = 0, size = 0, alignment = 1; - bitfield_layout_t bits = {0}; - bool has_flexible_array_member = 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; - lex_ident(T_identifier, token); - type_t *type = local_record_tag(token, GLOBAL_BLOCK, TYPE_struct); + 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 struct tag? */ + /* 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. @@ -1385,59 +1606,7 @@ void read_global_statement(void) return; } - lex_expect(T_open_curly); - do { - var_t *v = type_add_field(type, &i); - var_t *last = v; - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, block); - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, false); - size = is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field(flush_bitfield_layout(size, &bits), - v, &alignment); - - /* Handle multiple variable declarations with same base type */ - while (lex_accept(T_comma)) { - if (last->is_flexible_array_member) - error_at( - "Flexible array member must be the final struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, block); - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, false); - last = nv; - size = is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); - } - - lex_expect(T_semicolon); - if (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)); - - size = flush_bitfield_layout(size, &bits); - type->alignment = alignment; - type->size = ALIGN_UP(size, alignment); - type->num_fields = i; - type->has_flexible_array_member = has_flexible_array_member; + 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;". @@ -1445,124 +1614,21 @@ void read_global_statement(void) if (!lex_accept(T_semicolon)) read_global_declarator_list(block, type, is_const, is_static, is_volatile, is_extern, true); - } else if (lex_accept(T_union)) { - int i = 0, max_size = 0, alignment = 1; - bool has_flexible_array_member = false; - - lex_ident(T_identifier, token); - type_t *type = local_record_tag(token, GLOBAL_BLOCK, TYPE_union); - - /* A tagged union declaration may name an already-complete tag, just - * like `struct tag object;`. Do not require a second definition body - * before routing its declarators through the shared record path. - */ - if (!lex_peek(T_open_curly, NULL)) { - /* As for struct, a bare tag declaration may repeat a known tag. */ - if (lex_accept(T_semicolon)) - return; - - read_global_declarator_list(block, type, is_const, is_static, - is_volatile, is_extern, true); - return; - } - - lex_expect(T_open_curly); - do { - var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, block); - has_flexible_array_member |= is_flexible_array_member_container(v); - mark_flexible_array_member(v, true); - v->offset = 0; /* All union fields start at offset 0 */ - int field_size = is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - - /* 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, false, true); - read_bitfield_width(nv, block); - has_flexible_array_member |= - is_flexible_array_member_container(nv); - mark_flexible_array_member(nv, true); - field_size = is_bitfield(nv) - ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(nv) > alignment) - alignment = alignment_var(nv); - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - - type->alignment = alignment; - type->size = ALIGN_UP(max_size, alignment); - type->num_fields = i; - type->has_flexible_array_member = has_flexible_array_member; - - if (!lex_accept(T_semicolon)) - read_global_declarator_list(block, type, is_const, is_static, - is_volatile, is_extern, true); } else if (lex_accept(T_enum)) { - /* An enum definition is a declaration in its own right; it need not - * introduce a typedef. Its enumerators are integer constants and may - * use the same integer constant expressions accepted for array bounds - * and case labels. - */ - int val = 0; - bool has_tag = false; - type_t *type; - - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - has_tag = true; - } - if (!lex_peek(T_open_curly, NULL)) { - if (!has_tag) - error_at("Expected enum tag or definition", cur_token_loc()); - type = find_enum_tag(token, GLOBAL_BLOCK); - if (!type) - error_at("Unknown enum type", cur_token_loc()); - read_global_declarator_list(block, type, is_const, is_static, - is_volatile, is_extern, false); - return; - } - type = has_tag ? local_enum_tag(token, GLOBAL_BLOCK) : NULL; - if (!type) - type = add_type(); + bool is_reference = !lex_peek(T_open_curly, NULL) && + !(cur_token->next && cur_token->next->next && + cur_token->next->next->kind == T_open_curly); + type_t *type = read_global_enum_specifier(block); - initialize_enum_type(type); - - /* Register the tag at file scope as well, so that a struct or union - * specifier reusing the name, in any scope, sees an enum tag. + /* A definition may stand alone; a reference to a tag needs a + * declarator, since it can neither declare nor complete the tag. */ - if (has_tag) { - set_type_name(type, token); - if (!find_local_type_tag(token, GLOBAL_BLOCK)) - add_type_tag(GLOBAL_BLOCK, 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(block); - add_constant(token, val); - first = false; - } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); - lex_expect(T_close_curly); - if (!lex_accept(T_semicolon)) + if (is_reference && lex_peek(T_semicolon, NULL)) + error_at( + "enum declaration without an enumerator list declares " + "nothing", + next_token_loc()); + if (is_reference || !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)) { @@ -1883,12 +1949,106 @@ void parse_internal(void) cur_token_loc()); } +/* Whether @tk may continue a run of declaration specifiers around a scalar type + * keyword. `char`, `short` and `int` reach the parser as identifiers. + */ +static bool is_scalar_specifier_run_token(const token_t *tk) +{ + switch (tk->kind) { + case T_signed: + case T_unsigned: + case T_long: + case T_const: + case T_volatile: + case T_restrict: + case T_inline: + case T_static: + case T_extern: + case T_register: + case T_auto: + case T_typedef: + return true; + case T_identifier: + return !strcmp(tk->literal, "int") || !strcmp(tk->literal, "short") || + !strcmp(tk->literal, "char"); + default: + return false; + } +} + +/* C99 6.7.2p2 lets the scalar type words appear in any order among the other + * declaration specifiers, and lets `int` be spelled beside `short` or `long`: + * `short int`, `int long unsigned` and `long int long` name the same types as + * `short`, `unsigned long` and `long long`. The declaration readers take a type + * word only as the base after the modifiers, so normalize every run once, + * before parsing, rather than teaching each reader every spelling: drop that + * redundant `int`, and move a `char` or `short` base after the last `signed` or + * `unsigned`. A run with a second base keeps its words and is still rejected. + */ +static void normalize_scalar_specifiers(token_t *head) +{ + token_t *prev = head; + + while (prev->next) { + token_t *before_int = NULL, *before_base = NULL, *last_sign = NULL; + int ints = 0, widths = 0, bases = 0; + token_t *last = prev; + + while (last->next && is_scalar_specifier_run_token(last->next)) { + token_t *tk = last->next; + + if (tk->kind == T_long) + widths++; + else if (tk->kind == T_signed || tk->kind == T_unsigned) + last_sign = tk; + else if (tk->kind == T_identifier && tk->literal[0] == 'i') { + ints++; + before_int = last; + } else if (tk->kind == T_identifier) { + widths += tk->literal[0] == 's'; + bases++; + before_base = last; + } + last = tk; + } + if (last == prev) { + prev = prev->next; + continue; + } + if (ints == 1 && widths && bases <= 1) { + token_t *int_token = before_int->next; + + before_int->next = int_token->next; + if (last == int_token) + last = before_int; + if (before_base == int_token) + before_base = before_int; + } + if (bases == 1 && ints <= 1 && last_sign) { + token_t *base = before_base->next; + bool sign_follows = false; + + for (token_t *tk = base->next; tk != last->next; tk = tk->next) + sign_follows |= tk == last_sign; + if (sign_follows) { + before_base->next = base->next; + base->next = last_sign->next; + last_sign->next = base; + if (last == last_sign) + last = base; + } + } + prev = last; + } +} + void parse(token_t *tk) { token_t head; head.kind = T_start; head.next = tk; cur_token = &head; + normalize_scalar_specifiers(&head); parse_internal(); } diff --git a/src/parser-init.c b/src/parser-init.c index f0472be6..d1dfea8a 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -152,8 +152,6 @@ void emit_object_assignment(block_t *parent, var_t *dest, var_t *src) { - func_t *target = src->func_target; - if (is_record_object(dest) && is_record_object(src)) { emit_record_copy(parent, bb, dest, src); } else if (dest->is_func && src->is_func && @@ -167,27 +165,161 @@ void emit_object_assignment(block_t *parent, 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); - dest->func_target = src->func_target; - dest->func_target_invalid = - src->func_target_invalid || !target || !target->bbs; } 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; + +/* 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; + + 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; + } 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->func_signature || type->is_direct_function_type)) + return false; + if (type && type->ptr_level) + 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; + } + return token && has_type && is_pointer; +} + +/* Consume a cast that global_pointer_cast_starts_here() recognized and return + * the size of what its pointer type points to. + */ +int read_global_pointer_cast(block_t *scope) +{ + type_t *type; + int depth = 0; + int size; + + 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)) + ; + } + lex_expect(T_close_bracket); + if (!type) + error_at("Unknown type in pointer cast", cur_token_loc()); + if (depth + type->ptr_level > 1) + 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 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) +{ + var_t *address = require_var(parent); + int value = read_const_expr_operand(scope); + + if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) + value += read_global_address_offset(scope, parent, bb) * stride; + address->var_name = gen_name(); + address->init_val = value; + address->is_const = true; + 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) + var_t *object_addr, + bool decays) { var_t *target = *object; fixed_array_shape_t shape = fixed_array_shape_from_var(target); @@ -231,13 +363,19 @@ var_t *read_global_address_designator(block_t *scope, * 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()); if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) { int element_size = target->ptr_level ? PTR_SIZE : target->type->size; int index = read_global_address_offset(scope, parent, *bb); + int stride = global_pointer_cast_stride + ? global_pointer_cast_stride + : fixed_array_shape_stride( + &shape, subscripts - !decays, element_size); - object_addr = compute_element_address( - parent, bb, object_addr, index, - fixed_array_shape_stride(&shape, subscripts - 1, element_size)); + 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; } @@ -268,6 +406,20 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) val->is_func = true; return val; } + if (global_pointer_cast_starts_here(scope)) { + int saved_stride = global_pointer_cast_stride; + int stride = read_global_pointer_cast(scope); + + /* 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); + 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 (grouped_global_function_designator_starts_here(false)) { @@ -275,8 +427,22 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) grouped_function_designator = true; } - if (explicit_address || grouped_function_designator || - lex_peek(T_identifier, NULL)) { + 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) || + (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)) { @@ -307,7 +473,28 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_address_of, val, object, NULL, 0, NULL); return read_global_address_designator(scope, parent, bb, - &object, val); + &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); } } } @@ -316,39 +503,6 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) cur_token_loc()); error_at("Global aggregate initializer requires a constant value", cur_token_loc()); - } else if (typed_global_literal_appears_before_initializer_end( - cur_token->next)) { - /* The word-sized reader below parses a literal into an int, so a member - * initialized with 0x100000000LL kept only its low word. Take the - * two-word reader a scalar global initializer uses whenever the value - * has a wide or unsigned literal in it. - */ - val = read_wide_global_literal_expression(parent, *bb, scope); - } else 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) || lex_peek(T_wchar, NULL)) { - char chtok[MAX_TOKEN_LEN], unescaped[MAX_TOKEN_LEN]; - token_kind_t kind = lex_peek(T_wchar, NULL) ? T_wchar : T_char; - lex_ident(kind, chtok); - unescape_string(chtok, unescaped, MAX_TOKEN_LEN); - - val = require_typed_var(parent, TY_int); - val->var_name = gen_name(); - val->init_val = kind == T_wchar ? parse_wide_character_constant(chtok) - : parse_character_constant(chtok); - add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); } 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 @@ -364,21 +518,35 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) return val; } +/* Skip one static initializer value without lowering it: every token up to the + * ',' or '}' that ends it, outside the parentheses and brackets it contains. + * The value is read for real, and diagnosed, when its code is emitted. + */ 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_peek(T_wchar, NULL)) { + int depth = 0; + bool consumed = false; + + while (cur_token->next) { + token_kind_t kind = cur_token->next->kind; + + if (kind == T_open_curly || + (!depth && + (kind == T_comma || kind == T_close_curly || kind == T_semicolon))) + break; + if (kind == T_open_bracket || kind == T_open_square) { + depth++; + } else if (kind == T_close_bracket || kind == T_close_square) { + if (!depth) + break; + depth--; + } lex_next(); - } else { + consumed = true; + } + if (!consumed) error_at("Global array initialization requires constant values", next_token_loc()); - } } bool is_record_type(const type_t *type) @@ -406,6 +574,13 @@ bool parse_unbraced_record_init(block_t *parent, var_t *addr, bool emit_code); 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, + bool emit_code); /* Store zero into every byte of elements [from, to) of the array at @base. */ void emit_zero_elements(block_t *parent, @@ -476,6 +651,16 @@ bool parse_array_field_row_values(block_t *parent, 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, emit_code); + 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; @@ -729,8 +914,11 @@ void parse_string_field_init(block_t *parent, 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 > field->array_size) + if (len - 1 > field->array_size) error_at("String initializer is too long for character array", cur_token_loc()); if (!emit_code) @@ -787,12 +975,9 @@ void parse_wstring_field_init(block_t *parent, { int values[MAX_STRING_LEN]; int length; - int units; length = read_wstring_units(values, MAX_STRING_LEN); - - units = length + 1; - if (units > field->array_size) + if (length > field->array_size) error_at("Wide string initializer is too long for array", cur_token_loc()); if (!emit_code) @@ -812,6 +997,74 @@ void parse_wstring_field_init(block_t *parent, } } +/* 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, + bool emit_code) +{ + var_t row; + var_t *row_addr = NULL; + int width = string_row_width(array); + + 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; + if (emit_code) + 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, emit_code); + else + parse_string_field_init(parent, bb, &row, row_addr, emit_code); +} + void parse_array_field_init(block_t *parent, basic_block_t **bb, const var_t *field, @@ -884,6 +1137,10 @@ void parse_array_field_init(block_t *parent, parse_array_field_row_init(parent, bb, &slice, target_addr, count, emit_code); count += fixed_array_inner_count(&slice); + } else if (string_row_starts_here(&slice)) { + parse_string_row_init(parent, bb, &slice, target_addr, count, + emit_code); + count += string_row_width(&slice); } else { if (lex_peek(T_open_curly, NULL) && is_record_type(field->type)) { type_t *record_type = field->type; @@ -1243,6 +1500,7 @@ bool parse_struct_field_values(block_t *parent, (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 = @@ -1265,7 +1523,7 @@ bool parse_struct_field_values(block_t *parent, } if (!first_value && field && field->array_size && - !field->ptr_level && + !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)))) @@ -1362,10 +1620,13 @@ bool parse_struct_field_values(block_t *parent, is_record_object(field_val_raw)) { emit_record_copy_to_address(parent, bb, field_addr, field_val_raw); - } else if (parent == GLOBAL_BLOCK && field_val_raw->is_func) { + } 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. + * 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); @@ -1610,6 +1871,7 @@ basic_block_t *handle_if_statement(block_t *parent, basic_block_t *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); @@ -1663,6 +1925,7 @@ basic_block_t *handle_while_statement(block_t *parent, basic_block_t *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); @@ -1734,6 +1997,27 @@ basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) 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; +} + void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb, @@ -1765,6 +2049,23 @@ void parse_array_init(var_t *var, if (var->ptr_level > 0 || var->is_func) elem_size = PTR_SIZE; + /* 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; + } + if (emit_code) base_addr = var; @@ -1908,6 +2209,18 @@ void parse_array_init(var_t *var, if (!lex_accept(T_comma)) break; continue; + } else if (string_row_starts_here(initializer_var)) { + if (is_implicit && emit_code && parent != GLOBAL_BLOCK) + emit_zero_elements(parent, bb, base_addr, inferred_size, + count, elem_size); + parse_string_row_init(parent, bb, initializer_var, base_addr, + count, emit_code); + 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 (lex_peek(T_open_curly, NULL) && is_record_type(var->type)) { type_t *struct_type = var->type; @@ -1964,7 +2277,13 @@ void parse_array_init(var_t *var, if (parent == GLOBAL_BLOCK && (lex_peek(T_ampersand, NULL) || grouped_global_function_designator_starts_here(true) || - global_function_address_dereference_starts_here())) { + 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))) { + /* 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]; @@ -1979,10 +2298,16 @@ void parse_array_init(var_t *var, 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)) { + !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 @@ -2057,7 +2382,15 @@ void parse_array_init(var_t *var, stride *= object_constant->array_dim3; if (object_constant->array_dim4) stride *= object_constant->array_dim4; - if (lex_accept(T_plus)) { + 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, @@ -2203,6 +2536,17 @@ void parse_array_compound_literal(var_t *var, 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. */ diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 654e3923..66e99c26 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -94,126 +94,309 @@ int sizeof_array_object(const var_t *array) /* What a type-only walk over a sizeof operand has seen. */ typedef struct { - int members; /* record member selections */ - int subscripts; /* array subscripts */ - bool element_leaf; /* the last subscript selected a non-array element */ - bool addressed; /* the operand so far is "&array" */ + 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 (object->pointee_array_size > 0 && + depth == object->pointee_array_element_ptr_level + 1) { + /* A pointer to an array designates the complete array, so the + * dereference restores its bounds rather than decaying them. + */ + 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) + sizeof_walk_t *walk, + bool allow_cast) { token_t *token = *cursor; + token_t *op = token; if (!token) return false; if (token->kind == T_asterisk) { - bool addressed; - int depth; - token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) - return false; - addressed = walk->addressed; - depth = effective_pointer_depth(object); - if (depth <= 0) + if (!scan_sizeof_postfix_operand(scope, &token, object, walk, true) || + !sizeof_walk_indirect( + object, walk, "Cannot dereference non-pointer in sizeof", op)) return false; - if (object->pointee_array_size > 0 && - depth == object->pointee_array_element_ptr_level + 1) { - /* A pointer to an array designates the complete array, so the - * dereference restores its bounds rather than decaying them. - */ - 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; - } - walk->addressed = false; - walk->element_leaf = false; + walk->operators++; } else if (token->kind == T_ampersand) { token = token->next; - if (!scan_sizeof_postfix_operand(scope, &token, object, walk)) + if (!scan_sizeof_postfix_operand(scope, &token, object, walk, true)) return 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++; - walk->element_leaf = 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) || + 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; - if (!root) + /* 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)); + } 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; - memcpy(object, root, sizeof(*object)); token = token->next; } else return false; while (token && (token->kind == T_dot || token->kind == T_arrow || token->kind == T_open_square)) { - walk->addressed = false; + op = token; + walk->operators++; + if (walk->designator) + return false; if (token->kind == T_dot || token->kind == T_arrow) { - bool through_pointer = token->kind == T_arrow; - type_t *record; + bool rvalue = walk->rvalue; var_t *field; token = token->next; - if (!token || token->kind != T_identifier || - (through_pointer && effective_pointer_depth(object) != 1) || - (!through_pointer && effective_pointer_depth(object) != 0)) + if (!token || token->kind != T_identifier) return false; - record = through_pointer - ? pointee_type_from_pointer_typedef(object->type) - : object->type; - if (!is_record_type(record)) - return false; - field = find_member(token->literal, record); + 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) - return false; + error_at("Unknown struct or union member", &token->location); memcpy(object, field, sizeof(*object)); walk->members++; - walk->element_leaf = false; + walk->addressed = false; + walk->rvalue = rvalue; token = token->next; } else { int depth = 0; - fixed_array_shape_t shape; - /* An array may legitimately have pointer elements. Reject a scalar - * pointer here, but retain the element pointer depth until after - * this array subscript so a following `->` can consume it. + /* The subscript itself is read, unevaluated, by the consuming pass; + * only its extent matters here. */ - if (object->array_size <= 0) - return false; for (; token; token = token->next) { if (token->kind == T_open_square) depth++; @@ -224,12 +407,13 @@ static bool scan_sizeof_postfix_operand(block_t *scope, } if (depth) return false; - shape = fixed_array_shape_from_var(object); - if (!fixed_array_shape_drop_outer(&shape)) + 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; - fixed_array_shape_to_var(object, &shape); - walk->subscripts++; - walk->element_leaf = !object->array_size; } } *cursor = token; @@ -250,7 +434,15 @@ static void consume_sizeof_postfix_operand(block_t *parent, 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_accept(T_open_bracket)) { + 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 @@ -277,15 +469,20 @@ static void consume_sizeof_postfix_operand(block_t *parent, } /* The operand must end where sizeof's operand ends: at the closing parenthesis - * of a parenthesized one, and otherwise before anything that would keep it - * going as a call or an update. + * 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) { - return tail && (parenthesized ? tail->kind == T_close_bracket - : tail->kind != T_open_bracket && - tail->kind != T_increment && - tail->kind != T_decrement); + 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; } /* A non-mutating admission check for callers that need to hand a global sizeof @@ -301,38 +498,41 @@ static bool can_scan_sizeof_postfix_extent(block_t *scope, var_t object; sizeof_walk_t walk = {0}; - return scan_sizeof_postfix_operand(scope, &tail, &object, &walk) && + return scan_sizeof_postfix_operand(scope, &tail, &object, &walk, + parenthesized) && walk.members && sizeof_operand_tail_ends(tail, parenthesized) && - !effective_pointer_depth(&object) && + !walk.designator && !effective_pointer_depth(&object) && (object.array_size > 0 || (allow_flexible && object.is_flexible_array_member)); } /* Walk a local sizeof operand from @start and say whether this walker owns it: - * an operand that still designates an array, whatever the path to it, or a - * single element selected from one. Anything else keeps the ordinary - * unevaluated-expression path. + * 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) + var_t *object, + sizeof_walk_t *walk) { token_t *tail = start; - sizeof_walk_t walk = {0}; - if (!scan_sizeof_postfix_operand(parent, &tail, object, &walk) || + if (!scan_sizeof_postfix_operand(parent, &tail, object, walk, + parenthesized) || !sizeof_operand_tail_ends(tail, parenthesized)) return false; - if (object->is_flexible_array_member) - return walk.members > 0; - return object->array_size > 0 || walk.element_leaf; + 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, - * 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. + * 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, @@ -340,34 +540,50 @@ static bool read_sizeof_postfix_operand(block_t *parent, { 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)) { + &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)) + !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) + if (object.array_size > 0) { size = sizeof_array_object(&object); - else if (object.ptr_level || object.type->ptr_level || object.is_func) + } else if (object.ptr_level || object.type->ptr_level || object.is_func) { size = PTR_SIZE; - else + } 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; } @@ -463,7 +679,9 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) basic_block_t *unevaluated_bb = bb_create(parent); int saved_side_effects = se_idx; + unevaluated_expression_depth++; read_expr_operand(parent, &unevaluated_bb); + unevaluated_expression_depth--; se_idx = saved_side_effects; var_t *expr_var = opstack_pop(); if (is_bitfield(expr_var)) @@ -589,9 +807,7 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) type = has_long_type ? TY_long_double : TY_double; } else if (has_enum_type) { lex_ident(T_identifier, token); - type = find_enum_tag(token, parent); - if (!type) - error_at("Unknown enum type", cur_token_loc()); + type = reference_enum_tag(token, parent); } else if (has_long_type) { type = has_unsigned_type ? TY_ulong : TY_long; if (long_type_count > 1) { diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 7cee94a2..dce21870 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -227,13 +227,18 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) /* 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. */ - body = bb_create(parent); + 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(parent, body); - perform_side_effect(parent, body); + body = read_body_statement(blk, body); + perform_side_effect(blk, body); } /* Complete the deferred no-match dispatch after every case comparison is @@ -269,6 +274,70 @@ static void reject_ordinary_typedef_collision(block_t *block, var_t *var) 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) +{ + func_t *func = block->func; + + for (constant_t *constant = block->constants; constant; + constant = constant->next) { + if (!strcmp(constant->alias, name)) + error_at("identifier redeclared as a different kind of symbol", + cur_token_loc()); + } + if (block->parent || !func) + return; + for (int i = 0; i < func->num_params; i++) { + if (!strcmp(func->param_defs[i].var_name, name)) + 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; + + if (!name[0]) + return; + for (int i = 0; i < block->locals.size; i++) { + var_t *prior = block->locals.elements[i]; + bool prior_is_function; + + if (prior == var || prior->var_name[0] != name[0] || + strcmp(prior->var_name, name)) + 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. */ @@ -312,6 +381,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) 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", @@ -439,6 +509,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) 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); @@ -514,6 +585,7 @@ basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) 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); @@ -542,7 +614,6 @@ basic_block_t *handle_record_statement(block_t *parent, /* The caller has seen struct or union ahead. */ base_type_t kind = accept_record_keyword(); - bool is_union = kind == TYPE_union; bool has_tag = lex_peek(T_identifier, token); if (has_tag) @@ -551,109 +622,9 @@ basic_block_t *handle_record_statement(block_t *parent, error_at("Expected struct or union tag or definition", next_token_loc()); if (lex_peek(T_open_curly, NULL)) { - int i = 0; - int size = 0; - int alignment = 1; - int max_size = 0; - bitfield_layout_t bits = {0}; - bool has_flexible_array_member = false; - - /* An untagged definition names a type no other declaration can reach, - * so it needs no tag table entry. - */ - if (has_tag) - type = local_record_tag(token, parent, kind); - 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 block. */ - v->scope = parent; - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, parent); - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, is_union); - if (is_union) { - v->offset = 0; - int field_size = is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - } else { - size = is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), v, - &alignment); - } - - while (lex_accept(T_comma)) { - if (!is_union && last->is_flexible_array_member) - error_at( - "Flexible array member must be the final struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, parent); - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, is_union); - last = nv; - if (is_union) { - nv->offset = 0; - int field_size = - is_bitfield(nv) - ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(nv) > alignment) - alignment = alignment_var(nv); - } else { - size = - is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); - } - } - - 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->has_flexible_array_member = has_flexible_array_member; - - if (lex_peek(T_semicolon, NULL)) { - lex_expect(T_semicolon); + 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 @@ -722,13 +693,14 @@ int read_enum_constant(block_t *scope) return value; } -/* A block-scope enum definition contributes integer constants to the current - * expression parser just as a file-scope definition does. Like a record - * definition, it may introduce declarators after the closing brace. +/* The block-scope enum specifier that starts at the next token: a reference to + * a visible tag, or a tagged or untagged definition. A definition contributes + * integer constants to the current expression parser just as a file-scope + * definition does. @is_definition reports whether a body was read. + * + * Returns the enum type. */ -basic_block_t *handle_enum_statement(block_t *parent, - basic_block_t *bb, - const block_decl_specifiers_t *spec) +static type_t *read_block_enum_specifier(block_t *parent, bool *is_definition) { char token[MAX_ID_LEN]; int val = 0; @@ -741,22 +713,23 @@ basic_block_t *handle_enum_statement(block_t *parent, type = local_enum_tag(token, parent); has_tag = true; } - if (!lex_peek(T_open_curly, NULL)) { + *is_definition = lex_peek(T_open_curly, NULL); + if (!*is_definition) { if (!has_tag) error_at("Unknown enum type", next_token_loc()); - if (!type) - type = find_enum_tag(token, parent); - if (!type) - error_at("Unknown enum type", next_token_loc()); - return read_block_declarators(parent, bb, type, spec, true); + return type ? type : reference_enum_tag(token, parent); } - if (!type) - type = add_type(); + + /* Only a definition creates an enum tag, so one already declared in this + * block 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); - if (!find_local_type_tag(token, parent)) - add_type_tag(parent, token, type); + add_type_tag(parent, token, type); } lex_expect(T_open_curly); bool first = true; @@ -767,13 +740,38 @@ basic_block_t *handle_enum_statement(block_t *parent, if (lex_accept(T_assign)) { val = read_enum_constant(parent); } + + /* An enumeration constant has no linkage, so no other ordinary + * identifier of this block may share its name. + */ + for (int i = 0; i < parent->locals.size; i++) { + if (!strcmp(parent->locals.elements[i]->var_name, token)) + 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); first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); + return type; +} - if (lex_accept(T_semicolon)) +/* 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_block_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); } @@ -790,6 +788,7 @@ 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; @@ -819,6 +818,7 @@ 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; @@ -851,7 +851,7 @@ static basic_block_t *read_block_declarators( * list, like the leading ones in @spec. */ if (has_base_type) { - memcpy(&qualified, spec, sizeof(qualified)); + qualified = *spec; read_type_qualifiers(&qualified.is_const, &qualified.is_volatile, false); spec = &qualified; @@ -976,6 +976,7 @@ static basic_block_t *read_block_declarators( 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); @@ -1107,6 +1108,7 @@ static basic_block_t *read_block_declarators( if (is_incomplete_record_object(nv)) error_at("Incomplete struct/union type cannot define an object", cur_token_loc()); + reject_block_redeclaration(parent, nv, false, false); add_insn(spec->is_static ? GLOBAL_BLOCK : parent, spec->is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, NULL, 0, NULL); @@ -1349,6 +1351,40 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) 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; @@ -1358,11 +1394,45 @@ 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; - read_full_var_decl(&decl, false, false, false); + + /* 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_block_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; @@ -1518,12 +1588,45 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, error_at("typedef declaration cannot have an initializer", next_token_loc()); memcpy(alias, base, sizeof(*alias)); - alias->base_type = TYPE_typedef; - alias->base_struct = is_record_type(base) ? base : base->base_struct; + + /* 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 @@ -1639,6 +1742,16 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, 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);`. */ @@ -1706,6 +1819,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *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); @@ -1713,7 +1827,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (lex_accept(T_semicolon)) return bb; - if (grouped_scalar_pointee_row_store_starts()) + 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 diff --git a/src/parser.c b/src/parser.c index a2284f18..d90f75ee 100644 --- a/src/parser.c +++ b/src/parser.c @@ -41,6 +41,15 @@ int operand_stack_idx = 0; */ int unevaluated_expression_depth = 0; +/* Set before reading an operand that a following `+` must not extend. + * read_lvalue() normally takes `+ n` after a pointer as part of the operand. + * That is wrong for the operand of a unary operator, which binds tighter: a + * cast in `(char *) p + 1` applies to p alone, and the sum advances by one byte + * rather than by an int. It is equally wrong for the right operand of a binary + * operator binding at least as tightly as `+`, as in `end - start + 1`. + */ +bool reading_unary_operand = false; + /* 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. */ @@ -267,6 +276,8 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb, bool emit_code); +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); @@ -279,10 +290,38 @@ 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_member_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); +var_t *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 @@ -1014,6 +1053,15 @@ bool is_bool_type(const type_t *type) return type && type->is_bool; } +/* 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 is_bool_type(type) && !ptr_level && !type->ptr_level && + !type->array_size; +} + /* 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. @@ -1383,12 +1431,12 @@ var_t *normalize_bool(block_t *block, basic_block_t **bb, var_t *var) var_t *zero; var_t *rd; - if (var->type == TY_bool && !var->ptr_level) + 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 != 0; + 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; @@ -1406,6 +1454,23 @@ var_t *normalize_bool(block_t *block, basic_block_t **bb, var_t *var) return rd; } +/* 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. + */ +var_t *convert_stored_value(block_t *block, + basic_block_t **bb, + var_t *value, + type_t *type, + int ptr_level) +{ + if (!is_bool_scalar(type, ptr_level)) + return value; + return normalize_bool(block, bb, value); +} + 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, @@ -1415,7 +1480,7 @@ var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) if (is_from_ptr && is_to_ptr) return from; - if (!is_to_ptr && to->type == TY_bool) + if (!is_to_ptr && is_bool_scalar(to->type, 0)) return normalize_bool(block, bb, from); int from_size = get_size(from), to_size = get_size(to); @@ -1447,9 +1512,17 @@ var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) * 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) + 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); return from; diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 7cb084f7..649ffe59 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -2497,7 +2497,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 && diff --git a/src/ssa.c b/src/ssa.c index 196f851f..10508043 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -3469,6 +3469,22 @@ void prune_unused_funcs(void) 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; @@ -3760,10 +3776,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: diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 3b0bc408..df4980b5 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -1679,6 +1679,15 @@ void emit_arith(ph2_ir_t *ph2_ir, 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: diff --git a/tests/driver.sh b/tests/driver.sh index 6ec1fed8..983bdecb 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1219,6 +1219,98 @@ int main(void) { } EOF +# 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 + try_ 0 << EOF enum { wide_count = sizeof L"ab" / sizeof(wchar_t) }; wchar_t *global_pointer = L"xy"; @@ -1392,6 +1484,84 @@ int main(void) { } 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 +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_compile_error << EOF +char s[1] = "ab"; +int main(void) { return 0; } +EOF +try_compile_error << EOF +int main(void) { char s[1] = {"ab"}; return s[0]; } +EOF +try_compile_error << EOF +int main(void) { static char s[1] = "ab"; return s[0]; } +EOF +try_compile_error << EOF +struct R { char n[1]; } r = {"ab"}; +int main(void) { return 0; } +EOF +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 + +# 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) { + 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 + # C99 _Bool conversions store the truth value, not a truncated source byte. try_ 4 << EOF _Bool echo_bool(_Bool value) { return value; } @@ -1428,6 +1598,79 @@ int main(void) { } 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) +{ + 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 + +# 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) +{ + 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 + # 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. @@ -1626,6 +1869,54 @@ struct S g = {3}; int main(void) { typedef struct S ST; extern ST g; return g.a; } EOF +# 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 + +try_compile_error << EOF +int main(void) { int x static; return 0; } +EOF + +try_compile_error_message "duplicate static storage class specifier" << EOF +int main(void) { int static static x; return x; } +EOF + +try_compile_error_message "incompatible storage class specifiers" << EOF +int main(void) { unsigned static extern x; return 0; } +EOF + +try_compile_error_message "typedef cannot be combined" << EOF +extern int typedef T; +int main(void) { return 0; } +EOF + +try_compile_error << EOF +int f(int static a) { return a; } +int main(void) { return f(1); } +EOF + # 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. @@ -1708,6 +1999,23 @@ EOF # 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 + # 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 @@ -1983,6 +2291,53 @@ int main(void) { (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 try_ 1 << EOF int main(void) { return (unsigned long long) 1 == 1ULL; } EOF @@ -2153,6 +2508,25 @@ 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) { @@ -3874,6 +4248,22 @@ declare -a bitwise_tests=( ) 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); @@ -3921,6 +4311,97 @@ int main(void) { 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) { @@ -3963,6 +4444,141 @@ int main(void) { 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 @@ -4017,6 +4633,129 @@ int main(void) { 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) { @@ -4184,6 +4923,72 @@ try_compile_error_message "tag was previously declared as a different kind of re 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 +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 @@ -4271,6 +5076,82 @@ int main(void) { 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 +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 } }; @@ -6882,6 +7763,104 @@ int main(void) { 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 } }; @@ -7815,6 +8794,92 @@ int main(void) { 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; @@ -7951,6 +9016,185 @@ int main(void) { } 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 @@ -8248,6 +9492,68 @@ int main(void) { } 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 + # 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 @@ -8378,6 +9684,46 @@ int main(void) { 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 +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}; @@ -9115,6 +10461,60 @@ int main(void) 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; } @@ -9604,6 +11004,91 @@ int main(void) { } 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; }; @@ -9745,6 +11230,51 @@ 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. @@ -9824,6 +11354,25 @@ 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);" +# 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. @@ -10315,6 +11864,24 @@ int main() { } 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 @@ -11351,6 +12918,28 @@ int main() { } 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. @@ -11595,6 +13184,200 @@ 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 + +# 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 +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 @@ -12242,11 +14025,111 @@ static int global_dereferenced_size = 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; } +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 + +# 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 + +# 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]; @@ -12482,6 +14365,123 @@ int main(void) { } 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 @@ -14521,6 +16521,158 @@ int main(void) { } 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 @@ -14677,6 +16829,94 @@ items 12 "struct inner { int values[2][3]; }; struct holder { struct inner rows[ 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; @@ -15224,6 +17464,106 @@ 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; } @@ -20546,6 +22886,167 @@ int main(void) { return indirect.value + take_number(make_number(23)); } EOF + +# 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 + +# 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 +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 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 +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); @@ -20620,6 +23121,57 @@ row *invalid_pointers(void) { int main(void) { return 0; } EOF +# 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) +{ + 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 +try_compile_error << 'EOF' +int main(void) +{ + int local[2]; + static int *slots[] = {local}; + return 0; +} +EOF +try_compile_error << 'EOF' +int main(void) +{ + int local[2]; + static struct { int *p; } s = {local}; + return 0; +} +EOF + # 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 @@ -20837,6 +23389,59 @@ int main(void) { int (*callback)(int) = plus1; return legacy(callback); } EOF begin_category "Bit-fields" + +# 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 try_ 0 << EOF typedef _Bool bool_alias; struct flags { bool_alias value : 1; }; From ab1bf961511c538e913620724a374de537f9f544 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 03:14:00 +0800 Subject: [PATCH 25/40] Consolidate parser lookups and bound their costs The review fixes left parallel mechanisms and some costs behind. Name and tag lookups each had their own scope walk, a scope scan for enum constants ran for every identifier, scalar specifiers were read by six ladders and rewritten by a token pass, enum and record typedef bodies had two readers each, and initializers repeated zero fill, designator and record alias code. A member read through a dereference copied the whole record, so a large record took minutes; nested unary operators could exhaust the stack; a pointer plus a product was grouped as a sum times the factor; a function pointer returning _Bool was converted to _Bool; a cast with a qualifier among its specifiers looped forever; and type table overflow aborted. Share one ordinary and one tag lookup, one scalar specifier reader for declarations, casts, sizeof and va_arg, one enum reader and one record body reader. Read records through pointers in place, bound operand nesting, scale pointer arithmetic in the binary reducer, and report table overflow. --- src/defs.h | 13 + src/globals.c | 324 ++++++---- src/parser-call.c | 229 +++---- src/parser-const.c | 546 +---------------- src/parser-decl.c | 455 ++++++-------- src/parser-expr.c | 1402 ++++++++++++++++++------------------------- src/parser-global.c | 606 +++---------------- src/parser-init.c | 880 ++++++++++----------------- src/parser-sizeof.c | 158 +---- src/parser-stmt.c | 75 ++- src/parser.c | 54 +- tests/driver.sh | 616 ++++++++++++++++++- 12 files changed, 2185 insertions(+), 3173 deletions(-) diff --git a/src/defs.h b/src/defs.h index 3a73cd4f..d3a16398 100644 --- a/src/defs.h +++ b/src/defs.h @@ -788,6 +788,12 @@ struct var { 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. */ @@ -857,6 +863,7 @@ 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); @@ -1036,6 +1043,12 @@ struct type { 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. diff --git a/src/globals.c b/src/globals.c index 4a82e956..d7ecfe0e 100644 --- a/src/globals.c +++ b/src/globals.c @@ -17,6 +17,7 @@ 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); @@ -639,11 +640,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; } @@ -1194,11 +1192,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; @@ -1263,47 +1261,136 @@ void add_scoped_constant(block_t *block, char alias[], int value) block->constants = constant; } -/* 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). +/* 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. */ -constant_t *find_scoped_constant(char alias[], block_t *block) +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) { - func_t *func = block ? block->func : NULL; - char head = alias[0]; + 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; - for (; block && block != GLOBAL_BLOCK; block = block->parent) { + if (!binding) + binding = &unused; + if (kinds & ORDINARY_CONSTANT) { for (constant_t *constant = block->constants; constant; constant = constant->next) { - if (!strcmp(constant->alias, alias)) - return constant; + if (!strcmp(constant->alias, name)) { + *binding = constant; + return ORDINARY_CONSTANT; + } } + } + if (kinds & ORDINARY_VARIABLE) { + int pos = 0; - /* A block's locals include its IR temporaries; settle the first byte - * before the library call, as find_visible_type() does. - */ - for (int i = 0; i < block->locals.size; i++) { - var_t *var = block->locals.elements[i]; - - if (var && var->var_name && var->var_name[0] == head && - !strcmp(var->var_name, alias)) - return NULL; + *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; + } } - for (typedef_binding_t *binding = block->typedefs; binding; - binding = binding->next) - if (binding->name[0] == head && !strcmp(binding->name, alias)) - return NULL; } - if (func) { + if ((kinds & ORDINARY_PARAMETER) && !block->parent && block->func) { + func_t *func = block->func; + for (int i = 0; i < func->num_params; i++) { - const char *name = func->param_defs[i].var_name; + var_t *param = &func->param_defs[i]; - if (name && name[0] == head && !strcmp(name, alias)) - return NULL; + if (param->var_name[0] == head && !strcmp(param->var_name, name)) { + *binding = param; + return ORDINARY_PARAMETER; + } } } - return find_constant(alias); + 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) @@ -1326,18 +1413,42 @@ type_t *find_local_type_tag(char name[], block_t *block) 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 the - * other keyword is an error rather than a miss. + * 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_record_tag(type_t *type, base_type_t 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 record", + 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) { @@ -1349,20 +1460,11 @@ static type_t *declare_record_tag(char name[], block_t *block, base_type_t kind) } /* The struct or union tag @name as seen from @block, spelled with the keyword - * for @kind, or NULL when no such tag 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. + * for @kind, or NULL when no such tag is visible. */ type_t *find_record_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_record_tag(type, kind); + return find_visible_tag(name, block, kind); } /* A struct or union specifier with no member list: the visible tag, or else a @@ -1384,34 +1486,28 @@ 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) { - type_t *type = check_record_tag(find_local_type_tag(name, block), kind); + type_t *type = check_tag_kind(find_local_type_tag(name, block), kind); return type ? type : declare_record_tag(name, block, kind); } -/* An enum tag shares the struct and union name space, so an existing record tag - * of the same name is the wrong kind of tag rather than a miss. Looking in the - * tag table, not the type table, also keeps a typedef name out of it. +/* 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. */ -static type_t *check_enum_tag(type_t *type) +void begin_record_definition(type_t *tag) { - if (type && - (type->base_type == TYPE_struct || type->base_type == TYPE_union)) - error_at("tag was previously declared as a different kind of tag", - cur_token_loc()); - return type; + 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) { - 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_enum_tag(type); + return find_visible_tag(name, block, ENUM_TAG_KIND); } /* The enum tag @name named by a specifier seen from @block. Unlike a record @@ -1432,44 +1528,21 @@ type_t *reference_enum_tag(char name[], block_t *block) /* The enum tag @name that @block itself declares, or NULL. */ type_t *local_enum_tag(char name[], block_t *block) { - return check_enum_tag(find_local_type_tag(name, block)); + return check_tag_kind(find_local_type_tag(name, block), ENUM_TAG_KIND); } bool find_block_typedef(block_t *block, const char *name) { - for (typedef_binding_t *binding = block->typedefs; binding; - binding = binding->next) - if (!strcmp(binding->name, name)) - return true; - return false; -} - -static bool block_has_ordinary_name(block_t *block, const char *name) -{ - for (int i = 0; i < block->locals.size; i++) { - var_t *var = block->locals.elements[i]; - - if (var && var->var_name && !strcmp(var->var_name, name)) - return true; - } - - /* The function body's outermost block shares the parameter namespace. A - * nested block may, however, legally shadow a parameter with a typedef. - */ - if (!block->parent && block->func) { - for (int i = 0; i < block->func->num_params; i++) { - var_t *param = &block->func->param_defs[i]; - - if (param->var_name && !strcmp(param->var_name, name)) - return true; - } - } - return false; + 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_typedef(block, name) || block_has_ordinary_name(block, name)) + if (find_block_ordinary(block, name, ORDINARY_ANY, NULL)) error_at("typedef name conflicts with an ordinary identifier", cur_token_loc()); @@ -1488,37 +1561,18 @@ void add_block_typedef(block_t *block, char name[], type_t *type) */ type_t *find_visible_type(const char *name, block_t *block) { - func_t *func = block ? block->func : NULL; - char head = name[0]; - - /* Every declaration spelling passes through here, and a block's locals - * include all of its IR temporaries, so settle the first byte before the - * library call, as find_local_var() does. + /* 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) { - for (int i = block->locals.size - 1; i >= 0; i--) { - var_t *var = block->locals.elements[i]; - if (var && var->var_name && var->var_name[0] == head && - !strcmp(var->var_name, name)) - return NULL; - } - for (typedef_binding_t *binding = block->typedefs; binding; - binding = binding->next) - if (binding->name[0] == head && !strcmp(binding->name, name)) - return binding->type; - } - - /* Parameters remain visible throughout the function body unless an inner - * lexical binding above has already hidden them. - */ - if (func) { - for (int i = 0; i < func->num_params; i++) { - var_t *param = &func->param_defs[i]; + void *binding; + ordinary_kind_t kind = + find_block_ordinary(block, name, ORDINARY_ANY, &binding); - if (param->var_name && param->var_name[0] == head && - !strcmp(param->var_name, name)) - return NULL; - } + if (kind == ORDINARY_TYPEDEF) + return ((typedef_binding_t *) binding)->type; + if (kind != ORDINARY_NONE) + return NULL; } return find_type(name, 1); } @@ -1778,7 +1832,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; @@ -2397,6 +2451,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/parser-call.c b/src/parser-call.c index 16b7c35c..11b44c4b 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -440,16 +440,8 @@ typedef struct va_arg_type { void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) { - char name[MAX_ID_LEN]; type_t *type; - bool is_signed = false; - bool is_unsigned = false; - int long_count = 0; - bool is_short = false; - bool is_char = false; - bool saw_base = false; - - (void) parent; + result->ptr_level = 0; result->is_array = false; result->is_function = false; @@ -460,80 +452,7 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) result->pointee_array_dim4 = 0; result->pointee_element_size = 0; result->pointee_element_ptr_level = 0; - while (true) { - if (lex_accept(T_const) || lex_accept(T_volatile)) - continue; - if (lex_accept(T_signed)) { - if (is_signed) - error_at("duplicate signed type specifier", cur_token_loc()); - is_signed = true; - continue; - } - if (lex_accept(T_unsigned)) { - if (is_unsigned) - error_at("duplicate unsigned type specifier", cur_token_loc()); - is_unsigned = true; - continue; - } - if (lex_accept(T_long)) { - long_count++; - continue; - } - if (lex_peek(T_identifier, name) && !strcmp(name, "short")) { - if (is_short) - error_at("duplicate short type specifier", cur_token_loc()); - lex_expect(T_identifier); - is_short = true; - continue; - } - if (lex_peek(T_identifier, name) && - (!strcmp(name, "int") || !strcmp(name, "char"))) { - if (saw_base) - error_at("duplicate type specifier", cur_token_loc()); - is_char = !strcmp(name, "char"); - saw_base = true; - lex_expect(T_identifier); - continue; - } - break; - } - if (is_signed && is_unsigned) - error_at("both signed and unsigned specified", cur_token_loc()); - if (long_count > 2 || (is_short && long_count)) - error_at("invalid va_arg integer type", cur_token_loc()); - - base_type_t record_kind = accept_record_keyword(); - if (record_kind) { - if (is_signed || is_unsigned || long_count || is_short) - error_at("record type cannot have integer specifiers", - cur_token_loc()); - lex_ident(T_identifier, name); - type = find_record_tag(name, parent, record_kind); - } else if (lex_accept(T_enum)) { - if (is_signed || is_unsigned || long_count || is_short || saw_base) - error_at("enum type cannot have integer specifiers", - cur_token_loc()); - lex_ident(T_identifier, name); - type = reference_enum_tag(name, parent); - } else { - if (!saw_base && !is_signed && !is_unsigned && !long_count && - !is_short) { - lex_ident(T_identifier, name); - type = find_type(name, true); - goto parsed_base_type; - } - - if (long_count) - type = is_unsigned ? (long_count == 2 ? TY_ulong_long : TY_ulong) - : (long_count == 2 ? TY_long_long : TY_long); - else if (is_short) - type = is_unsigned ? TY_ushort : TY_short; - else if (is_char) - type = is_unsigned ? TY_uchar : (is_signed ? TY_schar : TY_char); - else - type = is_unsigned ? TY_uint : TY_int; - } -parsed_base_type: + 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)) @@ -702,6 +621,12 @@ void read_builtin_va_arg_type(block_t *parent, va_arg_type_t *result) } 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; } @@ -748,7 +673,10 @@ void mark_value_reference(var_t *value, var_t *address, var_t *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, so it is copied into @value's own storage instead. + * 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, @@ -757,10 +685,9 @@ void push_object_at(block_t *parent, int size) { if (is_record_object(value) && !value->func_signature && - !is_incomplete_record_object(value)) { - add_insn(parent, *bb, OP_allocat, value, NULL, NULL, 0, NULL); - emit_record_copy_from_address(parent, bb, value, address); - } else + !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); @@ -1347,8 +1274,7 @@ void read_lvalue(lvalue_t *lvalue, block_t *parent, basic_block_t **bb, bool eval, - opcode_t op, - bool allow_ptr_arith); + opcode_t op); static bool is_function_parameter(const var_t *var, block_t *parent); @@ -1395,7 +1321,6 @@ static void take_expression_address(block_t *parent, basic_block_t **bb) /* &*E is E, except that it is not an lvalue; neither operator is * evaluated (C99 6.5.3.2p3). */ - reading_unary_operand = true; read_expr_operand(parent, bb); operand = opstack_pop(); if (operand->is_func) { @@ -1462,11 +1387,10 @@ 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_t *vd, *rs1, *var = NULL; if (!lex_peek(T_identifier, token) || - (find_var(token, parent) && - is_swapped_subscript_base(find_var(token, parent)))) { + ((var = find_var(token, parent)) && is_swapped_subscript_base(var))) { take_expression_address(parent, bb); return; } @@ -1505,10 +1429,9 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) opstack_push(vd); return; } - var_t *var = find_var(token, parent); 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, true); + 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()); @@ -1724,12 +1647,58 @@ static bool pointee_array_shapes_compatible(const var_t *left, 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 @@ -1789,12 +1758,13 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) int sz; if (lex_peek(T_open_bracket, NULL) || lex_peek(T_increment, NULL) || - lex_peek(T_decrement, NULL) || lex_peek(T_ampersand, 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. + * through the member, and `*get(1)` through the call result. */ read_expr_operand(parent, bb); push_dereference(parent, bb, opstack_pop()); @@ -1845,9 +1815,13 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) opstack_push(rs1); return; } - read_lvalue(&lvalue, var, parent, bb, true, OP_generic, false); + read_lvalue(&lvalue, var, parent, bb, true, OP_generic); rs1 = opstack_pop(); + if (decays_when_dereferenced(rs1)) { + push_dereference(parent, bb, rs1); + return; + } if (var->is_func) { /* C99 6.3.2.1 makes a function-pointer dereference a function * designator. The pointer object itself therefore needs one load, @@ -1919,7 +1893,8 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) deref_count++; /* Check if we have a parenthesized expression or simple identifier */ - if (lex_peek(T_open_bracket, NULL) || lex_peek(T_ampersand, NULL)) { + 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); @@ -1934,11 +1909,15 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) 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); + read_lvalue(&lvalue, var, parent, bb, true, OP_generic); /* Apply dereferences one by one */ for (int i = 0; i < deref_count; i++) { rs1 = opstack_pop(); + if (decays_when_dereferenced(rs1)) { + push_dereference(parent, bb, rs1); + continue; + } /* A member lvalue has already been read into rs1. Each unary * asterisk must consume that evaluated pointer, not re-derive its @@ -1980,19 +1959,19 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) } /* Lower a postfix member chain from the record at @address, of @record_type, - * and return the selected value on the operand stack. The chain starts with - * `->` when @arrow_first, @address then being the pointer operand, and with `.` + * 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 copied - * out whole, as a record element is. + * with dimensions unconsumed decays to a pointer, and a record member is pushed + * through push_object_at(), as a record element is. */ -var_t *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_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; @@ -2045,8 +2024,6 @@ var_t *lower_member_postfix(var_t *address, error_at(arrow_first ? "Cannot apply dot operator to pointer" : "Cannot apply arrow operator to record", next_token_loc()); - if (arrow_first) - is_lvalue = true; } else if (lex_accept(T_arrow)) { /* Only a selected pointer-to-record member can be followed. */ if (depth < shape.rank || field->ptr_level != 1 || @@ -2127,30 +2104,4 @@ var_t *lower_member_postfix(var_t *address, lex_peek(T_decrement, NULL))) error_at("member of a function call result is not assignable", next_token_loc()); - return result; -} - -/* A record returned by value is an rvalue, yet C99 6.5.2.3 still lets a postfix - * member chain select from it: `make().inner.value`, `make().items[1]` or - * `make().next->value`. Aggregate calls already write their result to - * caller-owned storage, so each member is lowered from that storage's address. - */ -void lower_call_result_member_postfix(var_t **value, - block_t *parent, - basic_block_t **bb) -{ - var_t *base; - var_t *address; - - if (!value || !(base = *value) || !lex_peek(T_dot, NULL) || - !is_record_type(base->type) || effective_pointer_depth(base) || - base->array_size) - return; - - opstack_pop(); - address = require_ref_var(parent, base->type, 0); - address->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, address, base, NULL, 0, NULL); - *value = - lower_member_postfix(address, base->type, false, false, parent, bb); } diff --git a/src/parser-const.c b/src/parser-const.c index 41c1d6a0..9d6cbca1 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -12,100 +12,6 @@ * definition that precedes it there and cannot be compiled on its own. */ -/* Return the extent left after selecting leading array dimensions. The global - * constant evaluator retains dimensions in var_t instead of constructing an - * expression IR, so every sizeof object path uses this one calculation. - */ -static int sizeof_subscripted_array(var_t *object, int subscript_depth) -{ - int res; - - if (!subscript_depth) - return size_var(object); - - res = object->type->size; - if (subscript_depth == 1 && object->array_dim2 > 0) { - res *= object->array_dim2; - if (object->array_dim3 > 0) - res *= object->array_dim3; - if (object->array_dim4 > 0) - res *= object->array_dim4; - } else if (subscript_depth == 2 && object->array_dim3 > 0) { - res *= object->array_dim3; - if (object->array_dim4 > 0) - res *= object->array_dim4; - } else if (subscript_depth == 3 && object->array_dim4 > 0) { - res *= object->array_dim4; - } - return res; -} - -static void validate_global_sizeof_object(var_t *object) -{ - if (object->is_func) - error_at("sizeof(function) is invalid", cur_token_loc()); - if (is_bitfield(object)) - error_at("sizeof cannot be applied to a bit-field", cur_token_loc()); - if (object->is_flexible_array_member) - error_at("sizeof cannot be applied to a flexible array member", - cur_token_loc()); -} - -/* Resolve unevaluated record postfixes while preserving an array member's - * declared dimensions for the global sizeof constant evaluator. - */ -static var_t *read_const_object_members(var_t *object) -{ - while (lex_peek(T_dot, NULL) || lex_peek(T_arrow, NULL)) { - bool through_pointer = lex_accept(T_arrow); - char field_name[MAX_ID_LEN]; - var_t *field; - int pointer_depth = effective_pointer_depth(object); - - if ((through_pointer && pointer_depth != 1) || - (!through_pointer && pointer_depth != 0)) - error_at("Invalid record member access", cur_token_loc()); - if (!through_pointer) - lex_expect(T_dot); - lex_ident(T_identifier, field_name); - field = find_member(field_name, object->type); - if (!field) - error_at("Unknown record member", cur_token_loc()); - object = field; - } - return object; -} - -/* Parse the object portion of a constant address expression. sizeof only needs - * its pointer type, but still applies the ordinary addressability constraints. - */ -static var_t *read_const_addressed_object(block_t *scope, int *subscript_depth) -{ - char buffer[MAX_TOKEN_LEN]; - var_t *object; - - lex_expect(T_ampersand); - if (subscript_depth) - *subscript_depth = 0; - lex_ident(T_identifier, buffer); - object = find_var(buffer, scope); - if (!object) - error_at("sizeof address requires a declared object", cur_token_loc()); - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - if (subscript_depth) - *subscript_depth = *subscript_depth + 1; - } - if (is_bitfield(object)) - error_at("Cannot take address of bit-field", cur_token_loc()); - return object; -} - /* 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. @@ -131,331 +37,20 @@ int read_const_wstring_size(void) /* 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. Declared objects keep their extents through a type-only - * walk and type names use the declaration-only evaluator; every other operand - * goes through the ordinary sizeof parser in a detached block, so its size - * follows from its real type. + * 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 buffer holds only a copied identifier, at most MAX_TOKEN_LEN. */ - char buffer[MAX_TOKEN_LEN]; - int res; - /* The detached expression parser needs a block to hold its values. */ if (!scope) scope = GLOBAL_BLOCK; - - token_t *inside = - lex_peek(T_open_bracket, NULL) ? cur_token->next->next : NULL; - var_t *nested_array = NULL; - token_t *nested_root = NULL; - token_t *nested_after = NULL; - int nested_groups = 0; - int string_groups = sizeof_grouped_literal_depth(inside, T_string); - int wstring_groups = sizeof_grouped_literal_depth(inside, T_wstring); - - if (inside && inside->kind == T_open_bracket) { - token_t *token = inside; - - while (token && token->kind == T_open_bracket) { - nested_groups++; - token = token->next; - } - nested_root = token; - if (token && token->kind == T_identifier) - nested_array = find_var(token->literal, scope); - nested_after = token ? token->next : NULL; - for (int i = 0; i < nested_groups && nested_after && - nested_after->kind == T_close_bracket; - i++) - nested_after = nested_after->next; - } - if (can_scan_sizeof_postfix_extent(scope, - lex_peek(T_open_bracket, NULL) - ? cur_token->next->next - : cur_token->next, - lex_peek(T_open_bracket, NULL), false)) { - /* The shared postfix walker has already proved this is an - * identifier-rooted fixed array extent, so the detached parser can - * preserve it without a global-prefix spelling table. - */ - res = read_global_sizeof_expression(scope); - } else if (string_groups >= 0 || wstring_groups >= 0) { - /* sizeof's own parenthesis and any grouping inside it enclose the - * literal array; none of them is part of its extent. - */ - int groups = string_groups >= 0 ? string_groups : wstring_groups; - - lex_expect(T_open_bracket); - for (int i = 0; i < groups; i++) - lex_expect(T_open_bracket); - if (string_groups >= 0) - res = read_sizeof_string_literal(); - else - res = read_const_wstring_size(); - for (int i = 0; i < groups; i++) - lex_expect(T_close_bracket); - lex_expect(T_close_bracket); - } else if (nested_array && nested_array->array_size > 0 && - nested_groups > 0 && nested_after && - nested_after->kind == T_close_bracket) { - lex_expect(T_open_bracket); - for (int i = 0; i < nested_groups; i++) - lex_expect(T_open_bracket); - lex_expect(T_identifier); - for (int i = 0; i < nested_groups; i++) - lex_expect(T_close_bracket); - lex_expect(T_close_bracket); - res = size_var(nested_array); - } else if (nested_array && nested_groups > 0 && nested_root && - nested_root->next && - (nested_root->next->kind == T_dot || - nested_root->next->kind == T_arrow)) { - /* The local sizeof helper preserves a member-array lvalue through - * arbitrary grouping before a postfix subscript. Reuse it in a detached - * block for a global constant instead of duplicating that type-only - * traversal here. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_open_bracket && inside->next->next && - inside->next->next->kind == T_identifier) { - var_t *grouped_object = find_var(inside->next->next->literal, scope); - - if (grouped_object && !grouped_object->array_size && - !grouped_object->has_unsized_array) - res = read_global_sizeof_expression(scope); - else - res = read_const_sizeof_type(scope); - } else if (inside && - (inside->kind == T_increment || inside->kind == T_decrement || - inside->kind == T_plus || inside->kind == T_minus || - inside->kind == T_bit_not || inside->kind == T_log_not)) { - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - ((inside->next->kind == T_identifier && - find_type(inside->next->literal, true)) || - inside->next->kind == T_signed || - inside->next->kind == T_unsigned || - inside->next->kind == T_long || inside->next->kind == T_const || - inside->next->kind == T_volatile || - inside->next->kind == T_struct || - inside->next->kind == T_union || - inside->next->kind == T_enum)) { - /* A cast begins with an extra parenthesis and cannot be evaluated as an - * integer constant when its operand is a declared object. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_identifier && - (find_var(inside->literal, scope) || - find_func(inside->literal)) && - inside->next && inside->next->kind != T_close_bracket && - inside->next->kind != T_dot && inside->next->kind != T_arrow && - inside->next->kind != T_open_square) { - /* Parenthesized non-postfix operands need type derivation rather than - * constant evaluation: sizeof(global + 1), sizeof(global = 1), and - * calls are all unevaluated but need their expression type. Reuse the - * detached expression path used for local sizeof. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_asterisk && inside->next->next && - inside->next->next->kind == T_identifier && - find_var(inside->next->next->literal, scope)) { - var_t *pointer = find_var(inside->next->next->literal, scope); - var_t dereferenced; - var_t *object; - int subscript_depth = 0; - - if (effective_pointer_depth(pointer) != 1) - error_at("Cannot dereference non-pointer in sizeof", - cur_token_loc()); - - /* This is compile-time descriptor manipulation, not a source aggregate - * expression. A direct `dereferenced = *pointer` becomes one wide - * OP_read while self-hosting; ARM's scalar load backend correctly - * admits only 1/2/4-byte reads. Copy through libc instead, which keeps - * the generated compiler IR word-sized and preserves every descriptor - * field. - */ - memcpy(&dereferenced, pointer, sizeof(dereferenced)); - dereferenced.type = pointee_type_from_pointer_typedef(pointer->type); - dereferenced.ptr_level = 0; - object = &dereferenced; - - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - lex_expect(T_identifier); - lex_expect(T_close_bracket); - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_asterisk && inside->next && - inside->next->kind == T_identifier && inside->next->next && - inside->next->next->kind == T_open_square && - is_direct_fixed_array_pointer_slot( - find_var(inside->next->literal, scope))) { - /* The shared direct-pointer helper also owns the supported global - * pointer-array slot form. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_asterisk && inside->next && - inside->next->kind == T_ampersand) { - var_t *object; - int subscript_depth; - - lex_expect(T_open_bracket); - lex_expect(T_asterisk); - object = read_const_addressed_object(scope, &subscript_depth); - lex_expect(T_close_bracket); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && inside->kind == T_asterisk) { - /* Only "*&object" keeps a declared extent through the address walk; any - * other dereference, such as *pointer, needs the pointee type. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_ampersand) { - lex_expect(T_open_bracket); - read_const_addressed_object(scope, NULL); - lex_expect(T_close_bracket); - res = PTR_SIZE; - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_identifier && - find_var(inside->next->literal, scope) && inside->next->next && - inside->next->next->kind != T_close_bracket && - inside->next->next->kind != T_dot && - inside->next->next->kind != T_arrow && - inside->next->next->kind != T_open_square) { - /* A grouped object followed by an operator is an ordinary parenthesized - * expression, not the array/member postfix form handled below. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_open_bracket && inside->next && - inside->next->kind == T_identifier && - find_var(inside->next->literal, scope)) { - /* Preserve the array object type through an explicitly grouped object - * expression, e.g. sizeof((record.items)[0]). - */ - var_t *object = find_var(inside->next->literal, scope); - int subscript_depth = 0; - - lex_expect(T_open_bracket); - lex_expect(T_open_bracket); - lex_expect(T_identifier); - object = read_const_object_members(object); - lex_expect(T_close_bracket); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (inside && - (inside->kind == T_open_bracket || inside->kind == T_numeric || - inside->kind == T_char || inside->kind == T_wchar)) { - /* A literal has the type its suffix and value select, such as long long - * for 1LL, so only the ordinary expression parser knows its size. - */ - res = read_global_sizeof_expression(scope); - } else if (inside && inside->kind == T_identifier) { - var_t *object = find_var(inside->literal, scope); - constant_t *constant = find_scoped_constant(inside->literal, scope); - - if (object || constant) { - lex_expect(T_open_bracket); - lex_expect(T_identifier); - if (object) { - int subscript_depth = 0; - - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - lex_expect(T_close_bracket); - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else { - lex_expect(T_close_bracket); - res = TY_int->size; - } - } else { - res = read_const_sizeof_type(scope); - } - } else if (lex_peek(T_numeric, NULL) || lex_peek(T_char, NULL) || - lex_peek(T_wchar, NULL)) { - res = read_global_sizeof_expression(scope); - } else if (lex_peek(T_string, NULL)) { - res = read_sizeof_string_literal(); - } else if (lex_peek(T_wstring, NULL)) { - res = read_const_wstring_size(); - } else if (lex_peek(T_ampersand, NULL)) { - read_const_addressed_object(scope, NULL); - res = PTR_SIZE; - } else if (lex_peek(T_asterisk, NULL) && cur_token->next->next && - cur_token->next->next->kind == T_ampersand) { - var_t *object; - int subscript_depth; - - lex_expect(T_asterisk); - object = read_const_addressed_object(scope, &subscript_depth); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (lex_peek(T_asterisk, NULL)) { - /* As above, a dereference other than "*&object" needs its type. */ - res = read_global_sizeof_expression(scope); - } else if (lex_peek(T_minus, NULL) || lex_peek(T_plus, NULL) || - lex_peek(T_bit_not, NULL) || lex_peek(T_log_not, NULL)) { - res = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, buffer)) { - var_t *object = find_var(buffer, scope); - constant_t *constant = find_scoped_constant(buffer, scope); - - lex_expect(T_identifier); - if (object) { - int subscript_depth = 0; - - object = read_const_object_members(object); - while (lex_accept(T_open_square)) { - if (!object->array_size && !object->has_unsized_array) - error_at("Cannot apply square operator to non-array", - cur_token_loc()); - read_const_expr(scope); - lex_expect(T_close_square); - subscript_depth++; - } - validate_global_sizeof_object(object); - res = sizeof_subscripted_array(object, subscript_depth); - } else if (constant) { - res = TY_int->size; - } else { - error_at("sizeof requires a type or declared object", - cur_token_loc()); - res = 0; - } - } else { - res = read_const_sizeof_type(scope); - } - return res; + 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) @@ -589,34 +184,6 @@ void emit_global_scalar_assignment(block_t *parent, add_insn(parent, bb, OP_assign, dest, src, NULL, 0, NULL); } -/* 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) -{ - int bracket_depth = 0; - - for (; 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)) - 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; -} - /* Return whether token, an opening parenthesis, begins a cast to an integer * type: nothing but integer type specifiers and qualifiers before its closing * parenthesis. @@ -648,16 +215,23 @@ static bool global_integer_cast_starts_at(token_t *token, block_t *scope) return token && has_type; } -/* A scalar initializer containing an integer cast needs the two-word reader - * too: the word-sized evaluator has no notion of a type name. +/* 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 global_integer_cast_appears_before_initializer_end(token_t *token, - block_t *scope) +static bool initializer_needs_wide_reader(token_t *token, + block_t *scope, + bool casts) { int bracket_depth = 0; for (; token; token = token->next) { - if (global_integer_cast_starts_at(token, scope)) + 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++; @@ -672,6 +246,17 @@ static bool global_integer_cast_appears_before_initializer_end(token_t *token, 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); +} + var_t *read_wide_global_literal_expression(block_t *parent, basic_block_t *bb, block_t *scope); @@ -1063,71 +648,9 @@ var_t *read_wide_global_literal_primary(block_t *parent, return result; } if (lex_accept(T_sizeof)) { - char sizeof_name[MAX_ID_LEN]; - value = require_typed_var(parent, find_type("size_t", true)); value->var_name = gen_name(); - if (lex_peek(T_string, NULL)) - value->init_val = read_sizeof_string_literal(); - else if (lex_peek(T_wstring, NULL)) - value->init_val = read_const_wstring_size(); - else if (lex_peek(T_numeric, NULL)) - value->init_val = read_global_sizeof_expression(scope); - else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_string) { - lex_expect(T_open_bracket); - value->init_val = read_sizeof_string_literal(); - lex_expect(T_close_bracket); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_wstring) { - lex_expect(T_open_bracket); - value->init_val = read_const_wstring_size(); - lex_expect(T_close_bracket); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_numeric) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_open_bracket) { - /* A nested group is an expression operand, e.g. sizeof((void)1) or - * sizeof((*incomplete_pointer)). - */ - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_asterisk && - cur_token->next->next->next && - cur_token->next->next->next->kind == T_identifier && - cur_token->next->next->next->next && - cur_token->next->next->next->next->kind == T_open_square && - is_direct_fixed_array_pointer_slot( - find_var(cur_token->next->next->next->literal, scope))) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_identifier && - cur_token->next->next->next && - cur_token->next->next->next->kind == T_close_bracket && - find_var(cur_token->next->next->literal, scope)) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, sizeof_name) && - find_var(sizeof_name, scope)) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_open_bracket, NULL) && cur_token->next && - cur_token->next->next && - cur_token->next->next->kind == T_identifier && - cur_token->next->next->next && - cur_token->next->next->next->kind == T_close_bracket && - find_func(cur_token->next->next->literal)) { - value->init_val = read_global_sizeof_expression(scope); - } else if (lex_peek(T_identifier, sizeof_name) && - find_func(sizeof_name)) { - value->init_val = read_global_sizeof_expression(scope); - } else - value->init_val = read_sizeof_constant(scope); + 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); @@ -1542,10 +1065,7 @@ bool read_global_assignment_var(var_t *var) * lower each reduction into the global setup block instead of trying to * narrow the expression through that evaluator. */ - if (typed_global_literal_appears_before_initializer_end( - cur_token->next) || - global_integer_cast_appears_before_initializer_end(cur_token->next, - scope)) { + if (initializer_needs_wide_reader(cur_token->next, scope, true)) { rs1 = read_wide_global_literal_expression(parent, bb, scope); add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); return true; diff --git a/src/parser-decl.c b/src/parser-decl.c index 3ae7f32f..c7f03f4a 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -169,85 +169,140 @@ int sizeof_type_name_size(const type_t *type, 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]; - type_t *type = NULL; - bool is_unsigned = false; - bool is_signed = false; - int long_count = 0; + 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; - base_type_t record_kind = accept_record_keyword(); + 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 { - /* Declaration specifiers may appear in any order: all of "unsigned long - * int", "long unsigned int", and "int unsigned" name the same type. - */ - while (true) { - 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_signed)) { - if (is_signed) - error_at("duplicate signed type specifier", - cur_token_loc()); - is_signed = true; - } else if (lex_accept(T_long)) { - long_count++; - } else if (lex_accept(T_float)) { - type = TY_float; - } else if (lex_accept(T_double)) { - type = TY_double; - } else if (lex_accept(T_const) || lex_accept(T_volatile) || - lex_accept(T_restrict)) { - ; - } else if (lex_peek(T_identifier, token)) { - type_t *candidate = find_visible_type(token, scope); - - if (!candidate) - break; - lex_expect(T_identifier); - type = candidate; - } else { - break; - } - } - if (is_unsigned && is_signed) - error_at("both signed and unsigned specified", cur_token_loc()); - if (long_count > 2) - error_at("too many long type specifiers", cur_token_loc()); - if (type == TY_float && (is_unsigned || is_signed || long_count)) - error_at("invalid float type specifiers", cur_token_loc()); - if (type == TY_double) { - if (is_unsigned || is_signed || long_count > 1) - error_at("invalid double type specifiers", cur_token_loc()); - if (long_count) - type = TY_long_double; - } else if (long_count) { - type = is_unsigned ? TY_ulong : TY_long; - if (long_count == 2) - type = is_unsigned ? TY_ulong_long : TY_long_long; - } else if (is_unsigned) { - if (type == TY_char) - type = TY_uchar; - else if (type == TY_short) - type = TY_ushort; - else - type = TY_uint; - } else if (is_signed) { - type = type == TY_char ? TY_schar : (type ? type : TY_int); - } + } 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; } @@ -1052,9 +1107,9 @@ void read_inner_var_decl(var_t *vd, * * 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. "int" may follow another type word, as in "short int", but any - * other identifier after the type is the declarator, which ends the scan, so - * "int x static;" stays an error. + * 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) { @@ -1100,7 +1155,11 @@ void hoist_storage_class_specifiers(void) continue; } if (kind == T_identifier) { - if (saw_type_name && strcmp(tk->literal, "int")) + /* `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 && @@ -1169,10 +1228,7 @@ type_t *read_record_body(block_t *parent, if (has_tag) { type = local_record_tag(token, scope, kind); - - /* C99 6.7.2.3p1: a scope defines the content of a tag only once. */ - if (type->num_fields) - error_at("redefinition of struct or union tag", cur_token_loc()); + begin_record_definition(type); } else { type = add_type(); type->base_type = kind; @@ -1186,64 +1242,47 @@ type_t *read_record_body(block_t *parent, /* A member's type names tags visible in this scope. */ v->scope = parent; read_full_var_decl(v, false, false, true); - read_bitfield_width(v, 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(v); - else - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, is_union); - if (is_union) { - v->offset = 0; - int field_size = is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - } else { - size = is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field(flush_bitfield_layout(size, &bits), - v, &alignment); - } + /* Each declarator of the member declaration, sharing its specifier. */ + while (true) { + read_bitfield_width(last, scope); - while (lex_accept(T_comma)) { - if (!is_union && last->is_flexible_array_member) - error_at( - "Flexible array member must be the final struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, 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(nv); + is_flexible_array_member_container(last); else - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, is_union); - last = nv; + reject_flexible_array_member_container(last); + mark_flexible_array_member(last, is_union); if (is_union) { - nv->offset = 0; + last->offset = 0; int field_size = - is_bitfield(nv) ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); + 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(nv) > alignment) - alignment = alignment_var(nv); + if (alignment_var(last) > alignment) + alignment = alignment_var(last); } else { - size = is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); + 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); @@ -1266,6 +1305,7 @@ type_t *read_record_body(block_t *parent, ? 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; } @@ -1286,161 +1326,34 @@ void read_full_var_decl(var_t *vd, bool declaration_const = vd->is_const_qualified; bool declaration_inline = vd->is_inline; bool declaration_volatile = vd->is_volatile; - bool is_signed = false; - bool is_unsigned = false; bool is_const = false; - bool is_inline = false; + bool is_inline = declaration_inline; bool is_volatile = false; - bool is_long = false; - bool is_long_long = false; - type_t *leading_scalar_type = NULL; - - /* The declaration dispatcher normally leaves the base type for this routine - * to consume. C99 also permits the modifier after that base, e.g. `short - * unsigned value`; retain the base while consuming its following scalar - * modifiers instead of mistaking `unsigned` for the declarator name. - */ - if (lex_peek(T_identifier, type_name) && - (!strcmp(type_name, "char") || !strcmp(type_name, "short") || - !strcmp(type_name, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && - (after_base->kind == T_signed || after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(type_name, true); - } - } - - /* C permits these declaration specifiers in either order. Consume the - * scalar set as a group so `const unsigned int` and `unsigned const int` - * follow the same path. - */ - while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_const, NULL) || lex_peek(T_inline, NULL) || - lex_peek(T_volatile, NULL) || lex_peek(T_long, NULL)) { - 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_const)) - is_const = true; - else if (lex_accept(T_volatile)) - is_volatile = true; - else if (lex_accept(T_inline)) { - if (is_inline || declaration_inline) - error_at("duplicate inline function specifier", - cur_token_loc()); - is_inline = true; - } else { - lex_expect(T_long); - if (is_long_long) - error_at("too many long type specifiers", cur_token_loc()); - if (is_long) - is_long_long = true; - else - is_long = true; - } - } - if (is_signed && is_unsigned) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && lex_peek(T_identifier, type_name) && - !strcmp(type_name, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - bool is_enum_type = lex_accept(T_enum); - if (is_enum_type && (is_signed || is_unsigned || is_long)) - error_at("enum type cannot be combined with integer specifiers", - cur_token_loc()); - base_type_t record_kind = accept_record_keyword(); - type_t *type; + type_t *type = + read_scalar_type_specifiers(&is_const, &is_volatile, &is_inline); - /* `signed` has the existing signed scalar semantics. C permits its `int` - * spelling to be omitted, while `signed char` and `signed short` retain - * their explicit base type. - */ - if (parsing_sizeof_function_signature && lex_accept(T_float)) { - if (is_signed || is_unsigned || is_long) - error_at("invalid float type specifiers", cur_token_loc()); - type = TY_float; - } else if (parsing_sizeof_function_signature && lex_accept(T_double)) { - if (is_signed || is_unsigned || is_long_long) - error_at("invalid double type specifiers", cur_token_loc()); - type = is_long ? TY_long_double : TY_double; - } else if (is_enum_type) { + if (!type && lex_accept(T_enum)) { lex_ident(T_identifier, type_name); type = reference_enum_tag(type_name, vd->scope); - } else if (is_unsigned) { - if (is_long) { - if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = is_long_long ? TY_ulong_long : TY_ulong; - } else if (leading_scalar_type == TY_char) { - type = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = TY_ushort; - } else if (lex_peek(T_identifier, type_name) && - (!strcmp(type_name, "char") || !strcmp(type_name, "short") || - !strcmp(type_name, "int"))) { - lex_expect(T_identifier); - if (!strcmp(type_name, "char")) - type = TY_uchar; - else if (!strcmp(type_name, "short")) - type = TY_ushort; - else - type = TY_uint; - } else - type = TY_uint; /* `unsigned` is `unsigned int` */ - } else if (is_long) { - /* The current ABI gives long the same 32-bit representation as int, - * while retaining its distinct C type and rank. - */ - if (lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = is_long_long ? TY_long_long : TY_long; - } else if (is_signed && leading_scalar_type && - (leading_scalar_type == TY_char || - leading_scalar_type == TY_short)) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, type_name) && !strcmp(type_name, "int")) - lex_expect(T_identifier); - type = leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; - } else if (is_signed && lex_peek(T_identifier, type_name) && - (!strcmp(type_name, "char") || !strcmp(type_name, "short") || - !strcmp(type_name, "int"))) { - lex_expect(T_identifier); - type = !strcmp(type_name, "char") - ? TY_schar - : (!strcmp(type_name, "short") ? TY_short : TY_int); - } else if (is_signed && !record_kind && - (!lex_peek(T_identifier, type_name) || - (strcmp(type_name, "int") && strcmp(type_name, "char") && - strcmp(type_name, "short")))) { - type = TY_int; - } else if (leading_scalar_type) { - type = leading_scalar_type; - } else 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); + } 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); + 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) { @@ -1497,14 +1410,14 @@ void read_full_var_decl(var_t *vd, 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 || declaration_inline) + if (is_inline) error_at("duplicate inline function specifier", cur_token_loc()); is_inline = true; } else break; } - vd->is_inline = declaration_inline || is_inline; + vd->is_inline = is_inline; vd->is_volatile = declaration_volatile || is_volatile || type->is_volatile_qualified; if (is_const || declaration_const) { diff --git a/src/parser-expr.c b/src/parser-expr.c index c7e8fda7..82fb0a27 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -446,8 +446,11 @@ static void lower_value_member(block_t *parent, basic_block_t **bb) 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) { - /* A record loaded from an object: select from the object itself. */ + 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. + */ + is_lvalue = base->is_compound_literal_reference; address = base->compound_literal_address; } else { /* A named record or a compound literal is an lvalue; a temporary, such @@ -983,7 +986,7 @@ static void read_compound_literal_operand(block_t *parent, 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, true); + parse_array_init(compound_var, parent, bb); else parse_array_compound_literal(compound_var, parent, bb); @@ -1045,7 +1048,7 @@ static void read_compound_literal_operand(block_t *parent, 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, true); + 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 || @@ -1307,7 +1310,6 @@ static void read_cast_operand(block_t *parent, paren_type_name_t *tn) { /* Process cast: (type)expr Parse the expression to be cast */ - reading_unary_operand = true; read_expr_operand(parent, bb); /* Get the expression result */ @@ -1373,6 +1375,12 @@ static void read_cast_operand(block_t *parent, 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 @@ -1383,17 +1391,22 @@ static void read_cast_operand(block_t *parent, * 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); - if (cast_size < get_size(expr_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 && get_size(expr_var) <= TY_int->size && + 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 |= get_size(expr_var) << 16; + cast_size |= expr_size << 16; } add_insn(parent, *bb, cast_op, cast_var, expr_var, NULL, cast_size, NULL); @@ -1416,332 +1429,218 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) 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 typename followed by ) */ - token_t *type_start = cur_token; + /* 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_signed_type = false; - bool has_unsigned_type = false; - int long_type_count = 0; - type_t *leading_scalar_type = NULL; - - if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short") || - !strcmp(lookahead_token, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && - (after_base->kind == T_signed || after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(lookahead_token, true); - } - } - while (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)) { - if (lex_accept(T_const)) - has_const_type = true; - else if (lex_accept(T_volatile)) - tn.volatile_qualified = true; - else if (lex_accept(T_signed)) { - if (has_signed_type) - error_at("duplicate signed type specifier", cur_token_loc()); - has_signed_type = true; - } else if (lex_accept(T_unsigned)) { - if (has_unsigned_type) - error_at("duplicate unsigned type specifier", cur_token_loc()); - has_unsigned_type = true; - } else { - lex_expect(T_long); - long_type_count++; - } - } - if (long_type_count > 2) - error_at("too many long type specifiers", cur_token_loc()); - if (has_signed_type && has_unsigned_type) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && - lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - bool has_long_type = long_type_count > 0; - bool has_enum_type = lex_accept(T_enum); - if (has_enum_type && - (has_signed_type || has_unsigned_type || has_long_type)) - error_at("enum type cannot be combined with integer specifiers", - cur_token_loc()); - bool has_type_identifier = lex_peek(T_identifier, lookahead_token); - if (has_const_type || has_signed_type || has_unsigned_type || - has_long_type || has_enum_type || has_type_identifier || - lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { - /* Check if it's a basic type or typedef */ - token_t *saved_token = type_start; + 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(); - bool is_record = record_kind != TYPE_void; - if (is_record) - lex_ident(T_identifier, lookahead_token); - type_t *type; - if (has_enum_type) { + 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 (has_unsigned_type && !is_record) { - if (has_long_type) { - type = TY_ulong; - if (long_type_count > 1) { - type = TY_ulong_long; - } - if (lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - } else if (leading_scalar_type == TY_char) { - type = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - type = TY_ushort; - } else if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "int") || - !strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short"))) { + } else if (lex_peek(T_identifier, lookahead_token)) { + type = find_visible_type(lookahead_token, parent); + if (type) lex_expect(T_identifier); - if (!strcmp(lookahead_token, "char")) - type = TY_uchar; - else if (!strcmp(lookahead_token, "short")) - type = TY_ushort; - else - type = TY_uint; - } else - type = TY_uint; - } else if (has_long_type && !is_record) { - type = TY_long; - if (long_type_count > 1) { - type = TY_long_long; - } - lex_accept(T_signed); - lex_accept(T_const); - if (lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - } else if (has_signed_type && !is_record) { - if (leading_scalar_type == TY_char || - leading_scalar_type == TY_short) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, lookahead_token) && - !strcmp(lookahead_token, "int")) - lex_expect(T_identifier); - type = leading_scalar_type == TY_char ? TY_schar - : leading_scalar_type; - } else if (lex_peek(T_identifier, lookahead_token) && - (!strcmp(lookahead_token, "int") || - !strcmp(lookahead_token, "char") || - !strcmp(lookahead_token, "short"))) { - lex_expect(T_identifier); - type = !strcmp(lookahead_token, "char") - ? TY_schar - : find_type(lookahead_token, true); - } else { - type = TY_int; - } - } else if (leading_scalar_type) { - type = leading_scalar_type; - } else { - type = is_record - ? find_record_tag(lookahead_token, parent, record_kind) - : find_type(lookahead_token, true); } + } + tn.volatile_qualified = has_volatile_type; - if (type) { - if (type->is_floating) - error_at("Floating point types are not yet supported", - cur_token_loc()); - - /* Save current position to backtrack if needed Try to parse as - * typename - */ - if (!is_record && !has_enum_type && !has_signed_type && - !has_unsigned_type && !has_long_type) - lex_expect(T_identifier); + if (type) { + if (type->is_floating) + error_at("Floating point types are not yet supported", + cur_token_loc()); - /* A qualifier may appear before or after the base type. */ - while (lex_peek(T_const, NULL) || lex_peek(T_volatile, NULL)) { - if (lex_accept(T_const)) - has_const_type = true; - else - tn.volatile_qualified = true; + /* 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); - } + /* 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; - - /* 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; + bool is_array = false; - 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); + /* 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 (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", + 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()); - is_array = true; - tn.array_dims = 1; - tn.parenthesized_array = true; + } 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); - /* 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) + if (pointee_dims >= 4) error_at( - "Array declarators support at most four dimensions", + "Array declarators support at most four " + "dimensions", cur_token_loc()); - if (lex_peek(T_numeric, NULL)) { - char bound_token[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, bound_token, MAX_TOKEN_LEN); - bound = parse_numeric_constant(bound_token); - 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; + if (bound <= 0) + error_at("Array size must be positive", cur_token_loc()); + if (pointee_dims == 0) + tn.pointee_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; + if (pointee_dims == 1) + tn.pointee_array_dim2 = bound; + else if (pointee_dims == 2) + tn.pointee_array_dim3 = bound; else - tn.array_dim4 = bound; + tn.pointee_array_dim4 = bound; + tn.pointee_array_size *= bound; } - if (!tn.array_dims && !bound) - tn.array_outer_unsized = true; - tn.array_dims++; lex_expect(T_close_square); + pointee_dims++; } - 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; + 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)) { + char bound_token[MAX_TOKEN_LEN]; + lex_ident_n(T_numeric, bound_token, MAX_TOKEN_LEN); + bound = parse_numeric_constant(bound_token); + 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; + /* 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 { - /* Not a cast or compound literal - backtrack */ - cur_token = saved_token; + 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) { @@ -1753,13 +1652,36 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) } } -void read_expr_operand(block_t *parent, basic_block_t **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); + 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; - bool allow_ptr_arith = !reading_unary_operand; - - reading_unary_operand = false; if (grouped_scalar_pointee_row_postfix_starts(parent)) { handle_grouped_scalar_pointee_row_postfix(parent, bb); @@ -1828,7 +1750,6 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } if (lex_accept(T_plus)) { - reading_unary_operand = true; read_expr_operand(parent, bb); rs1 = opstack_pop(); reject_record_operand(rs1); @@ -1846,11 +1767,18 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } 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()); + /* 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)) @@ -1876,9 +1804,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lower_postfix_operators(parent, bb); } else if (lex_peek(T_numeric, NULL)) { read_numeric_param(parent, *bb, is_neg); - is_neg = false; } else if (lex_accept(T_log_not)) { - reading_unary_operand = true; read_expr_operand(parent, bb); rs1 = opstack_pop(); @@ -1909,7 +1835,6 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_log_not, vd, rs1, NULL, 0, NULL); } } else if (lex_accept(T_bit_not)) { - reading_unary_operand = true; read_expr_operand(parent, bb); rs1 = opstack_pop(); @@ -2027,8 +1952,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) cur_token->next->next->kind == T_open_bracket; read_lvalue(&lvalue, var, parent, bb, true, - deferred_call_prefix ? OP_generic : prefix_op, - allow_ptr_arith && !is_neg); + deferred_call_prefix ? OP_generic : prefix_op); /* is it an indirect call with function pointer? */ if (lex_peek(T_open_bracket, NULL)) { @@ -2101,37 +2025,22 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) error_at("Unrecognized expression token", next_token_loc()); } } +} - /* A numeric literal takes its minus sign as part of its value; any other - * operand, grouped ones included, is negated here. Only the identifier path - * used to apply it, so "-(x)" evaluated to x. - */ - if (is_neg) { - rs1 = opstack_pop(); - 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); +/* 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; - /* 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->type = rs1->type; - vd->is_const = true; - vd->init_val = -rs1->init_val; - vd->init_val_hi = ~rs1->init_val_hi + (vd->init_val == 0); - 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_negate, vd, rs1, NULL, 0, NULL); - } - } +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, @@ -2614,12 +2523,6 @@ void normalize_integer_binary_operands(block_t *parent, right[0] = resize_to(parent, bb, right[0], common, 0); } -/* Whether the right operand of @op binds tighter than a following `+`. */ -static bool operand_excludes_addition(opcode_t op) -{ - return op == OP_sub || op == OP_mul || op == OP_div || op == OP_mod; -} - void read_expr_body(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1, *rs2; @@ -2655,7 +2558,6 @@ void read_expr_body(block_t *parent, basic_block_t **bb) fatal("Expression too complex: operator stack exhausted"); oper_stack[oper_stack_idx++] = op; } - reading_unary_operand = operand_excludes_addition(op); read_expr_operand(parent, bb); op = get_operator(); @@ -2787,7 +2689,6 @@ void read_expr_body(block_t *parent, basic_block_t **bb) pprev_log_op = 0; } } - reading_unary_operand = operand_excludes_addition(op); read_expr_operand(parent, bb); if (!is_logical(op)) { if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) @@ -3039,393 +2940,284 @@ static bool lvalue_is_bool(const lvalue_t *lvalue) return !lvalue->is_func && is_bool_scalar(lvalue->type, level); } -/* What follows a completed lvalue: pointer arithmetic on it, or an update or - * read of the object it names. read_lvalue() finishes with this. +/* 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, - bool allow_ptr_arith, type_t *pointer_row_element_type) { var_t *vd, *rs1, *rs2; - /* 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. + /* Set and read only under 'is_reference'; the initializer says so to a + * compiler that cannot correlate the two tests. */ - bool pointee_row = is_pointee_array_pointer(var); - if (allow_ptr_arith && lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var) || pointee_row) && - !lvalue->is_reference) { - if (!is_array_declarator(var) && - is_direct_void_pointer_type(lvalue->type, lvalue->ptr_level)) - error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); - while (lex_peek(T_plus, NULL) && - (var->ptr_level || is_array_declarator(var) || pointee_row)) { - 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); + var_t *t = NULL; - /* 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 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; + + /* 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; + 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. */ - if (pointee_row) { - lvalue->size = pointee_array_row_stride(var); - } else if (is_array_declarator(var) && var->array_dim2 && - !has_effective_pointer(var) && !var->is_func) { - lvalue->size = fixed_array_decay_stride(var); - } else if (var->ptr_level > 1 || - (is_array_declarator(var) && var->ptr_level)) - 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); + 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(); - 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); - } + /* For pointer arithmetic, increment by the size of pointed-to type */ + if (!lvalue->is_reference && is_pointee_array_pointer(var)) + vd->init_val = pointee_array_row_stride(var); + else if (lvalue->ptr_level > 1) + vd->init_val = PTR_SIZE; + else if (lvalue->ptr_level) + vd->init_val = lvalue->type->size; + else if (lvalue->type && lvalue->type->ptr_level) + vd->init_val = get_pointer_element_size(var); + else + vd->init_val = 1; + 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(); + rs2 = opstack_pop(); + if (lvalue->is_reference) rs1 = opstack_pop(); - vd = require_var(parent); - - /* A pointer plus an integer is still a pointer. Array expressions - * first decay to a pointer, adding one level to the declaration's - * element indirection. - */ - if (var->ptr_level || is_array_declarator(var) || pointee_row) { - vd->type = lvalue->type; - vd->ptr_level = var->ptr_level + !!is_array_declarator(var); - if (pointee_row) - copy_pointee_array_shape(vd, var); - else if (is_array_declarator(var) && var->array_dim2 && - !has_effective_pointer(var) && !var->is_func) { - fixed_array_shape_t shape = fixed_array_shape_from_var(var); - - fixed_array_shape_drop_outer(&shape); - fixed_array_shape_to_pointee_var(vd, &shape); - } - } - vd->var_name = gen_name(); - vd->is_const_qualified = var->is_const_qualified; - vd->pointer_const_mask = var->pointer_const_mask; - vd->is_const_pointer = - vd->ptr_level > 0 && vd->ptr_level <= 32 && - (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); - 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; + 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 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; - - /* 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. + 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. */ - t->func_signature = lvalue->type->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); + write_bitfield_value(parent, bb, vd, rs1, lvalue->decl); + rs1 = read_bitfield_value(parent, bb, vd, lvalue->decl); + rs1->is_bitfield = false; } else { - /* A selected record is copied into storage of its own; a - * register holds no more than a pointer's worth of it. + 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' */ - push_object_at(parent, bb, t, rs1, lvalue->size); + 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); } - 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(); + } 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()); - /* For pointer arithmetic, increment by the size of pointed-to type + /* 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. */ - if (!lvalue->is_reference && is_pointee_array_pointer(var)) - vd->init_val = pointee_array_row_stride(var); - else if (lvalue->ptr_level > 1) - vd->init_val = PTR_SIZE; - else if (lvalue->ptr_level) - vd->init_val = lvalue->type->size; - else if (lvalue->type && lvalue->type->ptr_level) - vd->init_val = get_pointer_element_size(var); - else - vd->init_val = 1; - 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(); + opcode_t postfix_op = OP_sub; + if (lex_accept(T_increment)) + postfix_op = OP_add; 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); + 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); - if (lvalue->is_reference) { - rs1 = vd; - vd = opstack_pop(); + 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; + } - /* 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); + /* This arm appends up to five entries, so check for room once before + * writing any of them. + */ + if (se_idx + 5 > MAX_SIDE_EFFECT) + error_at("Too many postfix operators in one statement", + next_token_loc()); - /* 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()); + side_effect[se_idx].opcode = OP_load_constant; + vd = require_var(parent); + vd->var_name = gen_name(); - /* 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. + /* Calculate increment size based on pointer type */ + int increment_size = 1; + if (!lvalue->is_reference && is_pointee_array_pointer(var)) { + increment_size = pointee_array_row_stride(var); + } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { + increment_size = PTR_SIZE; + } else if (lvalue->ptr_level && !lvalue->is_reference) { + increment_size = lvalue->type->size; + } else if (!lvalue->is_reference && lvalue->type && + lvalue->type->ptr_level > 0) { + /* A typedef pointer steps by its pointee, as the prefix form does: + * the alias's own size is a pointer's, whether it points at a + * record or at another pointer. */ - 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; - } + increment_size = get_pointer_element_size(var); + } + vd->init_val = increment_size; + if (lvalue_is_wide_integer(lvalue)) + vd->type = lvalue->type; - /* This arm appends up to five entries, so check for room once - * before writing any of them. - */ - if (se_idx + 5 > MAX_SIDE_EFFECT) - error_at("Too many postfix operators in one statement", - next_token_loc()); + side_effect[se_idx].rd = vd; + side_effect[se_idx].rs1 = NULL; + side_effect[se_idx].rs2 = NULL; + se_idx++; - side_effect[se_idx].opcode = OP_load_constant; - vd = require_var(parent); - vd->var_name = gen_name(); + /* 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(); + 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; + side_effect[se_idx].rd = vd; + se_idx++; - /* Calculate increment size based on pointer type */ - int increment_size = 1; - if (!lvalue->is_reference && is_pointee_array_pointer(var)) { - increment_size = pointee_array_row_stride(var); - } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { - increment_size = PTR_SIZE; - } else 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; - if (lvalue_is_wide_integer(lvalue)) - vd->type = lvalue->type; + /* 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). The deferred entries are raw instructions, so spell out the + * comparison normalize_bool() would emit. + */ + if (lvalue_is_bool(lvalue)) { + var_t *zero = require_typed_var(parent, TY_int); + var_t *truth = require_typed_var(parent, TY_bool); - side_effect[se_idx].rd = vd; + zero->var_name = gen_name(); + zero->init_val = 0; + zero->is_const = true; + side_effect[se_idx].opcode = OP_load_constant; + side_effect[se_idx].rd = zero; 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(); - 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; - side_effect[se_idx].rd = vd; + truth->var_name = gen_name(); + side_effect[se_idx].opcode = OP_neq; + side_effect[se_idx].rd = truth; + side_effect[se_idx].rs1 = vd; + side_effect[se_idx].rs2 = zero; se_idx++; + vd = truth; + } - /* 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). The deferred entries are raw instructions, so spell out - * the comparison normalize_bool() would emit. - */ - if (lvalue_is_bool(lvalue)) { - var_t *zero = require_typed_var(parent, TY_int); - var_t *truth = require_typed_var(parent, TY_bool); - - zero->var_name = gen_name(); - zero->init_val = 0; - zero->is_const = true; - side_effect[se_idx].opcode = OP_load_constant; - side_effect[se_idx].rd = zero; - side_effect[se_idx].rs1 = NULL; - side_effect[se_idx].rs2 = NULL; - se_idx++; - - truth->var_name = gen_name(); - side_effect[se_idx].opcode = OP_neq; - side_effect[se_idx].rd = truth; - side_effect[se_idx].rs1 = vd; - side_effect[se_idx].rs2 = zero; - se_idx++; - vd = truth; - } - - 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++; - } + 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 { - if (lvalue->is_reference) { - /* pop the address and keep the read value */ - t = opstack_pop(); - opstack_pop(); - opstack_push(t); - } + 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); } } } @@ -3453,21 +3245,15 @@ static bool is_function_parameter(const var_t *var, block_t *parent) * x[expr].field =; * x[expr]->field =; * - * @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. + * 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, - bool allow_ptr_arith) + opcode_t prefix_op) { var_t *vd, *rs1, *rs2; var_t *loaded_scalar_row = NULL; @@ -3967,7 +3753,7 @@ void read_lvalue(lvalue_t *lvalue, error_at("assignment of read-only location", next_token_loc()); lower_lvalue_tail(lvalue, array_row ? array_row : var, parent, bb, - prefix_op, allow_ptr_arith, pointer_row_element_type); + prefix_op, pointer_row_element_type); } void read_logical(opcode_t op, block_t *parent, basic_block_t **bb) @@ -4414,16 +4200,10 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) * the operand's address instead and copy the chosen object into a * temporary after the join, which dominates every later use. */ - true_addr = require_ref_var(parent, true_val->type, 0); - false_addr = require_ref_var(parent, false_val->type, 0); + 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); - true_addr->var_name = gen_name(); - false_addr->var_name = gen_name(); selected->var_name = gen_name(); - add_insn(parent, then_, OP_address_of, true_addr, true_val, NULL, 0, - NULL); - add_insn(parent, else_, OP_address_of, false_addr, false_val, NULL, 0, - NULL); 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); @@ -4514,16 +4294,17 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) bb[0] = end_ternary; } -bool read_body_assignment(char *token, +/* 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 *var = find_local_var(token, parent), *vd, *rs1, *rs2, *t; - if (!var) - var = find_global_var(token); + var_t *vd, *rs1, *rs2, *t; if (assignment_result) assignment_result[0] = NULL; @@ -4534,7 +4315,7 @@ bool read_body_assignment(char *token, int size = 0; /* has memory address that we want to set */ - read_lvalue(&lvalue, var, parent, bb, false, OP_generic, true); + read_lvalue(&lvalue, var, parent, bb, false, OP_generic); while (lvalue_paren_depth-- > 0) lex_expect(T_close_bracket); size = lvalue.size; @@ -5152,9 +4933,51 @@ static void read_grouped_postfix_assignment(block_t *parent, basic_block_t **bb) 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; @@ -5162,11 +4985,16 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) return false; if (compound_literal_starts_expression()) return false; - if (grouped_postfix_lvalue_follows() || + + /* 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) || - (lex_peek(T_identifier, token) && find_var(token, parent) && - is_swapped_subscript_base(find_var(token, parent)))) { + grouped_postfix_lvalue_follows()) { read_grouped_postfix_assignment(parent, bb); return true; } @@ -5186,92 +5014,47 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) var_t *field = NULL; opcode_t compound_op = OP_generic; int store_size; - int grouped_fixed_array_dimensions = 0; 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. + * 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); - read_expr(parent, bb); - read_ternary_operation(parent, bb); - address = opstack_pop(); - object_address = address; - while (lvalue_paren_depth-- > 0) - lex_expect(T_close_bracket); + int stars = 0; + var_t *array = dereferenced_array_store_base(parent, &stars); - /* The ordinary `(*pointer)` assignment path historically stopped at the - * grouping close. Admit only exact scalar fixed-array suffixes here, - * retaining its common store/result lowering below. - */ - if (lex_peek(T_open_square, NULL)) { - var_t shape = {0}; - fixed_array_shape_t array_shape; - int depth = 0; - int dimensions; - - if (!address->pointee_array_size || - address->pointee_array_element_ptr_level || - effective_pointer_depth(address) != 1) - error_at( - "Grouped subscript requires a scalar fixed array pointer", - cur_token_loc()); - shape.type = address->type->pointee_array_element_type - ? address->type->pointee_array_element_type - : address->type; - array_shape = fixed_array_shape_from_pointee_var(address); - fixed_array_shape_to_var(&shape, &array_shape); - dimensions = array_shape.rank; - grouped_fixed_array_dimensions = dimensions; - if (dimensions > 4) - error_at( - "Grouped fixed-array assignment supports at most four " - "dimensions", - cur_token_loc()); - while (lex_accept(T_open_square)) { - var_t *index; - var_t *scale; - var_t *offset; - var_t *next; - 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_subscript_stride(&shape, depth, - shape.type->size); - offset = index; - if (stride != 1) { - 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); - offset = require_var(parent); - offset->var_name = gen_name(); - add_insn(parent, *bb, OP_mul, offset, index, scale, 0, - NULL); - } - next = require_typed_ptr_var(parent, shape.type, 1); - next->var_name = gen_name(); - next->is_const_qualified = object_address->is_const_qualified || - shape.type->is_const_qualified; - add_insn(parent, *bb, OP_add, next, address, offset, 0, NULL); - address = next; - depth++; + 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(); } - if (depth != dimensions) - error_at("Grouped fixed-array assignment needs all subscripts", + } 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; @@ -5318,14 +5101,6 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) if (!lex_accept(T_assign) && !accept_compound_assign_op(&compound_op)) error_at("Expected assignment after pointer dereference", cur_token_loc()); - if (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && - compound_op != OP_add && compound_op != OP_sub && - compound_op != OP_mul && compound_op != OP_div && - compound_op != OP_mod && compound_op != OP_lshift && - compound_op != OP_rshift && compound_op != OP_bit_and && - compound_op != OP_bit_xor && compound_op != OP_bit_or) - error_at("Unsupported rank-three/four grouped compound assignment", - cur_token_loc()); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); @@ -5333,15 +5108,6 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) } value = opstack_pop(); - if (grouped_fixed_array_dimensions >= 3 && compound_op != OP_generic && - (value_ptr_level != 0 || is_record_type(value_type) || - value_type->is_floating || value->is_func || - is_pointer_like_value(value) || is_record_type(value->type) || - value->type->is_floating)) - error_at( - "Invalid rank-three/four grouped compound assignment operand", - cur_token_loc()); - if (compound_op != OP_generic) { var_t *current; if (field && is_bitfield(field)) { @@ -5412,8 +5178,10 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) */ if (!lex_peek(T_identifier, token)) return false; + if (lvalue_paren_depth) + var = find_var(token, parent); - if (!read_body_assignment(token, parent, OP_generic, bb, &result, + if (!read_body_assignment(var, parent, OP_generic, bb, &result, lvalue_paren_depth)) return false; if (!result) diff --git a/src/parser-global.c b/src/parser-global.c index c1d25047..b0035f5e 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -24,7 +24,7 @@ void read_global_init_var(var_t *var, block_t *block) 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, true); + 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)) @@ -345,7 +345,7 @@ void parse_global_record_init(var_t *var, block_t *block) record_type = record_type->base_struct; lex_expect(T_open_curly); - parse_struct_field_init(block, &GLOBAL_FUNC->bbs, record_type, var, true); + parse_struct_field_init(block, &GLOBAL_FUNC->bbs, record_type, var); lex_expect(T_close_curly); global_constant_initializer_scope = saved_initializer_scope; } @@ -507,7 +507,7 @@ void parse_global_compound_array_init(var_t *var, block_t *block) 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, true); + parse_array_init(array, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); add_insn(block, GLOBAL_FUNC->bbs, OP_assign, var, array, NULL, 0, NULL); return; } @@ -581,7 +581,7 @@ void parse_global_compound_array_init(var_t *var, block_t *block) add_insn(GLOBAL_BLOCK, GLOBAL_FUNC->bbs, OP_allocat, array, NULL, NULL, 0, NULL); - parse_array_init(array, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + parse_array_init(array, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); add_insn(block, GLOBAL_FUNC->bbs, OP_assign, var, array, NULL, 0, NULL); } @@ -627,7 +627,7 @@ bool read_global_record_declarator(block_t *block, 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, true); + 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); @@ -799,124 +799,10 @@ static const type_t *read_global_typedef_base(bool *typedef_const, bool *typedef_volatile) { char base_type[MAX_ID_LEN]; - const type_t *base; - bool is_signed = false; - bool is_unsigned = false; - bool is_long = false; - bool is_long_long = false; - type_t *leading_scalar_type = NULL; - - if (lex_peek(T_identifier, base_type) && - (!strcmp(base_type, "char") || !strcmp(base_type, "short") || - !strcmp(base_type, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && - (after_base->kind == T_signed || after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(base_type, true); - } - } + const type_t *base = + read_scalar_type_specifiers(typedef_const, typedef_volatile, NULL); - /* Typedef declarations use the same freely ordered scalar specifier set as - * object declarations. Keeping this in a loop admits C99 spellings such as - * `long unsigned long` rather than treating the second specifier as the - * typedef name. - */ - while (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)) { - if (lex_accept(T_const)) - *typedef_const = true; - else if (lex_accept(T_volatile)) - *typedef_volatile = true; - else 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 { - lex_expect(T_long); - if (is_long_long) - error_at("too many long type specifiers", cur_token_loc()); - if (is_long) - is_long_long = true; - else - is_long = true; - } - } - if (is_signed && is_unsigned) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && lex_peek(T_identifier, base_type) && - !strcmp(base_type, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - - bool is_float = lex_accept(T_float); - bool is_double = lex_accept(T_double); - - if (is_float) { - if (is_signed || is_unsigned || is_long) - error_at("invalid float type specifiers", cur_token_loc()); - base = TY_float; - } else if (is_double) { - if (is_signed || is_unsigned || is_long_long) - error_at("invalid double type specifiers", cur_token_loc()); - base = is_long ? TY_long_double : TY_double; - } else if (is_long) { - if (lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) - lex_expect(T_identifier); - if (is_long_long) - base = is_unsigned ? TY_ulong_long : TY_long_long; - else - base = is_unsigned ? TY_ulong : TY_long; - } else if (is_unsigned) { - if (leading_scalar_type == TY_char) { - base = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) - lex_expect(T_identifier); - base = TY_ushort; - } else { - if (lex_peek(T_identifier, base_type) && - (!strcmp(base_type, "int") || !strcmp(base_type, "char") || - !strcmp(base_type, "short"))) { - lex_expect(T_identifier); - if (!strcmp(base_type, "char")) - base = TY_uchar; - else if (!strcmp(base_type, "short")) - base = TY_ushort; - else - base = TY_uint; - } else { - base = TY_uint; - } - } - } else if (is_signed && lex_peek(T_identifier, base_type) && - (!strcmp(base_type, "char") || !strcmp(base_type, "short") || - !strcmp(base_type, "int"))) { - lex_expect(T_identifier); - base = !strcmp(base_type, "char") - ? TY_schar - : (!strcmp(base_type, "short") ? TY_short : TY_int); - } else if (is_signed && (leading_scalar_type == TY_char || - leading_scalar_type == TY_short)) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, base_type) && !strcmp(base_type, "int")) - lex_expect(T_identifier); - base = leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; - } else if (is_signed && - (!lex_peek(T_identifier, base_type) || - (strcmp(base_type, "int") && strcmp(base_type, "char") && - strcmp(base_type, "short")))) { - base = TY_int; - } else if (leading_scalar_type) { - base = leading_scalar_type; - } else { + if (!base) { lex_ident(T_identifier, base_type); base = find_type(base_type, true); } @@ -1127,19 +1013,15 @@ static void read_global_typedef_declarator(block_t *block, } /* The declarator list of a file-scope typedef whose specifier resolved to - * @base, with the qualifiers read so far, through its terminating semicolon. - * The specifier and its qualifiers apply to each declarator. + * @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) { - /* `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); do { read_global_typedef_declarator(block, typedef_const, typedef_volatile, base); @@ -1148,9 +1030,9 @@ static void read_global_typedef_declarators(block_t *block, } /* The record that a later declarator of a file-scope record typedef derives - * from: the @first alias when it names the record itself, or else a nameless - * copy of that alias without its pointer, @size bytes wide and aligned to - * @alignment. + * 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, @@ -1158,6 +1040,8 @@ static const type_t *global_record_typedef_base(type_t *first, { if (!first->ptr_level) return first; + if (first->base_struct) + return first->base_struct; type_t *record = add_type(); @@ -1170,64 +1054,8 @@ static const type_t *global_record_typedef_base(type_t *first, return record; } -/* A file-scope enum specifier after its keyword: a reference to a known 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 and may use the same integer constant expressions accepted for - * array bounds and case labels. - * - * Returns the enum type. - */ -static type_t *read_global_enum_specifier(block_t *block) -{ - char token[MAX_ID_LEN]; - int val = 0; - bool has_tag = false; - type_t *type; - - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - has_tag = true; - } - if (!lex_peek(T_open_curly, NULL)) { - if (!has_tag) - error_at("Expected enum tag or definition", cur_token_loc()); - return reference_enum_tag(token, GLOBAL_BLOCK); - } - - /* Only a definition creates an enum tag, so one already declared here would - * be defined twice (C99 6.7.2.3p1). - */ - if (has_tag && local_enum_tag(token, GLOBAL_BLOCK)) - error_at("redefinition of enum tag", cur_token_loc()); - type = add_type(); - - initialize_enum_type(type); - - /* Register the tag at file scope as well, so that a struct or union - * specifier reusing the name, in any scope, sees an enum tag. - */ - if (has_tag) { - set_type_name(type, token); - add_type_tag(GLOBAL_BLOCK, 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(block); - add_constant(token, val); - first = false; - } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); - lex_expect(T_close_curly); - return type; -} - /* Whether the first declarator of a file-scope record typedef is only stars and - * a name, which the record branches below complete in place. Any other form, + * 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. */ @@ -1242,21 +1070,6 @@ static bool global_record_typedef_declarator_is_plain(void) token->next->kind != T_open_bracket; } -/* Read the declarators of a file-scope record typedef whose first declarator is - * not plain, deriving each from @record, the completed or referenced record. - */ -static void read_global_record_typedef_declarators(block_t *block, - bool typedef_const, - bool typedef_volatile, - const type_t *record) -{ - do { - read_global_typedef_declarator(block, typedef_const, typedef_volatile, - record); - } while (lex_accept(T_comma)); - lex_expect(T_semicolon); -} - /* A file-scope typedef, after its keyword: record, enum, callback pointer and * scalar aliases, each with a comma-separated declarator list. */ @@ -1271,245 +1084,90 @@ static void read_global_typedef(block_t *block) * which every declaration spelled with it then inherits. */ read_type_qualifiers(&typedef_const, &typedef_volatile, false); - if (lex_accept(T_enum)) { - /* The alias may name an existing tag, define a tagged or untagged enum, - * and derive a pointer or array from it like any other base. - */ - type_t *type = read_global_enum_specifier(block); + 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; - read_global_typedef_declarators(block, typedef_const, typedef_volatile, - type); - } else if (lex_accept(T_struct)) { - int i = 0, size = 0, alignment = 1; - bitfield_layout_t bits = {0}; - bool has_struct_def = false; - bool has_flexible_array_member = false; - type_t *tag = NULL, *type = add_type(); - - /* is struct definition? */ - if (lex_peek(T_identifier, token)) { + if (has_tag) lex_expect(T_identifier); - tag = local_record_tag(token, GLOBAL_BLOCK, TYPE_struct); - } - - /* typedef with struct definition */ - if (lex_accept(T_open_curly)) { - has_struct_def = true; - if (tag && tag->num_fields) - error_at("redefinition of struct or union tag", - cur_token_loc()); - do { - var_t *v = type_add_field(type, &i); - var_t *last = v; - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, block); - reject_flexible_array_member_container(v); - mark_flexible_array_member(v, false); - size = is_bitfield(v) - ? layout_bitfield_field(size, v, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), v, - &alignment); - - /* Handle multiple variable declarations with same base type */ - while (lex_accept(T_comma)) { - if (last->is_flexible_array_member) - error_at( - "Flexible array member must be the final " - "struct member", - cur_token_loc()); - var_t *nv = type_add_field(type, &i); - initialize_struct_field(nv, v, 0); - read_inner_var_decl(nv, false, false, true); - read_bitfield_width(nv, block); - reject_flexible_array_member_container(nv); - mark_flexible_array_member(nv, false); - last = nv; - size = - is_bitfield(nv) - ? layout_bitfield_field(size, nv, &alignment, &bits) - : layout_struct_field( - flush_bitfield_layout(size, &bits), nv, - &alignment); - } - - lex_expect(T_semicolon); - if (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)); - } + /* 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)) { + while (is_plain && lex_accept(T_asterisk)) type->ptr_level++; - type->size = PTR_SIZE; - } if (is_plain) lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); - size = flush_bitfield_layout(size, &bits); - type->alignment = type->ptr_level ? PTR_SIZE : alignment; - type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(size, alignment); - type->num_fields = i; - type->base_type = TYPE_typedef; - type->has_flexible_array_member = has_flexible_array_member; - - 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; - } - - /* Only the alias is qualified; the tag copy above must stay plain. */ - type->is_const_qualified = typedef_const; - type->is_volatile_qualified = typedef_volatile; - - if (!is_plain) { - read_global_record_typedef_declarators( - block, typedef_const, typedef_volatile, tag ? tag : type); - return; - } - - /* Later declarators derive from the record as a typedef of it would. */ - if (lex_accept(T_comma)) { - const type_t *record = global_record_typedef_base( - type, ALIGN_UP(size, alignment), alignment); - - do { - read_global_typedef_declarator(block, typedef_const, - typedef_volatile, record); - } while (lex_accept(T_comma)); - } - lex_expect(T_semicolon); - } else if (lex_accept(T_union)) { - int i = 0, max_size = 0, alignment = 1; - bool has_union_def = false; - bool has_flexible_array_member = false; - type_t *tag = NULL, *type = add_type(); - - /* is union definition? */ - if (lex_peek(T_identifier, token)) { - lex_expect(T_identifier); - - tag = local_record_tag(token, GLOBAL_BLOCK, TYPE_union); - } - /* typedef with union definition */ - if (lex_accept(T_open_curly)) { - has_union_def = true; - if (tag && tag->num_fields) - error_at("redefinition of struct or union tag", - cur_token_loc()); - do { - var_t *v = type_add_field(type, &i); - read_full_var_decl(v, false, false, true); - read_bitfield_width(v, block); - has_flexible_array_member |= - is_flexible_array_member_container(v); - mark_flexible_array_member(v, true); - v->offset = 0; /* All union fields start at offset 0 */ - int field_size = is_bitfield(v) - ? (v->bit_width ? v->bit_storage_size : 0) - : size_var(v); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(v) > alignment) - alignment = alignment_var(v); - - /* 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, false, true); - read_bitfield_width(nv, block); - has_flexible_array_member |= - is_flexible_array_member_container(nv); - mark_flexible_array_member(nv, true); - field_size = - is_bitfield(nv) - ? (nv->bit_width ? nv->bit_storage_size : 0) - : size_var(nv); - if (field_size > max_size) - max_size = field_size; - if (alignment_var(nv) > alignment) - alignment = alignment_var(nv); - } - - lex_expect(T_semicolon); - } while (!lex_accept(T_close_curly)); - } + /* 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; - 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++; - type->size = PTR_SIZE; - } - if (is_plain) - lex_ident_n(T_identifier, type->type_name, MAX_TYPE_LEN); type->alignment = type->ptr_level ? PTR_SIZE : alignment; - type->size = type->ptr_level ? PTR_SIZE : ALIGN_UP(max_size, alignment); - type->num_fields = i; - type->base_type = TYPE_typedef; - type->is_union = true; - type->has_flexible_array_member = has_flexible_array_member; - - 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->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_record_typedef_declarators( - block, typedef_const, typedef_volatile, tag ? tag : type); - return; - } - - if (lex_accept(T_comma)) { - const type_t *record = global_record_typedef_base( - type, ALIGN_UP(max_size, alignment), alignment); - - do { - read_global_typedef_declarator(block, typedef_const, - typedef_volatile, record); - } while (lex_accept(T_comma)); - } - lex_expect(T_semicolon); + 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 = - read_global_typedef_base(&typedef_const, &typedef_volatile); + 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); } @@ -1614,21 +1272,19 @@ void read_global_statement(void) if (!lex_accept(T_semicolon)) read_global_declarator_list(block, type, is_const, is_static, is_volatile, is_extern, true); - } else if (lex_accept(T_enum)) { - bool is_reference = !lex_peek(T_open_curly, NULL) && - !(cur_token->next && cur_token->next->next && - cur_token->next->next->kind == T_open_curly); - type_t *type = read_global_enum_specifier(block); + } 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_reference && lex_peek(T_semicolon, NULL)) + if (!is_definition && lex_peek(T_semicolon, NULL)) error_at( "enum declaration without an enumerator list declares " "nothing", next_token_loc()); - if (is_reference || !lex_accept(T_semicolon)) + 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)) { @@ -1949,106 +1605,12 @@ void parse_internal(void) cur_token_loc()); } -/* Whether @tk may continue a run of declaration specifiers around a scalar type - * keyword. `char`, `short` and `int` reach the parser as identifiers. - */ -static bool is_scalar_specifier_run_token(const token_t *tk) -{ - switch (tk->kind) { - case T_signed: - case T_unsigned: - case T_long: - case T_const: - case T_volatile: - case T_restrict: - case T_inline: - case T_static: - case T_extern: - case T_register: - case T_auto: - case T_typedef: - return true; - case T_identifier: - return !strcmp(tk->literal, "int") || !strcmp(tk->literal, "short") || - !strcmp(tk->literal, "char"); - default: - return false; - } -} - -/* C99 6.7.2p2 lets the scalar type words appear in any order among the other - * declaration specifiers, and lets `int` be spelled beside `short` or `long`: - * `short int`, `int long unsigned` and `long int long` name the same types as - * `short`, `unsigned long` and `long long`. The declaration readers take a type - * word only as the base after the modifiers, so normalize every run once, - * before parsing, rather than teaching each reader every spelling: drop that - * redundant `int`, and move a `char` or `short` base after the last `signed` or - * `unsigned`. A run with a second base keeps its words and is still rejected. - */ -static void normalize_scalar_specifiers(token_t *head) -{ - token_t *prev = head; - - while (prev->next) { - token_t *before_int = NULL, *before_base = NULL, *last_sign = NULL; - int ints = 0, widths = 0, bases = 0; - token_t *last = prev; - - while (last->next && is_scalar_specifier_run_token(last->next)) { - token_t *tk = last->next; - - if (tk->kind == T_long) - widths++; - else if (tk->kind == T_signed || tk->kind == T_unsigned) - last_sign = tk; - else if (tk->kind == T_identifier && tk->literal[0] == 'i') { - ints++; - before_int = last; - } else if (tk->kind == T_identifier) { - widths += tk->literal[0] == 's'; - bases++; - before_base = last; - } - last = tk; - } - if (last == prev) { - prev = prev->next; - continue; - } - if (ints == 1 && widths && bases <= 1) { - token_t *int_token = before_int->next; - - before_int->next = int_token->next; - if (last == int_token) - last = before_int; - if (before_base == int_token) - before_base = before_int; - } - if (bases == 1 && ints <= 1 && last_sign) { - token_t *base = before_base->next; - bool sign_follows = false; - - for (token_t *tk = base->next; tk != last->next; tk = tk->next) - sign_follows |= tk == last_sign; - if (sign_follows) { - before_base->next = base->next; - base->next = last_sign->next; - last_sign->next = base; - if (last == last_sign) - last = base; - } - } - prev = last; - } -} - void parse(token_t *tk) { token_t head; head.kind = T_start; head.next = tk; cur_token = &head; - normalize_scalar_specifiers(&head); parse_internal(); } diff --git a/src/parser-init.c b/src/parser-init.c index d1dfea8a..f602ce83 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -80,7 +80,8 @@ var_t *compute_field_address(block_t *parent, */ bool is_record_object(const var_t *var) { - return var && !var->ptr_level && !var->array_size && var->type && + 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 && @@ -117,6 +118,22 @@ void emit_record_copy_between(block_t *parent, } } +/* 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. */ @@ -125,10 +142,8 @@ void emit_record_copy_to_address(block_t *parent, var_t *dest_addr, var_t *src) { - var_t *src_addr = require_ref_var(parent, src->type, 0); + var_t *src_addr = record_value_address(parent, *bb, src); - src_addr->var_name = gen_name(); - add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); emit_record_copy_between(parent, bb, dest_addr, src_addr, size_var(src)); } @@ -138,12 +153,11 @@ void emit_record_copy(block_t *parent, var_t *src) { var_t *dest_addr = require_ref_var(parent, dest->type, 0); - var_t *src_addr = require_ref_var(parent, src->type, 0); + var_t *src_addr; dest_addr->var_name = gen_name(); - src_addr->var_name = gen_name(); add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); - add_insn(parent, *bb, OP_address_of, src_addr, src, NULL, 0, NULL); + src_addr = record_value_address(parent, *bb, src); emit_record_copy_between(parent, bb, dest_addr, src_addr, size_var(dest)); } @@ -383,12 +397,21 @@ var_t *read_global_address_designator(block_t *scope, 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; +} + var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) { var_t *val = NULL; - block_t *scope = global_constant_initializer_scope - ? global_constant_initializer_scope - : parent; + block_t *scope = initializer_name_scope(parent); bool address_dereference = global_function_address_dereference_starts_here(); token_t *address_dereference_identifier = NULL; @@ -518,37 +541,6 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) return val; } -/* Skip one static initializer value without lowering it: every token up to the - * ',' or '}' that ends it, outside the parentheses and brackets it contains. - * The value is read for real, and diagnosed, when its code is emitted. - */ -void consume_global_constant_syntax(void) -{ - int depth = 0; - bool consumed = false; - - while (cur_token->next) { - token_kind_t kind = cur_token->next->kind; - - if (kind == T_open_curly || - (!depth && - (kind == T_comma || kind == T_close_curly || kind == T_semicolon))) - break; - if (kind == T_open_bracket || kind == T_open_square) { - depth++; - } else if (kind == T_close_bracket || kind == T_close_square) { - if (!depth) - break; - depth--; - } - lex_next(); - consumed = true; - } - if (!consumed) - error_at("Global array initialization requires constant values", - next_token_loc()); -} - bool is_record_type(const type_t *type) { return type && @@ -559,28 +551,50 @@ bool is_record_type(const type_t *type) void parse_struct_field_init(block_t *parent, basic_block_t **bb, type_t *struct_type, - var_t *target_addr, - bool emit_code); + 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 emit_code, bool elided, var_t *first_value); bool parse_unbraced_record_init(block_t *parent, basic_block_t **bb, type_t *record_type, - var_t *addr, - bool emit_code); + var_t *addr); 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, - bool emit_code); + 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, @@ -594,34 +608,80 @@ void emit_zero_elements(block_t *parent, if (from >= to) return; - 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); - for (int i = from; i < to; i++) { - var_t *elem_addr = - compute_element_address(parent, bb, base, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } + 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) { - int index; + var_t slots = {0}; - if (!lex_accept(T_open_square)) + slots.array_size = bound; + if (!read_array_designator(scope, &slots, 1, false, slot, NULL)) return false; - index = read_const_expr(scope); - lex_expect(T_close_square); - if (index < 0 || index >= bound) - error_at("Array designator index is out of bounds", cur_token_loc()); lex_expect(T_assign); - *slot = index; return true; } @@ -638,14 +698,12 @@ bool parse_array_field_row_values(block_t *parent, const var_t *field, var_t *target_addr, int start, - bool emit_code, bool braced) { int count = 0; int filled = 0; bool comma_consumed = false; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + int elem_size = array_element_size(field); if (braced) { lex_expect(T_open_curly); @@ -654,7 +712,7 @@ bool parse_array_field_row_values(block_t *parent, /* 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, emit_code); + parse_string_row_init(parent, bb, field, target_addr, start); if (braced) { lex_accept(T_comma); lex_expect(T_close_curly); @@ -666,42 +724,34 @@ bool parse_array_field_row_values(block_t *parent, var_t *elem_addr; if (braced) - accept_slot_designator(field->scope ? field->scope : parent, + 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()); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, start + filled, - start + count, elem_size); + 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 = field->type; - if (record_type->base_type == TYPE_typedef && - record_type->base_struct) - record_type = record_type->base_struct; + type_t *record_type = resolve_record_type(field->type); lex_expect(T_open_curly); - parse_struct_field_init(parent, bb, record_type, elem_addr, - emit_code); + 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, emit_code); + comma_consumed = + parse_unbraced_record_init(parent, bb, field->type, elem_addr); } else if (parent == GLOBAL_BLOCK) { - if (emit_code) - value = parse_global_constant_value(parent, bb); - else - consume_global_constant_syntax(); + value = parse_global_constant_value(parent, bb); } else { read_expr(parent, bb); read_ternary_operation(parent, bb); value = opstack_pop(); } - if (value && emit_code) { + if (value) { var_t *stored = value->is_func ? value : resize_to(parent, bb, value, field->type, @@ -732,9 +782,8 @@ bool parse_array_field_row_values(block_t *parent, comma_consumed = false; } - if (emit_code) - emit_zero_elements(parent, bb, target_addr, start + filled, - start + field->array_dim2, elem_size); + emit_zero_elements(parent, bb, target_addr, start + filled, + start + field->array_dim2, elem_size); return comma_consumed; } @@ -742,11 +791,9 @@ void parse_array_field_row_init(block_t *parent, basic_block_t **bb, const var_t *field, var_t *target_addr, - int start, - bool emit_code) + int start) { - parse_array_field_row_values(parent, bb, field, target_addr, start, - emit_code, true); + parse_array_field_row_values(parent, bb, field, target_addr, start, true); } /* Parse one braced plane of a three-dimensional array. Rows reuse the @@ -757,15 +804,13 @@ void parse_array_field_plane_values(block_t *parent, const var_t *field, var_t *target_addr, int start, - bool emit_code, bool braced) { var_t row; int rows = 0; int filled = 0; int row_width = field->array_dim3; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + int elem_size = array_element_size(field); memcpy(&row, field, sizeof(row)); row.array_dim2 = row_width; @@ -775,16 +820,14 @@ void parse_array_field_plane_values(block_t *parent, reject_empty_initializer_in_strict_c99(); } while (!lex_peek(T_close_curly, NULL)) { - accept_slot_designator(field->scope ? field->scope : parent, + 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()); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, - start + filled * row_width, - start + rows * row_width, elem_size); + 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, emit_code, + parent, bb, &row, target_addr, start + rows * row_width, lex_peek(T_open_curly, NULL)); rows++; if (rows > filled) @@ -805,20 +848,17 @@ void parse_array_field_plane_values(block_t *parent, if (braced) lex_expect(T_close_curly); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, start + filled * row_width, - start + field->array_dim2 * row_width, elem_size); + 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, - bool emit_code) + int start) { - parse_array_field_plane_values(parent, bb, field, target_addr, start, - emit_code, true); + parse_array_field_plane_values(parent, bb, field, target_addr, start, true); } /* Parse one braced outer slice of a four-dimensional array. Each contained @@ -829,15 +869,13 @@ void parse_array_field_hyperplane_init(block_t *parent, basic_block_t **bb, const var_t *field, var_t *target_addr, - int start, - bool emit_code) + int start) { var_t plane; int planes = 0; int filled = 0; int plane_size = field->array_dim3 * field->array_dim4; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + int elem_size = array_element_size(field); memcpy(&plane, field, sizeof(plane)); plane.array_size = plane_size; @@ -847,16 +885,14 @@ void parse_array_field_hyperplane_init(block_t *parent, lex_expect(T_open_curly); reject_empty_initializer_in_strict_c99(); while (!lex_peek(T_close_curly, NULL)) { - accept_slot_designator(field->scope ? field->scope : parent, + 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()); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, - start + filled * plane_size, - start + planes * plane_size, elem_size); + 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, emit_code, + start + planes * plane_size, lex_peek(T_open_curly, NULL)); planes++; if (planes > filled) @@ -866,9 +902,8 @@ void parse_array_field_hyperplane_init(block_t *parent, } lex_expect(T_close_curly); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, start + filled * plane_size, - start + field->array_dim2 * plane_size, elem_size); + 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 @@ -908,8 +943,7 @@ int read_concatenated_string(char *combined) void parse_string_field_init(block_t *parent, basic_block_t **bb, const var_t *field, - var_t *target_addr, - bool emit_code) + var_t *target_addr) { char combined[MAX_STRING_LEN]; int len; @@ -921,8 +955,6 @@ void parse_string_field_init(block_t *parent, if (len - 1 > field->array_size) error_at("String initializer is too long for character array", cur_token_loc()); - if (!emit_code) - return; for (int i = 0; i < field->array_size; i++) { var_t *value = require_var(parent); @@ -970,8 +1002,7 @@ void validate_string_array_initializer(const var_t *var) void parse_wstring_field_init(block_t *parent, basic_block_t **bb, const var_t *field, - var_t *target_addr, - bool emit_code) + var_t *target_addr) { int values[MAX_STRING_LEN]; int length; @@ -980,8 +1011,6 @@ void parse_wstring_field_init(block_t *parent, if (length > field->array_size) error_at("Wide string initializer is too long for array", cur_token_loc()); - if (!emit_code) - return; for (int i = 0; i < field->array_size; i++) { var_t *value = require_typed_var(parent, field->type); @@ -1043,12 +1072,11 @@ void parse_string_row_init(block_t *parent, basic_block_t **bb, const var_t *array, var_t *target_addr, - int start, - bool emit_code) + int start) { var_t row; - var_t *row_addr = NULL; int width = string_row_width(array); + var_t *row_addr; if (start % width) error_at("String literal cannot initialize a single array element", @@ -1056,20 +1084,18 @@ void parse_string_row_init(block_t *parent, memcpy(&row, array, sizeof(row)); row.array_size = width; row.array_dim2 = row.array_dim3 = row.array_dim4 = 0; - if (emit_code) - row_addr = compute_element_address(parent, bb, target_addr, start, - array->type->size); + 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, emit_code); + parse_wstring_field_init(parent, bb, &row, row_addr); else - parse_string_field_init(parent, bb, &row, row_addr, emit_code); + 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, - bool emit_code) + var_t *target_addr) { int count = 0; @@ -1078,8 +1104,7 @@ void parse_array_field_init(block_t *parent, * clear rows that an earlier designator already stored. */ int filled = 0; - int elem_size = - (field->ptr_level || field->is_func) ? PTR_SIZE : field->type->size; + int elem_size = array_element_size(field); lex_expect(T_open_curly); reject_empty_initializer_in_strict_c99(); @@ -1095,77 +1120,49 @@ void parse_array_field_init(block_t *parent, * the shape the designator names and reordered rows stay valid. */ memcpy(&slice, field, sizeof(slice)); - if (lex_accept(T_open_square)) { - var_t element; - - memcpy(&element, field, sizeof(element)); - count = 0; - do { - int stride = fixed_array_inner_count(&element); - int index = - read_const_expr(field->scope ? field->scope : parent); - - lex_expect(T_close_square); - if (index < 0 || index >= element.array_size / stride) - error_at("Array designator index is out of bounds", - cur_token_loc()); - count += index * stride; - memcpy(&slice, &element, sizeof(slice)); - fixed_array_var_drop_outer(&element); - } while (element.array_size && lex_accept(T_open_square)); + 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()); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, filled, count, - elem_size); + 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, emit_code); + 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, - emit_code); + 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, - emit_code); + 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, - emit_code); + 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 = field->type; - if (record_type->base_type == TYPE_typedef && - record_type->base_struct) - record_type = record_type->base_struct; + type_t *record_type = resolve_record_type(field->type); lex_expect(T_open_curly); - parse_struct_field_init(parent, bb, record_type, elem_addr, - emit_code); + 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, emit_code); + parent, bb, field->type, elem_addr); } else if (parent == GLOBAL_BLOCK) { - if (emit_code) - value = parse_global_constant_value(parent, bb); - else - consume_global_constant_syntax(); + value = parse_global_constant_value(parent, bb); } else { read_expr(parent, bb); read_ternary_operation(parent, bb); value = opstack_pop(); } - if (value && emit_code) { + if (value) { var_t *stored = value->is_func ? value : resize_to(parent, bb, value, field->type, @@ -1183,16 +1180,8 @@ void parse_array_field_init(block_t *parent, } lex_expect(T_close_curly); - if (emit_code) - emit_zero_elements(parent, bb, target_addr, filled, field->array_size, - elem_size); -} - -type_t *resolve_record_type(type_t *type) -{ - if (type->base_type == TYPE_typedef && type->base_struct) - return type->base_struct; - return type; + 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 @@ -1208,18 +1197,16 @@ bool parse_record_from_value(block_t *parent, basic_block_t **bb, type_t *record_type, var_t *addr, - bool emit_code, var_t *value) { record_type = resolve_record_type(record_type); if (is_record_object(value) && resolve_record_type(value->type) == record_type) { - if (emit_code) - emit_record_copy_to_address(parent, bb, addr, value); + emit_record_copy_to_address(parent, bb, addr, value); return false; } - return parse_struct_field_values(parent, bb, record_type, addr, emit_code, - true, value); + return parse_struct_field_values(parent, bb, record_type, addr, true, + value); } /* A record slot whose initializer does not begin with a brace. Reading a block @@ -1231,8 +1218,7 @@ bool parse_record_from_value(block_t *parent, bool parse_unbraced_record_init(block_t *parent, basic_block_t **bb, type_t *record_type, - var_t *addr, - bool emit_code) + var_t *addr) { var_t *value = NULL; @@ -1242,8 +1228,7 @@ bool parse_unbraced_record_init(block_t *parent, read_ternary_operation(parent, bb); value = opstack_pop(); } - return parse_record_from_value(parent, bb, record_type, addr, emit_code, - value); + return parse_record_from_value(parent, bb, record_type, addr, value); } /* Whether an initializer starting at the next token for a slot of @elem's type @@ -1257,14 +1242,41 @@ bool unbraced_record_starts_here(const var_t *elem) !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, - bool emit_code) + var_t *target_addr) { - parse_struct_field_values(parent, bb, struct_type, target_addr, emit_code, - false, NULL); + parse_struct_field_values(parent, bb, struct_type, target_addr, false, + NULL); } /* Parse the members of the record at @target_addr. A braced list runs to its @@ -1277,7 +1289,6 @@ bool parse_struct_field_values(block_t *parent, basic_block_t **bb, type_t *struct_type, var_t *target_addr, - bool emit_code, bool elided, var_t *first_value) { @@ -1316,24 +1327,16 @@ bool parse_struct_field_values(block_t *parent, * initializers could defer this until the first omitted member, but a * designator may skip forward or return to an earlier member. */ - if (emit_code && parent != GLOBAL_BLOCK && - struct_type->base_type != TYPE_union && !struct_type->is_union) { - 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); + if (parent != GLOBAL_BLOCK && !is_union) { + var_t *zero = emit_zero_constant(parent, bb); for (int i = 0; i < struct_type->num_fields; i++) { var_t *field = &struct_type->fields[i]; - var_t *field_addr = - compute_field_address(parent, bb, target_addr, field); - int field_size = size_var(field); - - for (int offset = 0; offset < field_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, field_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } + + emit_zero_bytes( + parent, bb, + compute_field_address(parent, bb, target_addr, field), + size_var(field), zero); } } @@ -1341,7 +1344,7 @@ bool parse_struct_field_values(block_t *parent, for (;;) { var_t *field_val_raw = NULL; var_t *field = NULL; - var_t *designated_field_addr = NULL; + var_t *field_addr = NULL; bool consumed_pending_array_element = false; bool nested_comma_consumed = false; @@ -1376,67 +1379,30 @@ bool parse_struct_field_values(block_t *parent, error_at("Unknown field in record initializer", cur_token_loc()); - designated_field_addr = compute_field_address( - parent, bb, designator_base, field); - if (lex_accept(T_open_square)) { - int index, element_count, linear_index, stride; - int total_element_count; - int elem_size; - var_t *array_base_addr = designated_field_addr; + field_addr = compute_field_address(parent, bb, + designator_base, field); + 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", - cur_token_loc()); - index = read_const_expr(parent); - lex_expect(T_close_square); - total_element_count = field->array_size; - element_count = total_element_count; - stride = 1; - if (field->array_dim2) { - stride = field->array_dim2; - if (field->array_dim3) { - stride *= field->array_dim3; - if (field->array_dim4) - stride *= field->array_dim4; - } - element_count /= stride; - } - if (index < 0 || index >= element_count) - error_at("Array designator index is out of bounds", - cur_token_loc()); - elem_size = (field->ptr_level || field->is_func) - ? PTR_SIZE - : field->type->size; - linear_index = index * stride; - designated_field_addr = compute_element_address( - parent, bb, designated_field_addr, linear_index, - elem_size); - memcpy(&array_element, field, sizeof(var_t)); - fixed_array_var_drop_outer(&array_element); - field = &array_element; + next_token_loc()); - /* Each subscript leaves the remaining inner array in + /* 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. */ - while (field->array_size && lex_accept(T_open_square)) { - index = read_const_expr(parent); - lex_expect(T_close_square); - stride = field->array_dim2 ? field->array_dim2 : 1; - if (field->array_dim3) - stride *= field->array_dim3; - element_count = field->array_size / stride; - if (index < 0 || index >= element_count) - error_at( - "Array designator index is out of bounds", - cur_token_loc()); - designated_field_addr = compute_element_address( - parent, bb, designated_field_addr, - index * stride, elem_size); - linear_index += index * stride; - fixed_array_var_drop_outer(&array_element); - } + 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); if (!field->array_size) { memcpy(&pending_array_element, field, sizeof(var_t)); @@ -1452,37 +1418,25 @@ bool parse_struct_field_values(block_t *parent, if (!is_record_type(field->type)) error_at("Nested designator requires a record member", cur_token_loc()); - designator_type = field->type; - if (designator_type->base_type == TYPE_typedef && - designator_type->base_struct) - designator_type = designator_type->base_struct; - designator_base = designated_field_addr; + designator_type = resolve_record_type(field->type); + designator_base = field_addr; first = false; } lex_expect(T_assign); } else if (has_pending_array_element) { field = &pending_array_element; - designated_field_addr = compute_element_address( + field_addr = compute_element_address( parent, bb, pending_array_base, pending_array_index, pending_array_elem_size); consumed_pending_array_element = true; } - /* An unnamed bit-field is padding, not an aggregate member. It - * participates in layout but consumes no positional initializer; - * otherwise `{ low, high }` would incorrectly assign `high` to the - * padding field and leave the named field zero-initialized. - */ if (!field && !has_pending_array_element) - while (field_idx < struct_type->num_fields && - is_bitfield(&struct_type->fields[field_idx]) && - !struct_type->fields[field_idx].var_name[0]) - field_idx++; + field_idx = skip_unnamed_bitfields(struct_type, field_idx); if (field_idx >= struct_type->num_fields || - ((struct_type->base_type == TYPE_union || - struct_type->is_union) && - initializer_count > 0 && !consumed_pending_array_element)) + (is_union && initializer_count > 0 && + !consumed_pending_array_element)) error_at("Too many elements in record initializer", next_token_loc()); @@ -1495,7 +1449,7 @@ bool parse_struct_field_values(block_t *parent, * member's aggregate size instead corrupts the elements and gives * narrow backends a store width they cannot encode. */ - if (field && !designated_field_addr && !has_pending_array_element && + if (field && !field_addr && !has_pending_array_element && field->array_size && (first_value || (!lex_peek(T_open_curly, NULL) && @@ -1505,9 +1459,7 @@ bool parse_struct_field_values(block_t *parent, !lex_peek(T_wstring, NULL)))))) { pending_array_base = compute_field_address(parent, bb, target_addr, field); - pending_array_elem_size = (field->ptr_level || field->is_func) - ? PTR_SIZE - : field->type->size; + 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)); @@ -1516,7 +1468,7 @@ bool parse_struct_field_values(block_t *parent, pending_array_element.array_dim3 = 0; pending_array_element.array_dim4 = 0; field = &pending_array_element; - designated_field_addr = compute_element_address( + 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; @@ -1536,86 +1488,56 @@ bool parse_struct_field_values(block_t *parent, * record. A record member takes it on to that record's own * first member; see parse_record_from_value(). */ - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - if (!field->ptr_level && is_record_type(field->type)) nested_comma_consumed = parse_record_from_value( - parent, bb, field->type, field_addr, emit_code, + parent, bb, field->type, + member_address(parent, bb, target_addr, field, + field_addr), first_value); else field_val_raw = first_value; first_value = NULL; } else if (field && field->array_size && is_char_array(field) && lex_peek(T_string, NULL)) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - parse_string_field_init(parent, bb, field, field_addr, - emit_code); + 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)) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - parse_wstring_field_init(parent, bb, field, field_addr, - emit_code); + 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) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - parse_array_field_init(parent, bb, field, field_addr, - emit_code); + 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 = field->type; - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - - if (nested_type->base_type == TYPE_typedef && - nested_type->base_struct) - nested_type = nested_type->base_struct; + type_t *nested_type = resolve_record_type(field->type); lex_expect(T_open_curly); - parse_struct_field_init(parent, bb, nested_type, field_addr, - emit_code); + 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)) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - nested_comma_consumed = parse_unbraced_record_init( - parent, bb, field->type, field_addr, emit_code); + parent, bb, field->type, + member_address(parent, bb, target_addr, field, field_addr)); } else if (parent == GLOBAL_BLOCK) { - if (emit_code) { - field_val_raw = parse_global_constant_value(parent, bb); - } else { - consume_global_constant_syntax(); - } + 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_idx < struct_type->num_fields) { - var_t *field_addr = - designated_field_addr - ? designated_field_addr - : compute_field_address(parent, bb, target_addr, field); - + if (field_val_raw) { + field_addr = + member_address(parent, bb, target_addr, field, field_addr); if (is_record_type(field->type) && is_record_object(field_val_raw)) { emit_record_copy_to_address(parent, bb, field_addr, @@ -1684,10 +1606,7 @@ bool parse_struct_field_values(block_t *parent, * that follows to the enclosing list. */ if (elided && !has_pending_array_element) { - while (field_idx < struct_type->num_fields && - is_bitfield(&struct_type->fields[field_idx]) && - !struct_type->fields[field_idx].var_name[0]) - field_idx++; + field_idx = skip_unnamed_bitfields(struct_type, field_idx); if (field_idx >= struct_type->num_fields || is_union) { comma_consumed = nested_comma_consumed; break; @@ -1705,7 +1624,7 @@ bool parse_struct_field_values(block_t *parent, } } - if (emit_code && parent == GLOBAL_BLOCK) { + if (parent == GLOBAL_BLOCK) { for (int i = 0; i < global_bitfield_count; i++) { var_t *unit = global_bitfield_unit[i]; var_t *unit_addr = @@ -2018,10 +1937,7 @@ static bool string_ends_braced_initializer(const var_t *var, token_t *token) return token && token->kind == T_close_curly; } -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) { int count = 0; int inferred_size = 0; @@ -2045,9 +1961,7 @@ void parse_array_init(var_t *var, * first got a bogus address. `ptr_level` describes the array element type * even when its outer bound is inferred. */ - int elem_size = var->type->size; - if (var->ptr_level > 0 || var->is_func) - elem_size = PTR_SIZE; + 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 @@ -2066,29 +1980,16 @@ void parse_array_init(var_t *var, return; } - if (emit_code) - base_addr = var; + 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 && emit_code && !is_implicit) { - 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); - for (int i = 0; i < var->array_size; i++) { - var_t *elem_addr = - compute_element_address(parent, bb, base_addr, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } + 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(); @@ -2100,79 +2001,14 @@ void parse_array_init(var_t *var, int prior_count = count; bool elided_comma = false; - if (lex_accept(T_open_square)) { - int index; - int stride = 1; - int elements; - int remaining_dims = 0; - - index = read_const_expr(initializer_scope); - lex_expect(T_close_square); - if (var->array_dim2) { - stride = var->array_dim2; - remaining_dims = 1; - if (var->array_dim3) { - stride *= var->array_dim3; - remaining_dims = 2; - if (var->array_dim4) { - stride *= var->array_dim4; - remaining_dims = 3; - } - } - } - elements = var->array_size / stride; - if (index < 0 || (!is_implicit && index >= elements)) - error_at("Array designator index is out of bounds", - cur_token_loc()); - count = index * stride; - - /* A complete multidimensional designator names a scalar - * element. Keep the outer-only form on the existing braced - * slice path, but turn every complete path into the row-major - * flat offset used by the ordinary store path. - */ - while (remaining_dims && lex_accept(T_open_square)) { - index = read_const_expr(initializer_scope); - lex_expect(T_close_square); - if (remaining_dims == 3) { - elements = var->array_dim2; - stride = var->array_dim3 * var->array_dim4; - } else if (remaining_dims == 2) { - elements = var->array_dim2; - stride = var->array_dim3; - if (var->array_dim4) { - elements = var->array_dim3; - stride = var->array_dim4; - } - } else { - elements = var->array_dim2; - if (var->array_dim3) - elements = var->array_dim3; - if (var->array_dim4) - elements = var->array_dim4; - stride = 1; - } - if (index < 0 || index >= elements) - error_at("Array designator index is out of bounds", - cur_token_loc()); - count += index * stride; - remaining_dims--; - } - if (remaining_dims == 1 && - (var->array_dim3 || var->array_dim4)) { - /* A path ending one dimension short of a scalar names a - * row. Present it to the braced-row helper rather than - * asking a scalar initializer to consume `{ ... }`. - */ - int row_width = - var->array_dim4 ? var->array_dim4 : var->array_dim3; - memcpy(&designated_array, var, sizeof(designated_array)); - designated_array.array_size = row_width; - designated_array.array_dim2 = row_width; - designated_array.array_dim3 = 0; - designated_array.array_dim4 = 0; - initializer_var = &designated_array; - } + /* 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; lex_expect(T_assign); } @@ -2182,7 +2018,7 @@ void parse_array_init(var_t *var, if (initializer_var->array_dim4 && lex_peek(T_open_curly, NULL)) { parse_array_field_hyperplane_init(parent, bb, initializer_var, - base_addr, count, emit_code); + base_addr, count); count += fixed_array_inner_count(initializer_var); if (is_implicit && count > inferred_size) inferred_size = count; @@ -2192,7 +2028,7 @@ void parse_array_init(var_t *var, } else if (initializer_var->array_dim3 && lex_peek(T_open_curly, NULL)) { parse_array_field_plane_init(parent, bb, initializer_var, - base_addr, count, emit_code); + base_addr, count); count += fixed_array_inner_count(initializer_var); if (is_implicit && count > inferred_size) inferred_size = count; @@ -2202,7 +2038,7 @@ void parse_array_init(var_t *var, } else if (initializer_var->array_dim2 && lex_peek(T_open_curly, NULL)) { parse_array_field_row_init(parent, bb, initializer_var, - base_addr, count, emit_code); + base_addr, count); count += fixed_array_inner_count(initializer_var); if (is_implicit && count > inferred_size) inferred_size = count; @@ -2210,11 +2046,11 @@ void parse_array_init(var_t *var, break; continue; } else if (string_row_starts_here(initializer_var)) { - if (is_implicit && emit_code && parent != GLOBAL_BLOCK) + if (is_implicit && parent != GLOBAL_BLOCK) emit_zero_elements(parent, bb, base_addr, inferred_size, count, elem_size); parse_string_row_init(parent, bb, initializer_var, base_addr, - count, emit_code); + count); count += string_row_width(initializer_var); if (is_implicit && count > inferred_size) inferred_size = count; @@ -2223,38 +2059,14 @@ void parse_array_init(var_t *var, continue; } 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; - - 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 if (emit_code && unbraced_record_starts_here(var)) { + 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 @@ -2267,7 +2079,7 @@ void parse_array_init(var_t *var, emit_zero_elements(parent, bb, base_addr, inferred_size, count, elem_size); elided_comma = parse_unbraced_record_init(parent, bb, var->type, - elem_addr, emit_code); + elem_addr); } else { /* A global initializer is restricted to simple constants, but * it still has to be stored. Consuming the tokens and dropping @@ -2359,10 +2171,7 @@ void parse_array_init(var_t *var, if (parent == GLOBAL_BLOCK && object_constant && object_constant->is_global && object_constant->array_size) { - int stride = (object_constant->ptr_level || - object_constant->is_func) - ? PTR_SIZE - : object_constant->type->size; + int stride = array_element_size(object_constant); /* Array-to-pointer conversion is a permitted * address constant in static aggregate @@ -2433,7 +2242,7 @@ void parse_array_init(var_t *var, cur_token_loc()); } - if (val && emit_code && (is_implicit || count < var->array_size)) { + 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. @@ -2450,24 +2259,9 @@ void parse_array_init(var_t *var, * known bound yet, so zero just the newly skipped elements. * Global storage begins zeroed. */ - if (is_implicit && parent != GLOBAL_BLOCK && - count > prior_count) { - 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); - for (int i = prior_count; i < count; i++) { - var_t *gap_addr = compute_element_address( - parent, bb, base_addr, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = compute_element_address( - parent, bb, gap_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, - zero, 1, NULL); - } - } - } + if (is_implicit && parent != GLOBAL_BLOCK) + emit_zero_elements(parent, bb, base_addr, prior_count, + count, elem_size); var_t *elem_addr = compute_element_address( parent, bb, base_addr, count, elem_size); @@ -2506,7 +2300,7 @@ void parse_array_init(var_t *var, int inner = fixed_array_inner_count(var); int whole = (inferred_size + inner - 1) / inner * inner; - if (emit_code && parent != GLOBAL_BLOCK) + if (parent != GLOBAL_BLOCK) emit_zero_elements(parent, bb, base_addr, inferred_size, whole, elem_size); inferred_size = whole; @@ -2550,31 +2344,15 @@ void parse_array_compound_literal(var_t *var, /* A designated element can leave holes before or after it, so initialize * the declared object before parsing any explicit elements. */ - if (declared_size) { - 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); - for (int i = 0; i < declared_size; i++) { - var_t *elem_addr = - compute_element_address(parent, bb, var, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = - compute_element_address(parent, bb, elem_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, 1, NULL); - } - } - } + emit_zero_elements(parent, bb, var, 0, declared_size, elem_size); if (!lex_peek(T_close_curly, NULL)) { for (;;) { bool elided_comma = false; - if (lex_accept(T_open_square)) { - count = read_const_expr(parent); - lex_expect(T_close_square); + 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()); @@ -2589,23 +2367,9 @@ void parse_array_compound_literal(var_t *var, * later backward designator cannot make a forward gap overwrite an * earlier explicit value. */ - if (!declared_size && count > inferred_size) { - 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); - for (int i = inferred_size; i < count; i++) { - var_t *gap_addr = - compute_element_address(parent, bb, var, i, elem_size); - for (int offset = 0; offset < elem_size; offset++) { - var_t *byte_addr = compute_element_address( - parent, bb, gap_addr, offset, 1); - add_insn(parent, *bb, OP_write, NULL, byte_addr, zero, - 1, NULL); - } - } - } + 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); @@ -2616,18 +2380,14 @@ void parse_array_compound_literal(var_t *var, * it as an expression rejected the opening brace and made * (struct S[]){ { ... }, { ... } } unusable. */ - type_t *record_type = var->type; - if (record_type->base_type == TYPE_typedef && - record_type->base_struct) - record_type = record_type->base_struct; + type_t *record_type = resolve_record_type(var->type); lex_expect(T_open_curly); - parse_struct_field_init(parent, bb, record_type, elem_addr, - true); + 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, true); + elem_addr); } else { read_expr(parent, bb); read_ternary_operation(parent, bb); diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 66e99c26..513af434 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -485,27 +485,6 @@ static bool sizeof_operand_tail_ends(const token_t *tail, bool parenthesized) tail->kind != T_increment && tail->kind != T_decrement; } -/* A non-mutating admission check for callers that need to hand a global sizeof - * operand to the detached local parser. It admits only a member array, so type - * names and scalar forms remain owned by their existing global constant paths. - */ -static bool can_scan_sizeof_postfix_extent(block_t *scope, - token_t *start, - bool parenthesized, - bool allow_flexible) -{ - token_t *tail = start; - var_t object; - sizeof_walk_t walk = {0}; - - return scan_sizeof_postfix_operand(scope, &tail, &object, &walk, - parenthesized) && - walk.members && sizeof_operand_tail_ends(tail, parenthesized) && - !walk.designator && !effective_pointer_depth(&object) && - (object.array_size > 0 || - (allow_flexible && object.is_flexible_array_member)); -} - /* 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 @@ -739,136 +718,15 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) } } - /* Check if this is sizeof(type) or sizeof(expression) */ - bool has_signed_type = false; - bool has_unsigned_type = false; - int long_type_count = 0; - type_t *leading_scalar_type = NULL; - - if (lex_peek(T_identifier, token) && - (!strcmp(token, "char") || !strcmp(token, "short") || - !strcmp(token, "int"))) { - token_t *after_base = cur_token->next->next; - - while (after_base && after_base->kind == T_const) - after_base = after_base->next; - if (after_base && - (after_base->kind == T_signed || after_base->kind == T_unsigned)) { - lex_expect(T_identifier); - leading_scalar_type = find_type(token, true); - } - } - while (lex_peek(T_signed, NULL) || lex_peek(T_unsigned, NULL) || - lex_peek(T_long, NULL) || lex_peek(T_const, NULL) || - lex_peek(T_volatile, NULL)) { - if (lex_accept(T_signed)) { - if (has_signed_type) - error_at("duplicate signed type specifier", cur_token_loc()); - has_signed_type = true; - } else if (lex_accept(T_unsigned)) { - if (has_unsigned_type) - error_at("duplicate unsigned type specifier", cur_token_loc()); - has_unsigned_type = true; - } else if (lex_accept(T_long)) - long_type_count++; - else if (lex_accept(T_const)) - ; - else - lex_expect(T_volatile); - } - if (long_type_count > 2) - error_at("too many long type specifiers", cur_token_loc()); - if (has_signed_type && has_unsigned_type) - error_at("both signed and unsigned specified", cur_token_loc()); - if (leading_scalar_type == TY_int && lex_peek(T_identifier, token) && - !strcmp(token, "char")) - error_at("int cannot be combined with char", cur_token_loc()); - bool has_long_type = long_type_count > 0; - bool has_enum_type = lex_accept(T_enum); - if (has_enum_type && - (has_signed_type || has_unsigned_type || has_long_type)) - error_at("enum type cannot be combined with integer specifiers", - cur_token_loc()); - base_type_t record_kind = accept_record_keyword(); - - /* `sizeof` only consumes object representation metadata, so it can admit - * the C99 floating type names before value arithmetic and ABI lowering - * exist. Keep all expression/declaration admission gates intact. + /* 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. */ - if (lex_accept(T_float)) { - if (has_signed_type || has_unsigned_type || has_long_type || - has_enum_type || record_kind) - error_at("invalid float type specifiers", cur_token_loc()); - type = TY_float; - } else if (lex_accept(T_double)) { - if (has_signed_type || has_unsigned_type || has_enum_type || - record_kind || long_type_count > 1) - error_at("invalid double type specifiers", cur_token_loc()); - type = has_long_type ? TY_long_double : TY_double; - } else if (has_enum_type) { - lex_ident(T_identifier, token); - type = reference_enum_tag(token, parent); - } else if (has_long_type) { - type = has_unsigned_type ? TY_ulong : TY_long; - if (long_type_count > 1) { - /* sizeof only consumes type metadata. It does not materialize a - * long-long value, so 32-bit targets can correctly report the - * required eight-byte object size before their paired-register - * value ABI is implemented. - */ - if (has_unsigned_type) - type = TY_ulong_long; - else - type = TY_long_long; - } - lex_accept(T_signed); - lex_accept(T_const); - if (lex_peek(T_identifier, token) && !strcmp(token, "int")) - lex_expect(T_identifier); - } else if (has_unsigned_type) { - if (leading_scalar_type == TY_char) { - type = TY_uchar; - } else if (leading_scalar_type == TY_short) { - if (lex_peek(T_identifier, token) && !strcmp(token, "int")) - lex_expect(T_identifier); - type = TY_ushort; - } else if (lex_peek(T_identifier, token) && - (!strcmp(token, "int") || !strcmp(token, "char") || - !strcmp(token, "short"))) { - lex_expect(T_identifier); - if (!strcmp(token, "char")) - type = TY_uchar; - else if (!strcmp(token, "short")) - type = TY_ushort; - else - type = TY_uint; - } else - type = TY_uint; - } else if (has_signed_type) { - if (leading_scalar_type == TY_char || leading_scalar_type == TY_short) { - if (leading_scalar_type == TY_short && - lex_peek(T_identifier, token) && !strcmp(token, "int")) - lex_expect(T_identifier); - type = - leading_scalar_type == TY_char ? TY_schar : leading_scalar_type; - } else if (lex_peek(T_identifier, token) && - (!strcmp(token, "int") || !strcmp(token, "char") || - !strcmp(token, "short"))) { - lex_expect(T_identifier); - type = !strcmp(token, "char") ? TY_schar : find_type(token, true); - } else - type = TY_int; - } else if (leading_scalar_type) { - type = leading_scalar_type; - } else if (lex_peek(T_identifier, token)) { - /* Try to parse as a type first */ - type = record_kind ? find_record_tag(token, parent, record_kind) - : find_visible_type(token, parent); - if (type) { - /* sizeof(type) */ - lex_expect(T_identifier); - } - } + 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. diff --git a/src/parser-stmt.c b/src/parser-stmt.c index dce21870..47b6d8bb 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -280,21 +280,15 @@ static void reject_ordinary_typedef_collision(block_t *block, var_t *var) */ static void reject_unlinked_scope_name(block_t *block, const char *name) { - func_t *func = block->func; + ordinary_kind_t kind = find_block_ordinary( + block, name, ORDINARY_CONSTANT | ORDINARY_PARAMETER, NULL); - for (constant_t *constant = block->constants; constant; - constant = constant->next) { - if (!strcmp(constant->alias, name)) - error_at("identifier redeclared as a different kind of symbol", - cur_token_loc()); - } - if (block->parent || !func) - return; - for (int i = 0; i < func->num_params; i++) { - if (!strcmp(func->param_defs[i].var_name, name)) - error_at("redeclaration of parameter in the function body", - cur_token_loc()); - } + 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 @@ -312,15 +306,15 @@ static void reject_block_redeclaration(block_t *block, bool has_linkage) { const char *name = var->var_name; + int pos = 0; if (!name[0]) return; - for (int i = 0; i < block->locals.size; i++) { - var_t *prior = block->locals.elements[i]; + 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 || prior->var_name[0] != name[0] || - strcmp(prior->var_name, name)) + if (prior == var) continue; prior_is_function = prior->is_extern_function_alias; if (prior_is_function != is_function) @@ -693,24 +687,27 @@ int read_enum_constant(block_t *scope) return value; } -/* The block-scope enum specifier that starts at the next token: a reference to - * a visible tag, or a tagged or untagged definition. A definition contributes - * integer constants to the current expression parser just as a file-scope - * definition does. @is_definition reports whether a body was read. +/* 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. */ -static type_t *read_block_enum_specifier(block_t *parent, bool *is_definition) +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, parent); + type = local_enum_tag(token, scope); has_tag = true; } *is_definition = lex_peek(T_open_curly, NULL); @@ -721,7 +718,7 @@ static type_t *read_block_enum_specifier(block_t *parent, bool *is_definition) } /* Only a definition creates an enum tag, so one already declared in this - * block would be defined twice (C99 6.7.2.3p1). + * scope would be defined twice (C99 6.7.2.3p1). */ if (type) error_at("redefinition of enum tag", cur_token_loc()); @@ -729,7 +726,7 @@ static type_t *read_block_enum_specifier(block_t *parent, bool *is_definition) initialize_enum_type(type); if (has_tag) { set_type_name(type, token); - add_type_tag(parent, token, type); + add_type_tag(scope, token, type); } lex_expect(T_open_curly); bool first = true; @@ -738,7 +735,12 @@ static type_t *read_block_enum_specifier(block_t *parent, bool *is_definition) if (!first && !lex_peek(T_assign, NULL)) val = next_enum_value(val); if (lex_accept(T_assign)) { - val = read_enum_constant(parent); + val = read_enum_constant(scope); + } + first = false; + if (!parent) { + add_constant(token, val); + continue; } /* An enumeration constant has no linkage, so no other ordinary @@ -751,7 +753,6 @@ static type_t *read_block_enum_specifier(block_t *parent, bool *is_definition) } reject_unlinked_scope_name(parent, token); add_scoped_constant(parent, token, val); - first = false; } while (lex_accept(T_comma) && !lex_peek(T_close_curly, NULL)); lex_expect(T_close_curly); return type; @@ -765,7 +766,7 @@ basic_block_t *handle_enum_statement(block_t *parent, const block_decl_specifiers_t *spec) { bool is_definition; - type_t *type = read_block_enum_specifier(parent, &is_definition); + type_t *type = read_enum_specifier(parent, &is_definition); if (is_definition && lex_accept(T_semicolon)) return bb; @@ -991,7 +992,7 @@ static basic_block_t *read_block_declarators( * brace initializer belongs to the same constant-data lowering * as a file-scope array. */ - parse_array_init(var, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + 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)) { @@ -1016,7 +1017,7 @@ static basic_block_t *read_block_declarators( (var->array_size > 0 || var->has_unsized_array || var->ptr_level > 0)) { /* Emit code for locals in functions */ - parse_array_init(var, parent, &bb, 1); + 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 && @@ -1028,8 +1029,7 @@ static basic_block_t *read_block_declarators( 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, - true); + parse_struct_field_init(parent, &bb, struct_type, struct_addr); lex_expect(T_close_curly); } else { if (!read_assignment_expression(parent, &bb)) { @@ -1119,7 +1119,7 @@ static basic_block_t *read_block_declarators( if (lex_peek(T_open_curly, NULL) && (nv->array_size > 0 || nv->has_unsized_array || nv->ptr_level > 0)) { - parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs, true); + parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); } else if (global_compound_literal_starts_here() && !(nv->ptr_level || nv->type->ptr_level) && is_record_type(nv->type)) { @@ -1144,7 +1144,7 @@ static basic_block_t *read_block_declarators( (nv->array_size > 0 || nv->has_unsized_array || nv->ptr_level > 0)) { /* Emit code for locals */ - parse_array_init(nv, parent, &bb, 1); + parse_array_init(nv, parent, &bb); } else if (lex_peek(T_open_curly, NULL) && is_record_type(nv->type)) { type_t *struct_type = nv->type; @@ -1157,8 +1157,7 @@ static basic_block_t *read_block_declarators( add_insn(parent, bb, OP_address_of, struct_addr, nv, NULL, 0, NULL); lex_expect(T_open_curly); - parse_struct_field_init(parent, &bb, struct_type, struct_addr, - true); + parse_struct_field_init(parent, &bb, struct_type, struct_addr); lex_expect(T_close_curly); } else { if (!read_assignment_expression(parent, &bb)) { @@ -1410,7 +1409,7 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, if (lex_peek(T_enum, NULL)) { bool is_definition; - decl.type = read_block_enum_specifier(parent, &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); diff --git a/src/parser.c b/src/parser.c index d90f75ee..b798487b 100644 --- a/src/parser.c +++ b/src/parser.c @@ -41,15 +41,6 @@ int operand_stack_idx = 0; */ int unevaluated_expression_depth = 0; -/* Set before reading an operand that a following `+` must not extend. - * read_lvalue() normally takes `+ n` after a pointer as part of the operand. - * That is wrong for the operand of a unary operator, which binds tighter: a - * cast in `(char *) p + 1` applies to p alone, and the sum advances by one byte - * rather than by an int. It is equally wrong for the right operand of a binary - * operator binding at least as tightly as `+`, as in `end - start + 1`. - */ -bool reading_unary_operand = false; - /* 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. */ @@ -272,10 +263,7 @@ void read_inner_var_decl(var_t *vd, 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); @@ -290,9 +278,6 @@ 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_member_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, @@ -311,12 +296,12 @@ var_t *lower_reference_update(var_t *object, opcode_t op, block_t *parent, basic_block_t **bb); -var_t *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_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); @@ -763,6 +748,17 @@ static int callback_pointer_indirection(const var_t *var) return 0; } +/* 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 (type->base_type == TYPE_typedef && type->base_struct) + return type->base_struct; + return type; +} + /* A typedef naming a struct or union type with no derived declarator of its * own, as in "typedef volatile struct S vs_t;". */ @@ -929,12 +925,16 @@ func_t *find_visible_func(char *name, block_t *scope) /* 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. */ int pointer_typedef_pointee_size(type_t *type, type_t *pointee) { 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; int size = 0; for (int i = 0; i < type->num_fields; i++) { @@ -1473,8 +1473,14 @@ var_t *convert_stored_value(block_t *block, 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 || + /* 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 (is_from_ptr && is_to_ptr) diff --git a/tests/driver.sh b/tests/driver.sh index 983bdecb..de5eb61d 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -748,6 +748,13 @@ EOF try_compile_error_message "Token too long" << EOF int main(void) { return sizeof($(printf '1%.0s' {1..255}).f); } EOF + +# 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 @@ -1027,6 +1034,22 @@ int nested_hyperplane_designated(void) { int main(void) { return nested_hyperplane_designated(); } EOF +# 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 + try_ 0 << EOF int global_unbraced_designated[2][2][2] = { [0] = { [1] = 2, 3 }, 4, 5 }; int nested_unbraced_designated(void) { @@ -1624,6 +1647,22 @@ int main(void) } EOF +# 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 + # 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 @@ -2016,6 +2055,39 @@ int main(void) } EOF +# 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 +try_compile_error << EOF +int main(void) { int *p = 0; return - -p != 0; } +EOF +try_compile_error << EOF +int main(void) { return - ; } +EOF + # 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 @@ -2338,6 +2410,55 @@ 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 @@ -3942,6 +4063,81 @@ int main(void) { return pointer[1].left + pointer[1].right; } 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}; @@ -4857,38 +5053,38 @@ int first_owner(void) { struct own_tag { int a, b; } x; x.a = 0; return sizeof(s 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 record. -try_compile_error_message "tag was previously declared as a different kind of record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 record" << 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 @@ -4919,7 +5115,7 @@ try_compile_error_message "tag was previously declared as a different kind of ta 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 record" << 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 @@ -4980,6 +5176,61 @@ 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 }; @@ -9306,6 +9557,15 @@ int main(void) { } 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. @@ -9554,6 +9814,141 @@ int main(void) { } 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 @@ -10530,6 +10925,16 @@ try_compile_error_message "ordinary identifier conflicts with typedef name" << E 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_ 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" @@ -11354,6 +11759,60 @@ 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);" +# 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 @@ -14185,6 +14644,41 @@ 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; @@ -14867,6 +15361,19 @@ int main(void) { } 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 @@ -16736,6 +17243,49 @@ 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*)" @@ -24605,6 +25155,52 @@ int bad(int count, ...) { } EOF +# 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 +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; +} +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 + try_ 0 << EOF #include struct payload { int first; int second; int third; }; From e0b5e9c78c60ca11b06b828da36bb04a7e384135 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 05:10:09 +0800 Subject: [PATCH 26/40] Fix integer lowering found by differential fuzzing Random programs compared against gcc on all four targets exposed silent miscompiles in integer lowering. A file-scope long long initializer lost its high word on Arm and RISC-V; a store to an unsigned int global wrote eight bytes on x64 and AArch64; narrowed constants kept stale high bits; x64 right shifts and division clobbered or misextended their operands; an int shifted by a long long count was shifted in 64 bits on AArch64; a record kept its allocation register mapped, so a spill overwrote its first member; RV32 argument staging reused a register already holding an argument and widened a signed char to short without sign extension; and a 32-bit pair move was fused while its source was still read. Lower global setup operations as pairs, keep narrow loads and stores at their width, extend and fold constants by the target type, keep staged argument registers until the call, and refuse the unsafe fusions. --- src/opt-sccp.c | 23 +++++- src/parser-decl.c | 4 + src/parser-expr.c | 9 +++ src/peephole.c | 11 +++ src/reg-alloc.c | 167 ++++++++++++++++++++++++-------------- src/riscv-codegen.c | 13 +-- src/ssa.c | 8 +- src/x64-codegen.c | 21 +++-- tests/driver.sh | 192 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 370 insertions(+), 78 deletions(-) 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-decl.c b/src/parser-decl.c index c7f03f4a..3fc22ae8 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -1945,6 +1945,10 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) 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; diff --git a/src/parser-expr.c b/src/parser-expr.c index 82fb0a27..f9affd6c 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1673,6 +1673,11 @@ static void negate_operand(block_t *parent, basic_block_t **bb) 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); @@ -1852,6 +1857,10 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) 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 { diff --git a/src/peephole.c b/src/peephole.c index a731d8ae..49b2b732 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -297,6 +297,17 @@ bool pair_insn_fusion(basic_block_t *bb, ph2_ir_t *ph2_ir) 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); diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 649ffe59..34c02bb8 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -93,7 +93,12 @@ int var_slot_size(var_t *v) */ if (!is_record_type(v->type) && v->type->size == 8) return 8; - if (!v->address_taken) + + /* 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 @@ -407,12 +412,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. */ @@ -855,7 +870,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] && !pair_high_owner[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. */ @@ -2453,6 +2469,73 @@ 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; +} + /* Place one global initializer, which has no basic block of its own. */ void reg_alloc_global(insn_t *global_insn) { @@ -2619,6 +2702,7 @@ void reg_alloc_global(insn_t *global_insn) /* 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 @@ -2628,9 +2712,7 @@ void reg_alloc_global(insn_t *global_insn) 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); - ir->src0 = src0; - ir->dest = dest; - set_ptr_flags(ir, global_insn); + fill_unary_ph2_ir(ir, global_insn, src0, dest); break; } case OP_sub: @@ -2660,10 +2742,7 @@ void reg_alloc_global(insn_t *global_insn) dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, src1); ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = dest; - set_ptr_flags(ir, global_insn); + fill_binary_ph2_ir(ir, global_insn, src0, src1, dest); break; } case OP_write: { @@ -2832,6 +2911,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) bool riscv_indirect_target_staged = false; is_pushing_args = false; + abi_args_staged = 0; int args = 0; bb->visited++; @@ -2945,11 +3025,16 @@ 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) { + 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. + * 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; @@ -3334,10 +3419,12 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) * 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; } @@ -3350,6 +3437,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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: @@ -3365,6 +3453,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; @@ -3385,6 +3474,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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; @@ -3439,37 +3529,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->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; - if (right_size > ir->size_bytes) - ir->size_bytes = right_size; + fill_binary_ph2_ir(ir, insn, src0, src1, dest); break; case OP_negate: case OP_bit_not: @@ -3477,24 +3537,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->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; + fill_unary_ph2_ir(ir, insn, src0, dest); break; case OP_trunc: case OP_sign_ext: diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 13877fc2..bc62e1e7 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -1076,10 +1076,7 @@ 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; if (ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi < 0) { @@ -1104,8 +1101,12 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; } - /* Calculate shift amount based on target and source sizes */ - int shift_amount = (target_size - source_size) * 8; + /* 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 diff --git a/src/ssa.c b/src/ssa.c index 10508043..7ec9cb6c 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -4171,6 +4171,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; @@ -4183,7 +4184,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; diff --git a/src/x64-codegen.c b/src/x64-codegen.c index df4980b5..330e8955 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -722,8 +722,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 || @@ -1640,9 +1644,10 @@ void emit_arith(ph2_ir_t *ph2_ir, /* 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. + * 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(wide, 0, 10); /* MOV r10{d}, rax{d} (save) */ + 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) */ @@ -1672,7 +1677,7 @@ void emit_arith(ph2_ir_t *ph2_ir, /* MOV r11{d}, rax{d} | rdx{d} */ emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, ph2_ir->op == OP_div ? 0 : 2, 3)); - emit_rex(wide, 10, 0); /* MOV rax{d}, r10{d} (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) */ @@ -1879,7 +1884,13 @@ 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 */ + + /* As in the literal-count form above, an unsigned int is zero-extended + * first: an unsigned int subtraction leaves a borrow in the upper half, + * which SHR would bring down into the result. + */ + 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 */ diff --git a/tests/driver.sh b/tests/driver.sh index de5eb61d..d2ae972f 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -2190,6 +2190,95 @@ int main(void) { return (byte == 0U) + (half == 0U); } 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 +# 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 + +# 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 + +# 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 + +# 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 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 + +# 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 try_ 3 << EOF long long identity(long long value) { return value; } unsigned long long uidentity(unsigned long long value) { return value; } @@ -3413,6 +3502,47 @@ int test_ll(int a, int b, int c, int d, long long e) { 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 @@ -3534,6 +3664,19 @@ int main(void) { } 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 @@ -3556,6 +3699,38 @@ int main(void) { } 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. @@ -6843,6 +7018,23 @@ int main(void) { return callback(7); } 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 try_ 8 << EOF int plus1(int value) { return value + 1; } int main(void) { From bc2ab99535a67755e28cd2f24f5879a6fd160407 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 06:11:55 +0800 Subject: [PATCH 27/40] Keep every access to a volatile object C99 makes an access to a volatile object a side effect, but shecc dropped such accesses at every stage. A discarded name such as gv; was never read, dead code elimination and CSE removed or reused reads through pointers, the allocator served reads from register copies and dropped writes to objects nothing read by name, parameters stayed in their incoming register, and peephole and the x64 slot cache skipped reloads and stores. Emit a read for every evaluated volatile lvalue, keep volatile objects and parameters in memory, reload them before each use, store every write, and carry a volatile flag into phase-2 IR so the peephole rules, the consecutive-store merges included, and the slot cache leave those loads and stores alone. A bit-field store keeps the read that preserves the rest of its unit, but its read-back is optional like any other assignment reload. Tests count the emitted reads and stores. --- src/defs.h | 16 ++- src/globals.c | 29 ++++ src/parser-call.c | 21 +++ src/parser-expr.c | 49 ++++--- src/parser-init.c | 2 +- src/parser-stmt.c | 7 +- src/parser.c | 29 ++++ src/peephole.c | 19 ++- src/reg-alloc.c | 97 +++++++++++-- src/ssa.c | 101 +++++++++++++- src/x64-codegen.c | 9 +- tests/driver.sh | 341 ++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 678 insertions(+), 42 deletions(-) diff --git a/src/defs.h b/src/defs.h index d3a16398..aa027698 100644 --- a/src/defs.h +++ b/src/defs.h @@ -758,7 +758,14 @@ struct var { bool is_ternary_ret; bool is_logical_ret; bool is_const; /* whether a constant representaion or not */ - int phys_reg; /* low physical register (-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. */ @@ -940,6 +947,13 @@ struct ph2_ir { 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; diff --git a/src/globals.c b/src/globals.c index d7ecfe0e..55771461 100644 --- a/src/globals.c +++ b/src/globals.c @@ -675,6 +675,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); } @@ -2000,6 +2001,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, @@ -2034,6 +2048,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 */ @@ -2042,6 +2063,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 diff --git a/src/parser-call.c b/src/parser-call.c index 11b44c4b..7e31eb08 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -1237,6 +1237,27 @@ var_t *read_bitfield_value(block_t *parent, 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, diff --git a/src/parser-expr.c b/src/parser-expr.c index f9affd6c..1ef5e245 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -579,7 +579,7 @@ static void read_grouped_operand(block_t *parent, basic_block_t **bb) * Discard the completed left value after its IR is emitted; the final * expression supplies the result, which is not an lvalue. */ - opstack_pop(); + discard_operand(parent, *bb); perform_side_effect(parent, *bb); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); @@ -1375,6 +1375,19 @@ static void read_cast_operand(block_t *parent, 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); @@ -3327,6 +3340,8 @@ void read_lvalue(lvalue_t *lvalue, 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)) @@ -3934,7 +3949,7 @@ void read_control_expression(block_t *parent, basic_block_t **bb) } while (lex_accept(T_comma)) { - opstack_pop(); + discard_operand(parent, *bb); perform_side_effect(parent, *bb); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); @@ -3960,7 +3975,7 @@ basic_block_t *read_full_expression_statement(block_t *parent, basic_block_t *bb) { read_control_expression(parent, &bb); - opstack_pop(); + discard_operand(parent, bb); perform_side_effect(parent, bb); lex_expect(T_semicolon); return bb; @@ -4099,7 +4114,7 @@ void read_conditional_true_expression(block_t *parent, basic_block_t **bb) } while (lex_accept(T_comma)) { - opstack_pop(); + discard_operand(parent, *bb); perform_side_effect(parent, *bb); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); @@ -4450,14 +4465,14 @@ bool read_body_assignment(var_t *var, if (!lvalue.is_reference) { assignment_result[0] = vd; } else if (is_bitfield(lvalue.decl)) { - var_t *stored = - read_bitfield_value(parent, bb, t, lvalue.decl); - stored->is_bitfield = false; + 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; @@ -4570,9 +4585,8 @@ bool read_body_assignment(var_t *var, * reloaded just like an ordinary bit-field assignment. */ if (is_bitfield(lvalue.decl)) { - var_t *stored = - read_bitfield_value(parent, bb, t, lvalue.decl); - stored->is_bitfield = false; + var_t *stored = reload_assigned_bitfield(parent, bb, t, + lvalue.decl); assignment_result[0] = stored; } else { assignment_result[0] = vd; @@ -4672,14 +4686,14 @@ bool read_body_assignment(var_t *var, * leaking the unconverted right operand. */ if (is_bitfield(lvalue.decl)) { - var_t *stored = - read_bitfield_value(parent, bb, rs1, lvalue.decl); - stored->is_bitfield = false; + 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; @@ -4924,9 +4938,8 @@ static void read_grouped_postfix_assignment(block_t *parent, basic_block_t **bb) if (target->compound_literal_bitfield) { write_bitfield_value(parent, bb, address, value, target->compound_literal_bitfield); - result = read_bitfield_value(parent, bb, address, - target->compound_literal_bitfield); - result->is_bitfield = false; + result = reload_assigned_bitfield(parent, bb, address, + target->compound_literal_bitfield); opstack_push(result); return; } @@ -5167,12 +5180,12 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) * object representation, including narrow integer conversion. */ if (field && is_bitfield(field)) { - result = read_bitfield_value(parent, bb, address, field); - result->is_bitfield = false; + 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); } diff --git a/src/parser-init.c b/src/parser-init.c index f602ce83..2e30e508 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -1703,7 +1703,7 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) read_ternary_operation(parent, &bb); } while (lex_accept(T_comma)) { - opstack_pop(); + discard_operand(parent, bb); perform_side_effect(parent, bb); if (!read_assignment_expression(parent, &bb)) { read_expr(parent, &bb); diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 47b6d8bb..164942ea 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -474,7 +474,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) for_decl_semicolon_consumed = true; } else { read_control_expression(blk, &setup); - opstack_pop(); + discard_operand(blk, setup); perform_side_effect(blk, setup); } @@ -512,7 +512,7 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) /* increment after each loop */ if (!lex_accept(T_close_bracket)) { read_control_expression(blk, &inc_); - opstack_pop(); + discard_operand(blk, inc_); perform_side_effect(blk, inc_); lex_expect(T_close_bracket); } @@ -1895,6 +1895,9 @@ void read_func_body(func_t *func) 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); diff --git a/src/parser.c b/src/parser.c index b798487b..ab672b88 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1212,6 +1212,35 @@ var_t *opstack_pop(void) return operand_stack[--operand_stack_idx]; } +bool is_record_type(const type_t *type); + +/* 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. + * + * 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 discard_operand(block_t *parent, basic_block_t *bb) +{ + var_t *var = opstack_pop(); + bool unread = var && var == unread_volatile_object; + + unread_volatile_object = NULL; + if (!unread || var->is_func || var->array_size || + (!var->ptr_level && is_record_type(var->type))) + return; + + 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); +} + /* 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. diff --git a/src/peephole.c b/src/peephole.c index 49b2b732..27b53b6a 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -542,10 +542,13 @@ bool redundant_move_elim(basic_block_t *bb, ph2_ir_t *ph2_ir) if (!next) return false; + /* 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 (!is_plain_register_def(ph2_ir) || !is_plain_register_def(next) || - ph2_ir->dest != next->dest || + ph2_ir->is_volatile || ph2_ir->dest != next->dest || (ph2_ir->dest_hi >= 0 && ph2_ir->dest_hi != next->dest_hi) || - (next->op == OP_assign && next->src0 == ph2_ir->dest)) + ir_reads_reg(next, ph2_ir->dest) || ir_reads_reg(next, ph2_ir->dest_hi)) return false; memcpy(ph2_ir, next, sizeof(ph2_ir_t)); @@ -579,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) { @@ -590,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 @@ -834,7 +844,7 @@ 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 */ @@ -853,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, diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 34c02bb8..bd191b90 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -331,6 +331,7 @@ ph2_ir_t *bb_add_ph2_ir(basic_block_t *bb, opcode_t op) 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; @@ -517,7 +518,7 @@ 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 || - var->is_volatile) + var_is_volatile_object(var)) return false; if (var->is_global || var->ofs_based_on_stack_top) return false; @@ -850,6 +851,7 @@ void store_var(basic_block_t *bb, var_t *var, int idx) 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; } @@ -891,7 +893,7 @@ bool var_is_pinnable(var_t *var) { if (!var || var->is_const || !var->base) return false; - if (var->is_volatile) + if (var_is_volatile_object(var)) return false; /* slot_is_private() rules out everything reachable other than by name: @@ -1126,6 +1128,7 @@ 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; @@ -1219,10 +1222,25 @@ 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; @@ -1242,13 +1260,24 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) * 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 || REGS[i].polluted)) { + 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); @@ -1749,6 +1778,7 @@ void load_low_word(basic_block_t *bb, var_t *var, int reg) 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; } @@ -3060,6 +3090,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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; } @@ -3255,12 +3286,26 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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. */ @@ -3290,6 +3335,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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; } @@ -3312,6 +3358,7 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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: @@ -3831,6 +3878,17 @@ void reg_alloc(void) } } + /* 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); } @@ -3858,6 +3916,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++) { @@ -3911,16 +3978,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/ssa.c b/src/ssa.c index 7ec9cb6c..fc32239c 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -31,6 +31,75 @@ 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) @@ -1278,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: @@ -3568,6 +3643,10 @@ bool cse(insn_t *insn, const basic_block_t *bb) if (base->is_global || idx->is_global) return false; + /* 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 @@ -3987,6 +4066,16 @@ 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; @@ -4151,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 */ @@ -4390,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 */ @@ -4438,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) { diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 330e8955..229593c6 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -2918,8 +2918,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 && @@ -2988,7 +2991,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) diff --git a/tests/driver.sh b/tests/driver.sh index d2ae972f..7e129013 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -534,6 +534,49 @@ function try_compile_warning() 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" @@ -1748,6 +1791,304 @@ int main(void) { } 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 + +# 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 + +# 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 + +# 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_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 + +# 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_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_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 +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) +{ + vw = 0x200000001LL; + return f1(1) + f2(2) + f3() + f4(0x100000003LL) - (int) (vw >> 32) + 1; +} +EOF + +# 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 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(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_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(void) +{ + static int backing; + p = &backing; + return f(); +} +EOF +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_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_ 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 + +# 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(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 +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 + +# 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 +try_ir_count 0 f '= \(%x[0-9]+\)' << EOF +volatile int *p; +int f(void) +{ + *p = 1; + return 0; +} +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 + # A file-scope record keeps its volatile qualifier like a scalar does, so every # redeclaration of either must repeat it. try_ 8 << EOF From b1effbeb3e523b6f5ed7c687f817ed075dfc9817 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 05:26:01 +0800 Subject: [PATCH 28/40] Complete designators and static address constants Several C99 6.7.8 forms were rejected or stored wrong values. A row designator such as .a[1] = 3, 4 wrote a scalar over the whole row, positional values after a nested designator went to the outer object, later elided values wiped earlier designated members, chained designators like [1].fn = f did not parse, a second designator into a union member was refused, and an automatic union was not cleared. A string literal was accepted as a single element of a non-character array. Static initializers refused "ab"[1], "abc" + 1 and &(int){8}, and stored a pointer cast into a _Bool without converting it. Continue positional values inside the object a designator entered, keep designated members when elided values follow, clear unions and gaps, reject misplaced strings, and fold string elements, string offsets and file-scope compound literal addresses as address constants. --- src/parser-const.c | 113 ++++++++++- src/parser-decl.c | 165 ++++++++++++++++ src/parser-init.c | 408 ++++++++++++++++++++++++++------------ tests/driver.sh | 474 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1033 insertions(+), 127 deletions(-) diff --git a/src/parser-const.c b/src/parser-const.c index 9d6cbca1..e088c5c9 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -257,6 +257,32 @@ 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); @@ -710,6 +736,23 @@ var_t *read_wide_global_literal_primary(block_t *parent, 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->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 @@ -919,7 +962,7 @@ bool read_global_assignment_var(var_t *var) stride = saved_stride; if (!global_address_operand_starts_here(scope)) { rs1 = read_global_cast_integer_address(parent, bb, scope, stride); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + emit_global_scalar_assignment(parent, bb, var, rs1); return true; } global_pointer_cast_stride = stride; @@ -1056,21 +1099,85 @@ bool read_global_assignment_var(var_t *var) 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; + + lex_expect(T_open_bracket); + literal_type = read_type_name_specifiers(GLOBAL_BLOCK); + while (lex_accept(T_asterisk)) + literal_ptr_level++; + lex_expect(T_close_bracket); + 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)) { + 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); add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); return true; } - if (lex_peek(T_string, NULL) || lex_peek(T_wstring, NULL)) { + 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 diff --git a/src/parser-decl.c b/src/parser-decl.c index 3fc22ae8..ccec0535 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -364,6 +364,49 @@ static int read_offsetof_array_subscripts(var_t *field, block_t *scope) } } +/* 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]; @@ -2004,3 +2047,125 @@ void read_wstring_param(block_t *parent, basic_block_t *bb) 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-init.c b/src/parser-init.c index 2e30e508..4824b5f9 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -28,6 +28,15 @@ int read_const_expr(block_t *scope); var_t *read_wide_global_literal_expression(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); @@ -444,6 +453,10 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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); @@ -457,7 +470,7 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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) || + lex_peek(T_wchar, NULL) || subscripted_string_literal_starts_here() || (lex_peek(T_identifier, constant_name) && find_scoped_constant(constant_name, scope)))) { /* Any integer constant expression, including casts, sizeof and grouped @@ -557,11 +570,13 @@ bool parse_struct_field_values(block_t *parent, 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); + 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, @@ -741,8 +756,8 @@ bool parse_array_field_row_values(block_t *parent, 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); + 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 { @@ -1153,7 +1168,7 @@ void parse_array_field_init(block_t *parent, lex_expect(T_close_curly); } else if (unbraced_record_starts_here(field)) { comma_consumed = parse_unbraced_record_init( - parent, bb, field->type, elem_addr); + parent, bb, field->type, elem_addr, count < filled); } else if (parent == GLOBAL_BLOCK) { value = parse_global_constant_value(parent, bb); } else { @@ -1190,6 +1205,10 @@ void parse_array_field_init(block_t *parent, * 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. */ @@ -1197,7 +1216,8 @@ bool parse_record_from_value(block_t *parent, basic_block_t **bb, type_t *record_type, var_t *addr, - var_t *value) + var_t *value, + bool cleared) { record_type = resolve_record_type(record_type); if (is_record_object(value) && @@ -1206,7 +1226,7 @@ bool parse_record_from_value(block_t *parent, return false; } return parse_struct_field_values(parent, bb, record_type, addr, true, - value); + cleared, value); } /* A record slot whose initializer does not begin with a brace. Reading a block @@ -1218,7 +1238,8 @@ bool parse_record_from_value(block_t *parent, bool parse_unbraced_record_init(block_t *parent, basic_block_t **bb, type_t *record_type, - var_t *addr) + var_t *addr, + bool cleared) { var_t *value = NULL; @@ -1228,7 +1249,8 @@ bool parse_unbraced_record_init(block_t *parent, read_ternary_operation(parent, bb); value = opstack_pop(); } - return parse_record_from_value(parent, bb, record_type, addr, value); + 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 @@ -1276,20 +1298,40 @@ void parse_struct_field_init(block_t *parent, var_t *target_addr) { parse_struct_field_values(parent, bb, struct_type, target_addr, false, - NULL); + 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. + * 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; @@ -1319,18 +1361,30 @@ bool parse_struct_field_values(block_t *parent, * per unit after the whole initializer has been parsed, avoiding a global * setup read before the global-pointer frame is established. */ - var_t *global_bitfield_unit[MAX_FIELDS]; - unsigned global_bitfield_value[MAX_FIELDS]; - int global_bitfield_count = 0; + 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. + * 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 && !is_union) { + if (parent != GLOBAL_BLOCK && !cleared) { var_t *zero = emit_zero_constant(parent, bb); - for (int i = 0; i < struct_type->num_fields; i++) { + 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( @@ -1347,12 +1401,16 @@ bool parse_struct_field_values(block_t *parent, 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]; - type_t *designator_type = struct_type; - var_t *designator_base = target_addr; - bool first = true; + + /* 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 @@ -1360,69 +1418,100 @@ bool parse_struct_field_values(block_t *parent, * of resuming the stale array slots. */ has_pending_array_element = false; + designated = true; - for (;;) { - bool found = false; - - lex_ident(T_identifier, field_name); - for (int i = 0; i < designator_type->num_fields; i++) { - if (!strcmp(designator_type->fields[i].var_name, - field_name)) { - if (first) - field_idx = i; - field = &designator_type->fields[i]; - found = true; - break; - } + 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 (!found) - error_at("Unknown field in record initializer", - cur_token_loc()); - - field_addr = compute_field_address(parent, bb, - designator_base, field); - 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); - if (!field->array_size) { - memcpy(&pending_array_element, field, - sizeof(var_t)); - pending_array_base = array_base_addr; - pending_array_index = linear_index + 1; - pending_array_count = total_element_count; - pending_array_elem_size = elem_size; - has_pending_array_element = true; - } + } + 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; } - if (!lex_accept(T_dot)) - break; - if (!is_record_type(field->type)) + } + + /* 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", - cur_token_loc()); - designator_type = resolve_record_type(field->type); - designator_base = field_addr; - first = false; + 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; } - lex_expect(T_assign); } else if (has_pending_array_element) { field = &pending_array_element; field_addr = compute_element_address( @@ -1434,8 +1523,12 @@ bool parse_struct_field_values(block_t *parent, 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 && + (is_union && initializer_count > 0 && !designated && !consumed_pending_array_element)) error_at("Too many elements in record initializer", next_token_loc()); @@ -1483,7 +1576,9 @@ bool parse_struct_field_values(block_t *parent, "String literal initializer has incompatible array element " "type", cur_token_loc()); - if (first_value) { + 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(). @@ -1493,7 +1588,7 @@ bool parse_struct_field_values(block_t *parent, parent, bb, field->type, member_address(parent, bb, target_addr, field, field_addr), - first_value); + first_value, true); else field_val_raw = first_value; first_value = NULL; @@ -1526,7 +1621,8 @@ bool parse_struct_field_values(block_t *parent, 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)); + 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 { @@ -1558,18 +1654,23 @@ bool parse_struct_field_values(block_t *parent, int field_size = size_var(field); if (is_bitfield(field) && parent == GLOBAL_BLOCK) { int unit = -1; - for (int i = 0; i < global_bitfield_count; i++) - if (global_bitfield_unit[i]->offset == - field->offset && - global_bitfield_unit[i]->bit_storage_size == + 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) { - unit = global_bitfield_count++; - global_bitfield_unit[unit] = field; - global_bitfield_value[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; @@ -1577,10 +1678,9 @@ bool parse_struct_field_values(block_t *parent, is_bool_type(field->type) ? field_val_raw->init_val != 0 : (unsigned) field_val_raw->init_val; - global_bitfield_value[unit] = - (global_bitfield_value[unit] & ~mask) | - ((init_val & bitfield_mask(field)) - << field->bit_offset); + 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); @@ -1624,15 +1724,12 @@ bool parse_struct_field_values(block_t *parent, } } - if (parent == GLOBAL_BLOCK) { - for (int i = 0; i < global_bitfield_count; i++) { - var_t *unit = global_bitfield_unit[i]; - var_t *unit_addr = - compute_field_address(parent, bb, target_addr, unit); - var_t *value = - bitfield_constant(parent, bb, global_bitfield_value[i]); - add_insn(parent, *bb, OP_write, NULL, unit_addr, value, - unit->bit_storage_size, NULL); + /* 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; @@ -1937,6 +2034,36 @@ static bool string_ends_braced_initializer(const var_t *var, token_t *token) 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; @@ -1998,9 +2125,13 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) var_t *val = NULL; var_t designated_array; var_t *initializer_var = var; - int prior_count = count; 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, @@ -2009,13 +2140,29 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) if (read_array_designator(initializer_scope, var, 0, is_implicit, &count, &designated_array)) { initializer_var = &designated_array; - lex_expect(T_assign); + 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); @@ -2046,9 +2193,6 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) break; continue; } else if (string_row_starts_here(initializer_var)) { - if (is_implicit && parent != GLOBAL_BLOCK) - emit_zero_elements(parent, bb, base_addr, inferred_size, - count, elem_size); parse_string_row_init(parent, bb, initializer_var, base_addr, count); count += string_row_width(initializer_var); @@ -2057,6 +2201,23 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) 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); @@ -2075,24 +2236,29 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) var_t *elem_addr = compute_element_address( parent, bb, base_addr, count, elem_size); - if (is_implicit && parent != GLOBAL_BLOCK) - emit_zero_elements(parent, bb, base_addr, inferred_size, - count, elem_size); - elided_comma = parse_unbraced_record_init(parent, bb, var->type, - elem_addr); + /* 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); if (parent == GLOBAL_BLOCK && (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))) { + 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. */ @@ -2127,6 +2293,8 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) * 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. @@ -2254,15 +2422,6 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) else v = resize_to(parent, bb, val, var->type, 0); - /* A forward designator leaves a gap. Explicit arrays were - * zeroed before parsing; inferred local arrays do not have a - * known bound yet, so zero just the newly skipped elements. - * Global storage begins zeroed. - */ - if (is_implicit && parent != GLOBAL_BLOCK) - emit_zero_elements(parent, bb, base_addr, prior_count, - count, elem_size); - var_t *elem_addr = compute_element_address( parent, bb, base_addr, count, elem_size); @@ -2386,8 +2545,9 @@ void parse_array_compound_literal(var_t *var, 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); + 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); diff --git a/tests/driver.sh b/tests/driver.sh index 7e129013..db983b28 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1377,6 +1377,102 @@ try_compile_error_message "String literal cannot initialize a single array eleme int main(void) { char text[2][4] = {1, "abc"}; return 0; } EOF +# 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 + +# 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 + try_ 0 << EOF enum { wide_count = sizeof L"ab" / sizeof(wchar_t) }; wchar_t *global_pointer = L"xy"; @@ -11292,6 +11388,32 @@ 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 + # Test: Empty array compound literal (edge case) try_ 0 << EOF int main() { @@ -15024,6 +15146,18 @@ static int gd_member_size = sizeof *&gd_member.values[1]; int main(void) { return gd_member_size; } 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. @@ -15753,6 +15887,346 @@ int main(void) { } 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 From 9dc4f3f0859d51bdb3d00c3f0afce9310d7a8be2 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 06:24:37 +0800 Subject: [PATCH 29/40] Fix declarator, typedef and conversion gaps Declarators and conversions diverged from C99 in several places. Compound literals of long types, parenthesized declarator names, file- scope pointer-to-array and function typedefs, arrays of function typedef pointers, function-type parameters and variadic or pointer returning function typedefs were rejected. Nested pointers to arrays lost their bounds, sizeof of an array parameter gave the array size, a qualified typedef of an incomplete record lost its qualifier, an enumerator could reuse a block typedef, and strict mode accepted a function declarator after the first one in a for declaration. Integer to pointer and incompatible function pointer conversions were accepted without a diagnostic. Accept each form with the representation its simpler spelling already had, keep array and qualifier shape through derived declarators, and diagnose the constraint violations of C99 6.5.16.1 while keeping null pointer constants and compatible prototypes valid. --- lib/c.c | 20 +- lib/c.h | 6 +- src/defs.h | 5 + src/globals.c | 12 +- src/parser-call.c | 17 ++ src/parser-const.c | 17 ++ src/parser-decl.c | 108 ++++++++- src/parser-expr.c | 68 +++++- src/parser-global.c | 69 +++++- src/parser-init.c | 7 + src/parser-sizeof.c | 38 ++- src/parser-stmt.c | 30 ++- src/parser.c | 112 ++++++++- tests/arm-abi.sh | 6 +- tests/driver.sh | 561 +++++++++++++++++++++++++++++++++++++++++++- tests/riscv-abi.sh | 9 +- 16 files changed, 1020 insertions(+), 65 deletions(-) diff --git a/lib/c.c b/lib/c.c index 2c78c241..7fac7bcc 100644 --- a/lib/c.c +++ b/lib/c.c @@ -696,7 +696,7 @@ FILE *fopen(const char *filename, const char *mode) */ if (fd < 0) return NULL; - return fd; + return (FILE *) fd; } int fclose(FILE *stream) @@ -827,9 +827,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; @@ -840,9 +840,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; @@ -882,9 +882,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 30ec7090..9c3e74fb 100644 --- a/lib/c.h +++ b/lib/c.h @@ -110,9 +110,9 @@ int isblank(int c); typedef int FILE; /* 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) FILE *fopen(const char *filename, const char *mode); int fclose(FILE *stream); diff --git a/src/defs.h b/src/defs.h index aa027698..60fab87a 100644 --- a/src/defs.h +++ b/src/defs.h @@ -812,6 +812,11 @@ struct var { */ bool is_string_literal; + /* 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; + /* `&__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 diff --git a/src/globals.c b/src/globals.c index 55771461..01fe4200 100644 --- a/src/globals.c +++ b/src/globals.c @@ -607,23 +607,25 @@ 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. */ type_t *alias = &TYPES[i]; type_t *base = alias->base_struct; if (alias->base_type != TYPE_typedef || alias->size || - alias->ptr_level) + 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 || !base->size || - (!alias->is_const_qualified && - !alias->is_volatile_qualified)) + 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; diff --git a/src/parser-call.c b/src/parser-call.c index 7e31eb08..3ae41aea 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -57,6 +57,8 @@ void read_func_parameters_with_sret(func_t *func, "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 @@ -1329,6 +1331,16 @@ static void copy_pointee_array_shape(var_t *destination, const var_t *source) 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); +} + /* The address of an operand that does not start with an identifier: `&*p`, * `&(*q).member`, `&2[arr]` or `&(int){1}`. */ @@ -1770,6 +1782,7 @@ void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1) vd->ptr_level = 1; sz = PTR_SIZE; } + keep_pointee_array_shape(vd, rs1); push_object_at(parent, bb, vd, rs1, sz); } @@ -1897,6 +1910,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) vd->ptr_level = 1; sz = PTR_SIZE; } + keep_pointee_array_shape(vd, rs1); push_object_at(parent, bb, vd, rs1, sz); } } @@ -1939,6 +1953,8 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) 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 @@ -1974,6 +1990,7 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) vd->ptr_level = 1; sz = PTR_SIZE; } + keep_pointee_array_shape(vd, rs1); push_object_at(parent, bb, vd, rs1, sz); } } diff --git a/src/parser-const.c b/src/parser-const.c index e088c5c9..268c6b20 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -174,11 +174,24 @@ int eval_expression_imm(opcode_t op, int op1, int op2) bool read_global_assignment_var(var_t *var); +/* The integer evaluators below yield constants only, so a nonzero value for a + * pointer object is an integer converted without a cast. + */ +static void reject_global_integer_pointer(const var_t *dest, const var_t *src) +{ + if (effective_pointer_depth(dest) && !dest->array_size && + !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); @@ -1002,6 +1015,7 @@ bool read_global_assignment_var(var_t *var) symbol->is_func = true; symbol->var_name = intern_string(address_dereference_identifier->literal); + diagnose_function_pointer_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; @@ -1026,6 +1040,7 @@ bool read_global_assignment_var(var_t *var) addr->var_name = gen_name(); symbol->is_func = true; symbol->var_name = intern_string(token); + diagnose_function_pointer_conversion(symbol, var); lex_expect(T_identifier); if (grouped_function_designator) lex_expect(T_close_bracket); @@ -1173,6 +1188,7 @@ bool read_global_assignment_var(var_t *var) 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; } @@ -1227,6 +1243,7 @@ bool read_global_assignment_var(var_t *var) 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; } diff --git a/src/parser-decl.c b/src/parser-decl.c index ccec0535..28cd9682 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -555,11 +555,65 @@ int read_const_expr(block_t *scope) 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 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) { + bool grouped_name; + /* Preserve typedef pointer level - don't reset if already inherited */ vd->init_val = 0; vd->has_direct_array_declarator = false; @@ -613,8 +667,19 @@ void read_inner_var_decl(var_t *vd, fixed_array_shape_to_var(vd, &empty_shape); } + grouped_name = (!anon || is_param) && + grouped_declarator_name_follows(vd->scope, is_param); + + /* 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 (lex_accept(T_open_bracket)) { + 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; @@ -878,11 +943,15 @@ void read_inner_var_decl(var_t *vd, * 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); @@ -1070,6 +1139,15 @@ void read_inner_var_decl(var_t *vd, "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 @@ -1112,6 +1190,34 @@ void read_inner_var_decl(var_t *vd, */ 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 diff --git a/src/parser-expr.c b/src/parser-expr.c index 1ef5e245..9a7f1ab2 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1016,6 +1016,8 @@ static void read_compound_literal_operand(block_t *parent, 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); @@ -1053,7 +1055,9 @@ static void read_compound_literal_operand(block_t *parent, 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_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){} */ @@ -1068,9 +1072,8 @@ static void read_compound_literal_operand(block_t *parent, emit_object_assignment(parent, bb, compound_var, zero); opstack_push(compound_var); compound_object = compound_var; - } else if (lex_peek(T_numeric, NULL) || lex_peek(T_identifier, NULL) || - lex_peek(T_char, NULL)) { - /* Parse first element */ + } else { + /* Parse first element, any assignment expression */ read_expr(parent, bb); read_ternary_operation(parent, bb); @@ -1161,6 +1164,8 @@ static void read_compound_literal_operand(block_t *parent, } 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); @@ -1362,6 +1367,9 @@ static void read_cast_operand(block_t *parent, */ 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. @@ -1799,11 +1807,18 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) is_neg = true; } - if (lex_peek(T_string, NULL)) - read_literal_param(parent, *bb); - else if (lex_peek(T_wstring, NULL)) - read_wstring_param(parent, *bb); - else if (lex_peek(T_char, NULL)) + 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); @@ -2994,6 +3009,13 @@ static void lower_lvalue_tail(lvalue_t *lvalue, if (pointer_row_element_type) t->is_const_qualified = lvalue->type->is_const_qualified; + /* A loaded pointer-to-array member, as in `*s.rows`, still points to a + * whole row. + */ + if (!lvalue->subscript_depth && lvalue->decl && + lvalue->decl->pointee_array_size && !lvalue->pointee_func_signature) + 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. @@ -3435,10 +3457,19 @@ void read_lvalue(lvalue_t *lvalue, !!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 > 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 @@ -4291,6 +4322,15 @@ void read_ternary_operation(block_t *parent, basic_block_t **bb) 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); @@ -4602,6 +4642,8 @@ bool read_body_assignment(var_t *var, 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 @@ -4637,6 +4679,8 @@ bool read_body_assignment(var_t *var, } 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. @@ -4727,6 +4771,8 @@ bool read_body_assignment(var_t *var, } else if (incompatible_const_pointer_conversion(rs1, vd)) { diagnose_const_pointer_conversion(rs1, vd); } else { + 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); diff --git a/src/parser-global.c b/src/parser-global.c index b0035f5e..980bc670 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -196,6 +196,9 @@ bool read_global_function_declarator(block_t *block, if (!allow_definition) error_at("function definition must be the only declarator", next_token_loc()); + if (inherited_direct_function_type) + 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)) @@ -312,7 +315,8 @@ bool read_global_declarator(block_t *block, 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)) + 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; @@ -389,14 +393,14 @@ void parse_global_compound_record_init(var_t *var, block_t *block) */ void parse_global_compound_scalar_init(var_t *var, block_t *block) { - char type_name[MAX_ID_LEN]; type_t *compound_type; UNUSED(block); + /* The type name may be spelled with keywords, as in `(unsigned long)`. */ lex_expect(T_open_bracket); - lex_ident(T_identifier, type_name); - compound_type = find_type(type_name, true); + 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 || @@ -605,7 +609,8 @@ bool read_global_record_declarator(block_t *block, 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)) + 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; @@ -695,7 +700,9 @@ void read_global_decl(block_t *block, read_full_var_decl(var, false, false, false); var->is_extern = is_extern && !lex_peek(T_assign, NULL); - if (lex_peek(T_open_bracket, 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 { @@ -932,6 +939,32 @@ static void read_global_typedef_declarator(block_t *block, type->is_volatile_qualified = true; 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. + */ + if (!declarator.is_func && + declarator.has_direct_pointee_array_declarator && + !declarator.array_size && !base->ptr_level && !base->array_size && + !base->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'; + 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 = (type_t *) base; + return; + } if (!declarator.is_func) error_at( "Typedef parenthesized declarator must be a function " @@ -967,6 +1000,30 @@ static void read_global_typedef_declarator(block_t *block, 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. diff --git a/src/parser-init.c b/src/parser-init.c index 4824b5f9..a6b992ba 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -322,6 +322,9 @@ var_t *read_global_cast_integer_address(block_t *parent, address->var_name = gen_name(); address->init_val = value; address->is_const = true; + + /* The cast's result is a pointer, not the integer it was spelled with. */ + address->ptr_level = 1; add_insn(parent, bb, OP_load_constant, address, NULL, NULL, 0, NULL); return address; } @@ -1634,6 +1637,7 @@ bool parse_struct_field_values(block_t *parent, if (field_val_raw) { field_addr = member_address(parent, bb, target_addr, field, field_addr); + diagnose_function_pointer_conversion(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, @@ -1848,6 +1852,7 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) * 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; } @@ -1867,6 +1872,8 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) * 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); diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 513af434..39740fb9 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -151,10 +151,11 @@ static bool sizeof_walk_indirect(var_t *object, } if (depth <= 0) error_at(message, &op->location); - if (object->pointee_array_size > 0 && - depth == object->pointee_array_element_ptr_level + 1) { + 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. + * 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); @@ -342,6 +343,25 @@ static bool scan_sizeof_postfix_operand(block_t *scope, "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( @@ -601,6 +621,18 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) 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; diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 164942ea..2e486f25 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -744,13 +744,12 @@ type_t *read_enum_specifier(block_t *parent, bool *is_definition) } /* An enumeration constant has no linkage, so no other ordinary - * identifier of this block may share its name. + * identifier of this block, object or typedef name, may share its name. */ - for (int i = 0; i < parent->locals.size; i++) { - if (!strcmp(parent->locals.elements[i]->var_name, token)) - error_at("identifier redeclared as a different kind of symbol", - cur_token_loc()); - } + 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)); @@ -847,6 +846,11 @@ static basic_block_t *read_block_declarators( 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. @@ -1077,6 +1081,8 @@ static basic_block_t *read_block_declarators( error_at("incompatible callback slot types in initializer", cur_token_loc()); 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); } } @@ -1096,6 +1102,10 @@ static basic_block_t *read_block_declarators( 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) @@ -1444,10 +1454,9 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, 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.ptr_level && !decl.array_size && + decl.func_signature && !decl.array_size && !decl.pointee_array_size && !base->ptr_level && !is_record_type(base) && !base->is_floating && - !direct_function_signature->va_args && !direct_function_signature->returns_aggregate; bool callback_pointer_alias = decl.is_func && decl.func_signature && @@ -1637,6 +1646,11 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, 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; } diff --git a/src/parser.c b/src/parser.c index ab672b88..db4a1f57 100644 --- a/src/parser.c +++ b/src/parser.c @@ -239,6 +239,7 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, void perform_side_effect(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); @@ -366,7 +367,9 @@ bool global_compound_literal_starts_here(void) 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_struct || next->kind == T_union || + 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 @@ -1646,6 +1649,106 @@ void diagnose_const_pointer_conversion(const var_t *from, const var_t *to) error_at("discarding const qualifier", cur_token_loc()); } +/* 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 diagnose_integer_to_pointer_conversion(var_t *from, + const var_t *to, + bool array_is_pointer) +{ + 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 (effective_pointer_depth(from) || from->array_size || + from->has_unsized_array || from->is_func || from->func_signature || + from->pointee_func_signature || from->is_string_literal || + 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()); +} + +/* 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. + */ +static const func_t *pointed_function_type(const var_t *var) +{ + 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; +} + +/* 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) +{ + const func_t *prototyped = from->has_prototype ? from : to; + + 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; +} + +/* 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) +{ + 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; + } + if (from_function && effective_pointer_depth(to) && !to->array_size && + !to->has_unsized_array) + error_at("incompatible function pointer types", cur_token_loc()); +} + /* 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. @@ -1690,7 +1793,12 @@ bool incompatible_pointee_callback_conversion(const var_t *from, if (!to_signature && to->func_signature && (to->array_size || to->has_unsized_array)) to_signature = to->func_signature; - if (!from_signature && effective_pointer_depth(from) > 0 && from->type && + + /* 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 && diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index 0171be9f..263ccc19 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -370,8 +370,7 @@ test_long_long_after_int() typedef int (*call_t)(int, long long); int words(int a, int skipped, int low, int high) { return low == 3 && high == 2; } int main() { - void *raw = words; - call_t call = raw; + call_t call = (call_t) words; if (call(1, 0x200000003LL)) { printf("PASS\n"); return 0; @@ -393,8 +392,7 @@ 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() { - void *raw = words; - call_t call = raw; + call_t call = (call_t) words; if (call(1, 2, 3, 0x200000003LL, 9)) { printf("PASS\n"); return 0; diff --git a/tests/driver.sh b/tests/driver.sh index db983b28..00c59249 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1387,6 +1387,29 @@ try_compile_error_message "String literal initializer has incompatible array ele int main(void) { int values[3] = {"ab"}; return values[0]; } EOF +# 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 + # 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. @@ -4525,6 +4548,91 @@ int main(void) { return p[1][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; @@ -7456,6 +7564,31 @@ int main(void) { } 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 @@ -7472,6 +7605,78 @@ int main(void) { 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) { @@ -8446,10 +8651,13 @@ int main(void) { return 0; } EOF -try_compile_error << EOF +try_ 3 << EOF +int count(int n, ...) { return n; } int main(void) { typedef int scalar_t, variadic_t(int, ...); - return 0; + variadic_t *callback = count; + scalar_t value = callback(3, 1, 2); + return value; } EOF try_compile_error << EOF @@ -10742,6 +10950,61 @@ int main(void) { (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 @@ -11414,6 +11677,26 @@ 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() { @@ -11585,6 +11868,9 @@ EOF 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; } } @@ -14428,6 +14714,26 @@ int main(void) { } 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. @@ -15146,6 +15452,29 @@ 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 @@ -17309,6 +17638,224 @@ 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 +# 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_compile_error_message "integer converted to pointer without a cast" << EOF +int main(void) { int *p = 7; return 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 main(void) { int x = 1; int *p = x ? 1 : 2; return 0; } +EOF + # Category: Const Qualifiers begin_category "Const Qualifiers" "Testing const qualifier support for variables and parameters" @@ -22004,10 +22551,9 @@ EOF # 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. + # cases define "stdout" as descriptor 1 cast to FILE *, the standard output. try_output 0 "awritten = a" << EOF -#define stdout 1 +#define stdout ((FILE *) 1) int main() { int c = fputc('a', stdout); @@ -22017,7 +22563,7 @@ int main() EOF try_output 1 "" << EOF -#define stdout 1 +#define stdout ((FILE *) 1) int main() { __syscall(__syscall_close, 1); @@ -25315,6 +25861,9 @@ 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 diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index a7b264d7..34c3aadb 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -303,8 +303,7 @@ test_long_long_after_int() typedef int (*call_t)(int, long long); int words(int a, int low, int high, int unused) { return low == 3 && high == 2; } int main() { - void *raw = words; - call_t call = raw; + call_t call = (call_t) words; if (call(1, 0x200000003LL)) { printf("PASS\n"); return 0; @@ -323,8 +322,7 @@ test_variadic_long_long() typedef int (*call_t)(int, ...); int words(int a, int skipped, int low, int high) { return low == 3 && high == 2; } int main() { - void *raw = words; - call_t call = raw; + call_t call = (call_t) words; if (call(1, 0x200000003LL)) { printf("PASS\n"); return 0; @@ -352,8 +350,7 @@ int words(int a, int b, int c, int d, int e, int f, int g, int low, ...) { return low == 3 && high == 2; } int main() { - void *raw = words; - call_t call = raw; + call_t call = (call_t) words; if (call(1, 2, 3, 4, 5, 6, 7, 0x200000003LL)) { printf("PASS\n"); return 0; From 8c14996d390c2d5c7820433587e40d50524b293c Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 08:42:26 +0800 Subject: [PATCH 30/40] Fix expression lowering and function pointer forms Postfix updates were applied at the end of the full expression rather than at the sequence points of &&, ||, ?: and the comma, so x-- == 7 && x == 6 was false. Pointers compared as signed values, ++ and compound assignment stepped selected pointers by the wrong size, a record- defining pointer typedef was treated as a record, and a string literal assigned to a char pointer was not stored. Many function pointer forms were rejected or miscompiled: calls through dereferenced elements, pointers to function pointer arrays and their arithmetic, casts to written-out function pointer types, functions returning function pointers, compound literals of them, callbacks returning records, slots deeper than two stars and qualifiers on inner slot levels. Old-style function definitions and string literal subscripts were not accepted. Apply each update where its operand is evaluated, compare pointers as unsigned, step by the pointee, and model a callback slot by its depth so declarators, casts, calls, dereferences and conversions agree at any level. --- src/defs.h | 8 +- src/parser-call.c | 229 ++++++++++-- src/parser-const.c | 103 +++++- src/parser-decl.c | 274 +++++++++++++- src/parser-expr.c | 398 +++++++++++++------- src/parser-global.c | 101 +++++- src/parser-init.c | 111 +++++- src/parser-sizeof.c | 7 - src/parser-stmt.c | 30 +- src/parser.c | 73 +++- src/reg-alloc.c | 8 + tests/driver.sh | 857 ++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 1961 insertions(+), 238 deletions(-) diff --git a/src/defs.h b/src/defs.h index 60fab87a..182dcebd 100644 --- a/src/defs.h +++ b/src/defs.h @@ -76,8 +76,6 @@ #define MAX_FIELDS 96 #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. */ @@ -817,6 +815,12 @@ struct var { */ 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 diff --git a/src/parser-call.c b/src/parser-call.c index 3ae41aea..7b4ce24c 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -1018,7 +1018,11 @@ var_t *materialize_function_designator(block_t *parent, func_t *func; var_t *object; - if (!value || !value->is_func) + /* 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); @@ -1086,6 +1090,28 @@ var_t *prepare_aggregate_call_result(block_t *parent, 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; + var_t *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. */ @@ -1104,7 +1130,11 @@ var_t *emit_direct_call_result(func_t *func, read_func_call_with_sret(func, sret, parent, bb); } else { read_func_call(func, parent, bb); - if (want_value) { + 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 @@ -1143,7 +1173,11 @@ var_t *emit_indirect_call_result(var_t *callee, read_indirect_call_with_sret(callee, signature, sret, parent, bb); } else { read_indirect_call(callee, parent, bb); - if (want_value && signature) { + 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(); @@ -1341,6 +1375,26 @@ static void keep_pointee_array_shape(var_t *vd, const var_t *source) 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}`. */ @@ -1383,8 +1437,10 @@ static void take_expression_address(block_t *parent, basic_block_t **bb) read_expr_operand(parent, bb); operand = opstack_pop(); - if (operand->is_func) { - /* A function designator, possibly parenthesized, is its address. */ + 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; } @@ -1406,6 +1462,8 @@ static void take_expression_address(block_t *parent, basic_block_t **bb) result->is_const_qualified = operand->is_const_qualified; 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, @@ -1512,6 +1570,13 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) ? 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), @@ -1563,6 +1628,17 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) 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 @@ -1748,6 +1824,15 @@ void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1) 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 */ @@ -1776,16 +1861,78 @@ void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1) * actual dereference consumes that object-pointer level and yields the * pointer-valued callback result for a following postfix call. */ - if (deref_type && deref_type->func_signature && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = deref_type->func_signature; + 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; @@ -1856,14 +2003,8 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) push_dereference(parent, bb, rs1); return; } - if (var->is_func) { - /* C99 6.3.2.1 makes a function-pointer dereference a function - * designator. The pointer object itself therefore needs one load, - * not a dereference of its return type. - */ - opstack_push(load_function_pointer_object(parent, bb, rs1)); + if (push_dereferenced_function(parent, bb, &lvalue, var, rs1)) return; - } if (lower_pointee_array_dereference(rs1, parent, bb)) return; @@ -1899,17 +2040,20 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) * cleared to prevent the invalid `slot(...)` form, so restore the * element signature only after the real dereference has occurred. */ - if ((rs1->func_signature || rs1->pointee_func_signature || - (deref_type && deref_type->func_signature)) && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = rs1->func_signature - ? rs1->func_signature - : (rs1->pointee_func_signature - ? rs1->pointee_func_signature - : deref_type->func_signature); + 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); } @@ -1946,9 +2090,21 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) var_t *var = find_var(token, parent); read_lvalue(&lvalue, var, parent, bb, true, OP_generic); - /* Apply dereferences one by one */ + /* 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; @@ -1976,20 +2132,25 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) vd->ptr_level > 0 && vd->ptr_level <= 32 && (vd->pointer_const_mask & (1U << (vd->ptr_level - 1))); - /* Only the final dereference of `**slots` (or deeper spelling) - * reaches the callback object. Earlier reads still produce a - * pointer-to-callback and must not be callable. + /* 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 (i + 1 == deref_count && - (rs1->func_signature || - (deref_type && deref_type->func_signature)) && - effective_pointer_depth(rs1) == 1) { - vd->func_signature = rs1->func_signature - ? rs1->func_signature - : deref_type->func_signature; + 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); } diff --git a/src/parser-const.c b/src/parser-const.c index 268c6b20..a41bb913 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -19,12 +19,10 @@ static int read_global_sizeof_expression(block_t *scope) { basic_block_t *unevaluated_bb = bb_create(scope); - int saved_side_effects = se_idx; var_t *result; handle_sizeof_operator(scope, &unevaluated_bb); result = opstack_pop(); - se_idx = saved_side_effects; return result->init_val; } @@ -174,6 +172,23 @@ int eval_expression_imm(opcode_t op, int op1, int op2) 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 is an integer converted without a cast. */ @@ -697,7 +712,6 @@ var_t *read_wide_global_literal_primary(block_t *parent, } if (global_integer_cast_starts_at(cur_token->next, scope)) { basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; token_t *cast_start = cur_token; var_t *operand; type_t *type; @@ -709,7 +723,6 @@ var_t *read_wide_global_literal_primary(block_t *parent, * it as a wide primary, so a grouped operand keeps its high word. */ read_expr_operand(parent, &unevaluated_bb); - se_idx = saved_side_effects; operand = opstack_pop(); type = operand->type; cur_token = cast_start; @@ -965,6 +978,22 @@ bool read_global_assignment_var(var_t *var) 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; @@ -1015,7 +1044,7 @@ bool read_global_assignment_var(var_t *var) symbol->is_func = true; symbol->var_name = intern_string(address_dereference_identifier->literal); - diagnose_function_pointer_conversion(symbol, var); + 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; @@ -1040,7 +1069,7 @@ bool read_global_assignment_var(var_t *var) addr->var_name = gen_name(); symbol->is_func = true; symbol->var_name = intern_string(token); - diagnose_function_pointer_conversion(symbol, var); + diagnose_global_function_conversion(symbol, var); lex_expect(T_identifier); if (grouped_function_designator) lex_expect(T_close_bracket); @@ -1084,8 +1113,9 @@ bool read_global_assignment_var(var_t *var) (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL))) { int index = read_global_address_offset(scope, parent, bb); - int stride = - object->ptr_level ? PTR_SIZE : object->type->size; + 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); @@ -1099,6 +1129,8 @@ bool read_global_assignment_var(var_t *var) 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; @@ -1131,12 +1163,67 @@ bool read_global_assignment_var(var_t *var) 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 || diff --git a/src/parser-decl.c b/src/parser-decl.c index 28cd9682..f12eedbf 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -570,6 +570,52 @@ static bool grouped_declarator_name_follows(block_t *scope, bool is_param) !(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. @@ -607,6 +653,127 @@ static void read_adjusted_function_parameter(var_t *vd) 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 = 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, @@ -670,6 +837,12 @@ void read_inner_var_decl(var_t *vd, 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)`. */ @@ -684,14 +857,26 @@ void read_inner_var_decl(var_t *vd, 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) + if (nested_ptr_level >= 2) vd->parenthesized_function_pointer_outer_const = true; else vd->parenthesized_function_pointer_inner_qualified = @@ -701,7 +886,7 @@ void read_inner_var_decl(var_t *vd, vd->pointer_const_mask |= 1U << (vd->ptr_level + nested_ptr_level - 1); } else if (lex_accept(T_volatile)) { - if (nested_ptr_level == 2) + if (nested_ptr_level >= 2) vd->parenthesized_function_pointer_outer_volatile = true; else @@ -710,7 +895,7 @@ void read_inner_var_decl(var_t *vd, vd->is_volatile = true; } else if (lex_accept(T_restrict)) { vd->parenthesized_function_pointer_restrict = true; - if (nested_ptr_level == 2) + if (nested_ptr_level >= 2) vd->parenthesized_function_pointer_outer_restrict = true; else @@ -907,32 +1092,45 @@ void read_inner_var_decl(var_t *vd, vd->func_signature = func; vd->is_func = true; vd->parenthesized_function_pointer_level = nested_ptr_level; - if (nested_ptr_level == 2) { + 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. + * restores the callback signature before a call. Each further star, + * as in `int (***slot)(int)`, adds one more object pointer. */ - if (vd->parenthesized_function_pointer_inner_qualified) - error_at( - "inner callback-slot pointer qualifiers are not yet " - "supported", - cur_token_loc()); - if (vd->parenthesized_function_pointer_restrict && - !vd->parenthesized_function_pointer_outer_restrict) - error_at("callback-slot restrict typedef is not yet supported", + 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 that star's const bit from level two to level one. + * move each slot star's const bit one level down, dropping the + * callback pointer's own. */ - if (vd->parenthesized_function_pointer_outer_const) { - vd->pointer_const_mask &= ~2U; - vd->pointer_const_mask |= 1U; - vd->is_const_pointer = true; + 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; @@ -1624,6 +1822,31 @@ void read_parameter_list_decl(func_t *func, bool anon) /* 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)) { @@ -1665,6 +1888,21 @@ void read_parameter_list_decl(func_t *func, bool anon) 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()); diff --git a/src/parser-expr.c b/src/parser-expr.c index 9a7f1ab2..9ee1b706 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -309,6 +309,12 @@ typedef struct { 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 @@ -580,7 +586,6 @@ static void read_grouped_operand(block_t *parent, basic_block_t **bb) * expression supplies the result, which is not an lvalue. */ discard_operand(parent, *bb); - perform_side_effect(parent, *bb); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); read_ternary_operation(parent, bb); @@ -957,6 +962,13 @@ static void read_compound_literal_operand(block_t *parent, 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) @@ -997,6 +1009,48 @@ static void read_compound_literal_operand(block_t *parent, } 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; @@ -1195,6 +1249,11 @@ static void read_compound_literal_operand(block_t *parent, 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 @@ -1333,7 +1392,7 @@ static void read_cast_operand(block_t *parent, * have no local defining IR, so materialize the code address before OP_cast * treats it as an ordinary operand. */ - if (tn->type->func_signature) + if (tn->type->func_signature || tn->func_signature) expr_var = materialize_function_designator(parent, bb, expr_var); /* Create variable for cast result */ @@ -1348,6 +1407,19 @@ static void read_cast_operand(block_t *parent, 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 @@ -1519,6 +1591,12 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) 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 @@ -2005,6 +2083,13 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) !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()); @@ -2143,6 +2228,11 @@ int get_pointer_element_size(var_t *ptr_var) 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. @@ -2232,6 +2322,26 @@ static bool decay_fixed_array_rows(var_t *decayed, const var_t *var) 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, @@ -2243,6 +2353,9 @@ void handle_pointer_arithmetic(block_t *parent, 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. @@ -2314,8 +2427,10 @@ void handle_pointer_arithmetic(block_t *parent, !!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); - bool right_row = is_pointee_array_pointer(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)) @@ -2362,7 +2477,7 @@ void handle_pointer_arithmetic(block_t *parent, ptr_var = rs1; int_var = rs2; element_size = - is_pointee_array_pointer(rs1) + 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 @@ -2374,7 +2489,7 @@ void handle_pointer_arithmetic(block_t *parent, ptr_var = rs2; int_var = rs1; element_size = - is_pointee_array_pointer(rs2) + 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 @@ -2414,7 +2529,12 @@ void handle_pointer_arithmetic(block_t *parent, * typedefs cannot be mistaken for a direct callback value. */ vd->ptr_level = ptr_var->ptr_level + !!ptr_var->array_size; - if (is_pointee_array_pointer(ptr_var)) + vd->pointee_func_signature = ptr_var->pointee_func_signature; + + /* 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) { @@ -2609,6 +2729,16 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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 @@ -2753,10 +2883,13 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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 && (rs1->is_func || get_func_signature(rs1))) || - (rs2 && (rs2->is_func || get_func_signature(rs2))))) + ((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()); @@ -2977,6 +3110,54 @@ static bool lvalue_is_bool(const lvalue_t *lvalue) 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. */ @@ -3010,10 +3191,15 @@ static void lower_lvalue_tail(lvalue_t *lvalue, t->is_const_qualified = lvalue->type->is_const_qualified; /* A loaded pointer-to-array member, as in `*s.rows`, still points to a - * whole row. + * 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->subscript_depth && lvalue->decl && - lvalue->decl->pointee_array_size && !lvalue->pointee_func_signature) + 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 @@ -3021,6 +3207,24 @@ static void lower_lvalue_tail(lvalue_t *lvalue, * 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 @@ -3061,17 +3265,7 @@ static void lower_lvalue_tail(lvalue_t *lvalue, vd = require_var(parent); vd->var_name = gen_name(); - /* For pointer arithmetic, increment by the size of pointed-to type */ - if (!lvalue->is_reference && is_pointee_array_pointer(var)) - vd->init_val = pointee_array_row_stride(var); - else if (lvalue->ptr_level > 1) - vd->init_val = PTR_SIZE; - else if (lvalue->ptr_level) - vd->init_val = lvalue->type->size; - else if (lvalue->type && lvalue->type->ptr_level) - vd->init_val = get_pointer_element_size(var); - else - vd->init_val = 1; + vd->init_val = lvalue_step_size(lvalue, var); if (lvalue_is_wide_integer(lvalue)) vd->type = lvalue->type; opstack_push(vd); @@ -3151,41 +3345,20 @@ static void lower_lvalue_tail(lvalue_t *lvalue, return; } - /* This arm appends up to five entries, so check for room once before - * writing any of them. + /* 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. */ - if (se_idx + 5 > 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->is_reference && is_pointee_array_pointer(var)) { - increment_size = pointee_array_row_stride(var); - } else if (lvalue->ptr_level > 1 && !lvalue->is_reference) { - increment_size = PTR_SIZE; - } else if (lvalue->ptr_level && !lvalue->is_reference) { - increment_size = lvalue->type->size; - } else if (!lvalue->is_reference && lvalue->type && - lvalue->type->ptr_level > 0) { - /* A typedef pointer steps by its pointee, as the prefix form does: - * the alias's own size is a pointer's, whether it points at a - * record or at another pointer. - */ - increment_size = get_pointer_element_size(var); - } - vd->init_val = increment_size; + vd->init_val = lvalue_step_size(lvalue, var); if (lvalue_is_wide_integer(lvalue)) vd->type = lvalue->type; - - side_effect[se_idx].rd = vd; - side_effect[se_idx].rs1 = NULL; - side_effect[se_idx].rs2 = NULL; - se_idx++; + 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 @@ -3197,12 +3370,27 @@ static void lower_lvalue_tail(lvalue_t *lvalue, 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]; + + /* 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)) { @@ -3211,51 +3399,25 @@ static void lower_lvalue_tail(lvalue_t *lvalue, copy_pointee_array_shape(vd, var); } else if (lvalue_is_wide_integer(lvalue)) vd->type = lvalue->type; - side_effect[se_idx].rd = vd; - se_idx++; + 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). The deferred entries are raw instructions, so spell out the - * comparison normalize_bool() would emit. + * 6.5.2.4). */ - if (lvalue_is_bool(lvalue)) { - var_t *zero = require_typed_var(parent, TY_int); - var_t *truth = require_typed_var(parent, TY_bool); - - zero->var_name = gen_name(); - zero->init_val = 0; - zero->is_const = true; - side_effect[se_idx].opcode = OP_load_constant; - side_effect[se_idx].rd = zero; - side_effect[se_idx].rs1 = NULL; - side_effect[se_idx].rs2 = NULL; - se_idx++; - - truth->var_name = gen_name(); - side_effect[se_idx].opcode = OP_neq; - side_effect[se_idx].rd = truth; - side_effect[se_idx].rs1 = vd; - side_effect[se_idx].rs2 = zero; - se_idx++; - vd = truth; - } + if (lvalue_is_bool(lvalue)) + vd = normalize_bool(parent, bb, vd); 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++; + add_insn(parent, *bb, OP_write, NULL, opstack_pop(), vd, + lvalue->size, NULL); } 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++; + 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 */ @@ -3547,9 +3709,12 @@ void read_lvalue(lvalue_t *lvalue, /* 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. + * **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) + if (lvalue->value_ptr_level > 0 || + (var->pointee_func_signature && !subscript_depth && + var->ptr_level == 1)) lvalue->size = PTR_SIZE; } @@ -3684,7 +3849,9 @@ void read_lvalue(lvalue_t *lvalue, var->is_const_qualified || (lvalue->pointee_func_signature && var->type->array_element_is_const_pointer) || - (record_is_const && is_array_declarator(var)); + (record_is_const && is_array_declarator(var)) || + (var->callback_is_const && subscript_depth == 1 && + callback_slot_depth(var) == 1); } else { char token[MAX_ID_LEN]; @@ -3981,7 +4148,6 @@ void read_control_expression(block_t *parent, basic_block_t **bb) while (lex_accept(T_comma)) { discard_operand(parent, *bb); - perform_side_effect(parent, *bb); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); read_ternary_operation(parent, bb); @@ -4007,7 +4173,6 @@ basic_block_t *read_full_expression_statement(block_t *parent, { read_control_expression(parent, &bb); discard_operand(parent, bb); - perform_side_effect(parent, bb); lex_expect(T_semicolon); return bb; } @@ -4146,7 +4311,6 @@ void read_conditional_true_expression(block_t *parent, basic_block_t **bb) while (lex_accept(T_comma)) { discard_operand(parent, *bb); - perform_side_effect(parent, *bb); if (!read_assignment_expression(parent, bb)) { read_expr(parent, bb); read_ternary_operation(parent, bb); @@ -4432,25 +4596,11 @@ bool read_body_assignment(var_t *var, error_at("Pointer arithmetic on void* is invalid", cur_token_loc()); - /* if we have a pointer, shift it by element size But not if we are - * operating on a dereferenced value (array indexing) + /* A pointer is shifted by its element size, as ++ and -- step it, + * including a pointer selected by a subscript or member. */ - if (!one && (op == OP_add || op == OP_sub) && - !lvalue.is_reference && is_pointee_array_pointer(var)) - increment_size = pointee_array_row_stride(var); - else if (lvalue.ptr_level > 1 && !lvalue.is_reference) - increment_size = PTR_SIZE; - else 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) { - /* Keep typedef-hidden depth: a void ** alias advances over - * pointer objects, whereas a direct void * was rejected above - * and never reaches this scaling path. - */ - increment_size = get_pointer_element_size(var); - } + 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 @@ -4768,9 +4918,11 @@ bool read_body_assignment(var_t *var, } else if (incompatible_pointee_callback_conversion(rs1, vd)) { error_at("incompatible callback slot types in assignment", cur_token_loc()); - } else if (incompatible_const_pointer_conversion(rs1, vd)) { - diagnose_const_pointer_conversion(rs1, vd); } 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); @@ -5164,7 +5316,9 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) if ((field && field->is_const_qualified) || address->is_const_qualified || (address_depth > 1 && address_depth <= 32 && - (address_mask & (1U << (address_depth - 2))))) + (address_mask & (1U << (address_depth - 2)))) || + (!field && object_address->callback_is_const && + callback_slot_depth(object_address) == 1)) 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", diff --git a/src/parser-global.c b/src/parser-global.c index 980bc670..ddc32a2e 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -40,6 +40,72 @@ void read_global_init_var(var_t *var, block_t *block) 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. @@ -127,6 +193,8 @@ bool read_global_function_declarator(block_t *block, * 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) { @@ -149,6 +217,19 @@ bool read_global_function_declarator(block_t *block, 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 @@ -164,6 +245,20 @@ bool read_global_function_declarator(block_t *block, &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 @@ -196,7 +291,8 @@ bool read_global_function_declarator(block_t *block, if (!allow_definition) error_at("function definition must be the only declarator", next_token_loc()); - if (inherited_direct_function_type) + 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) @@ -937,6 +1033,9 @@ static void read_global_typedef_declarator(block_t *block, 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; } diff --git a/src/parser-init.c b/src/parser-init.c index a6b992ba..7542f17b 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -207,6 +207,60 @@ int read_global_address_offset(block_t *scope, */ 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; +} + /* 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. @@ -430,6 +484,18 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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; + return val; + } + if (address_dereference) { address_dereference_identifier = consume_global_function_address_dereference(); @@ -559,9 +625,19 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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->base_type == TYPE_typedef && type->num_fields > 0 && + !type->ptr_level)); } void parse_struct_field_init(block_t *parent, @@ -1805,7 +1881,6 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) } while (lex_accept(T_comma)) { discard_operand(parent, bb); - perform_side_effect(parent, bb); if (!read_assignment_expression(parent, &bb)) { read_expr(parent, &bb); read_ternary_operation(parent, &bb); @@ -1832,21 +1907,6 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) 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); - /* 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. @@ -2398,6 +2458,23 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) 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); + } + if (val && var->type->array_element_pointee_func_signature) { /* The array descriptor is not an element descriptor. Build the * slot type that this scalar initializer is about to store so diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 39740fb9..4719279b 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -474,15 +474,12 @@ static void consume_sizeof_postfix_operand(block_t *parent, lex_expect(T_identifier); else { basic_block_t *unevaluated_bb; - int saved_side_effects; lex_expect(T_open_square); unevaluated_bb = bb_create(parent); - saved_side_effects = se_idx; read_expr(parent, &unevaluated_bb); read_ternary_operation(parent, &unevaluated_bb); opstack_pop(); - se_idx = saved_side_effects; lex_expect(T_close_square); } } @@ -689,11 +686,9 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) } basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; unevaluated_expression_depth++; read_expr_operand(parent, &unevaluated_bb); unevaluated_expression_depth--; - se_idx = saved_side_effects; var_t *expr_var = opstack_pop(); if (is_bitfield(expr_var)) error_at("sizeof cannot be applied to a bit-field", @@ -776,14 +771,12 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) /* sizeof(expression) - parse the expression and get its type */ basic_block_t *unevaluated_bb = bb_create(parent); - int saved_side_effects = se_idx; unevaluated_expression_depth++; if (!read_assignment_expression(parent, &unevaluated_bb)) { read_expr(parent, &unevaluated_bb); read_ternary_operation(parent, &unevaluated_bb); } unevaluated_expression_depth--; - se_idx = saved_side_effects; var_t *expr_var = opstack_pop(); if (is_bitfield(expr_var)) error_at("sizeof cannot be applied to a bit-field", diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 2e486f25..fc51c49d 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -12,16 +12,6 @@ * definition that precedes it there and cannot be compiled on its own. */ -void perform_side_effect(block_t *parent, basic_block_t *bb) -{ - 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; -} - basic_block_t *read_code_block(func_t *func, block_t *parent, basic_block_t *bb); @@ -238,7 +228,6 @@ basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) lex_expect(T_open_curly); while (!lex_accept(T_close_curly)) { body = read_body_statement(blk, body); - perform_side_effect(blk, body); } /* Complete the deferred no-match dispatch after every case comparison is @@ -475,7 +464,6 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) } else { read_control_expression(blk, &setup); discard_operand(blk, setup); - perform_side_effect(blk, setup); } if (!for_decl_semicolon_consumed) @@ -513,7 +501,6 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) if (!lex_accept(T_close_bracket)) { read_control_expression(blk, &inc_); discard_operand(blk, inc_); - perform_side_effect(blk, inc_); lex_expect(T_close_bracket); } @@ -1091,9 +1078,6 @@ static basic_block_t *read_block_declarators( 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); nv->is_static = spec->is_static; @@ -1330,7 +1314,6 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) if (func) { lex_expect(T_identifier); emit_direct_call_result(func, false, parent, &bb); - perform_side_effect(parent, bb); lex_expect(T_semicolon); return bb; } @@ -1475,14 +1458,14 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, * 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, and no deeper pointer - * composition. + * 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 == 1 && !decl.array_size && - !decl.pointee_array_size && !base->ptr_level && + 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 && @@ -1492,7 +1475,7 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, 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 && + base->ptr_level >= 1 && base->pointee_func_signature && !decl.is_const_qualified && !decl.parenthesized_function_pointer_inner_qualified && !decl.parenthesized_function_pointer_restrict; @@ -1880,7 +1863,6 @@ basic_block_t *read_code_block(func_t *func, block_t *parent, basic_block_t *bb) while (!lex_accept(T_close_curly)) { bb = read_body_statement(blk, bb); - perform_side_effect(blk, bb); } block_depth--; diff --git a/src/parser.c b/src/parser.c index db4a1f57..0eadfb19 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; @@ -236,7 +232,6 @@ static void compose_block_typedef_array(type_t *alias, basic_block_t *handle_block_typedef_statement(block_t *parent, basic_block_t *bb); -void perform_side_effect(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); @@ -318,6 +313,28 @@ bool has_effective_pointer(const var_t *var) return var && (var->ptr_level || (var->type && var->type->ptr_level)); } +/* 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 @@ -692,6 +709,12 @@ type_t *pointee_type_from_pointer_typedef(type_t *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; @@ -1769,6 +1792,14 @@ bool incompatible_character_pointer_conversion(const var_t *from, !compatible_decl_type(from_pointee, to_pointee); } +/* Whether @var is a `void *` with no function type behind it. */ +static bool is_plain_void_pointer(const var_t *var) +{ + return var->type && var->type == TY_void && var->ptr_level == 1 && + !var->func_signature && !var->pointee_func_signature && + !var->is_func && !var->array_size; +} + bool incompatible_pointee_callback_conversion(const var_t *from, const var_t *to) { @@ -1782,9 +1813,36 @@ bool incompatible_pointee_callback_conversion(const var_t *from, !(from->ptr_level || from->type->ptr_level) && !from->is_func && !from->init_val && !from->init_val_hi)) return false; + + /* A callback slot is an object pointer, which converts to and from void * + * at any depth. + */ + if ((from->pointee_func_signature && is_plain_void_pointer(to)) || + (to->pointee_func_signature && is_plain_void_pointer(from))) + return false; from_signature = from->pointee_func_signature; to_signature = to->pointee_func_signature; + /* 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; + + /* Slots at different depths point to different types. An element of a + * callback slot array keeps its depth on the array descriptor instead. + */ + 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; + /* 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 @@ -1794,6 +1852,11 @@ bool incompatible_pointee_callback_conversion(const var_t *from, (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. */ diff --git a/src/reg-alloc.c b/src/reg-alloc.c index bd191b90..4909dec3 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -61,6 +61,14 @@ void set_ptr_flags(ph2_ir_t *ir, insn_t *insn) 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. diff --git a/tests/driver.sh b/tests/driver.sh index 00c59249..62140fb5 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -1930,6 +1930,18 @@ int f(void) int main(void) { return f() - 1; } EOF +# 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 + # 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 @@ -2541,6 +2553,147 @@ int main(void) { 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 + +# ++ 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 +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 + +# 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 @@ -4784,6 +4937,33 @@ int main(void) { } 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 @@ -7141,6 +7321,174 @@ 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) { @@ -12700,6 +13048,20 @@ 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. @@ -17638,6 +18000,478 @@ 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 +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 + +# 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 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 + +# 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 @@ -17890,6 +18724,29 @@ int main(void) { } 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; From 25b69d8f7e2c52bd02d3dcf139ebb4f460323ead Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 07:39:33 +0800 Subject: [PATCH 31/40] Complete printf and variadic macro comma elision The static library's printf family lacked %u, the long and long long conversions, and most C99 flags, widths, precisions and length modifiers, so shecc's own output could not match glibc's. Adding them made the inlined library exceed the DCE worklist, since the inliner put the whole library into one function. Separately, f(fmt, ##__VA_ARGS__) was treated as an invalid paste instead of the GNU comma elision. Implement every C99 integer and character conversion without floating point, split the inlined library into bounded functions, and follow the GNU extension for a comma pasted to an empty variadic argument. --- COMPLIANCE.md | 1 + lib/c.c | 520 ++++++++++++++++++++++++--------------------- src/preprocessor.c | 30 +++ tests/driver.sh | 118 ++++++++++ tools/inliner.c | 45 ++-- 5 files changed, 457 insertions(+), 257 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index c5400bd1..29c2efe8 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -113,6 +113,7 @@ This document tracks compliance gaps and non-standard behaviors. - Binary literals: `0b101010` - Escape sequence: `\e` for ESC character - 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/lib/c.c b/lib/c.c index 7fac7bcc..95ec26ca 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. * @@ -218,103 +217,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. Form it from int-range literals so - * 32-bit targets need no long-long lowering, and the digit loop never walks - * its stack buffer backwards indefinitely. - */ - if (val == (-2147483647 - 1)) { - 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. @@ -376,90 +284,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, const 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; @@ -467,89 +416,170 @@ void __format_to_buf(fmtbuf_t *fmtbuf, const 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; } } @@ -558,7 +588,7 @@ void __format_to_buf(fmtbuf_t *fmtbuf, const char *format, int *var_args) fmtbuf->buf[0] = 0; } -int __write_fmt(int fd, const 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; @@ -566,7 +596,7 @@ int __write_fmt(int fd, const 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; @@ -585,7 +615,7 @@ int __write_fmt(int fd, const 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); @@ -595,7 +625,7 @@ int __write_fmt(int fd, const char *str, int *var_args) int printf(const char *str, ...) { - return __write_fmt(1, str, &str + 1); + return __write_fmt(1, str, &str + 1, 1); } int sprintf(char *buffer, const char *str, ...) @@ -605,7 +635,7 @@ int sprintf(char *buffer, const char *str, ...) 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; } @@ -616,7 +646,7 @@ int snprintf(char *buffer, int n, const char *str, ...) 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; } @@ -624,7 +654,7 @@ int __free_all(void); 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) diff --git a/src/preprocessor.c b/src/preprocessor.c index 983c0bfd..c63864a6 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -2017,6 +2017,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. */ diff --git a/tests/driver.sh b/tests/driver.sh index 62140fb5..8b7d2f48 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -21859,6 +21859,110 @@ int main() { } 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'; @@ -22283,6 +22387,20 @@ int main() } 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 +#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 + # macro parameter substitution works in expression contexts try_ 15 << EOF #define ADD_PARAMS(a, b) ((a) + (b)) 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); From ef39f5bc60f71c94b4d599226ebe73cdb799ec6f Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 10:17:28 +0800 Subject: [PATCH 32/40] Reach the RISC-V PLT beyond the JAL range Under dynamic linking the PLT follows the code and read-only data, and every call to a shared library function, like the entry point's call to __libc_start_main, was a JAL, which reaches only 1 MiB. A large enough program, shecc itself built with --dynlink, put the PLT out of that range and the code generator aborted with "Offset too large". Emit a PLT call as AUIPC and JALR, which reach any offset, and count the extra instruction both at the entry point, moving the main wrapper and the global function four bytes further, and for each call to a function without a body, so the offset estimate matches the emitted code. --- src/riscv-codegen.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index bc62e1e7..a41a61d4 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -142,8 +142,11 @@ void update_elf_offset(ph2_ir_t *ph2_ir) case OP_write: elf_offset += ph2_ir->src1_hi >= 0 ? 8 : 4; return; - case OP_jump: 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_lshift: @@ -281,14 +284,14 @@ void cfg_flatten(void) func_t *func; if (dynlink) { - /* When using dynamic linking, 24 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 and clear a global stack, then jump to global init * function. */ - elf_offset = 96; + elf_offset = 100; } else { /* Under static linking, "__syscall" must be generated to allow the * program to invoke system calls. @@ -589,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() */ @@ -1182,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)); @@ -1191,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 * From dfb071d6e8270e6d06868960de591fd9696dff47 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 10:31:21 +0800 Subject: [PATCH 33/40] Diagnose pointer conversions in every initializer Only the first declarator of a block declaration had its initializer checked and converted, so int *a = 0, *b = 7; was accepted and int a = 1, b = (int[]){2, 3} stored the literal's address in b. A file-scope function pointer accepted a plain integer, an object pointer accepted a cast to a function pointer type, a static aggregate element lost the type of an explicit cast, a cast such as (int (**)(void)) 4 was rejected as a non-constant, and a zero enumeration constant was not a null pointer constant. Share one conversion path for every declarator, reject an integer for a function pointer unless it is a null pointer constant or explicitly cast, reject a function pointer for any object pointer destination, recognize casts to pointers to function pointers as object pointer casts, carry the cast type of an element, and mark enumeration constants as constants. --- src/parser-const.c | 29 +++-- src/parser-expr.c | 4 + src/parser-init.c | 118 +++++++++++++++--- src/parser-stmt.c | 302 +++++++++++++++++++-------------------------- src/parser.c | 10 ++ tests/driver.sh | 172 ++++++++++++++++++++++++++ 6 files changed, 434 insertions(+), 201 deletions(-) diff --git a/src/parser-const.c b/src/parser-const.c index a41bb913..4b5ddbc1 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -190,15 +190,20 @@ static void diagnose_global_function_conversion(var_t *symbol, var_t *var) } /* The integer evaluators below yield constants only, so a nonzero value for a - * pointer object is an integer converted without a cast. + * 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->array_size && - !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()); + 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, @@ -997,13 +1002,17 @@ bool read_global_assignment_var(var_t *var) /* global initialization must be constant */ if (global_pointer_cast_starts_here(scope)) { int saved_stride = global_pointer_cast_stride; - int stride = read_global_pointer_cast(scope); + 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); + 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; } @@ -1138,9 +1147,7 @@ bool read_global_assignment_var(var_t *var) if (explicit_address) object_addr = read_global_address_designator( scope, parent, &bb, &object, object_addr, false); - if (incompatible_pointee_callback_conversion(object_addr, var)) - error_at("incompatible callback slot types in initializer", - cur_token_loc()); + diagnose_callback_slot_initializer(object_addr, var); add_insn(parent, bb, OP_assign, var, object_addr, NULL, 0, NULL); return true; diff --git a/src/parser-expr.c b/src/parser-expr.c index 9ee1b706..c8fdbf9c 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -2047,8 +2047,12 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) 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); diff --git a/src/parser-init.c b/src/parser-init.c index 7542f17b..7b55c8d7 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -261,6 +261,8 @@ func_t *read_global_function_pointer_cast(block_t *scope) 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. @@ -280,6 +282,25 @@ bool global_pointer_cast_starts_here(block_t *scope) if (!has_type) return false; is_pointer = true; + } 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; @@ -288,9 +309,11 @@ bool global_pointer_cast_starts_here(block_t *scope) if (!is_tag && !type) return false; - if (type && (type->func_signature || type->is_direct_function_type)) + if (type && type->is_direct_function_type) return false; - if (type && type->ptr_level) + + /* 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 || @@ -303,18 +326,25 @@ bool global_pointer_cast_starts_here(block_t *scope) return false; previous = token->kind; } + + /* Without a star, a callback typedef is the function pointer cast that + * read_global_function_pointer_cast() reads. + */ return token && has_type && is_pointer; } /* Consume a cast that global_pointer_cast_starts_here() recognized and return - * the size of what its pointer type points to. + * 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) +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)) { @@ -323,10 +353,27 @@ int read_global_pointer_cast(block_t *scope) lex_accept(T_restrict)) ; } - lex_expect(T_close_bracket); if (!type) error_at("Unknown type in pointer cast", cur_token_loc()); - if (depth + type->ptr_level > 1) + + /* 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 && + !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) @@ -360,13 +407,15 @@ bool global_address_operand_starts_here(block_t *scope) return false; } -/* Read an integer constant operand of a pointer cast with @stride, and any - * offset that follows it, as the address constant the cast produces. +/* 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) + int stride, + func_t *slot_signature) { var_t *address = require_var(parent); int value = read_const_expr_operand(scope); @@ -377,8 +426,11 @@ var_t *read_global_cast_integer_address(block_t *parent, address->init_val = value; address->is_const = true; - /* The cast's result is a pointer, not the integer it was spelled with. */ + /* 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; } @@ -493,6 +545,14 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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; } @@ -509,13 +569,15 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) } if (global_pointer_cast_starts_here(scope)) { int saved_stride = global_pointer_cast_stride; - int stride = read_global_pointer_cast(scope); + 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); + 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; @@ -1714,6 +1776,8 @@ bool parse_struct_field_values(block_t *parent, 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, @@ -2473,9 +2537,31 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) ? 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); } - if (val && var->type->array_element_pointee_func_signature) { + /* 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. @@ -2483,9 +2569,11 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) var_t element_target = {0}; element_target.type = var->type; - element_target.ptr_level = var->type->array_element_ptr_level; + element_target.ptr_level = + element_slot ? var->type->array_element_ptr_level + : var->ptr_level; element_target.pointee_func_signature = - var->type->array_element_pointee_func_signature; + element_slot ? element_slot : var->pointee_func_signature; if (incompatible_pointee_callback_conversion(val, &element_target)) error_at( diff --git a/src/parser-stmt.c b/src/parser-stmt.c index fc51c49d..c68afcc1 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -816,6 +816,131 @@ static bool read_block_function_declarator(block_t *parent, var_t *var) 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 @@ -965,116 +1090,7 @@ static basic_block_t *read_block_declarators( reject_ordinary_typedef_collision(parent, 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); - } - - var_t *expr_result = opstack_pop(); - - /* 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; - } - - if (incompatible_pointee_callback_conversion(expr_result, var)) - error_at("incompatible callback slot types in initializer", - cur_token_loc()); - 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); - } - } - if (spec->is_static) - discard_global_declarator_operand(var); + read_block_declarator_storage(parent, &bb, spec, var); while (lex_accept(T_comma)) { var_t *nv; @@ -1099,71 +1115,7 @@ static basic_block_t *read_block_declarators( return bb; continue; } - if (is_incomplete_record_object(nv)) - error_at("Incomplete struct/union type cannot define an object", - cur_token_loc()); - reject_block_redeclaration(parent, nv, false, false); - add_insn(spec->is_static ? GLOBAL_BLOCK : parent, - spec->is_static ? GLOBAL_FUNC->bbs : bb, OP_allocat, nv, NULL, - NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - validate_string_array_initializer(nv); - if (spec->is_static) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - parse_array_init(nv, GLOBAL_BLOCK, &GLOBAL_FUNC->bbs); - } else if (global_compound_literal_starts_here() && - !(nv->ptr_level || nv->type->ptr_level) && - is_record_type(nv->type)) { - parse_global_compound_record_init(nv, GLOBAL_BLOCK); - } else if (global_compound_literal_starts_here() && - (nv->ptr_level || nv->type->ptr_level)) { - parse_global_compound_array_init(nv, GLOBAL_BLOCK); - } else if (global_compound_literal_starts_here()) { - parse_global_compound_scalar_init(nv, GLOBAL_BLOCK); - } else if (lex_peek(T_open_curly, NULL)) { - parse_global_record_init(nv, GLOBAL_BLOCK); - } else { - read_global_assignment_var(nv); - } - } else if ((nv->has_unsized_array || nv->array_size > 0) && - is_char_array(nv) && lex_peek(T_string, NULL)) { - parse_string_array_init(nv, parent, &bb); - } else if ((nv->has_unsized_array || nv->array_size > 0) && - is_wchar_array(nv) && lex_peek(T_wstring, NULL)) { - parse_wstring_array_init(nv, parent, &bb); - } else if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->has_unsized_array || - nv->ptr_level > 0)) { - /* Emit code for locals */ - parse_array_init(nv, parent, &bb); - } else if (lex_peek(T_open_curly, NULL) && - is_record_type(nv->type)) { - type_t *struct_type = nv->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, nv, 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_object_assignment(parent, &bb, nv, opstack_pop()); - } - } - if (spec->is_static) - discard_global_declarator_operand(nv); + read_block_declarator_storage(parent, &bb, spec, nv); } lex_expect(T_semicolon); return bb; diff --git a/src/parser.c b/src/parser.c index 0eadfb19..e84c9ce4 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1873,6 +1873,16 @@ bool incompatible_pointee_callback_conversion(const var_t *from, !compatible_function_signature(from_signature, to_signature); } +/* 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) +{ + 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. diff --git a/tests/driver.sh b/tests/driver.sh index 8b7d2f48..dcb62f67 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -11818,6 +11818,15 @@ int main() { } 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() { @@ -18094,6 +18103,81 @@ int main(void) list[1].g != 0 || t[2]() != 7 || so.f(0) != 1; } 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 + +# 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); }; @@ -18653,6 +18737,21 @@ int main(void) { (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 @@ -18687,9 +18786,79 @@ 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" @@ -26827,6 +26996,9 @@ 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 From 41810d0892f099f2803dc8c0fcc465665cde7995 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 15 Sep 2026 23:15:04 +0800 Subject: [PATCH 34/40] Fix function pointer typedefs and const lvalues A pointer-to-array typedef whose element came from a pointer or callback typedef was rejected or lost the callback element, at block scope even for typedef arr_t *rows_t, a call to a function returning a callback slot typedef lost the slot, and a function returning a function pointer could not be qualified or declared through a typedef. A cast to a callback typedef followed by a star was taken for a function pointer, and a static initializer rejected a cast to a function typedef followed by two stars. Constness was lost in several lvalues: stores through a pointer to a const callback typedef, through a const element of a row pointer, to members of a const record reached through (*ps).a, and through a decayed array member of a const record or pointer arithmetic on a pointer to const were accepted, while some stores that only change a pointer whose pointee is const were rejected. Keep typedef element depth and slot metadata on call results, build a row of callbacks as the pointee of its pointer typedef through one helper at either scope, accept the qualified and typedef forms, and carry each level's qualifier from the object or record that the lvalue selects. --- src/parser-call.c | 68 ++++- src/parser-decl.c | 26 +- src/parser-expr.c | 137 ++++++++-- src/parser-global.c | 49 +++- src/parser-init.c | 15 +- src/parser-stmt.c | 25 +- src/parser.c | 105 +++++++- tests/driver.sh | 613 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 996 insertions(+), 42 deletions(-) diff --git a/src/parser-call.c b/src/parser-call.c index 7b4ce24c..1d92e399 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -712,7 +712,12 @@ var_t *lower_reference_update(var_t *object, object->array_size || object->is_func || object->func_signature) error_at("Increment or decrement requires a scalar modifiable lvalue", cur_token_loc()); - if (object->is_const_qualified) + + /* 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(); @@ -1099,8 +1104,21 @@ 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; @@ -1460,6 +1478,7 @@ static void take_expression_address(block_t *parent, basic_block_t **bb) 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) @@ -1667,6 +1686,22 @@ static bool lower_pointee_array_dereference(var_t *source, 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); @@ -1852,6 +1887,7 @@ void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1) 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 && @@ -2177,6 +2213,12 @@ void lower_member_postfix(var_t *address, 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]; @@ -2237,8 +2279,13 @@ void lower_member_postfix(var_t *address, address = pointer; record_type = field->type; 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()); @@ -2255,7 +2302,9 @@ void lower_member_postfix(var_t *address, : NULL; } - if (depth < shape.rank && unevaluated_expression_depth) { + 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); @@ -2263,7 +2312,7 @@ void lower_member_postfix(var_t *address, fixed_array_shape_to_var(result, &shape); result->var_name = gen_name(); opstack_push(result); - } else if (depth < shape.rank) { + } else if (decayed) { for (int i = 0; i <= depth; i++) fixed_array_shape_drop_outer(&shape); result = @@ -2298,6 +2347,19 @@ void lower_member_postfix(var_t *address, } 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))) diff --git a/src/parser-decl.c b/src/parser-decl.c index f12eedbf..ec92afc4 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -686,7 +686,8 @@ static bool function_pointer_return_follows(void) if (!open || open->kind != T_open_bracket || !(token = open->next) || token->kind != T_asterisk) return false; - while (token && token->kind == T_asterisk) + 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 && @@ -799,6 +800,17 @@ void read_inner_var_decl(var_t *vd, 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++; @@ -999,6 +1011,13 @@ void read_inner_var_decl(var_t *vd, 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; @@ -1089,6 +1108,11 @@ void read_inner_var_decl(var_t *vd, 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; diff --git a/src/parser-expr.c b/src/parser-expr.c index c8fdbf9c..27eb3642 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -332,7 +332,20 @@ static void lower_value_subscript(block_t *parent, basic_block_t **bb) int subscript_depth = 0; int array_dims = 1 + !!base->array_dim2 + !!base->array_dim3 + !!base->array_dim4; - int element_size = base->ptr_level ? PTR_SIZE : base->type->size; + 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 @@ -389,6 +402,16 @@ static void lower_value_subscript(block_t *parent, basic_block_t **bb) vd = require_typed_ptr_var(parent, base->type, base->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; @@ -454,10 +477,13 @@ static void lower_value_member(block_t *parent, basic_block_t **bb) 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. + * 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. @@ -465,6 +491,7 @@ static void lower_value_member(block_t *parent, basic_block_t **bb) 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); @@ -1402,7 +1429,13 @@ static void read_cast_operand(block_t *parent, cast_var->is_const_pointer = tn->const_pointer; cast_var->pointer_const_mask = tn->pointer_const_mask; cast_var->is_volatile = tn->volatile_qualified; - if (tn->type->func_signature) { + + /* 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; } @@ -2535,6 +2568,14 @@ void handle_pointer_arithmetic(block_t *parent, 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. */ @@ -3194,6 +3235,13 @@ static void lower_lvalue_tail(lvalue_t *lvalue, 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]`. @@ -3432,6 +3480,27 @@ static void lower_lvalue_tail(lvalue_t *lvalue, } } +/* 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. @@ -3847,15 +3916,20 @@ void read_lvalue(lvalue_t *lvalue, } /* A subscript designates the pointee, whose qualification is the - * declaration's base qualification rather than `* const`. + * 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. */ - 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)) || - (var->callback_is_const && subscript_depth == 1 && - callback_slot_depth(var) == 1); + 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]; @@ -3939,13 +4013,21 @@ void read_lvalue(lvalue_t *lvalue, vd = require_var(parent); vd->var_name = gen_name(); - /* 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 (is_array_declarator(var) && !var->array_dim2) { - vd->type = var->type; - vd->ptr_level = var->ptr_level + 1; + 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); @@ -5288,6 +5370,8 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) * 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]; @@ -5303,6 +5387,9 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) 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; @@ -5317,12 +5404,20 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) int address_depth = effective_pointer_depth(object_address); unsigned int address_mask = effective_pointer_const_mask(object_address); - if ((field && field->is_const_qualified) || + + /* 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 && object_address->callback_is_const && - callback_slot_depth(object_address) == 1)) + (!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", diff --git a/src/parser-global.c b/src/parser-global.c index ddc32a2e..ca247a84 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -979,13 +979,17 @@ static void read_global_typedef_declarator(block_t *block, } /* 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) + 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. @@ -998,6 +1002,9 @@ static void read_global_typedef_declarator(block_t *block, } } + 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 @@ -1041,17 +1048,36 @@ static void read_global_typedef_declarator(block_t *block, /* `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. + * 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->ptr_level && !base->array_size && - !base->func_signature && + !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; @@ -1061,7 +1087,10 @@ static void read_global_typedef_declarator(block_t *block, 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 = (type_t *) base; + type->pointee_array_element_type = + base->ptr_level + ? pointee_type_from_pointer_typedef((type_t *) base) + : (type_t *) base; return; } if (!declarator.is_func) @@ -1069,6 +1098,16 @@ static void read_global_typedef_declarator(block_t *block, "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; diff --git a/src/parser-init.c b/src/parser-init.c index 7b55c8d7..456cc5ee 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -273,6 +273,8 @@ bool global_pointer_cast_starts_here(block_t *scope) 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; @@ -282,6 +284,7 @@ bool global_pointer_cast_starts_here(block_t *scope) 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. @@ -310,7 +313,7 @@ bool global_pointer_cast_starts_here(block_t *scope) if (!is_tag && !type) return false; if (type && type->is_direct_function_type) - return false; + function_type = true; /* A callback typedef takes a star to point to a pointer object. */ if (type && type->ptr_level && !type->func_signature) @@ -328,9 +331,10 @@ bool global_pointer_cast_starts_here(block_t *scope) } /* Without a star, a callback typedef is the function pointer cast that - * read_global_function_pointer_cast() reads. + * 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; + return token && has_type && is_pointer && (!function_type || depth > 1); } /* Consume a cast that global_pointer_cast_starts_here() recognized and return @@ -370,8 +374,9 @@ int read_global_pointer_cast(block_t *scope, func_t **slot_signature) return PTR_SIZE; } lex_expect(T_close_bracket); - if (type->func_signature && !type->is_direct_function_type && - !type->ptr_level && depth == 1) + 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; diff --git a/src/parser-stmt.c b/src/parser-stmt.c index c68afcc1..b16a754e 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -1386,6 +1386,14 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, 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 && @@ -1508,10 +1516,11 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, */ if (decl.has_unsized_array || ((decl.is_func || decl.func_signature) && !direct_function_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) || + !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) || @@ -1671,6 +1680,14 @@ basic_block_t *handle_block_typedef_statement(block_t *parent, : 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; diff --git a/src/parser.c b/src/parser.c index e84c9ce4..b0900a23 100644 --- a/src/parser.c +++ b/src/parser.c @@ -230,6 +230,48 @@ static void compose_block_typedef_array(type_t *alias, 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); @@ -313,6 +355,23 @@ 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. @@ -680,6 +739,14 @@ unsigned int effective_pointer_const_mask(const var_t *var) 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); } @@ -819,6 +886,16 @@ bool compatible_decl_type(const type_t *left, const type_t *right) 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 || @@ -1688,11 +1765,15 @@ void diagnose_integer_to_pointer_conversion(var_t *from, return; if (!array_is_pointer && (to->array_size || to->has_unsized_array)) return; + + /* 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->is_string_literal || - from->type == TY_void || is_record_type(from->type) || - is_null_pointer_constant(from)) + 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()); } @@ -1820,9 +1901,27 @@ bool incompatible_pointee_callback_conversion(const var_t *from, 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. + */ + 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; + /* 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; + /* 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. */ diff --git a/tests/driver.sh b/tests/driver.sh index dcb62f67..66a5c3ff 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -4701,6 +4701,470 @@ int main(void) { 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]; @@ -18104,6 +18568,33 @@ int main(void) } 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 @@ -18147,6 +18638,38 @@ 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 @@ -18274,6 +18797,27 @@ int main(void) } 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). @@ -18451,6 +18995,36 @@ 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 @@ -18477,6 +19051,35 @@ int main(void) } 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 @@ -18755,6 +19358,16 @@ 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 From e0d4a4e6197939fff1d15047bcea73addf62cfa0 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 16 Sep 2026 08:30:37 +0800 Subject: [PATCH 35/40] Keep narrow and folded values in range An int constant folded at parse time recorded only its low word, so a cast to a wide type read a zero high word and (long long) (0 - 42) was 4294967254 on every target. A narrow conversion that changes signedness left the value extended by its source type, so unsigned short v = c for a signed char c read as ffffffef on arm, arm64 and riscv. An array operand of a comparison was integer promoted, which sign extended the address of a char array and made p == buf false. On x86-64 a zero test, a conditional move, a logical not and a fused mask test read the whole 64-bit register although an int result only defines its low word, so (~v) ? 1 : 2 chose the wrong arm, and a signed right shift replicated the sign bit of that garbage. Sign extend a folded constant by its own result type, truncate a narrowing conversion in the shared lowering rather than in each backend, leave pointer-like operands out of integer promotion, and test a value at the width its instruction carries. --- src/parser-expr.c | 16 +++++- src/reg-alloc.c | 48 +++++++++++++++- src/x64-codegen.c | 77 +++++++++++++++++++------- tests/driver.sh | 137 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 22 deletions(-) diff --git a/src/parser-expr.c b/src/parser-expr.c index 27eb3642..d4e9efa4 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -2674,7 +2674,14 @@ type_t *integer_binary_result_type(opcode_t op, var_t *integer_promote_operand(block_t *parent, basic_block_t **bb, var_t *var) { - if (!var || var->ptr_level || !var->type || var->type->size >= TY_int->size) + /* 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); } @@ -3085,6 +3092,13 @@ void read_expr_body(block_t *parent, basic_block_t **bb) 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); diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 4909dec3..6960043e 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -2574,6 +2574,40 @@ void fill_unary_ph2_ir(ph2_ir_t *ir, insn_t *insn, int src0, int dest) 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) { @@ -2904,6 +2938,8 @@ void reg_alloc_global(insn_t *global_insn) 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); @@ -3289,7 +3325,15 @@ 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; } @@ -3609,6 +3653,8 @@ void reg_alloc_bb(func_t *func, basic_block_t *bb) 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"); diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 229593c6..4ceff77e 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 @@ -1860,8 +1882,15 @@ void emit_bitwise(ph2_ir_t *ph2_ir, * the save/restore around it: SAR r64, imm8 is one instruction. */ 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( @@ -1885,14 +1914,20 @@ void emit_bitwise(ph2_ir_t *ph2_ir, emit_byte(0x89); emit_byte(modrm(MOD_DIRECT, 1, 2)); - /* As in the literal-count form above, an unsigned int is zero-extended - * first: an unsigned int subtraction leaves a borrow in the upper half, - * which SHR would bring down into the result. + /* 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. */ - 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)); + 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)); @@ -2017,10 +2052,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. * @@ -2241,11 +2277,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 @@ -2726,9 +2762,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 */ } @@ -3053,13 +3088,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))); } diff --git a/tests/driver.sh b/tests/driver.sh index 66a5c3ff..ba92e0e4 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -2803,6 +2803,51 @@ int main(void) { return (byte == 0U) + (half == 0U); } EOF + +# 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) { @@ -2814,6 +2859,25 @@ int main(void) { grouped != 0xfffffff7ULL || signed_flip != -6; } EOF + +# 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) { @@ -2824,6 +2888,26 @@ int main(void) { } EOF +# 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 @@ -6718,6 +6802,59 @@ int main(void) { 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]; }; From bc173463d3a9eff1c2870197bff005449cc7321b Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 16 Sep 2026 09:44:34 +0800 Subject: [PATCH 36/40] Resolve declarations in their declaring scope A file-scope array compound literal read its element type as a single identifier, so (const int[]){1, 2} and (unsigned char[]){1, 2} were rejected although the same literals worked inside a function. An array declared without a bound could not be completed later: extern int a[] followed by int a[3] reported conflicting types. A typedef declared inside a function was invisible to a for initializer and to the label lookahead, so for (T i = 0; ...) was rejected while a label followed by a declaration through such a typedef was accepted in strict mode, and either form shadowing a global name resolved to the wrong kind. Inside a function sizeof yielded a signed int, so (sizeof(int) - 5) > 0 was false and -1 < sizeof(int) was true. Read the full type name of an array literal, compare an outer bound only when both declarations give one, look up a type through the enclosing block, and give a block-scope sizeof the size_t type, folding the unsigned and wide constants that null pointer constant checks then need. --- src/parser-expr.c | 70 ++++++++++++++++++ src/parser-global.c | 30 ++++++-- src/parser-sizeof.c | 5 +- src/parser-stmt.c | 19 +++-- src/parser.c | 5 +- tests/driver.sh | 169 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 18 deletions(-) diff --git a/src/parser-expr.c b/src/parser-expr.c index d4e9efa4..1e1bb0ff 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -2732,6 +2732,74 @@ void normalize_integer_binary_operands(block_t *parent, 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; @@ -2999,6 +3067,8 @@ void read_expr_body(block_t *parent, basic_block_t **bb) } 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 */ diff --git a/src/parser-global.c b/src/parser-global.c index ca247a84..3580ed99 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -359,13 +359,24 @@ var_t *resolve_global_declarator(block_t *block, *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->array_size != var->array_size || + 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 || @@ -382,6 +393,12 @@ var_t *resolve_global_declarator(block_t *block, 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. @@ -520,17 +537,16 @@ void parse_global_compound_scalar_init(var_t *var, block_t *block) */ void parse_global_compound_array_init(var_t *var, block_t *block) { - char type_name[MAX_ID_LEN]; 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); - base_type_t record_kind = accept_record_keyword(); - lex_ident(T_identifier, type_name); - element_type = record_kind - ? find_record_tag(type_name, var->scope, record_kind) - : find_type(type_name, 1); + 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) || diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 4719279b..2bd9e79e 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -70,11 +70,12 @@ int sizeof_grouped_literal_depth(token_t *token, token_kind_t kind) /* 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. + * 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_var(parent); + var_t *result = require_typed_var(parent, find_type("size_t", true)); result->init_val = size; result->is_const = true; diff --git a/src/parser-stmt.c b/src/parser-stmt.c index b16a754e..742aa6d4 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -32,7 +32,11 @@ typedef struct switch_label_context { switch_label_context_t switch_label_contexts[MAX_NESTING]; int switch_label_context_idx = 0; -void reject_label_followed_by_declaration_in_strict_c99(void) +/* 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]; @@ -47,7 +51,7 @@ void reject_label_followed_by_declaration_in_strict_c99(void) 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_type(name, true))) + (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()); } @@ -170,7 +174,7 @@ basic_block_t *read_switch_label_statement(block_t *parent, basic_block_t *body) context->dispatch_tail = next_dispatch; } lex_expect(T_colon); - reject_label_followed_by_declaration_in_strict_c99(); + reject_label_followed_by_declaration_in_strict_c99(parent); return label_body; } @@ -431,9 +435,10 @@ basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) /* 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_type(token, 1) - : NULL; + 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()); @@ -1285,7 +1290,7 @@ basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) const label_t *l = find_label(token); if (l) error_at("label redefinition", &id_tk->location); - reject_label_followed_by_declaration_in_strict_c99(); + 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); diff --git a/src/parser.c b/src/parser.c index b0900a23..5fbb8329 100644 --- a/src/parser.c +++ b/src/parser.c @@ -444,8 +444,9 @@ bool global_compound_literal_starts_here(void) if (!next || !((next->kind == T_identifier && find_type(next->literal, true)) || next->kind == T_struct || next->kind == T_union || - next->kind == T_signed || next->kind == T_unsigned || - next->kind == T_long)) + 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 diff --git a/tests/driver.sh b/tests/driver.sh index ba92e0e4..6fc69577 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -898,6 +898,39 @@ int main(void) { } EOF +# 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 + +# 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 + try_ 0 << EOF int global_abstract_array_size = sizeof(int[2][3]); int main(void) { return global_abstract_array_size != 6 * sizeof(int); } @@ -2258,6 +2291,49 @@ extern int value; int main(void) { return 0; } EOF +# 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 + # 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. @@ -7722,6 +7798,43 @@ int main(void) { } } 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; @@ -12113,6 +12226,36 @@ int main(void) { 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; @@ -27900,6 +28043,32 @@ EOF try_compile_error_flag --std=c99 << EOF int main(void) { label: typedef int value; return 0; } EOF + +# 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_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) { From 9cac258bc63cb9ad83efbf4a4727ce5ec36294f8 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 16 Sep 2026 08:31:03 +0800 Subject: [PATCH 37/40] Subscript rows and members of a call result A pointer to an array kept its remaining bounds only for the first subscript, so p[0][1] for an int (*p)[2][3] loaded an element instead of the row it selects and compared unequal to the same row of the object, while a row of pointers lost one indirection and crashed. A call returning such a pointer lost the shape entirely: partial subscripts gave a plain pointer with the wrong stride, sizeof measured a pointer, a record element was loaded as one scalar and lost the bytes past the first word, and a row of pointers reached through a typedef counted its element stars twice and crashed. A member selected through a pointer typedef was rejected after a call, an assignment whose left operand starts with a call was an unexpected token, and a partial row of an array compound literal measured a pointer. Carry what the subscripts leave, take the element depth from the alias that spells the stars, keep a record element at its address so the record copy path applies, select a member through its effective pointer depth, and route a call-rooted assignment through the postfix path. --- src/parser-call.c | 132 ++++++++++++++++--- src/parser-expr.c | 116 ++++++++++++++++- src/parser-sizeof.c | 11 +- tests/driver.sh | 301 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 537 insertions(+), 23 deletions(-) diff --git a/src/parser-call.c b/src/parser-call.c index 1d92e399..8d759fac 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -189,22 +189,75 @@ void copy_call_result_array_shape(var_t *result, const var_t *return_def) ? 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->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 || @@ -213,6 +266,15 @@ void lower_call_result_array_postfix(var_t **value, 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. */ @@ -221,8 +283,7 @@ void lower_call_result_array_postfix(var_t **value, address = base; do { var_t *index; - int stride = - base->pointee_array_element_ptr_level ? PTR_SIZE : base->type->size; + int stride = element_ptr_level ? PTR_SIZE : base->type->size; if (depth >= dimensions) error_at("Too many subscripts for function result", @@ -258,11 +319,12 @@ void lower_call_result_array_postfix(var_t **value, } while (lex_peek(T_open_square, NULL)); if (depth == dimensions) { - var_t *element = require_typed_var(parent, base->type); + var_t *element = require_typed_var(parent, element_type); opcode_t compound_op = OP_generic; bool assignment; + bool record; - element->ptr_level = base->pointee_array_element_ptr_level; + 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. @@ -273,8 +335,18 @@ void lower_call_result_array_postfix(var_t **value, element->is_const_qualified = base->is_const_qualified || base->type->is_const_qualified; element->var_name = gen_name(); - add_insn(parent, *bb, OP_read, element, address, NULL, - element->ptr_level ? PTR_SIZE : base->type->size, NULL); + + /* 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; @@ -290,6 +362,14 @@ void lower_call_result_array_postfix(var_t **value, 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, @@ -325,6 +405,11 @@ void lower_call_result_array_postfix(var_t **value, 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); @@ -346,7 +431,12 @@ void lower_call_result_array_postfix(var_t **value, } *value = element; } else { - *value = address; + /* 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; @@ -1678,6 +1768,12 @@ static bool lower_pointee_array_dereference(var_t *source, 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); @@ -2266,18 +2362,26 @@ void lower_member_postfix(var_t *address, : "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. */ - if (depth < shape.rank || field->ptr_level != 1 || - field->type->ptr_level || field->is_func || - !is_record_type(field->type)) + /* 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, 1); + 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 = field->type; + record_type = pointee; is_lvalue = true; record_const = field->is_const_qualified || field->type->is_const_qualified; diff --git a/src/parser-expr.c b/src/parser-expr.c index 1e1bb0ff..579245e6 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -396,10 +396,26 @@ static void lower_value_subscript(block_t *parent, basic_block_t **bb) subscript_depth++; } while (subscript_depth < array_dims && lex_accept(T_open_square)); - if (subscript_depth < array_dims) { + 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 { - vd = require_typed_ptr_var(parent, base->type, base->ptr_level); + /* 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; @@ -680,9 +696,13 @@ static void lower_array_literal_subscripts(block_t *parent, if (subscript_depth < tn->array_dims) { /* A partially selected row decays to its first element. It remains a - * pointer value, not a scalar read. + * pointer value, not a scalar read, and keeps the bounds the subscripts + * left for sizeof and for its own later subscripts. */ - opstack_push(address); + 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)) { @@ -3960,6 +3980,32 @@ void read_lvalue(lvalue_t *lvalue, 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 @@ -5223,6 +5269,58 @@ static bool grouped_postfix_lvalue_follows(void) 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. */ @@ -5238,6 +5336,14 @@ static void read_grouped_postfix_assignment(block_t *parent, basic_block_t **bb) 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()); @@ -5384,7 +5490,7 @@ bool read_assignment_expression(block_t *parent, basic_block_t **bb) 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()) { + grouped_postfix_lvalue_follows() || call_postfix_lvalue_follows()) { read_grouped_postfix_assignment(parent, bb); return true; } diff --git a/src/parser-sizeof.c b/src/parser-sizeof.c index 2bd9e79e..0d386d5c 100644 --- a/src/parser-sizeof.c +++ b/src/parser-sizeof.c @@ -705,10 +705,10 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) int size = type->size; - if (array_size > 0) - size = array_size * type->size; if (ptr_cnt) size = PTR_SIZE; + if (array_size > 0) + size *= array_size; push_sizeof_result(parent, *bb, size); return; } @@ -801,10 +801,13 @@ void handle_sizeof_operator(block_t *parent, basic_block_t **bb) array_size = type->array_size; int size = type->size; - if (array_size > 0) - size = array_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/tests/driver.sh b/tests/driver.sh index 6fc69577..c033a8a2 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -10920,6 +10920,27 @@ 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) { @@ -15902,6 +15923,61 @@ int main(void) { 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 @@ -27165,6 +27241,62 @@ 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 + # 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 @@ -27182,6 +27314,57 @@ int main(void) { 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; } @@ -27250,6 +27433,30 @@ row *pointers(void) { } int main(void) { return pointers()[0][1] == &second ? 4 : 0; } EOF + +# 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) +{ + 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) { @@ -27370,6 +27577,100 @@ row *callbacks(void) { int main(void) { return callbacks()[0][1](4); } EOF +# 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 + +# 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 + # Normal case try_output 0 "Hello" << EOF void func(char *ptr); From d54121539d003f00117565fddcf5fd5e36f6f4cb Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 16 Sep 2026 08:31:26 +0800 Subject: [PATCH 38/40] Evaluate every static initializer as a constant A file-scope brace initializer read an element that begins with a number or a character constant through the runtime expression reader, which emits a branch for a conditional or a logical operator and moved the global setup entry block, so int a = 7; int d[] = {1 ? 2 : 3, 4}; left both a and d[0] reading zero. A discarded conditional arm was skipped to the first colon, which dropped the declarator after int a = 1 ? 2 : 3, b = 4; and rejected a nested conditional, and an address constant could not be an operand of a conditional or a logical operator at all. Narrow operands of a wide initializer skipped integer promotion, so ~(unsigned char)0 was 4294967295; the minimum of a signed wide type divided by minus one was folded instead of diagnosed; an array bound or a bit-field width overflowed the compiler's own int accumulator, giving int a[3000000000U / 1000000000U] one element; and a cast address such as (int *) 0x100000010 lost its high word. Route every one of those forms through the constant evaluator, count brackets and pending question marks when skipping a discarded arm, promote narrow operands, diagnose the division overflow, and keep both words of a wide constant or address. An address operand is true, and one offset by an integer constant, in either order, advances by the stride its designator recorded. That stride is one more var_t member, so the member limit rises to 128, above the 127 C99 requires. --- src/defs.h | 14 ++- src/globals.c | 7 +- src/parser-const.c | 227 +++++++++++++++++++++++++++++++---- src/parser-decl.c | 25 ++++ src/parser-expr.c | 7 +- src/parser-init.c | 88 ++++++++++++-- src/parser-stmt.c | 20 ++-- src/preprocessor.c | 12 ++ tests/driver.sh | 289 +++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 640 insertions(+), 49 deletions(-) diff --git a/src/defs.h b/src/defs.h index 182dcebd..647b6799 100644 --- a/src/defs.h +++ b/src/defs.h @@ -70,10 +70,11 @@ #define MAX_LOCALS 3200 /* 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. Field tables - * are allocated lazily, so this does not inflate scalar type storage. + * 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 96 +#define MAX_FIELDS 128 #define MAX_TYPES 256 #define MAX_LABELS 256 /* Elements captured from an implicitly sized array initializer. */ @@ -810,6 +811,12 @@ struct var { */ 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. */ @@ -873,6 +880,7 @@ 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); diff --git a/src/globals.c b/src/globals.c index 01fe4200..6c5c8ac6 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1123,10 +1123,15 @@ bool wide_character_constant(const char *literal, int *value) 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. diff --git a/src/parser-const.c b/src/parser-const.c index 4b5ddbc1..25bafd69 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -371,6 +371,22 @@ 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. @@ -413,6 +429,51 @@ bool emit_wide_global_word_arithmetic(block_t *parent, 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 @@ -491,8 +552,8 @@ bool emit_wide_global_word_arithmetic(block_t *parent, } if (op == OP_log_and || op == OP_log_or) { - bool left_true = lo || hi; - bool right_true = rhs_lo || rhs_hi; + 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 @@ -516,6 +577,15 @@ bool emit_wide_global_word_arithmetic(block_t *parent, 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); @@ -638,6 +708,34 @@ bool emit_wide_global_word_arithmetic(block_t *parent, 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. @@ -670,7 +768,8 @@ var_t *read_wide_global_literal_primary(block_t *parent, force_wide_global_literal_type(value, literal); return value; } - value = read_wide_global_literal_primary(parent, bb, scope); + 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; @@ -685,7 +784,8 @@ var_t *read_wide_global_literal_primary(block_t *parent, if (lex_accept(T_bit_not)) { var_t *result; - value = read_wide_global_literal_primary(parent, bb, scope); + 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; @@ -699,13 +799,31 @@ var_t *read_wide_global_literal_primary(block_t *parent, value = read_wide_global_literal_primary(parent, bb, scope); result = require_typed_var(parent, TY_int); result->var_name = gen_name(); - result->init_val = !(value->init_val || value->init_val_hi); + 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(); @@ -762,7 +880,12 @@ var_t *read_wide_global_literal_primary(block_t *parent, 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; @@ -780,6 +903,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, 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; @@ -820,8 +944,8 @@ var_t *read_wide_global_literal_expression(block_t *parent, int op_stack_index = 0, val_stack_index = 0; opcode_t op; - val_stack[val_stack_index++] = - read_wide_global_literal_primary(parent, bb, scope); + 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) { @@ -854,7 +978,7 @@ var_t *read_wide_global_literal_expression(block_t *parent, val_stack[val_stack_index++] = result; } condition = val_stack[--val_stack_index]; - condition_true = condition->init_val || condition->init_val_hi; + 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 @@ -874,6 +998,13 @@ var_t *read_wide_global_literal_expression(block_t *parent, 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); @@ -905,20 +1036,21 @@ var_t *read_wide_global_literal_expression(block_t *parent, 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] = - is_logical(op) && - ((op == OP_log_and && - !(val_stack[val_stack_index - 1]->init_val || - val_stack[val_stack_index - 1]->init_val_hi)) || - (op == OP_log_or && - (val_stack[val_stack_index - 1]->init_val || - val_stack[val_stack_index - 1]->init_val_hi))); + 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++] = - read_wide_global_literal_primary(parent, bb, scope); + 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) { @@ -941,20 +1073,56 @@ var_t *read_wide_global_literal_expression(block_t *parent, 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) { - while (!lex_peek(T_colon, NULL)) { - lex_next(); - } - lex_accept(T_colon); + skip_discarded_conditional_arm(true); + lex_expect(T_colon); read_global_assignment_var(var); } else { read_global_assignment_var(var); lex_expect(T_colon); - while (!lex_peek(T_semicolon, NULL)) { - lex_next(); - } + skip_discarded_conditional_arm(false); } } @@ -983,6 +1151,17 @@ bool read_global_assignment_var(var_t *var) 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. */ diff --git a/src/parser-decl.c b/src/parser-decl.c index ec92afc4..268d9663 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -495,12 +495,37 @@ int read_const_expr_operand(block_t *scope) 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) { diff --git a/src/parser-expr.c b/src/parser-expr.c index 579245e6..508ec68d 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -1724,9 +1724,10 @@ static void read_parenthesized_operand(block_t *parent, basic_block_t **bb) error_at("Array declarators support at most four dimensions", cur_token_loc()); if (lex_peek(T_numeric, NULL)) { - char bound_token[MAX_TOKEN_LEN]; - lex_ident_n(T_numeric, bound_token, MAX_TOKEN_LEN); - bound = parse_numeric_constant(bound_token); + /* 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()); diff --git a/src/parser-init.c b/src/parser-init.c index 456cc5ee..a7333cef 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -28,6 +28,9 @@ 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, @@ -423,12 +426,36 @@ var_t *read_global_cast_integer_address(block_t *parent, func_t *slot_signature) { var_t *address = require_var(parent); - int value = read_const_expr_operand(scope); + 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); - if (lex_peek(T_plus, NULL) || lex_peek(T_minus, NULL)) - value += read_global_address_offset(scope, parent, bb) * stride; + 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 = value; + 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 @@ -503,19 +530,21 @@ var_t *read_global_address_designator(block_t *scope, 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 element_size = target->ptr_level ? PTR_SIZE : target->type->size; int index = read_global_address_offset(scope, parent, *bb); - int stride = global_pointer_cast_stride - ? global_pointer_cast_stride - : fixed_array_shape_stride( - &shape, subscripts - !decays, element_size); 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; } @@ -531,6 +560,32 @@ block_t *initializer_name_scope(block_t *parent) 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; @@ -607,6 +662,7 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) 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 @@ -2385,8 +2441,20 @@ void parse_array_init(var_t *var, block_t *parent, basic_block_t **bb) * 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_ampersand, NULL) || + (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) || diff --git a/src/parser-stmt.c b/src/parser-stmt.c index 742aa6d4..e62da729 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -56,19 +56,22 @@ void reject_label_followed_by_declaration_in_strict_c99(block_t *parent) cur_token_loc()); } -/* The word-sized constant evaluator narrows a case expression to int. Select - * the typed two-word evaluator when a literal needing a wider or unsigned type - * appears before the label's colon; a colon closing a nested '?' is not it. +/* 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 case_label_needs_typed_value(token_t *token) +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) + if (token->kind == T_open_bracket || token->kind == T_open_square) bracket_depth++; - else if (token->kind == T_close_bracket) { + else if (token->kind == T_close_bracket || + token->kind == T_close_square) { if (bracket_depth == 0) return false; bracket_depth--; @@ -80,7 +83,8 @@ bool case_label_needs_typed_value(token_t *token) if (pending_ternaries) pending_ternaries--; } else if (token->kind == T_semicolon || token->kind == T_open_curly || - token->kind == T_close_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) || @@ -115,7 +119,7 @@ basic_block_t *read_switch_label_statement(block_t *parent, basic_block_t *body) basic_block_t *next_dispatch; lex_expect(T_case); - if (case_label_needs_typed_value(cur_token->next)) { + if (constant_expression_needs_typed_value(cur_token->next)) { pp_integer_t typed_value; block_t *saved_scope = pp_integer_constant_scope; diff --git a/src/preprocessor.c b/src/preprocessor.c index c63864a6..1dad813c 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -1473,6 +1473,18 @@ token_t *pp_read_constant_expr_operand(token_t *tk, tk = pp_lex_next_token(tk, 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); diff --git a/tests/driver.sh b/tests/driver.sh index c033a8a2..04c73334 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -3620,11 +3620,196 @@ 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; @@ -3841,6 +4026,24 @@ int main(void) { } 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; @@ -16719,6 +16922,52 @@ int main(void) } 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 @@ -21496,6 +21745,46 @@ int main(void) } 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" From 205bad929d7e3779a4ff9700d543405fba9c6873 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 16 Sep 2026 08:31:35 +0800 Subject: [PATCH 39/40] Complete preprocessor argument and directive rules An argument was macro replaced with the invoked macro already hidden, so A(A(1)) left the nested use unexpanded, although C99 6.10.3.1 replaces an argument outside the macro it belongs to. A character constant in a scoped constant expression carried no width, so enum { A = 'a' << 1U } failed as a shift count out of range. The #line directive accepted a hexadecimal, suffixed or out of range operand where it takes decimal digits alone. An angle header name was resolved from its physical spelling, so a line splice or a trigraph inside it was never found. Expand an argument with the invocation's own hide set, give a character constant int width, accept only a decimal digit sequence after #line, and read an angle header through the same splice and trigraph handling as the rest of the source. --- src/preprocessor.c | 58 ++++++++++++++++++++++++++++++++------ tests/driver.sh | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/src/preprocessor.c b/src/preprocessor.c index 1dad813c..27bfe5f6 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -585,9 +585,11 @@ void install_stdarg_header(void) hashmap_put(MACROS, macro->name, macro); } -/* An angle header is lexed as ordinary preprocessing tokens. Recover its raw +/* 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. + * 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, @@ -595,13 +597,19 @@ bool resolve_angle_include(token_t *open, int resolved_size) { strbuf_t *source = get_file_buf(open->location.physical_filename); - int start = open->location.pos + open->location.len; - int len = close->location.pos - start; + int pos = open->location.pos + open->location.len; + int end = close->location.pos; + int len = 0; char name[MAX_LINE_LEN]; - if (len <= 0 || len >= 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); - memcpy(name, source->elements + start, len); name[len] = '\0'; for (int i = 0; i < len; i++) if (name[i] == ' ' || name[i] == '\t') @@ -802,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 */ @@ -835,6 +849,7 @@ token_t *pp_expand_line_operands(token_t *directive) 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); @@ -853,7 +868,21 @@ void pp_read_line_operands(token_t *directive, 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); - *requested_line = parse_numeric_constant(cursor->literal); + + /* 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)) { @@ -1383,6 +1412,7 @@ token_t *pp_expand_function_macro_in_constant_expr(token_t *before) 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); @@ -1455,9 +1485,13 @@ token_t *pp_read_constant_expr_operand(token_t *tk, 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; } @@ -1521,6 +1555,7 @@ token_t *pp_read_constant_expr_operand(token_t *tk, 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); @@ -2176,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; @@ -2202,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 @@ -2264,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); @@ -2293,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); @@ -2457,6 +2495,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) 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; @@ -2947,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; diff --git a/tests/driver.sh b/tests/driver.sh index 04c73334..d7f7d6e9 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -22012,6 +22012,22 @@ 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 }; @@ -22579,6 +22595,16 @@ try_compile_error << EOF #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; } @@ -23578,6 +23604,28 @@ try_compile_error << EOF 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. @@ -23615,6 +23663,27 @@ 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 From 90eacfad5a1e753a6d48ac83c560390ef34aa0e5 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Wed, 16 Sep 2026 08:31:44 +0800 Subject: [PATCH 40/40] Reach host libc objects through the dynamic linker With --dynlink the standard streams were the descriptors 0, 1 and 2 cast to FILE pointers, which the host libc then dereferenced, so fprintf(stderr, ...) crashed. The address of an imported function was never patched, because the reference was recorded only for a function that has a body, so &strlen was null and a call through it crashed; on arm an indirect call into a PLT entry also clobbered the register that holds the global base. memcpy was declared with char pointers rather than the void pointers C99 gives it, and SOURCE_DATE_EPOCH reached a shell command line before it was checked to be a number. Look the stream objects up in the host image, naming libdl.so.2 as well when dlsym is called because glibc before 2.34 keeps it there, resolve the address of a body-less function to its PLT entry and reload the global base after an indirect call, declare memcpy with void pointers, and accept only a decimal epoch. COMPLIANCE.md now calls a static for initializer an extension, which is what it is. --- COMPLIANCE.md | 2 +- Makefile | 8 ++++++ lib/c.c | 14 +++++----- lib/c.h | 16 ++++++++++- mk/arm.mk | 1 + mk/arm64.mk | 1 + mk/riscv.mk | 1 + mk/x64.mk | 1 + src/arm-codegen.c | 15 ++++++++++- src/elf.c | 17 ++++++++++++ src/globals.c | 10 +++---- src/x64-codegen.c | 15 ++++++++--- tests/driver.sh | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 152 insertions(+), 17 deletions(-) diff --git a/COMPLIANCE.md b/COMPLIANCE.md index 29c2efe8..bfa65e32 100644 --- a/COMPLIANCE.md +++ b/COMPLIANCE.md @@ -51,7 +51,7 @@ This document tracks compliance gaps and non-standard behaviors. | Feature | Status | Impact | |---------|--------|--------| -| `static` | Partial | File-scope internal linkage and persistent block-scope objects work, including C99 `for` initializers; cross-translation-unit linkage remains incomplete. | +| `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. | diff --git a/Makefile b/Makefile index a48d5c36..434ca363 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,14 @@ BUILTIN_LIBC_HEADER := c.h 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. diff --git a/lib/c.c b/lib/c.c index 95ec26ca..ce6ac3fc 100644 --- a/lib/c.c +++ b/lib/c.c @@ -164,23 +164,25 @@ char *strncpy(char *dest, const char *src, int len) return dest; } -char *memcpy(char *dest, const 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; } diff --git a/lib/c.h b/lib/c.h index 9c3e74fb..cb335794 100644 --- a/lib/c.h +++ b/lib/c.h @@ -109,10 +109,24 @@ 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 ((FILE *) 0) #define stdout ((FILE *) 1) #define stderr ((FILE *) 2) +#endif FILE *fopen(const char *filename, const char *mode); int fclose(FILE *stream); @@ -139,7 +153,7 @@ 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, const char *src, int count); +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); 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 f0fdda95..359c4ab5 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -123,9 +123,12 @@ void update_elf_offset(ph2_ir_t *ph2_ir) 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_lshift: case OP_rshift: elf_offset += ph2_ir->dest_hi >= 0 && ph2_ir->src0_hi >= 0 ? 48 : 4; @@ -691,6 +694,16 @@ 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) diff --git a/src/elf.c b/src/elf.c index 9483fc56..c2ff605f 100644 --- a/src/elf.c +++ b/src/elf.c @@ -928,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 */ @@ -951,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. @@ -1095,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 diff --git a/src/globals.c b/src/globals.c index 6c5c8ac6..2dad38be 100644 --- a/src/globals.c +++ b/src/globals.c @@ -2466,11 +2466,11 @@ __noreturn void fatal(const char *msg) * 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(); diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 4ceff77e..adc8461d 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -155,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 @@ -2547,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); @@ -4121,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/tests/driver.sh b/tests/driver.sh index d7f7d6e9..2ff6d437 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -25046,6 +25046,20 @@ else echo "Skip test cases because of using dynamic linking mode" fi # "LINK_MODE" = "static" +# 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 + # tests integer type conversion excerpted and modified from issue #166 try_output 0 "a = -127, b = -78, c = -93, d = -44" << EOF int main() @@ -25111,6 +25125,60 @@ int main(void) } EOF +# 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) +{ + 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 + +# 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) +{ + int (*local_len)(const char *) = &strlen; + struct ops local_ops = {strlen, memset}; + int word = 0; + + 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 + try_output 0 "2748 6719 105884 0" << EOF int main() {