diff --git a/.gitignore b/.gitignore index 80996e92..19d5d8ea 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,7 @@ **/*.s **/a.out /tmp* +/thirdparty /chibicc /test/*.exe +/stage2 diff --git a/Makefile b/Makefile index 000d77e3..c22748cc 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS=-std=c11 -g -fno-common +CFLAGS=-std=c11 -g -fno-common -Wall -Wno-switch SRCS=$(wildcard *.c) OBJS=$(SRCS:.c=.o) @@ -6,21 +6,45 @@ OBJS=$(SRCS:.c=.o) TEST_SRCS=$(wildcard test/*.c) TESTS=$(TEST_SRCS:.c=.exe) +# Stage 1 + chibicc: $(OBJS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(OBJS): chibicc.h test/%.exe: chibicc test/%.c - $(CC) -o- -E -P -C test/$*.c | ./chibicc -o test/$*.s - - $(CC) -o $@ test/$*.s -xc test/common + ./chibicc -Iinclude -Itest -c -o test/$*.o test/$*.c + $(CC) -pthread -o $@ test/$*.o -xc test/common test: $(TESTS) for i in $^; do echo $$i; ./$$i || exit 1; echo; done - test/driver.sh + test/driver.sh ./chibicc + +test-all: test test-stage2 + +# Stage 2 + +stage2/chibicc: $(OBJS:%=stage2/%) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +stage2/%.o: chibicc %.c + mkdir -p stage2/test + ./chibicc -c -o $(@D)/$*.o $*.c + +stage2/test/%.exe: stage2/chibicc test/%.c + mkdir -p stage2/test + ./stage2/chibicc -Iinclude -Itest -c -o stage2/test/$*.o test/$*.c + $(CC) -pthread -o $@ stage2/test/$*.o -xc test/common + +test-stage2: $(TESTS:test/%=stage2/test/%) + for i in $^; do echo $$i; ./$$i || exit 1; echo; done + test/driver.sh ./stage2/chibicc + +# Misc. clean: - rm -rf chibicc tmp* $(TESTS) test/*.s test/*.exe + rm -rf chibicc tmp* $(TESTS) test/*.s test/*.exe stage2 find * -type f '(' -name '*~' -o -name '*.o' ')' -exec rm {} ';' -.PHONY: test clean +.PHONY: test clean test-stage2 diff --git a/README.md b/README.md index 60eb4047..289a630f 100644 --- a/README.md +++ b/README.md @@ -1 +1,209 @@ -This is the reference implementation of https://www.sigbus.info/compilerbook. +# chibicc: A Small C Compiler + +(The old master has moved to +[historical/old](https://github.com/rui314/chibicc/tree/historical/old) +branch. This is a new one uploaded in September 2020.) + +chibicc is yet another small C compiler that implements most C11 +features. Even though it still probably falls into the "toy compilers" +category just like other small compilers do, chibicc can compile +several real-world programs, including [Git](https://git-scm.com/), +[SQLite](https://sqlite.org), +[libpng](http://www.libpng.org/pub/png/libpng.html) and chibicc +itself, without making modifications to the compiled programs. +Generated executables of these programs pass their corresponding test +suites. So, chibicc actually supports a wide variety of C11 features +and is able to compile hundreds of thousands of lines of real-world C +code correctly. + +chibicc is developed as the reference implementation for a book I'm +currently writing about the C compiler and the low-level programming. +The book covers the vast topic with an incremental approach; in the first +chapter, readers will implement a "compiler" that accepts just a single +number as a "language", which will then gain one feature at a time in each +section of the book until the language that the compiler accepts matches +what the C11 spec specifies. I took this incremental approach from [the +paper](http://scheme2006.cs.uchicago.edu/11-ghuloum.pdf) by Abdulaziz +Ghuloum. + +Each commit of this project corresponds to a section of the book. For this +purpose, not only the final state of the project but each commit was +carefully written with readability in mind. Readers should be able to learn +how a C language feature can be implemented just by reading one or a few +commits of this project. For example, this is how +[while](https://github.com/rui314/chibicc/commit/773115ab2a9c4b96f804311b95b20e9771f0190a), +[[]](https://github.com/rui314/chibicc/commit/75fbd3dd6efde12eac8225d8b5723093836170a5), +[?:](https://github.com/rui314/chibicc/commit/1d0e942fd567a35d296d0f10b7693e98b3dd037c), +and [thread-local +variable](https://github.com/rui314/chibicc/commit/79644e54cc1805e54428cde68b20d6d493b76d34) +are implemented. If you have plenty of spare time, it might be fun to read +it from the [first +commit](https://github.com/rui314/chibicc/commit/0522e2d77e3ab82d3b80a5be8dbbdc8d4180561c). + +If you like this project, please consider purchasing a copy of the book +when it becomes available! 😀 I publish the source code here to give people +early access to it, because I was planing to do that anyway with a +permissive open-source license after publishing the book. If I don't charge +for the source code, it doesn't make much sense to me to keep it private. I +hope to publish the book in 2021. +You can sign up [here](https://forms.gle/sgrMWHGeGjeeEJcX7) to receive a +notification when a free chapter is available online or the book is published. + +I pronounce chibicc as _chee bee cee cee_. "chibi" means "mini" or +"small" in Japanese. "cc" stands for C compiler. + +## Status + +chibicc supports almost all mandatory features and most optional +features of C11 as well as a few GCC language extensions. + +Features that are often missing in a small compiler but supported by +chibicc include (but not limited to): + +- Preprocessor +- float, double and long double (x87 80-bit floating point numbers) +- Bit-fields +- alloca() +- Variable-length arrays +- Compound literals +- Thread-local variables +- Atomic variables +- Common symbols +- Designated initializers +- L, u, U and u8 string literals +- Functions that take or return structs as values, as specified by the + x86-64 SystemV ABI + +chibicc does not support complex numbers, K&R-style function prototypes +and GCC-style inline assembly. Digraphs and trigraphs are intentionally +left out. + +chibicc outputs a simple but nice error message when it finds an error in +source code. + +There's no optimization pass. chibicc emits terrible code which is probably +twice or more slower than GCC's output. I have a plan to add an +optimization pass once the frontend is done. + +I'm using Ubuntu 20.04 for x86-64 as a development platform. I made a +few small changes so that chibicc works on Ubuntu 18.04, Fedora 32 and +Gentoo 2.6, but portability is not my goal at this moment. It may or +may not work on systems other than Ubuntu 20.04. + +## Internals + +chibicc consists of the following stages: + +- Tokenize: A tokenizer takes a string as an input, breaks it into a list + of tokens and returns them. + +- Preprocess: A preprocessor takes as an input a list of tokens and output + a new list of macro-expanded tokens. It interprets preprocessor + directives while expanding macros. + +- Parse: A recursive descendent parser constructs abstract syntax trees + from the output of the preprocessor. It also adds a type to each AST + node. + +- Codegen: A code generator emits an assembly text for given AST nodes. + +## Contributing + +When I find a bug in this compiler, I go back to the original commit that +introduced the bug and rewrite the commit history as if there were no such +bug from the beginning. This is an unusual way of fixing bugs, but as a +part of a book, it is important to keep every commit bug-free. + +Thus, I do not take pull requests in this repo. You can send me a pull +request if you find a bug, but it is very likely that I will read your +patch and then apply that to my previous commits by rewriting history. I'll +credit your name somewhere, but your changes will be rewritten by me before +submitted to this repository. + +Also, please assume that I will occasionally force-push my local repository +to this public one to rewrite history. If you clone this project and make +local commits on top of it, your changes will have to be rebased by hand +when I force-push new commits. + +## Design principles + +chibicc's core value is its simplicity and the reability of its source +code. To achieve this goal, I was careful not to be too clever when +writing code. Let me explain what that means. + +Oftentimes, as you get used to the code base, you are tempted to +_improve_ the code using more abstractions and clever tricks. +But that kind of _improvements_ don't always improve readability for +first-time readers and can actually hurts it. I tried to avoid the +pitfall as much as possible. I wrote this code not for me but for +first-time readers. + +If you take a look at the source code, you'll find a couple of +dumb-looking pieces of code. These are written intentionally that way +(but at some places I might be actually missing something, +though). Here is a few notable examples: + +- The recursive descendent parser contains many similar-looking functions + for similar-looking generative grammar rules. You might be tempted + to _improve_ it to reduce the duplication using higher-order functions + or macros, but I thought that that's too complicated. It's better to + allow small duplications instead. + +- chibicc doesn't try too hard to save memory. An entire input source + file is read to memory first before the tokenizer kicks in, for example. + +- Slow algorithms are fine if we know that n isn't too big. + For example, we use a linked list as a set in the preprocessor, so + the membership check takes O(n) where n is the size of the set. But + that's fine because we know n is usually very small. + And even if n can be very big, I stick with a simple slow algorithm + until it is proved by benchmarks that that's a bottleneck. + +- Each AST node type uses only a few members of the `Node` struct members. + Other unused `Node` members are just a waste of memory at runtime. + We could save memory using unions, but I decided to simply put everything + in the same struct instead. I believe the inefficiency is negligible. + Even if it matters, we can always change the code to use unions + at any time. I wanted to avoid premature optimization. + +- chibicc always allocates heap memory using `calloc`, which is a + variant of `malloc` that clears memory with zero. `calloc` is + slightly slower than `malloc`, but that should be neligible. + +- Last but not least, chibicc allocates memory using `calloc` but never + calls `free`. Allocated heap memory is not freed until the process exits. + I'm sure that this memory management policy (or lack thereof) looks + very odd, but it makes sense for short-lived programs such as compilers. + DMD, a compiler for the D programming language, uses the same memory + management scheme for the same reason, for example [1]. + +## About the Author + +I'm Rui Ueyama. I'm the creator of [8cc](https://github.com/rui314/8cc), +which is a hobby C compiler, and also the original creator of the current +version of [LLVM lld](https://lld.llvm.org) linker, which is a +production-quality linker used by various operating systems and large-scale +build systems. + +## References + +- [tcc](https://bellard.org/tcc/): A small C compiler written by Fabrice + Bellard. I learned a lot from this compiler, but the design of tcc and + chibicc are different. In particular, tcc is a one-pass compiler, while + chibicc is a multi-pass one. + +- [lcc](https://github.com/drh/lcc): Another small C compiler. The creators + wrote a [book](https://sites.google.com/site/lccretargetablecompiler/) + about the internals of lcc, which I found a good resource to see how a + compiler is implemented. + +- [An Incremental Approach to Compiler + Construction](http://scheme2006.cs.uchicago.edu/11-ghuloum.pdf) + +- [Rob Pike's 5 Rules of Programming](https://users.ece.utexas.edu/~adnan/pike.html) + +[1] https://www.drdobbs.com/cpp/increasing-compiler-speed-by-over-75/240158941 + +> DMD does memory allocation in a bit of a sneaky way. Since compilers +> are short-lived programs, and speed is of the essence, DMD just +> mallocs away, and never frees. diff --git a/chibicc.h b/chibicc.h index 219df304..f73eeec0 100644 --- a/chibicc.h +++ b/chibicc.h @@ -2,22 +2,47 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include + +#define MAX(x, y) ((x) < (y) ? (y) : (x)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +#ifndef __GNUC__ +# define __attribute__(x) +#endif typedef struct Type Type; typedef struct Node Node; typedef struct Member Member; +typedef struct Relocation Relocation; +typedef struct Hideset Hideset; // // strings.c // -char *format(char *fmt, ...); +typedef struct { + char **data; + int capacity; + int len; +} StringArray; + +void strarray_push(StringArray *arr, char *s); +char *format(char *fmt, ...) __attribute__((format(printf, 1, 2))); // // tokenize.c @@ -30,34 +55,69 @@ typedef enum { TK_KEYWORD, // Keywords TK_STR, // String literals TK_NUM, // Numeric literals + TK_PP_NUM, // Preprocessing numbers TK_EOF, // End-of-file markers } TokenKind; +typedef struct { + char *name; + int file_no; + char *contents; + + // For #line directive + char *display_name; + int line_delta; +} File; + // Token type typedef struct Token Token; struct Token { - TokenKind kind; // Token kind - Token *next; // Next token - int64_t val; // If kind is TK_NUM, its value - char *loc; // Token location - int len; // Token length - Type *ty; // Used if TK_STR - char *str; // String literal contents including terminating '\0' - - int line_no; // Line number + TokenKind kind; // Token kind + Token *next; // Next token + int64_t val; // If kind is TK_NUM, its value + long double fval; // If kind is TK_NUM, its value + char *loc; // Token location + int len; // Token length + Type *ty; // Used if TK_NUM or TK_STR + char *str; // String literal contents including terminating '\0' + + File *file; // Source location + char *filename; // Filename + int line_no; // Line number + int line_delta; // Line number + bool at_bol; // True if this token is at beginning of line + bool has_space; // True if this token follows a space character + Hideset *hideset; // For macro expansion + Token *origin; // If this is expanded from a macro, the original token }; -void error(char *fmt, ...); -void error_at(char *loc, char *fmt, ...); -void error_tok(Token *tok, char *fmt, ...); +noreturn void error(char *fmt, ...) __attribute__((format(printf, 1, 2))); +noreturn void error_at(char *loc, char *fmt, ...) __attribute__((format(printf, 2, 3))); +noreturn void error_tok(Token *tok, char *fmt, ...) __attribute__((format(printf, 2, 3))); +void warn_tok(Token *tok, char *fmt, ...) __attribute__((format(printf, 2, 3))); bool equal(Token *tok, char *op); Token *skip(Token *tok, char *op); bool consume(Token **rest, Token *tok, char *str); +void convert_pp_tokens(Token *tok); +File **get_input_files(void); +File *new_file(char *name, int file_no, char *contents); +Token *tokenize_string_literal(Token *tok, Type *basety); +Token *tokenize(File *file); Token *tokenize_file(char *filename); #define unreachable() \ error("internal error at %s:%d", __FILE__, __LINE__) +// +// preprocess.c +// + +char *search_include_paths(char *filename); +void init_macros(void); +void define_macro(char *name, char *buf); +void undef_macro(char *name); +Token *preprocess(Token *tok); + // // parse.c // @@ -68,49 +128,100 @@ struct Obj { Obj *next; char *name; // Variable name Type *ty; // Type + Token *tok; // representative token bool is_local; // local or global/function + int align; // alignment // Local variable int offset; // Global variable or function bool is_function; + bool is_definition; + bool is_static; // Global variable + bool is_tentative; + bool is_tls; char *init_data; + Relocation *rel; // Function + bool is_inline; Obj *params; Node *body; Obj *locals; + Obj *va_area; + Obj *alloca_bottom; int stack_size; + + // Static inline function + bool is_live; + bool is_root; + StringArray refs; +}; + +// Global variable can be initialized either by a constant expression +// or a pointer to another global variable. This struct represents the +// latter. +typedef struct Relocation Relocation; +struct Relocation { + Relocation *next; + int offset; + char **label; + long addend; }; // AST node typedef enum { + ND_NULL_EXPR, // Do nothing ND_ADD, // + ND_SUB, // - ND_MUL, // * ND_DIV, // / ND_NEG, // unary - + ND_MOD, // % + ND_BITAND, // & + ND_BITOR, // | + ND_BITXOR, // ^ + ND_SHL, // << + ND_SHR, // >> ND_EQ, // == ND_NE, // != ND_LT, // < ND_LE, // <= ND_ASSIGN, // = + ND_COND, // ?: ND_COMMA, // , ND_MEMBER, // . (struct member access) ND_ADDR, // unary & ND_DEREF, // unary * + ND_NOT, // ! + ND_BITNOT, // ~ + ND_LOGAND, // && + ND_LOGOR, // || ND_RETURN, // "return" ND_IF, // "if" ND_FOR, // "for" or "while" + ND_DO, // "do" + ND_SWITCH, // "switch" + ND_CASE, // "case" ND_BLOCK, // { ... } + ND_GOTO, // "goto" + ND_GOTO_EXPR, // "goto" labels-as-values + ND_LABEL, // Labeled statement + ND_LABEL_VAL, // [GNU] Labels-as-values ND_FUNCALL, // Function call ND_EXPR_STMT, // Expression statement ND_STMT_EXPR, // Statement expression ND_VAR, // Variable + ND_VLA_PTR, // VLA designator ND_NUM, // Integer + ND_CAST, // Type cast + ND_MEMZERO, // Zero-clear a stack variable + ND_ASM, // "asm" + ND_CAS, // Atomic compare-and-swap + ND_EXCH, // Atomic exchange } NodeKind; // AST node type @@ -130,6 +241,10 @@ struct Node { Node *init; Node *inc; + // "break" and "continue" labels + char *brk_label; + char *cont_label; + // Block or statement expression Node *body; @@ -137,13 +252,46 @@ struct Node { Member *member; // Function call - char *funcname; + Type *func_ty; Node *args; + bool pass_by_stack; + Obj *ret_buffer; + + // Goto or labeled statement, or labels-as-values + char *label; + char *unique_label; + Node *goto_next; + + // Switch + Node *case_next; + Node *default_case; + + // Case + long begin; + long end; + + // "asm" string literal + char *asm_str; + + // Atomic compare-and-swap + Node *cas_addr; + Node *cas_old; + Node *cas_new; + + // Atomic op= operators + Obj *atomic_addr; + Node *atomic_expr; - Obj *var; // Used if kind == ND_VAR - int64_t val; // Used if kind == ND_NUM + // Variable + Obj *var; + + // Numeric literal + int64_t val; + long double fval; }; +Node *new_cast(Node *expr, Type *ty); +int64_t const_expr(Token **rest, Token *tok); Obj *parse(Token *tok); // @@ -151,21 +299,31 @@ Obj *parse(Token *tok); // typedef enum { + TY_VOID, + TY_BOOL, TY_CHAR, TY_SHORT, TY_INT, TY_LONG, + TY_FLOAT, + TY_DOUBLE, + TY_LDOUBLE, + TY_ENUM, TY_PTR, TY_FUNC, TY_ARRAY, + TY_VLA, // variable-length array TY_STRUCT, TY_UNION, } TypeKind; struct Type { TypeKind kind; - int size; // sizeof() value - int align; // alignment + int size; // sizeof() value + int align; // alignment + bool is_unsigned; // unsigned or signed + bool is_atomic; // true if _Atomic + Type *origin; // for type compatibility check // Pointer-to or array-of type. We intentionally use the same member // to represent pointer/array duality in C. @@ -179,16 +337,24 @@ struct Type { // Declaration Token *name; + Token *name_pos; // Array int array_len; + // Variable-length array + Node *vla_len; // # of elements + Obj *vla_size; // sizeof() value + // Struct Member *members; + bool is_flexible; + bool is_packed; // Function type Type *return_ty; Type *params; + bool is_variadic; Type *next; }; @@ -196,20 +362,46 @@ struct Type { struct Member { Member *next; Type *ty; + Token *tok; // for error message Token *name; + int idx; + int align; int offset; + + // Bitfield + bool is_bitfield; + int bit_offset; + int bit_width; }; +extern Type *ty_void; +extern Type *ty_bool; + extern Type *ty_char; extern Type *ty_short; extern Type *ty_int; extern Type *ty_long; +extern Type *ty_uchar; +extern Type *ty_ushort; +extern Type *ty_uint; +extern Type *ty_ulong; + +extern Type *ty_float; +extern Type *ty_double; +extern Type *ty_ldouble; + bool is_integer(Type *ty); +bool is_flonum(Type *ty); +bool is_numeric(Type *ty); +bool is_compatible(Type *t1, Type *t2); Type *copy_type(Type *ty); Type *pointer_to(Type *base); Type *func_type(Type *return_ty); Type *array_of(Type *base, int size); +Type *vla_of(Type *base, Node *expr); +Type *enum_type(void); +Type *struct_type(void); void add_type(Node *node); // @@ -218,3 +410,48 @@ void add_type(Node *node); void codegen(Obj *prog, FILE *out); int align_to(int n, int align); + +// +// unicode.c +// + +int encode_utf8(char *buf, uint32_t c); +uint32_t decode_utf8(char **new_pos, char *p); +bool is_ident1(uint32_t c); +bool is_ident2(uint32_t c); +int display_width(char *p, int len); + +// +// hashmap.c +// + +typedef struct { + char *key; + int keylen; + void *val; +} HashEntry; + +typedef struct { + HashEntry *buckets; + int capacity; + int used; +} HashMap; + +void *hashmap_get(HashMap *map, char *key); +void *hashmap_get2(HashMap *map, char *key, int keylen); +void hashmap_put(HashMap *map, char *key, void *val); +void hashmap_put2(HashMap *map, char *key, int keylen, void *val); +void hashmap_delete(HashMap *map, char *key); +void hashmap_delete2(HashMap *map, char *key, int keylen); +void hashmap_test(void); + +// +// main.c +// + +bool file_exists(char *path); + +extern StringArray include_paths; +extern bool opt_fpic; +extern bool opt_fcommon; +extern char *base_file; diff --git a/codegen.c b/codegen.c index cd3051d0..e71de673 100644 --- a/codegen.c +++ b/codegen.c @@ -1,5 +1,8 @@ #include "chibicc.h" +#define GP_MAX 6 +#define FP_MAX 8 + static FILE *output_file; static int depth; static char *argreg8[] = {"%dil", "%sil", "%dl", "%cl", "%r8b", "%r9b"}; @@ -11,6 +14,7 @@ static Obj *current_fn; static void gen_expr(Node *node); static void gen_stmt(Node *node); +__attribute__((format(printf, 1, 2))) static void println(char *fmt, ...) { va_list ap; va_start(ap, fmt); @@ -34,24 +38,117 @@ static void pop(char *arg) { depth--; } +static void pushf(void) { + println(" sub $8, %%rsp"); + println(" movsd %%xmm0, (%%rsp)"); + depth++; +} + +static void popf(int reg) { + println(" movsd (%%rsp), %%xmm%d", reg); + println(" add $8, %%rsp"); + depth--; +} + // Round up `n` to the nearest multiple of `align`. For instance, // align_to(5, 8) returns 8 and align_to(11, 8) returns 16. int align_to(int n, int align) { return (n + align - 1) / align * align; } +static char *reg_dx(int sz) { + switch (sz) { + case 1: return "%dl"; + case 2: return "%dx"; + case 4: return "%edx"; + case 8: return "%rdx"; + } + unreachable(); +} + +static char *reg_ax(int sz) { + switch (sz) { + case 1: return "%al"; + case 2: return "%ax"; + case 4: return "%eax"; + case 8: return "%rax"; + } + unreachable(); +} + // Compute the absolute address of a given node. // It's an error if a given node does not reside in memory. static void gen_addr(Node *node) { switch (node->kind) { case ND_VAR: + // Variable-length array, which is always local. + if (node->var->ty->kind == TY_VLA) { + println(" mov %d(%%rbp), %%rax", node->var->offset); + return; + } + + // Local variable if (node->var->is_local) { - // Local variable println(" lea %d(%%rbp), %%rax", node->var->offset); - } else { - // Global variable - println(" lea %s(%%rip), %%rax", node->var->name); + return; } + + if (opt_fpic) { + // Thread-local variable + if (node->var->is_tls) { + println(" data16 lea %s@tlsgd(%%rip), %%rdi", node->var->name); + println(" .value 0x6666"); + println(" rex64"); + println(" call __tls_get_addr@PLT"); + return; + } + + // Function or global variable + println(" mov %s@GOTPCREL(%%rip), %%rax", node->var->name); + return; + } + + // Thread-local variable + if (node->var->is_tls) { + println(" mov %%fs:0, %%rax"); + println(" add $%s@tpoff, %%rax", node->var->name); + return; + } + + // Here, we generate an absolute address of a function or a global + // variable. Even though they exist at a certain address at runtime, + // their addresses are not known at link-time for the following + // two reasons. + // + // - Address randomization: Executables are loaded to memory as a + // whole but it is not known what address they are loaded to. + // Therefore, at link-time, relative address in the same + // exectuable (i.e. the distance between two functions in the + // same executable) is known, but the absolute address is not + // known. + // + // - Dynamic linking: Dynamic shared objects (DSOs) or .so files + // are loaded to memory alongside an executable at runtime and + // linked by the runtime loader in memory. We know nothing + // about addresses of global stuff that may be defined by DSOs + // until the runtime relocation is complete. + // + // In order to deal with the former case, we use RIP-relative + // addressing, denoted by `(%rip)`. For the latter, we obtain an + // address of a stuff that may be in a shared object file from the + // Global Offset Table using `@GOTPCREL(%rip)` notation. + + // Function + if (node->ty->kind == TY_FUNC) { + if (node->var->is_definition) + println(" lea %s(%%rip), %%rax", node->var->name); + else + println(" mov %s@GOTPCREL(%%rip), %%rax", node->var->name); + return; + } + + // Global variable + println(" lea %s(%%rip), %%rax", node->var->name); return; case ND_DEREF: gen_expr(node->lhs); @@ -64,6 +161,15 @@ static void gen_addr(Node *node) { gen_addr(node->lhs); println(" add $%d, %%rax", node->member->offset); return; + case ND_FUNCALL: + if (node->ret_buffer) { + gen_expr(node); + return; + } + break; + case ND_VLA_PTR: + println(" lea %d(%%rbp), %%rax", node->var->offset); + return; } error_tok(node->tok, "not an lvalue"); @@ -71,7 +177,12 @@ static void gen_addr(Node *node) { // Load a value from where %rax is pointing to. static void load(Type *ty) { - if (ty->kind == TY_ARRAY || ty->kind == TY_STRUCT || ty->kind == TY_UNION) { + switch (ty->kind) { + case TY_ARRAY: + case TY_STRUCT: + case TY_UNION: + case TY_FUNC: + case TY_VLA: // If it is an array, do not attempt to load a value to the // register because in general we can't load an entire array to a // register. As a result, the result of an evaluation of an array @@ -79,12 +190,28 @@ static void load(Type *ty) { // This is where "array is automatically converted to a pointer to // the first element of the array in C" occurs. return; + case TY_FLOAT: + println(" movss (%%rax), %%xmm0"); + return; + case TY_DOUBLE: + println(" movsd (%%rax), %%xmm0"); + return; + case TY_LDOUBLE: + println(" fldt (%%rax)"); + return; } + char *insn = ty->is_unsigned ? "movz" : "movs"; + + // When we load a char or a short value to a register, we always + // extend them to the size of int, so we can assume the lower half of + // a register always contains a valid value. The upper half of a + // register for char, short and int may contain garbage. When we load + // a long value to a register, it simply occupies the entire register. if (ty->size == 1) - println(" movsbq (%%rax), %%rax"); + println(" %sbl (%%rax), %%eax", insn); else if (ty->size == 2) - println(" movswq (%%rax), %%rax"); + println(" %swl (%%rax), %%eax", insn); else if (ty->size == 4) println(" movsxd (%%rax), %%rax"); else @@ -95,12 +222,23 @@ static void load(Type *ty) { static void store(Type *ty) { pop("%rdi"); - if (ty->kind == TY_STRUCT || ty->kind == TY_UNION) { + switch (ty->kind) { + case TY_STRUCT: + case TY_UNION: for (int i = 0; i < ty->size; i++) { println(" mov %d(%%rax), %%r8b", i); println(" mov %%r8b, %d(%%rdi)", i); } return; + case TY_FLOAT: + println(" movss %%xmm0, (%%rdi)"); + return; + case TY_DOUBLE: + println(" movsd %%xmm0, (%%rdi)"); + return; + case TY_LDOUBLE: + println(" fstpt (%%rdi)"); + return; } if (ty->size == 1) @@ -113,23 +251,514 @@ static void store(Type *ty) { println(" mov %%rax, (%%rdi)"); } +static void cmp_zero(Type *ty) { + switch (ty->kind) { + case TY_FLOAT: + println(" xorps %%xmm1, %%xmm1"); + println(" ucomiss %%xmm1, %%xmm0"); + return; + case TY_DOUBLE: + println(" xorpd %%xmm1, %%xmm1"); + println(" ucomisd %%xmm1, %%xmm0"); + return; + case TY_LDOUBLE: + println(" fldz"); + println(" fucomip"); + println(" fstp %%st(0)"); + return; + } + + if (is_integer(ty) && ty->size <= 4) + println(" cmp $0, %%eax"); + else + println(" cmp $0, %%rax"); +} + +enum { I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, F80 }; + +static int getTypeId(Type *ty) { + switch (ty->kind) { + case TY_CHAR: + return ty->is_unsigned ? U8 : I8; + case TY_SHORT: + return ty->is_unsigned ? U16 : I16; + case TY_INT: + return ty->is_unsigned ? U32 : I32; + case TY_LONG: + return ty->is_unsigned ? U64 : I64; + case TY_FLOAT: + return F32; + case TY_DOUBLE: + return F64; + case TY_LDOUBLE: + return F80; + } + return U64; +} + +// The table for type casts +static char i32i8[] = "movsbl %al, %eax"; +static char i32u8[] = "movzbl %al, %eax"; +static char i32i16[] = "movswl %ax, %eax"; +static char i32u16[] = "movzwl %ax, %eax"; +static char i32f32[] = "cvtsi2ssl %eax, %xmm0"; +static char i32i64[] = "movsxd %eax, %rax"; +static char i32f64[] = "cvtsi2sdl %eax, %xmm0"; +static char i32f80[] = "mov %eax, -4(%rsp); fildl -4(%rsp)"; + +static char u32f32[] = "mov %eax, %eax; cvtsi2ssq %rax, %xmm0"; +static char u32i64[] = "mov %eax, %eax"; +static char u32f64[] = "mov %eax, %eax; cvtsi2sdq %rax, %xmm0"; +static char u32f80[] = "mov %eax, %eax; mov %rax, -8(%rsp); fildll -8(%rsp)"; + +static char i64f32[] = "cvtsi2ssq %rax, %xmm0"; +static char i64f64[] = "cvtsi2sdq %rax, %xmm0"; +static char i64f80[] = "movq %rax, -8(%rsp); fildll -8(%rsp)"; + +static char u64f32[] = "cvtsi2ssq %rax, %xmm0"; +static char u64f64[] = + "test %rax,%rax; js 1f; pxor %xmm0,%xmm0; cvtsi2sd %rax,%xmm0; jmp 2f; " + "1: mov %rax,%rdi; and $1,%eax; pxor %xmm0,%xmm0; shr %rdi; " + "or %rax,%rdi; cvtsi2sd %rdi,%xmm0; addsd %xmm0,%xmm0; 2:"; +static char u64f80[] = + "mov %rax, -8(%rsp); fildq -8(%rsp); test %rax, %rax; jns 1f;" + "mov $1602224128, %eax; mov %eax, -4(%rsp); fadds -4(%rsp); 1:"; + +static char f32i8[] = "cvttss2sil %xmm0, %eax; movsbl %al, %eax"; +static char f32u8[] = "cvttss2sil %xmm0, %eax; movzbl %al, %eax"; +static char f32i16[] = "cvttss2sil %xmm0, %eax; movswl %ax, %eax"; +static char f32u16[] = "cvttss2sil %xmm0, %eax; movzwl %ax, %eax"; +static char f32i32[] = "cvttss2sil %xmm0, %eax"; +static char f32u32[] = "cvttss2siq %xmm0, %rax"; +static char f32i64[] = "cvttss2siq %xmm0, %rax"; +static char f32u64[] = "cvttss2siq %xmm0, %rax"; +static char f32f64[] = "cvtss2sd %xmm0, %xmm0"; +static char f32f80[] = "movss %xmm0, -4(%rsp); flds -4(%rsp)"; + +static char f64i8[] = "cvttsd2sil %xmm0, %eax; movsbl %al, %eax"; +static char f64u8[] = "cvttsd2sil %xmm0, %eax; movzbl %al, %eax"; +static char f64i16[] = "cvttsd2sil %xmm0, %eax; movswl %ax, %eax"; +static char f64u16[] = "cvttsd2sil %xmm0, %eax; movzwl %ax, %eax"; +static char f64i32[] = "cvttsd2sil %xmm0, %eax"; +static char f64u32[] = "cvttsd2siq %xmm0, %rax"; +static char f64i64[] = "cvttsd2siq %xmm0, %rax"; +static char f64u64[] = "cvttsd2siq %xmm0, %rax"; +static char f64f32[] = "cvtsd2ss %xmm0, %xmm0"; +static char f64f80[] = "movsd %xmm0, -8(%rsp); fldl -8(%rsp)"; + +#define FROM_F80_1 \ + "fnstcw -10(%rsp); movzwl -10(%rsp), %eax; or $12, %ah; " \ + "mov %ax, -12(%rsp); fldcw -12(%rsp); " + +#define FROM_F80_2 " -24(%rsp); fldcw -10(%rsp); " + +static char f80i8[] = FROM_F80_1 "fistps" FROM_F80_2 "movsbl -24(%rsp), %eax"; +static char f80u8[] = FROM_F80_1 "fistps" FROM_F80_2 "movzbl -24(%rsp), %eax"; +static char f80i16[] = FROM_F80_1 "fistps" FROM_F80_2 "movzbl -24(%rsp), %eax"; +static char f80u16[] = FROM_F80_1 "fistpl" FROM_F80_2 "movswl -24(%rsp), %eax"; +static char f80i32[] = FROM_F80_1 "fistpl" FROM_F80_2 "mov -24(%rsp), %eax"; +static char f80u32[] = FROM_F80_1 "fistpl" FROM_F80_2 "mov -24(%rsp), %eax"; +static char f80i64[] = FROM_F80_1 "fistpq" FROM_F80_2 "mov -24(%rsp), %rax"; +static char f80u64[] = FROM_F80_1 "fistpq" FROM_F80_2 "mov -24(%rsp), %rax"; +static char f80f32[] = "fstps -8(%rsp); movss -8(%rsp), %xmm0"; +static char f80f64[] = "fstpl -8(%rsp); movsd -8(%rsp), %xmm0"; + +static char *cast_table[][11] = { + // i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 f80 + {NULL, NULL, NULL, i32i64, i32u8, i32u16, NULL, i32i64, i32f32, i32f64, i32f80}, // i8 + {i32i8, NULL, NULL, i32i64, i32u8, i32u16, NULL, i32i64, i32f32, i32f64, i32f80}, // i16 + {i32i8, i32i16, NULL, i32i64, i32u8, i32u16, NULL, i32i64, i32f32, i32f64, i32f80}, // i32 + {i32i8, i32i16, NULL, NULL, i32u8, i32u16, NULL, NULL, i64f32, i64f64, i64f80}, // i64 + + {i32i8, NULL, NULL, i32i64, NULL, NULL, NULL, i32i64, i32f32, i32f64, i32f80}, // u8 + {i32i8, i32i16, NULL, i32i64, i32u8, NULL, NULL, i32i64, i32f32, i32f64, i32f80}, // u16 + {i32i8, i32i16, NULL, u32i64, i32u8, i32u16, NULL, u32i64, u32f32, u32f64, u32f80}, // u32 + {i32i8, i32i16, NULL, NULL, i32u8, i32u16, NULL, NULL, u64f32, u64f64, u64f80}, // u64 + + {f32i8, f32i16, f32i32, f32i64, f32u8, f32u16, f32u32, f32u64, NULL, f32f64, f32f80}, // f32 + {f64i8, f64i16, f64i32, f64i64, f64u8, f64u16, f64u32, f64u64, f64f32, NULL, f64f80}, // f64 + {f80i8, f80i16, f80i32, f80i64, f80u8, f80u16, f80u32, f80u64, f80f32, f80f64, NULL}, // f80 +}; + +static void cast(Type *from, Type *to) { + if (to->kind == TY_VOID) + return; + + if (to->kind == TY_BOOL) { + cmp_zero(from); + println(" setne %%al"); + println(" movzx %%al, %%eax"); + return; + } + + int t1 = getTypeId(from); + int t2 = getTypeId(to); + if (cast_table[t1][t2]) + println(" %s", cast_table[t1][t2]); +} + +// Structs or unions equal or smaller than 16 bytes are passed +// using up to two registers. +// +// If the first 8 bytes contains only floating-point type members, +// they are passed in an XMM register. Otherwise, they are passed +// in a general-purpose register. +// +// If a struct/union is larger than 8 bytes, the same rule is +// applied to the the next 8 byte chunk. +// +// This function returns true if `ty` has only floating-point +// members in its byte range [lo, hi). +static bool has_flonum(Type *ty, int lo, int hi, int offset) { + if (ty->kind == TY_STRUCT || ty->kind == TY_UNION) { + for (Member *mem = ty->members; mem; mem = mem->next) + if (!has_flonum(mem->ty, lo, hi, offset + mem->offset)) + return false; + return true; + } + + if (ty->kind == TY_ARRAY) { + for (int i = 0; i < ty->array_len; i++) + if (!has_flonum(ty->base, lo, hi, offset + ty->base->size * i)) + return false; + return true; + } + + return offset < lo || hi <= offset || ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE; +} + +static bool has_flonum1(Type *ty) { + return has_flonum(ty, 0, 8, 0); +} + +static bool has_flonum2(Type *ty) { + return has_flonum(ty, 8, 16, 0); +} + +static void push_struct(Type *ty) { + int sz = align_to(ty->size, 8); + println(" sub $%d, %%rsp", sz); + depth += sz / 8; + + for (int i = 0; i < ty->size; i++) { + println(" mov %d(%%rax), %%r10b", i); + println(" mov %%r10b, %d(%%rsp)", i); + } +} + +static void push_args2(Node *args, bool first_pass) { + if (!args) + return; + push_args2(args->next, first_pass); + + if ((first_pass && !args->pass_by_stack) || (!first_pass && args->pass_by_stack)) + return; + + gen_expr(args); + + switch (args->ty->kind) { + case TY_STRUCT: + case TY_UNION: + push_struct(args->ty); + break; + case TY_FLOAT: + case TY_DOUBLE: + pushf(); + break; + case TY_LDOUBLE: + println(" sub $16, %%rsp"); + println(" fstpt (%%rsp)"); + depth += 2; + break; + default: + push(); + } +} + +// Load function call arguments. Arguments are already evaluated and +// stored to the stack as local variables. What we need to do in this +// function is to load them to registers or push them to the stack as +// specified by the x86-64 psABI. Here is what the spec says: +// +// - Up to 6 arguments of integral type are passed using RDI, RSI, +// RDX, RCX, R8 and R9. +// +// - Up to 8 arguments of floating-point type are passed using XMM0 to +// XMM7. +// +// - If all registers of an appropriate type are already used, push an +// argument to the stack in the right-to-left order. +// +// - Each argument passed on the stack takes 8 bytes, and the end of +// the argument area must be aligned to a 16 byte boundary. +// +// - If a function is variadic, set the number of floating-point type +// arguments to RAX. +static int push_args(Node *node) { + int stack = 0, gp = 0, fp = 0; + + // If the return type is a large struct/union, the caller passes + // a pointer to a buffer as if it were the first argument. + if (node->ret_buffer && node->ty->size > 16) + gp++; + + // Load as many arguments to the registers as possible. + for (Node *arg = node->args; arg; arg = arg->next) { + Type *ty = arg->ty; + + switch (ty->kind) { + case TY_STRUCT: + case TY_UNION: + if (ty->size > 16) { + arg->pass_by_stack = true; + stack += align_to(ty->size, 8) / 8; + } else { + bool fp1 = has_flonum1(ty); + bool fp2 = has_flonum2(ty); + + if (fp + fp1 + fp2 < FP_MAX && gp + !fp1 + !fp2 < GP_MAX) { + fp = fp + fp1 + fp2; + gp = gp + !fp1 + !fp2; + } else { + arg->pass_by_stack = true; + stack += align_to(ty->size, 8) / 8; + } + } + break; + case TY_FLOAT: + case TY_DOUBLE: + if (fp++ >= FP_MAX) { + arg->pass_by_stack = true; + stack++; + } + break; + case TY_LDOUBLE: + arg->pass_by_stack = true; + stack += 2; + break; + default: + if (gp++ >= GP_MAX) { + arg->pass_by_stack = true; + stack++; + } + } + } + + if ((depth + stack) % 2 == 1) { + println(" sub $8, %%rsp"); + depth++; + stack++; + } + + push_args2(node->args, true); + push_args2(node->args, false); + + // If the return type is a large struct/union, the caller passes + // a pointer to a buffer as if it were the first argument. + if (node->ret_buffer && node->ty->size > 16) { + println(" lea %d(%%rbp), %%rax", node->ret_buffer->offset); + push(); + } + + return stack; +} + +static void copy_ret_buffer(Obj *var) { + Type *ty = var->ty; + int gp = 0, fp = 0; + + if (has_flonum1(ty)) { + assert(ty->size == 4 || 8 <= ty->size); + if (ty->size == 4) + println(" movss %%xmm0, %d(%%rbp)", var->offset); + else + println(" movsd %%xmm0, %d(%%rbp)", var->offset); + fp++; + } else { + for (int i = 0; i < MIN(8, ty->size); i++) { + println(" mov %%al, %d(%%rbp)", var->offset + i); + println(" shr $8, %%rax"); + } + gp++; + } + + if (ty->size > 8) { + if (has_flonum2(ty)) { + assert(ty->size == 12 || ty->size == 16); + if (ty->size == 12) + println(" movss %%xmm%d, %d(%%rbp)", fp, var->offset + 8); + else + println(" movsd %%xmm%d, %d(%%rbp)", fp, var->offset + 8); + } else { + char *reg1 = (gp == 0) ? "%al" : "%dl"; + char *reg2 = (gp == 0) ? "%rax" : "%rdx"; + for (int i = 8; i < MIN(16, ty->size); i++) { + println(" mov %s, %d(%%rbp)", reg1, var->offset + i); + println(" shr $8, %s", reg2); + } + } + } +} + +static void copy_struct_reg(void) { + Type *ty = current_fn->ty->return_ty; + int gp = 0, fp = 0; + + println(" mov %%rax, %%rdi"); + + if (has_flonum(ty, 0, 8, 0)) { + assert(ty->size == 4 || 8 <= ty->size); + if (ty->size == 4) + println(" movss (%%rdi), %%xmm0"); + else + println(" movsd (%%rdi), %%xmm0"); + fp++; + } else { + println(" mov $0, %%rax"); + for (int i = MIN(8, ty->size) - 1; i >= 0; i--) { + println(" shl $8, %%rax"); + println(" mov %d(%%rdi), %%al", i); + } + gp++; + } + + if (ty->size > 8) { + if (has_flonum(ty, 8, 16, 0)) { + assert(ty->size == 12 || ty->size == 16); + if (ty->size == 4) + println(" movss 8(%%rdi), %%xmm%d", fp); + else + println(" movsd 8(%%rdi), %%xmm%d", fp); + } else { + char *reg1 = (gp == 0) ? "%al" : "%dl"; + char *reg2 = (gp == 0) ? "%rax" : "%rdx"; + println(" mov $0, %s", reg2); + for (int i = MIN(16, ty->size) - 1; i >= 8; i--) { + println(" shl $8, %s", reg2); + println(" mov %d(%%rdi), %s", i, reg1); + } + } + } +} + +static void copy_struct_mem(void) { + Type *ty = current_fn->ty->return_ty; + Obj *var = current_fn->params; + + println(" mov %d(%%rbp), %%rdi", var->offset); + + for (int i = 0; i < ty->size; i++) { + println(" mov %d(%%rax), %%dl", i); + println(" mov %%dl, %d(%%rdi)", i); + } +} + +static void builtin_alloca(void) { + // Align size to 16 bytes. + println(" add $15, %%rdi"); + println(" and $0xfffffff0, %%edi"); + + // Shift the temporary area by %rdi. + println(" mov %d(%%rbp), %%rcx", current_fn->alloca_bottom->offset); + println(" sub %%rsp, %%rcx"); + println(" mov %%rsp, %%rax"); + println(" sub %%rdi, %%rsp"); + println(" mov %%rsp, %%rdx"); + println("1:"); + println(" cmp $0, %%rcx"); + println(" je 2f"); + println(" mov (%%rax), %%r8b"); + println(" mov %%r8b, (%%rdx)"); + println(" inc %%rdx"); + println(" inc %%rax"); + println(" dec %%rcx"); + println(" jmp 1b"); + println("2:"); + + // Move alloca_bottom pointer. + println(" mov %d(%%rbp), %%rax", current_fn->alloca_bottom->offset); + println(" sub %%rdi, %%rax"); + println(" mov %%rax, %d(%%rbp)", current_fn->alloca_bottom->offset); +} + // Generate code for a given node. static void gen_expr(Node *node) { - println(" .loc 1 %d", node->tok->line_no); + println(" .loc %d %d", node->tok->file->file_no, node->tok->line_no); switch (node->kind) { - case ND_NUM: + case ND_NULL_EXPR: + return; + case ND_NUM: { + switch (node->ty->kind) { + case TY_FLOAT: { + union { float f32; uint32_t u32; } u = { node->fval }; + println(" mov $%u, %%eax # float %Lf", u.u32, node->fval); + println(" movq %%rax, %%xmm0"); + return; + } + case TY_DOUBLE: { + union { double f64; uint64_t u64; } u = { node->fval }; + println(" mov $%lu, %%rax # double %Lf", u.u64, node->fval); + println(" movq %%rax, %%xmm0"); + return; + } + case TY_LDOUBLE: { + union { long double f80; uint64_t u64[2]; } u; + memset(&u, 0, sizeof(u)); + u.f80 = node->fval; + println(" mov $%lu, %%rax # long double %Lf", u.u64[0], node->fval); + println(" mov %%rax, -16(%%rsp)"); + println(" mov $%lu, %%rax", u.u64[1]); + println(" mov %%rax, -8(%%rsp)"); + println(" fldt -16(%%rsp)"); + return; + } + } + println(" mov $%ld, %%rax", node->val); return; + } case ND_NEG: gen_expr(node->lhs); + + switch (node->ty->kind) { + case TY_FLOAT: + println(" mov $1, %%rax"); + println(" shl $31, %%rax"); + println(" movq %%rax, %%xmm1"); + println(" xorps %%xmm1, %%xmm0"); + return; + case TY_DOUBLE: + println(" mov $1, %%rax"); + println(" shl $63, %%rax"); + println(" movq %%rax, %%xmm1"); + println(" xorpd %%xmm1, %%xmm0"); + return; + case TY_LDOUBLE: + println(" fchs"); + return; + } + println(" neg %%rax"); return; case ND_VAR: - case ND_MEMBER: gen_addr(node); load(node->ty); return; + case ND_MEMBER: { + gen_addr(node); + load(node->ty); + + Member *mem = node->member; + if (mem->is_bitfield) { + println(" shl $%d, %%rax", 64 - mem->bit_width - mem->bit_offset); + if (mem->ty->is_unsigned) + println(" shr $%d, %%rax", 64 - mem->bit_width); + else + println(" sar $%d, %%rax", 64 - mem->bit_width); + } + return; + } case ND_DEREF: gen_expr(node->lhs); load(node->ty); @@ -141,6 +770,29 @@ static void gen_expr(Node *node) { gen_addr(node->lhs); push(); gen_expr(node->rhs); + + if (node->lhs->kind == ND_MEMBER && node->lhs->member->is_bitfield) { + println(" mov %%rax, %%r8"); + + // If the lhs is a bitfield, we need to read the current value + // from memory and merge it with a new value. + Member *mem = node->lhs->member; + println(" mov %%rax, %%rdi"); + println(" and $%ld, %%rdi", (1L << mem->bit_width) - 1); + println(" shl $%d, %%rdi", mem->bit_offset); + + println(" mov (%%rsp), %%rax"); + load(mem->ty); + + long mask = ((1L << mem->bit_width) - 1) << mem->bit_offset; + println(" mov $%ld, %%r9", ~mask); + println(" and %%r9, %%rax"); + println(" or %%rdi, %%rax"); + store(node->ty); + println(" mov %%r8, %%rax"); + return; + } + store(node->ty); return; case ND_STMT_EXPR: @@ -151,21 +803,287 @@ static void gen_expr(Node *node) { gen_expr(node->lhs); gen_expr(node->rhs); return; + case ND_CAST: + gen_expr(node->lhs); + cast(node->lhs->ty, node->ty); + return; + case ND_MEMZERO: + // `rep stosb` is equivalent to `memset(%rdi, %al, %rcx)`. + println(" mov $%d, %%rcx", node->var->ty->size); + println(" lea %d(%%rbp), %%rdi", node->var->offset); + println(" mov $0, %%al"); + println(" rep stosb"); + return; + case ND_COND: { + int c = count(); + gen_expr(node->cond); + cmp_zero(node->cond->ty); + println(" je .L.else.%d", c); + gen_expr(node->then); + println(" jmp .L.end.%d", c); + println(".L.else.%d:", c); + gen_expr(node->els); + println(".L.end.%d:", c); + return; + } + case ND_NOT: + gen_expr(node->lhs); + cmp_zero(node->lhs->ty); + println(" sete %%al"); + println(" movzx %%al, %%rax"); + return; + case ND_BITNOT: + gen_expr(node->lhs); + println(" not %%rax"); + return; + case ND_LOGAND: { + int c = count(); + gen_expr(node->lhs); + cmp_zero(node->lhs->ty); + println(" je .L.false.%d", c); + gen_expr(node->rhs); + cmp_zero(node->rhs->ty); + println(" je .L.false.%d", c); + println(" mov $1, %%rax"); + println(" jmp .L.end.%d", c); + println(".L.false.%d:", c); + println(" mov $0, %%rax"); + println(".L.end.%d:", c); + return; + } + case ND_LOGOR: { + int c = count(); + gen_expr(node->lhs); + cmp_zero(node->lhs->ty); + println(" jne .L.true.%d", c); + gen_expr(node->rhs); + cmp_zero(node->rhs->ty); + println(" jne .L.true.%d", c); + println(" mov $0, %%rax"); + println(" jmp .L.end.%d", c); + println(".L.true.%d:", c); + println(" mov $1, %%rax"); + println(".L.end.%d:", c); + return; + } case ND_FUNCALL: { - int nargs = 0; + if (node->lhs->kind == ND_VAR && !strcmp(node->lhs->var->name, "alloca")) { + gen_expr(node->args); + println(" mov %%rax, %%rdi"); + builtin_alloca(); + return; + } + + int stack_args = push_args(node); + gen_expr(node->lhs); + + int gp = 0, fp = 0; + + // If the return type is a large struct/union, the caller passes + // a pointer to a buffer as if it were the first argument. + if (node->ret_buffer && node->ty->size > 16) + pop(argreg64[gp++]); + for (Node *arg = node->args; arg; arg = arg->next) { - gen_expr(arg); - push(); - nargs++; + Type *ty = arg->ty; + + switch (ty->kind) { + case TY_STRUCT: + case TY_UNION: + if (ty->size > 16) + continue; + + bool fp1 = has_flonum1(ty); + bool fp2 = has_flonum2(ty); + + if (fp + fp1 + fp2 < FP_MAX && gp + !fp1 + !fp2 < GP_MAX) { + if (fp1) + popf(fp++); + else + pop(argreg64[gp++]); + + if (ty->size > 8) { + if (fp2) + popf(fp++); + else + pop(argreg64[gp++]); + } + } + break; + case TY_FLOAT: + case TY_DOUBLE: + if (fp < FP_MAX) + popf(fp++); + break; + case TY_LDOUBLE: + break; + default: + if (gp < GP_MAX) + pop(argreg64[gp++]); + } } - for (int i = nargs - 1; i >= 0; i--) - pop(argreg64[i]); + println(" mov %%rax, %%r10"); + println(" mov $%d, %%rax", fp); + println(" call *%%r10"); + println(" add $%d, %%rsp", stack_args * 8); + + depth -= stack_args; + + // It looks like the most significant 48 or 56 bits in RAX may + // contain garbage if a function return type is short or bool/char, + // respectively. We clear the upper bits here. + switch (node->ty->kind) { + case TY_BOOL: + println(" movzx %%al, %%eax"); + return; + case TY_CHAR: + if (node->ty->is_unsigned) + println(" movzbl %%al, %%eax"); + else + println(" movsbl %%al, %%eax"); + return; + case TY_SHORT: + if (node->ty->is_unsigned) + println(" movzwl %%ax, %%eax"); + else + println(" movswl %%ax, %%eax"); + return; + } + + // If the return type is a small struct, a value is returned + // using up to two registers. + if (node->ret_buffer && node->ty->size <= 16) { + copy_ret_buffer(node->ret_buffer); + println(" lea %d(%%rbp), %%rax", node->ret_buffer->offset); + } - println(" mov $0, %%rax"); - println(" call %s", node->funcname); return; } + case ND_LABEL_VAL: + println(" lea %s(%%rip), %%rax", node->unique_label); + return; + case ND_CAS: { + gen_expr(node->cas_addr); + push(); + gen_expr(node->cas_new); + push(); + gen_expr(node->cas_old); + println(" mov %%rax, %%r8"); + load(node->cas_old->ty->base); + pop("%rdx"); // new + pop("%rdi"); // addr + + int sz = node->cas_addr->ty->base->size; + println(" lock cmpxchg %s, (%%rdi)", reg_dx(sz)); + println(" sete %%cl"); + println(" je 1f"); + println(" mov %s, (%%r8)", reg_ax(sz)); + println("1:"); + println(" movzbl %%cl, %%eax"); + return; + } + case ND_EXCH: { + gen_expr(node->lhs); + push(); + gen_expr(node->rhs); + pop("%rdi"); + + int sz = node->lhs->ty->base->size; + println(" xchg %s, (%%rdi)", reg_ax(sz)); + return; + } + } + + switch (node->lhs->ty->kind) { + case TY_FLOAT: + case TY_DOUBLE: { + gen_expr(node->rhs); + pushf(); + gen_expr(node->lhs); + popf(1); + + char *sz = (node->lhs->ty->kind == TY_FLOAT) ? "ss" : "sd"; + + switch (node->kind) { + case ND_ADD: + println(" add%s %%xmm1, %%xmm0", sz); + return; + case ND_SUB: + println(" sub%s %%xmm1, %%xmm0", sz); + return; + case ND_MUL: + println(" mul%s %%xmm1, %%xmm0", sz); + return; + case ND_DIV: + println(" div%s %%xmm1, %%xmm0", sz); + return; + case ND_EQ: + case ND_NE: + case ND_LT: + case ND_LE: + println(" ucomi%s %%xmm0, %%xmm1", sz); + + if (node->kind == ND_EQ) { + println(" sete %%al"); + println(" setnp %%dl"); + println(" and %%dl, %%al"); + } else if (node->kind == ND_NE) { + println(" setne %%al"); + println(" setp %%dl"); + println(" or %%dl, %%al"); + } else if (node->kind == ND_LT) { + println(" seta %%al"); + } else { + println(" setae %%al"); + } + + println(" and $1, %%al"); + println(" movzb %%al, %%rax"); + return; + } + + error_tok(node->tok, "invalid expression"); + } + case TY_LDOUBLE: { + gen_expr(node->lhs); + gen_expr(node->rhs); + + switch (node->kind) { + case ND_ADD: + println(" faddp"); + return; + case ND_SUB: + println(" fsubrp"); + return; + case ND_MUL: + println(" fmulp"); + return; + case ND_DIV: + println(" fdivrp"); + return; + case ND_EQ: + case ND_NE: + case ND_LT: + case ND_LE: + println(" fcomip"); + println(" fstp %%st(0)"); + + if (node->kind == ND_EQ) + println(" sete %%al"); + else if (node->kind == ND_NE) + println(" setne %%al"); + else if (node->kind == ND_LT) + println(" seta %%al"); + else + println(" setae %%al"); + + println(" movzb %%al, %%rax"); + return; + } + + error_tok(node->tok, "invalid expression"); + } } gen_expr(node->rhs); @@ -173,50 +1091,101 @@ static void gen_expr(Node *node) { gen_expr(node->lhs); pop("%rdi"); + char *ax, *di, *dx; + + if (node->lhs->ty->kind == TY_LONG || node->lhs->ty->base) { + ax = "%rax"; + di = "%rdi"; + dx = "%rdx"; + } else { + ax = "%eax"; + di = "%edi"; + dx = "%edx"; + } + switch (node->kind) { case ND_ADD: - println(" add %%rdi, %%rax"); + println(" add %s, %s", di, ax); return; case ND_SUB: - println(" sub %%rdi, %%rax"); + println(" sub %s, %s", di, ax); return; case ND_MUL: - println(" imul %%rdi, %%rax"); + println(" imul %s, %s", di, ax); return; case ND_DIV: - println(" cqo"); - println(" idiv %%rdi"); + case ND_MOD: + if (node->ty->is_unsigned) { + println(" mov $0, %s", dx); + println(" div %s", di); + } else { + if (node->lhs->ty->size == 8) + println(" cqo"); + else + println(" cdq"); + println(" idiv %s", di); + } + + if (node->kind == ND_MOD) + println(" mov %%rdx, %%rax"); + return; + case ND_BITAND: + println(" and %s, %s", di, ax); + return; + case ND_BITOR: + println(" or %s, %s", di, ax); + return; + case ND_BITXOR: + println(" xor %s, %s", di, ax); return; case ND_EQ: case ND_NE: case ND_LT: case ND_LE: - println(" cmp %%rdi, %%rax"); + println(" cmp %s, %s", di, ax); - if (node->kind == ND_EQ) + if (node->kind == ND_EQ) { println(" sete %%al"); - else if (node->kind == ND_NE) + } else if (node->kind == ND_NE) { println(" setne %%al"); - else if (node->kind == ND_LT) - println(" setl %%al"); - else if (node->kind == ND_LE) - println(" setle %%al"); + } else if (node->kind == ND_LT) { + if (node->lhs->ty->is_unsigned) + println(" setb %%al"); + else + println(" setl %%al"); + } else if (node->kind == ND_LE) { + if (node->lhs->ty->is_unsigned) + println(" setbe %%al"); + else + println(" setle %%al"); + } println(" movzb %%al, %%rax"); return; + case ND_SHL: + println(" mov %%rdi, %%rcx"); + println(" shl %%cl, %s", ax); + return; + case ND_SHR: + println(" mov %%rdi, %%rcx"); + if (node->lhs->ty->is_unsigned) + println(" shr %%cl, %s", ax); + else + println(" sar %%cl, %s", ax); + return; } error_tok(node->tok, "invalid expression"); } static void gen_stmt(Node *node) { - println(" .loc 1 %d", node->tok->line_no); + println(" .loc %d %d", node->tok->file->file_no, node->tok->line_no); switch (node->kind) { case ND_IF: { int c = count(); gen_expr(node->cond); - println(" cmp $0, %%rax"); + cmp_zero(node->cond->ty); println(" je .L.else.%d", c); gen_stmt(node->then); println(" jmp .L.end.%d", c); @@ -233,27 +1202,98 @@ static void gen_stmt(Node *node) { println(".L.begin.%d:", c); if (node->cond) { gen_expr(node->cond); - println(" cmp $0, %%rax"); - println(" je .L.end.%d", c); + cmp_zero(node->cond->ty); + println(" je %s", node->brk_label); } gen_stmt(node->then); + println("%s:", node->cont_label); if (node->inc) gen_expr(node->inc); println(" jmp .L.begin.%d", c); - println(".L.end.%d:", c); + println("%s:", node->brk_label); return; } + case ND_DO: { + int c = count(); + println(".L.begin.%d:", c); + gen_stmt(node->then); + println("%s:", node->cont_label); + gen_expr(node->cond); + cmp_zero(node->cond->ty); + println(" jne .L.begin.%d", c); + println("%s:", node->brk_label); + return; + } + case ND_SWITCH: + gen_expr(node->cond); + + for (Node *n = node->case_next; n; n = n->case_next) { + char *ax = (node->cond->ty->size == 8) ? "%rax" : "%eax"; + char *di = (node->cond->ty->size == 8) ? "%rdi" : "%edi"; + + if (n->begin == n->end) { + println(" cmp $%ld, %s", n->begin, ax); + println(" je %s", n->label); + continue; + } + + // [GNU] Case ranges + println(" mov %s, %s", ax, di); + println(" sub $%ld, %s", n->begin, di); + println(" cmp $%ld, %s", n->end - n->begin, di); + println(" jbe %s", n->label); + } + + if (node->default_case) + println(" jmp %s", node->default_case->label); + + println(" jmp %s", node->brk_label); + gen_stmt(node->then); + println("%s:", node->brk_label); + return; + case ND_CASE: + println("%s:", node->label); + gen_stmt(node->lhs); + return; case ND_BLOCK: for (Node *n = node->body; n; n = n->next) gen_stmt(n); return; - case ND_RETURN: + case ND_GOTO: + println(" jmp %s", node->unique_label); + return; + case ND_GOTO_EXPR: gen_expr(node->lhs); + println(" jmp *%%rax"); + return; + case ND_LABEL: + println("%s:", node->unique_label); + gen_stmt(node->lhs); + return; + case ND_RETURN: + if (node->lhs) { + gen_expr(node->lhs); + Type *ty = node->lhs->ty; + + switch (ty->kind) { + case TY_STRUCT: + case TY_UNION: + if (ty->size <= 16) + copy_struct_reg(); + else + copy_struct_mem(); + break; + } + } + println(" jmp .L.return.%s", current_fn->name); return; case ND_EXPR_STMT: gen_expr(node->lhs); return; + case ND_ASM: + println(" %s", node->asm_str); + return; } error_tok(node->tok, "invalid statement"); @@ -265,32 +1305,136 @@ static void assign_lvar_offsets(Obj *prog) { if (!fn->is_function) continue; - int offset = 0; + // If a function has many parameters, some parameters are + // inevitably passed by stack rather than by register. + // The first passed-by-stack parameter resides at RBP+16. + int top = 16; + int bottom = 0; + + int gp = 0, fp = 0; + + // Assign offsets to pass-by-stack parameters. + for (Obj *var = fn->params; var; var = var->next) { + Type *ty = var->ty; + + switch (ty->kind) { + case TY_STRUCT: + case TY_UNION: + if (ty->size <= 16) { + bool fp1 = has_flonum(ty, 0, 8, 0); + bool fp2 = has_flonum(ty, 8, 16, 8); + if (fp + fp1 + fp2 < FP_MAX && gp + !fp1 + !fp2 < GP_MAX) { + fp = fp + fp1 + fp2; + gp = gp + !fp1 + !fp2; + continue; + } + } + break; + case TY_FLOAT: + case TY_DOUBLE: + if (fp++ < FP_MAX) + continue; + break; + case TY_LDOUBLE: + break; + default: + if (gp++ < GP_MAX) + continue; + } + + top = align_to(top, 8); + var->offset = top; + top += var->ty->size; + } + + // Assign offsets to pass-by-register parameters and local variables. for (Obj *var = fn->locals; var; var = var->next) { - offset += var->ty->size; - offset = align_to(offset, var->ty->align); - var->offset = -offset; + if (var->offset) + continue; + + // AMD64 System V ABI has a special alignment rule for an array of + // length at least 16 bytes. We need to align such array to at least + // 16-byte boundaries. See p.14 of + // https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-draft.pdf. + int align = (var->ty->kind == TY_ARRAY && var->ty->size >= 16) + ? MAX(16, var->align) : var->align; + + bottom += var->ty->size; + bottom = align_to(bottom, align); + var->offset = -bottom; } - fn->stack_size = align_to(offset, 16); + + fn->stack_size = align_to(bottom, 16); } } static void emit_data(Obj *prog) { for (Obj *var = prog; var; var = var->next) { - if (var->is_function) + if (var->is_function || !var->is_definition) continue; - println(" .data"); - println(" .globl %s", var->name); - println("%s:", var->name); + if (var->is_static) + println(" .local %s", var->name); + else + println(" .globl %s", var->name); + + int align = (var->ty->kind == TY_ARRAY && var->ty->size >= 16) + ? MAX(16, var->align) : var->align; + + // Common symbol + if (opt_fcommon && var->is_tentative) { + println(" .comm %s, %d, %d", var->name, var->ty->size, align); + continue; + } + // .data or .tdata if (var->init_data) { - for (int i = 0; i < var->ty->size; i++) - println(" .byte %d", var->init_data[i]); - } else { - println(" .zero %d", var->ty->size); + if (var->is_tls) + println(" .section .tdata,\"awT\",@progbits"); + else + println(" .data"); + + println(" .type %s, @object", var->name); + println(" .size %s, %d", var->name, var->ty->size); + println(" .align %d", align); + println("%s:", var->name); + + Relocation *rel = var->rel; + int pos = 0; + while (pos < var->ty->size) { + if (rel && rel->offset == pos) { + println(" .quad %s%+ld", *rel->label, rel->addend); + rel = rel->next; + pos += 8; + } else { + println(" .byte %d", var->init_data[pos++]); + } + } + continue; } + + // .bss or .tbss + if (var->is_tls) + println(" .section .tbss,\"awT\",@nobits"); + else + println(" .bss"); + + println(" .align %d", align); + println("%s:", var->name); + println(" .zero %d", var->ty->size); + } +} + +static void store_fp(int r, int offset, int sz) { + switch (sz) { + case 4: + println(" movss %%xmm%d, %d(%%rbp)", r, offset); + return; + case 8: + println(" movsd %%xmm%d, %d(%%rbp)", r, offset); + return; } + unreachable(); } static void store_gp(int r, int offset, int sz) { @@ -307,17 +1451,32 @@ static void store_gp(int r, int offset, int sz) { case 8: println(" mov %s, %d(%%rbp)", argreg64[r], offset); return; + default: + for (int i = 0; i < sz; i++) { + println(" mov %s, %d(%%rbp)", argreg8[r], offset + i); + println(" shr $8, %s", argreg64[r]); + } + return; } - unreachable(); } static void emit_text(Obj *prog) { for (Obj *fn = prog; fn; fn = fn->next) { - if (!fn->is_function) + if (!fn->is_function || !fn->is_definition) + continue; + + // No code is emitted for "static inline" functions + // if no one is referencing them. + if (!fn->is_live) continue; - println(" .globl %s", fn->name); + if (fn->is_static) + println(" .local %s", fn->name); + else + println(" .globl %s", fn->name); + println(" .text"); + println(" .type %s, @function", fn->name); println("%s:", fn->name); current_fn = fn; @@ -325,16 +1484,89 @@ static void emit_text(Obj *prog) { println(" push %%rbp"); println(" mov %%rsp, %%rbp"); println(" sub $%d, %%rsp", fn->stack_size); + println(" mov %%rsp, %d(%%rbp)", fn->alloca_bottom->offset); + + // Save arg registers if function is variadic + if (fn->va_area) { + int gp = 0, fp = 0; + for (Obj *var = fn->params; var; var = var->next) { + if (is_flonum(var->ty)) + fp++; + else + gp++; + } + + int off = fn->va_area->offset; + + // va_elem + println(" movl $%d, %d(%%rbp)", gp * 8, off); // gp_offset + println(" movl $%d, %d(%%rbp)", fp * 8 + 48, off + 4); // fp_offset + println(" movq %%rbp, %d(%%rbp)", off + 8); // overflow_arg_area + println(" addq $16, %d(%%rbp)", off + 8); + println(" movq %%rbp, %d(%%rbp)", off + 16); // reg_save_area + println(" addq $%d, %d(%%rbp)", off + 24, off + 16); + + // __reg_save_area__ + println(" movq %%rdi, %d(%%rbp)", off + 24); + println(" movq %%rsi, %d(%%rbp)", off + 32); + println(" movq %%rdx, %d(%%rbp)", off + 40); + println(" movq %%rcx, %d(%%rbp)", off + 48); + println(" movq %%r8, %d(%%rbp)", off + 56); + println(" movq %%r9, %d(%%rbp)", off + 64); + println(" movsd %%xmm0, %d(%%rbp)", off + 72); + println(" movsd %%xmm1, %d(%%rbp)", off + 80); + println(" movsd %%xmm2, %d(%%rbp)", off + 88); + println(" movsd %%xmm3, %d(%%rbp)", off + 96); + println(" movsd %%xmm4, %d(%%rbp)", off + 104); + println(" movsd %%xmm5, %d(%%rbp)", off + 112); + println(" movsd %%xmm6, %d(%%rbp)", off + 120); + println(" movsd %%xmm7, %d(%%rbp)", off + 128); + } // Save passed-by-register arguments to the stack - int i = 0; - for (Obj *var = fn->params; var; var = var->next) - store_gp(i++, var->offset, var->ty->size); + int gp = 0, fp = 0; + for (Obj *var = fn->params; var; var = var->next) { + if (var->offset > 0) + continue; + + Type *ty = var->ty; + + switch (ty->kind) { + case TY_STRUCT: + case TY_UNION: + assert(ty->size <= 16); + if (has_flonum(ty, 0, 8, 0)) + store_fp(fp++, var->offset, MIN(8, ty->size)); + else + store_gp(gp++, var->offset, MIN(8, ty->size)); + + if (ty->size > 8) { + if (has_flonum(ty, 8, 16, 0)) + store_fp(fp++, var->offset + 8, ty->size - 8); + else + store_gp(gp++, var->offset + 8, ty->size - 8); + } + break; + case TY_FLOAT: + case TY_DOUBLE: + store_fp(fp++, var->offset, ty->size); + break; + default: + store_gp(gp++, var->offset, ty->size); + } + } // Emit code gen_stmt(fn->body); assert(depth == 0); + // [https://www.sigbus.info/n1570#5.1.2.2.3p1] The C spec defines + // a special rule for the main function. Reaching the end of the + // main function is equivalent to returning 0, even though the + // behavior is undefined for the other functions. + if (strcmp(fn->name, "main") == 0) + println(" mov $0, %%rax"); + // Epilogue println(".L.return.%s:", fn->name); println(" mov %%rbp, %%rsp"); @@ -346,6 +1578,10 @@ static void emit_text(Obj *prog) { void codegen(Obj *prog, FILE *out) { output_file = out; + File **files = get_input_files(); + for (int i = 0; files[i]; i++) + println(" .file \"%s\"", files[i]->name); + assign_lvar_offsets(prog); emit_data(prog); emit_text(prog); diff --git a/hashmap.c b/hashmap.c new file mode 100644 index 00000000..a83934d1 --- /dev/null +++ b/hashmap.c @@ -0,0 +1,165 @@ +// This is an implementation of the open-addressing hash table. + +#include "chibicc.h" + +// Initial hash bucket size +#define INIT_SIZE 16 + +// Rehash if the usage exceeds 70%. +#define HIGH_WATERMARK 70 + +// We'll keep the usage below 50% after rehashing. +#define LOW_WATERMARK 50 + +// Represents a deleted hash entry +#define TOMBSTONE ((void *)-1) + +static uint64_t fnv_hash(char *s, int len) { + uint64_t hash = 0xcbf29ce484222325; + for (int i = 0; i < len; i++) { + hash *= 0x100000001b3; + hash ^= (unsigned char)s[i]; + } + return hash; +} + +// Make room for new entires in a given hashmap by removing +// tombstones and possibly extending the bucket size. +static void rehash(HashMap *map) { + // Compute the size of the new hashmap. + int nkeys = 0; + for (int i = 0; i < map->capacity; i++) + if (map->buckets[i].key && map->buckets[i].key != TOMBSTONE) + nkeys++; + + int cap = map->capacity; + while ((nkeys * 100) / cap >= LOW_WATERMARK) + cap = cap * 2; + assert(cap > 0); + + // Create a new hashmap and copy all key-values. + HashMap map2 = {}; + map2.buckets = calloc(cap, sizeof(HashEntry)); + map2.capacity = cap; + + for (int i = 0; i < map->capacity; i++) { + HashEntry *ent = &map->buckets[i]; + if (ent->key && ent->key != TOMBSTONE) + hashmap_put2(&map2, ent->key, ent->keylen, ent->val); + } + + assert(map2.used == nkeys); + *map = map2; +} + +static bool match(HashEntry *ent, char *key, int keylen) { + return ent->key && ent->key != TOMBSTONE && + ent->keylen == keylen && memcmp(ent->key, key, keylen) == 0; +} + +static HashEntry *get_entry(HashMap *map, char *key, int keylen) { + if (!map->buckets) + return NULL; + + uint64_t hash = fnv_hash(key, keylen); + + for (int i = 0; i < map->capacity; i++) { + HashEntry *ent = &map->buckets[(hash + i) % map->capacity]; + if (match(ent, key, keylen)) + return ent; + if (ent->key == NULL) + return NULL; + } + unreachable(); +} + +static HashEntry *get_or_insert_entry(HashMap *map, char *key, int keylen) { + if (!map->buckets) { + map->buckets = calloc(INIT_SIZE, sizeof(HashEntry)); + map->capacity = INIT_SIZE; + } else if ((map->used * 100) / map->capacity >= HIGH_WATERMARK) { + rehash(map); + } + + uint64_t hash = fnv_hash(key, keylen); + + for (int i = 0; i < map->capacity; i++) { + HashEntry *ent = &map->buckets[(hash + i) % map->capacity]; + + if (match(ent, key, keylen)) + return ent; + + if (ent->key == TOMBSTONE) { + ent->key = key; + ent->keylen = keylen; + return ent; + } + + if (ent->key == NULL) { + ent->key = key; + ent->keylen = keylen; + map->used++; + return ent; + } + } + unreachable(); +} + +void *hashmap_get(HashMap *map, char *key) { + return hashmap_get2(map, key, strlen(key)); +} + +void *hashmap_get2(HashMap *map, char *key, int keylen) { + HashEntry *ent = get_entry(map, key, keylen); + return ent ? ent->val : NULL; +} + +void hashmap_put(HashMap *map, char *key, void *val) { + hashmap_put2(map, key, strlen(key), val); +} + +void hashmap_put2(HashMap *map, char *key, int keylen, void *val) { + HashEntry *ent = get_or_insert_entry(map, key, keylen); + ent->val = val; +} + +void hashmap_delete(HashMap *map, char *key) { + hashmap_delete2(map, key, strlen(key)); +} + +void hashmap_delete2(HashMap *map, char *key, int keylen) { + HashEntry *ent = get_entry(map, key, keylen); + if (ent) + ent->key = TOMBSTONE; +} + +void hashmap_test(void) { + HashMap *map = calloc(1, sizeof(HashMap)); + + for (int i = 0; i < 5000; i++) + hashmap_put(map, format("key %d", i), (void *)(size_t)i); + for (int i = 1000; i < 2000; i++) + hashmap_delete(map, format("key %d", i)); + for (int i = 1500; i < 1600; i++) + hashmap_put(map, format("key %d", i), (void *)(size_t)i); + for (int i = 6000; i < 7000; i++) + hashmap_put(map, format("key %d", i), (void *)(size_t)i); + + for (int i = 0; i < 1000; i++) + assert((size_t)hashmap_get(map, format("key %d", i)) == i); + for (int i = 1000; i < 1500; i++) + assert(hashmap_get(map, "no such key") == NULL); + for (int i = 1500; i < 1600; i++) + assert((size_t)hashmap_get(map, format("key %d", i)) == i); + for (int i = 1600; i < 2000; i++) + assert(hashmap_get(map, "no such key") == NULL); + for (int i = 2000; i < 5000; i++) + assert((size_t)hashmap_get(map, format("key %d", i)) == i); + for (int i = 5000; i < 6000; i++) + assert(hashmap_get(map, "no such key") == NULL); + for (int i = 6000; i < 7000; i++) + hashmap_put(map, format("key %d", i), (void *)(size_t)i); + + assert(hashmap_get(map, "no such key") == NULL); + printf("OK\n"); +} diff --git a/include/float.h b/include/float.h new file mode 100644 index 00000000..6c53cdc4 --- /dev/null +++ b/include/float.h @@ -0,0 +1,42 @@ +#ifndef __STDFLOAT_H +#define __STDFLOAT_H + +#define DECIMAL_DIG 21 +#define FLT_EVAL_METHOD 0 // C11 5.2.4.2.2p9 +#define FLT_RADIX 2 +#define FLT_ROUNDS 1 // C11 5.2.4.2.2p8: to nearest + +#define FLT_DIG 6 +#define FLT_EPSILON 0x1p-23 +#define FLT_MANT_DIG 24 +#define FLT_MAX 0x1.fffffep+127 +#define FLT_MAX_10_EXP 38 +#define FLT_MAX_EXP 128 +#define FLT_MIN 0x1p-126 +#define FLT_MIN_10_EXP -37 +#define FLT_MIN_EXP -125 +#define FLT_TRUE_MIN 0x1p-149 + +#define DBL_DIG 15 +#define DBL_EPSILON 0x1p-52 +#define DBL_MANT_DIG 53 +#define DBL_MAX 0x1.fffffffffffffp+1023 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define DBL_MIN 0x1p-1022 +#define DBL_MIN_10_EXP -307 +#define DBL_MIN_EXP -1021 +#define DBL_TRUE_MIN 0x0.0000000000001p-1022 + +#define LDBL_DIG 15 +#define LDBL_EPSILON 0x1p-52 +#define LDBL_MANT_DIG 53 +#define LDBL_MAX 0x1.fffffffffffffp+1023 +#define LDBL_MAX_10_EXP 308 +#define LDBL_MAX_EXP 1024 +#define LDBL_MIN 0x1p-1022 +#define LDBL_MIN_10_EXP -307 +#define LDBL_MIN_EXP -1021 +#define LDBL_TRUE_MIN 0x0.0000000000001p-1022 + +#endif diff --git a/include/stdalign.h b/include/stdalign.h new file mode 100644 index 00000000..d845e235 --- /dev/null +++ b/include/stdalign.h @@ -0,0 +1,9 @@ +#ifndef __STDALIGN_H +#define __STDALIGN_H + +#define alignas _Alignas +#define alignof _Alignof +#define __alignas_is_defined 1 +#define __alignof_is_defined 1 + +#endif diff --git a/include/stdarg.h b/include/stdarg.h new file mode 100644 index 00000000..29df41d5 --- /dev/null +++ b/include/stdarg.h @@ -0,0 +1,57 @@ +#ifndef __STDARG_H +#define __STDARG_H + +typedef struct { + unsigned int gp_offset; + unsigned int fp_offset; + void *overflow_arg_area; + void *reg_save_area; +} __va_elem; + +typedef __va_elem va_list[1]; + +#define va_start(ap, last) \ + do { *(ap) = *(__va_elem *)__va_area__; } while (0) + +#define va_end(ap) + +static void *__va_arg_mem(__va_elem *ap, int sz, int align) { + void *p = ap->overflow_arg_area; + if (align > 8) + p = (p + 15) / 16 * 16; + ap->overflow_arg_area = ((unsigned long)p + sz + 7) / 8 * 8; + return p; +} + +static void *__va_arg_gp(__va_elem *ap, int sz, int align) { + if (ap->gp_offset >= 48) + return __va_arg_mem(ap, sz, align); + + void *r = ap->reg_save_area + ap->gp_offset; + ap->gp_offset += 8; + return r; +} + +static void *__va_arg_fp(__va_elem *ap, int sz, int align) { + if (ap->fp_offset >= 112) + return __va_arg_mem(ap, sz, align); + + void *r = ap->reg_save_area + ap->fp_offset; + ap->fp_offset += 8; + return r; +} + +#define va_arg(ap, ty) \ + ({ \ + int klass = __builtin_reg_class(ty); \ + *(ty *)(klass == 0 ? __va_arg_gp(ap, sizeof(ty), _Alignof(ty)) : \ + klass == 1 ? __va_arg_fp(ap, sizeof(ty), _Alignof(ty)) : \ + __va_arg_mem(ap, sizeof(ty), _Alignof(ty))); \ + }) + +#define va_copy(dest, src) ((dest)[0] = (src)[0]) + +#define __GNUC_VA_LIST 1 +typedef va_list __gnuc_va_list; + +#endif diff --git a/include/stdatomic.h b/include/stdatomic.h new file mode 100644 index 00000000..7778e11a --- /dev/null +++ b/include/stdatomic.h @@ -0,0 +1,102 @@ +#ifndef __STDATOMIC_H +#define __STDATOMIC_H + +#define ATOMIC_BOOL_LOCK_FREE 1 +#define ATOMIC_CHAR_LOCK_FREE 1 +#define ATOMIC_CHAR16_T_LOCK_FREE 1 +#define ATOMIC_CHAR32_T_LOCK_FREE 1 +#define ATOMIC_WCHAR_T_LOCK_FREE 1 +#define ATOMIC_SHORT_LOCK_FREE 1 +#define ATOMIC_INT_LOCK_FREE 1 +#define ATOMIC_LONG_LOCK_FREE 1 +#define ATOMIC_LLONG_LOCK_FREE 1 +#define ATOMIC_POINTER_LOCK_FREE 1 + +typedef enum { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, +} memory_order; + +#define ATOMIC_FLAG_INIT(x) (x) +#define atomic_init(addr, val) (*(addr) = (val)) +#define kill_dependency(x) (x) +#define atomic_thread_fence(order) +#define atomic_signal_fence(order) +#define atomic_is_lock_free(x) 1 + +#define atomic_load(addr) (*(addr)) +#define atomic_store(addr, val) (*(addr) = (val)) + +#define atomic_load_explicit(addr, order) (*(addr)) +#define atomic_store_explicit(addr, val, order) (*(addr) = (val)) + +#define atomic_fetch_add(obj, val) (*(obj) += (val)) +#define atomic_fetch_sub(obj, val) (*(obj) -= (val)) +#define atomic_fetch_or(obj, val) (*(obj) |= (val)) +#define atomic_fetch_xor(obj, val) (*(obj) ^= (val)) +#define atomic_fetch_and(obj, val) (*(obj) &= (val)) + +#define atomic_fetch_add_explicit(obj, val, order) (*(obj) += (val)) +#define atomic_fetch_sub_explicit(obj, val, order) (*(obj) -= (val)) +#define atomic_fetch_or_explicit(obj, val, order) (*(obj) |= (val)) +#define atomic_fetch_xor_explicit(obj, val, order) (*(obj) ^= (val)) +#define atomic_fetch_and_explicit(obj, val, order) (*(obj) &= (val)) + +#define atomic_compare_exchange_weak(p, old, new) \ + __builtin_compare_and_swap((p), (old), (new)) + +#define atomic_compare_exchange_strong(p, old, new) \ + __builtin_compare_and_swap((p), (old), (new)) + +#define atomic_exchange(obj, val) __builtin_atomic_exchange((obj), (val)) +#define atomic_exchange_explicit(obj, val, order) __builtin_atomic_exchange((obj), (val)) + +#define atomic_flag_test_and_set(obj) atomic_exchange((obj), 1) +#define atomic_flag_test_and_set_explicit(obj, order) atomic_exchange((obj), 1) +#define atomic_flag_clear(obj) (*(obj) = 0) +#define atomic_flag_clear_explicit(obj, order) (*(obj) = 0) + +typedef _Atomic _Bool atomic_flag; +typedef _Atomic _Bool atomic_bool; +typedef _Atomic char atomic_char; +typedef _Atomic signed char atomic_schar; +typedef _Atomic unsigned char atomic_uchar; +typedef _Atomic short atomic_short; +typedef _Atomic unsigned short atomic_ushort; +typedef _Atomic int atomic_int; +typedef _Atomic unsigned int atomic_uint; +typedef _Atomic long atomic_long; +typedef _Atomic unsigned long atomic_ulong; +typedef _Atomic long long atomic_llong; +typedef _Atomic unsigned long long atomic_ullong; +typedef _Atomic unsigned short atomic_char16_t; +typedef _Atomic unsigned atomic_char32_t; +typedef _Atomic unsigned atomic_wchar_t; +typedef _Atomic signed char atomic_int_least8_t; +typedef _Atomic unsigned char atomic_uint_least8_t; +typedef _Atomic short atomic_int_least16_t; +typedef _Atomic unsigned short atomic_uint_least16_t; +typedef _Atomic int atomic_int_least32_t; +typedef _Atomic unsigned int atomic_uint_least32_t; +typedef _Atomic long atomic_int_least64_t; +typedef _Atomic unsigned long atomic_uint_least64_t; +typedef _Atomic signed char atomic_int_fast8_t; +typedef _Atomic unsigned char atomic_uint_fast8_t; +typedef _Atomic short atomic_int_fast16_t; +typedef _Atomic unsigned short atomic_uint_fast16_t; +typedef _Atomic int atomic_int_fast32_t; +typedef _Atomic unsigned int atomic_uint_fast32_t; +typedef _Atomic long atomic_int_fast64_t; +typedef _Atomic unsigned long atomic_uint_fast64_t; +typedef _Atomic long atomic_intptr_t; +typedef _Atomic unsigned long atomic_uintptr_t; +typedef _Atomic unsigned long atomic_size_t; +typedef _Atomic long atomic_ptrdiff_t; +typedef _Atomic long atomic_intmax_t; +typedef _Atomic unsigned long atomic_uintmax_t; + +#endif diff --git a/include/stdbool.h b/include/stdbool.h new file mode 100644 index 00000000..3827dc29 --- /dev/null +++ b/include/stdbool.h @@ -0,0 +1,9 @@ +#ifndef __STDBOOL_H +#define __STDBOOL_H + +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#endif diff --git a/include/stddef.h b/include/stddef.h new file mode 100644 index 00000000..ccd3857d --- /dev/null +++ b/include/stddef.h @@ -0,0 +1,13 @@ +#ifndef __STDDEF_H +#define __STDDEF_H + +#define NULL ((void *)0) + +typedef unsigned long size_t; +typedef long ptrdiff_t; +typedef unsigned int wchar_t; +typedef long max_align_t; + +#define offsetof(type, member) ((size_t)&(((type *)0)->member)) + +#endif diff --git a/include/stdnoreturn.h b/include/stdnoreturn.h new file mode 100644 index 00000000..c5c3a2cc --- /dev/null +++ b/include/stdnoreturn.h @@ -0,0 +1,6 @@ +#ifndef __STDNORETURN_H +#define __STDNORETURN_H + +#define noreturn _Noreturn + +#endif diff --git a/main.c b/main.c index d0eb21fd..ffaabf41 100644 --- a/main.c +++ b/main.c @@ -1,23 +1,142 @@ #include "chibicc.h" +typedef enum { + FILE_NONE, FILE_C, FILE_ASM, FILE_OBJ, FILE_AR, FILE_DSO, +} FileType; + +StringArray include_paths; +bool opt_fcommon = true; +bool opt_fpic; + +static FileType opt_x; +static StringArray opt_include; +static bool opt_E; +static bool opt_M; +static bool opt_MD; +static bool opt_MMD; +static bool opt_MP; +static bool opt_S; +static bool opt_c; +static bool opt_cc1; +static bool opt_hash_hash_hash; +static bool opt_static; +static bool opt_shared; +static char *opt_MF; +static char *opt_MT; static char *opt_o; -static char *input_path; +static StringArray ld_extra_args; +static StringArray std_include_paths; + +char *base_file; +static char *output_file; + +static StringArray input_paths; +static StringArray tmpfiles; static void usage(int status) { fprintf(stderr, "chibicc [ -o ] \n"); exit(status); } +static bool take_arg(char *arg) { + char *x[] = { + "-o", "-I", "-idirafter", "-include", "-x", "-MF", "-MT", "-Xlinker", + }; + + for (int i = 0; i < sizeof(x) / sizeof(*x); i++) + if (!strcmp(arg, x[i])) + return true; + return false; +} + +static void add_default_include_paths(char *argv0) { + // We expect that chibicc-specific include files are installed + // to ./include relative to argv[0]. + strarray_push(&include_paths, format("%s/include", dirname(strdup(argv0)))); + + // Add standard include paths. + strarray_push(&include_paths, "/usr/local/include"); + strarray_push(&include_paths, "/usr/include/x86_64-linux-gnu"); + strarray_push(&include_paths, "/usr/include"); + + // Keep a copy of the standard include paths for -MMD option. + for (int i = 0; i < include_paths.len; i++) + strarray_push(&std_include_paths, include_paths.data[i]); +} + +static void define(char *str) { + char *eq = strchr(str, '='); + if (eq) + define_macro(strndup(str, eq - str), eq + 1); + else + define_macro(str, "1"); +} + +static FileType parse_opt_x(char *s) { + if (!strcmp(s, "c")) + return FILE_C; + if (!strcmp(s, "assembler")) + return FILE_ASM; + if (!strcmp(s, "none")) + return FILE_NONE; + error(": unknown argument for -x: %s", s); +} + +static char *quote_makefile(char *s) { + char *buf = calloc(1, strlen(s) * 2 + 1); + + for (int i = 0, j = 0; s[i]; i++) { + switch (s[i]) { + case '$': + buf[j++] = '$'; + buf[j++] = '$'; + break; + case '#': + buf[j++] = '\\'; + buf[j++] = '#'; + break; + case ' ': + case '\t': + for (int k = i - 1; k >= 0 && s[k] == '\\'; k--) + buf[j++] = '\\'; + buf[j++] = '\\'; + buf[j++] = s[i]; + break; + default: + buf[j++] = s[i]; + break; + } + } + return buf; +} + static void parse_args(int argc, char **argv) { + // Make sure that all command line options that take an argument + // have an argument. + for (int i = 1; i < argc; i++) + if (take_arg(argv[i])) + if (!argv[++i]) + usage(1); + + StringArray idirafter = {}; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-###")) { + opt_hash_hash_hash = true; + continue; + } + + if (!strcmp(argv[i], "-cc1")) { + opt_cc1 = true; + continue; + } + if (!strcmp(argv[i], "--help")) usage(0); if (!strcmp(argv[i], "-o")) { - if (!argv[++i]) - usage(1); - opt_o = argv[i]; + opt_o = argv[++i]; continue; } @@ -26,14 +145,206 @@ static void parse_args(int argc, char **argv) { continue; } + if (!strcmp(argv[i], "-S")) { + opt_S = true; + continue; + } + + if (!strcmp(argv[i], "-fcommon")) { + opt_fcommon = true; + continue; + } + + if (!strcmp(argv[i], "-fno-common")) { + opt_fcommon = false; + continue; + } + + if (!strcmp(argv[i], "-c")) { + opt_c = true; + continue; + } + + if (!strcmp(argv[i], "-E")) { + opt_E = true; + continue; + } + + if (!strncmp(argv[i], "-I", 2)) { + strarray_push(&include_paths, argv[i] + 2); + continue; + } + + if (!strcmp(argv[i], "-D")) { + define(argv[++i]); + continue; + } + + if (!strncmp(argv[i], "-D", 2)) { + define(argv[i] + 2); + continue; + } + + if (!strcmp(argv[i], "-U")) { + undef_macro(argv[++i]); + continue; + } + + if (!strncmp(argv[i], "-U", 2)) { + undef_macro(argv[i] + 2); + continue; + } + + if (!strcmp(argv[i], "-include")) { + strarray_push(&opt_include, argv[++i]); + continue; + } + + if (!strcmp(argv[i], "-x")) { + opt_x = parse_opt_x(argv[++i]); + continue; + } + + if (!strncmp(argv[i], "-x", 2)) { + opt_x = parse_opt_x(argv[i] + 2); + continue; + } + + if (!strncmp(argv[i], "-l", 2) || !strncmp(argv[i], "-Wl,", 4)) { + strarray_push(&input_paths, argv[i]); + continue; + } + + if (!strcmp(argv[i], "-Xlinker")) { + strarray_push(&ld_extra_args, argv[++i]); + continue; + } + + if (!strcmp(argv[i], "-s")) { + strarray_push(&ld_extra_args, "-s"); + continue; + } + + if (!strcmp(argv[i], "-M")) { + opt_M = true; + continue; + } + + if (!strcmp(argv[i], "-MF")) { + opt_MF = argv[++i]; + continue; + } + + if (!strcmp(argv[i], "-MP")) { + opt_MP = true; + continue; + } + + if (!strcmp(argv[i], "-MT")) { + if (opt_MT == NULL) + opt_MT = argv[++i]; + else + opt_MT = format("%s %s", opt_MT, argv[++i]); + continue; + } + + if (!strcmp(argv[i], "-MD")) { + opt_MD = true; + continue; + } + + if (!strcmp(argv[i], "-MQ")) { + if (opt_MT == NULL) + opt_MT = quote_makefile(argv[++i]); + else + opt_MT = format("%s %s", opt_MT, quote_makefile(argv[++i])); + continue; + } + + if (!strcmp(argv[i], "-MMD")) { + opt_MD = opt_MMD = true; + continue; + } + + if (!strcmp(argv[i], "-fpic") || !strcmp(argv[i], "-fPIC")) { + opt_fpic = true; + continue; + } + + if (!strcmp(argv[i], "-cc1-input")) { + base_file = argv[++i]; + continue; + } + + if (!strcmp(argv[i], "-cc1-output")) { + output_file = argv[++i]; + continue; + } + + if (!strcmp(argv[i], "-idirafter")) { + strarray_push(&idirafter, argv[i++]); + continue; + } + + if (!strcmp(argv[i], "-static")) { + opt_static = true; + strarray_push(&ld_extra_args, "-static"); + continue; + } + + if (!strcmp(argv[i], "-shared")) { + opt_shared = true; + strarray_push(&ld_extra_args, "-shared"); + continue; + } + + if (!strcmp(argv[i], "-L")) { + strarray_push(&ld_extra_args, "-L"); + strarray_push(&ld_extra_args, argv[++i]); + continue; + } + + if (!strncmp(argv[i], "-L", 2)) { + strarray_push(&ld_extra_args, "-L"); + strarray_push(&ld_extra_args, argv[i] + 2); + continue; + } + + if (!strcmp(argv[i], "-hashmap-test")) { + hashmap_test(); + exit(0); + } + + // These options are ignored for now. + if (!strncmp(argv[i], "-O", 2) || + !strncmp(argv[i], "-W", 2) || + !strncmp(argv[i], "-g", 2) || + !strncmp(argv[i], "-std=", 5) || + !strcmp(argv[i], "-ffreestanding") || + !strcmp(argv[i], "-fno-builtin") || + !strcmp(argv[i], "-fno-omit-frame-pointer") || + !strcmp(argv[i], "-fno-stack-protector") || + !strcmp(argv[i], "-fno-strict-aliasing") || + !strcmp(argv[i], "-m64") || + !strcmp(argv[i], "-mno-red-zone") || + !strcmp(argv[i], "-w")) + continue; + if (argv[i][0] == '-' && argv[i][1] != '\0') error("unknown argument: %s", argv[i]); - input_path = argv[i]; + strarray_push(&input_paths, argv[i]); } - if (!input_path) + for (int i = 0; i < idirafter.len; i++) + strarray_push(&include_paths, idirafter.data[i]); + + if (input_paths.len == 0) error("no input files"); + + // -E implies that the input is the C macro language. + if (opt_E) + opt_x = FILE_C; } static FILE *open_file(char *path) { @@ -46,16 +357,435 @@ static FILE *open_file(char *path) { return out; } -int main(int argc, char **argv) { - parse_args(argc, argv); +static bool endswith(char *p, char *q) { + int len1 = strlen(p); + int len2 = strlen(q); + return (len1 >= len2) && !strcmp(p + len1 - len2, q); +} + +// Replace file extension +static char *replace_extn(char *tmpl, char *extn) { + char *filename = basename(strdup(tmpl)); + char *dot = strrchr(filename, '.'); + if (dot) + *dot = '\0'; + return format("%s%s", filename, extn); +} + +static void cleanup(void) { + for (int i = 0; i < tmpfiles.len; i++) + unlink(tmpfiles.data[i]); +} + +static char *create_tmpfile(void) { + char *path = strdup("/tmp/chibicc-XXXXXX"); + int fd = mkstemp(path); + if (fd == -1) + error("mkstemp failed: %s", strerror(errno)); + close(fd); + + strarray_push(&tmpfiles, path); + return path; +} + +static void run_subprocess(char **argv) { + // If -### is given, dump the subprocess's command line. + if (opt_hash_hash_hash) { + fprintf(stderr, "%s", argv[0]); + for (int i = 1; argv[i]; i++) + fprintf(stderr, " %s", argv[i]); + fprintf(stderr, "\n"); + } + + if (fork() == 0) { + // Child process. Run a new command. + execvp(argv[0], argv); + fprintf(stderr, "exec failed: %s: %s\n", argv[0], strerror(errno)); + _exit(1); + } + + // Wait for the child process to finish. + int status; + while (wait(&status) > 0); + if (status != 0) + exit(1); +} + +static void run_cc1(int argc, char **argv, char *input, char *output) { + char **args = calloc(argc + 10, sizeof(char *)); + memcpy(args, argv, argc * sizeof(char *)); + args[argc++] = "-cc1"; + + if (input) { + args[argc++] = "-cc1-input"; + args[argc++] = input; + } + + if (output) { + args[argc++] = "-cc1-output"; + args[argc++] = output; + } + + run_subprocess(args); +} + +// Print tokens to stdout. Used for -E. +static void print_tokens(Token *tok) { + FILE *out = open_file(opt_o ? opt_o : "-"); + + int line = 1; + for (; tok->kind != TK_EOF; tok = tok->next) { + if (line > 1 && tok->at_bol) + fprintf(out, "\n"); + if (tok->has_space && !tok->at_bol) + fprintf(out, " "); + fprintf(out, "%.*s", tok->len, tok->loc); + line++; + } + fprintf(out, "\n"); +} + +static bool in_std_include_path(char *path) { + for (int i = 0; i < std_include_paths.len; i++) { + char *dir = std_include_paths.data[i]; + int len = strlen(dir); + if (strncmp(dir, path, len) == 0 && path[len] == '/') + return true; + } + return false; +} + +// If -M options is given, the compiler write a list of input files to +// stdout in a format that "make" command can read. This feature is +// used to automate file dependency management. +static void print_dependencies(void) { + char *path; + if (opt_MF) + path = opt_MF; + else if (opt_MD) + path = replace_extn(opt_o ? opt_o : base_file, ".d"); + else if (opt_o) + path = opt_o; + else + path = "-"; + + FILE *out = open_file(path); + if (opt_MT) + fprintf(out, "%s:", opt_MT); + else + fprintf(out, "%s:", quote_makefile(replace_extn(base_file, ".o"))); + + File **files = get_input_files(); + + for (int i = 0; files[i]; i++) { + if (opt_MMD && in_std_include_path(files[i]->name)) + continue; + fprintf(out, " \\\n %s", files[i]->name); + } + + fprintf(out, "\n\n"); + + if (opt_MP) { + for (int i = 1; files[i]; i++) { + if (opt_MMD && in_std_include_path(files[i]->name)) + continue; + fprintf(out, "%s:\n\n", quote_makefile(files[i]->name)); + } + } +} + +static Token *must_tokenize_file(char *path) { + Token *tok = tokenize_file(path); + if (!tok) + error("%s: %s", path, strerror(errno)); + return tok; +} + +static Token *append_tokens(Token *tok1, Token *tok2) { + if (!tok1 || tok1->kind == TK_EOF) + return tok2; + + Token *t = tok1; + while (t->next->kind != TK_EOF) + t = t->next; + t->next = tok2; + return tok1; +} + +static void cc1(void) { + Token *tok = NULL; + + // Process -include option + for (int i = 0; i < opt_include.len; i++) { + char *incl = opt_include.data[i]; + + char *path; + if (file_exists(incl)) { + path = incl; + } else { + path = search_include_paths(incl); + if (!path) + error("-include: %s: %s", incl, strerror(errno)); + } + + Token *tok2 = must_tokenize_file(path); + tok = append_tokens(tok, tok2); + } // Tokenize and parse. - Token *tok = tokenize_file(input_path); + Token *tok2 = must_tokenize_file(base_file); + tok = append_tokens(tok, tok2); + tok = preprocess(tok); + + // If -M or -MD are given, print file dependencies. + if (opt_M || opt_MD) { + print_dependencies(); + if (opt_M) + return; + } + + // If -E is given, print out preprocessed C code as a result. + if (opt_E) { + print_tokens(tok); + return; + } + Obj *prog = parse(tok); + // Open a temporary output buffer. + char *buf; + size_t buflen; + FILE *output_buf = open_memstream(&buf, &buflen); + // Traverse the AST to emit assembly. - FILE *out = open_file(opt_o); - fprintf(out, ".file 1 \"%s\"\n", input_path); - codegen(prog, out); + codegen(prog, output_buf); + fclose(output_buf); + + // Write the asembly text to a file. + FILE *out = open_file(output_file); + fwrite(buf, buflen, 1, out); + fclose(out); +} + +static void assemble(char *input, char *output) { + char *cmd[] = {"as", "-c", input, "-o", output, NULL}; + run_subprocess(cmd); +} + +static char *find_file(char *pattern) { + char *path = NULL; + glob_t buf = {}; + glob(pattern, 0, NULL, &buf); + if (buf.gl_pathc > 0) + path = strdup(buf.gl_pathv[buf.gl_pathc - 1]); + globfree(&buf); + return path; +} + +// Returns true if a given file exists. +bool file_exists(char *path) { + struct stat st; + return !stat(path, &st); +} + +static char *find_libpath(void) { + if (file_exists("/usr/lib/x86_64-linux-gnu/crti.o")) + return "/usr/lib/x86_64-linux-gnu"; + if (file_exists("/usr/lib64/crti.o")) + return "/usr/lib64"; + error("library path is not found"); +} + +static char *find_gcc_libpath(void) { + char *paths[] = { + "/usr/lib/gcc/x86_64-linux-gnu/*/crtbegin.o", + "/usr/lib/gcc/x86_64-pc-linux-gnu/*/crtbegin.o", // For Gentoo + "/usr/lib/gcc/x86_64-redhat-linux/*/crtbegin.o", // For Fedora + }; + + for (int i = 0; i < sizeof(paths) / sizeof(*paths); i++) { + char *path = find_file(paths[i]); + if (path) + return dirname(path); + } + + error("gcc library path is not found"); +} + +static void run_linker(StringArray *inputs, char *output) { + StringArray arr = {}; + + strarray_push(&arr, "ld"); + strarray_push(&arr, "-o"); + strarray_push(&arr, output); + strarray_push(&arr, "-m"); + strarray_push(&arr, "elf_x86_64"); + + char *libpath = find_libpath(); + char *gcc_libpath = find_gcc_libpath(); + + if (opt_shared) { + strarray_push(&arr, format("%s/crti.o", libpath)); + strarray_push(&arr, format("%s/crtbeginS.o", gcc_libpath)); + } else { + strarray_push(&arr, format("%s/crt1.o", libpath)); + strarray_push(&arr, format("%s/crti.o", libpath)); + strarray_push(&arr, format("%s/crtbegin.o", gcc_libpath)); + } + + strarray_push(&arr, format("-L%s", gcc_libpath)); + strarray_push(&arr, "-L/usr/lib/x86_64-linux-gnu"); + strarray_push(&arr, "-L/usr/lib64"); + strarray_push(&arr, "-L/lib64"); + strarray_push(&arr, "-L/usr/lib/x86_64-linux-gnu"); + strarray_push(&arr, "-L/usr/lib/x86_64-pc-linux-gnu"); + strarray_push(&arr, "-L/usr/lib/x86_64-redhat-linux"); + strarray_push(&arr, "-L/usr/lib"); + strarray_push(&arr, "-L/lib"); + + if (!opt_static) { + strarray_push(&arr, "-dynamic-linker"); + strarray_push(&arr, "/lib64/ld-linux-x86-64.so.2"); + } + + for (int i = 0; i < ld_extra_args.len; i++) + strarray_push(&arr, ld_extra_args.data[i]); + + for (int i = 0; i < inputs->len; i++) + strarray_push(&arr, inputs->data[i]); + + if (opt_static) { + strarray_push(&arr, "--start-group"); + strarray_push(&arr, "-lgcc"); + strarray_push(&arr, "-lgcc_eh"); + strarray_push(&arr, "-lc"); + strarray_push(&arr, "--end-group"); + } else { + strarray_push(&arr, "-lc"); + strarray_push(&arr, "-lgcc"); + strarray_push(&arr, "--as-needed"); + strarray_push(&arr, "-lgcc_s"); + strarray_push(&arr, "--no-as-needed"); + } + + if (opt_shared) + strarray_push(&arr, format("%s/crtendS.o", gcc_libpath)); + else + strarray_push(&arr, format("%s/crtend.o", gcc_libpath)); + + strarray_push(&arr, format("%s/crtn.o", libpath)); + strarray_push(&arr, NULL); + + run_subprocess(arr.data); +} + +static FileType get_file_type(char *filename) { + if (opt_x != FILE_NONE) + return opt_x; + + if (endswith(filename, ".a")) + return FILE_AR; + if (endswith(filename, ".so")) + return FILE_DSO; + if (endswith(filename, ".o")) + return FILE_OBJ; + if (endswith(filename, ".c")) + return FILE_C; + if (endswith(filename, ".s")) + return FILE_ASM; + + error(": unknown file extension: %s", filename); +} + +int main(int argc, char **argv) { + atexit(cleanup); + init_macros(); + parse_args(argc, argv); + + if (opt_cc1) { + add_default_include_paths(argv[0]); + cc1(); + return 0; + } + + if (input_paths.len > 1 && opt_o && (opt_c || opt_S | opt_E)) + error("cannot specify '-o' with '-c,' '-S' or '-E' with multiple files"); + + StringArray ld_args = {}; + + for (int i = 0; i < input_paths.len; i++) { + char *input = input_paths.data[i]; + + if (!strncmp(input, "-l", 2)) { + strarray_push(&ld_args, input); + continue; + } + + if (!strncmp(input, "-Wl,", 4)) { + char *s = strdup(input + 4); + char *arg = strtok(s, ","); + while (arg) { + strarray_push(&ld_args, arg); + arg = strtok(NULL, ","); + } + continue; + } + + char *output; + if (opt_o) + output = opt_o; + else if (opt_S) + output = replace_extn(input, ".s"); + else + output = replace_extn(input, ".o"); + + FileType type = get_file_type(input); + + // Handle .o or .a + if (type == FILE_OBJ || type == FILE_AR || type == FILE_DSO) { + strarray_push(&ld_args, input); + continue; + } + + // Handle .s + if (type == FILE_ASM) { + if (!opt_S) + assemble(input, output); + continue; + } + + assert(type == FILE_C); + + // Just preprocess + if (opt_E || opt_M) { + run_cc1(argc, argv, input, NULL); + continue; + } + + // Compile + if (opt_S) { + run_cc1(argc, argv, input, output); + continue; + } + + // Compile and assemble + if (opt_c) { + char *tmp = create_tmpfile(); + run_cc1(argc, argv, input, tmp); + assemble(tmp, output); + continue; + } + + // Compile, assemble and link + char *tmp1 = create_tmpfile(); + char *tmp2 = create_tmpfile(); + run_cc1(argc, argv, input, tmp1); + assemble(tmp1, tmp2); + strarray_push(&ld_args, tmp2); + continue; + } + + if (ld_args.len > 0) + run_linker(&ld_args, opt_o ? opt_o : "a.out"); return 0; } diff --git a/parse.c b/parse.c index 73b105f1..23cca693 100644 --- a/parse.c +++ b/parse.c @@ -18,31 +18,66 @@ #include "chibicc.h" -// Scope for local or global variables. -typedef struct VarScope VarScope; -struct VarScope { - VarScope *next; - char *name; +// Scope for local variables, global variables, typedefs +// or enum constants +typedef struct { Obj *var; -}; - -// Scope for struct or union tags -typedef struct TagScope TagScope; -struct TagScope { - TagScope *next; - char *name; - Type *ty; -}; + Type *type_def; + Type *enum_ty; + int enum_val; +} VarScope; // Represents a block scope. typedef struct Scope Scope; struct Scope { Scope *next; - // C has two block scopes; one is for variables and the other is - // for struct tags. - VarScope *vars; - TagScope *tags; + // C has two block scopes; one is for variables/typedefs and + // the other is for struct/union/enum tags. + HashMap vars; + HashMap tags; +}; + +// Variable attributes such as typedef or extern. +typedef struct { + bool is_typedef; + bool is_static; + bool is_extern; + bool is_inline; + bool is_tls; + int align; +} VarAttr; + +// This struct represents a variable initializer. Since initializers +// can be nested (e.g. `int x[2][2] = {{1, 2}, {3, 4}}`), this struct +// is a tree data structure. +typedef struct Initializer Initializer; +struct Initializer { + Initializer *next; + Type *ty; + Token *tok; + bool is_flexible; + + // If it's not an aggregate type and has an initializer, + // `expr` has an initialization expression. + Node *expr; + + // If it's an initializer for an aggregate type (e.g. array or struct), + // `children` has initializers for its children. + Initializer **children; + + // Only one member can be initialized for a union. + // `mem` is used to clarify which member is initialized. + Member *mem; +}; + +// For local variable initializer. +typedef struct InitDesg InitDesg; +struct InitDesg { + InitDesg *next; + int idx; + Member *member; + Obj *var; }; // All local variable instances created during parsing are @@ -54,23 +89,76 @@ static Obj *globals; static Scope *scope = &(Scope){}; -static Type *declspec(Token **rest, Token *tok); +// Points to the function object the parser is currently parsing. +static Obj *current_fn; + +// Lists of all goto statements and labels in the curent function. +static Node *gotos; +static Node *labels; + +// Current "goto" and "continue" jump targets. +static char *brk_label; +static char *cont_label; + +// Points to a node representing a switch if we are parsing +// a switch statement. Otherwise, NULL. +static Node *current_switch; + +static Obj *builtin_alloca; + +static bool is_typename(Token *tok); +static Type *declspec(Token **rest, Token *tok, VarAttr *attr); +static Type *typename(Token **rest, Token *tok); +static Type *enum_specifier(Token **rest, Token *tok); +static Type *typeof_specifier(Token **rest, Token *tok); +static Type *type_suffix(Token **rest, Token *tok, Type *ty); static Type *declarator(Token **rest, Token *tok, Type *ty); -static Node *declaration(Token **rest, Token *tok); +static Node *declaration(Token **rest, Token *tok, Type *basety, VarAttr *attr); +static void array_initializer2(Token **rest, Token *tok, Initializer *init, int i); +static void struct_initializer2(Token **rest, Token *tok, Initializer *init, Member *mem); +static void initializer2(Token **rest, Token *tok, Initializer *init); +static Initializer *initializer(Token **rest, Token *tok, Type *ty, Type **new_ty); +static Node *lvar_initializer(Token **rest, Token *tok, Obj *var); +static void gvar_initializer(Token **rest, Token *tok, Obj *var); static Node *compound_stmt(Token **rest, Token *tok); static Node *stmt(Token **rest, Token *tok); static Node *expr_stmt(Token **rest, Token *tok); static Node *expr(Token **rest, Token *tok); +static int64_t eval(Node *node); +static int64_t eval2(Node *node, char ***label); +static int64_t eval_rval(Node *node, char ***label); +static bool is_const_expr(Node *node); static Node *assign(Token **rest, Token *tok); +static Node *logor(Token **rest, Token *tok); +static double eval_double(Node *node); +static Node *conditional(Token **rest, Token *tok); +static Node *logand(Token **rest, Token *tok); +static Node *bitor(Token **rest, Token *tok); +static Node *bitxor(Token **rest, Token *tok); +static Node *bitand(Token **rest, Token *tok); static Node *equality(Token **rest, Token *tok); static Node *relational(Token **rest, Token *tok); +static Node *shift(Token **rest, Token *tok); static Node *add(Token **rest, Token *tok); +static Node *new_add(Node *lhs, Node *rhs, Token *tok); +static Node *new_sub(Node *lhs, Node *rhs, Token *tok); static Node *mul(Token **rest, Token *tok); +static Node *cast(Token **rest, Token *tok); +static Member *get_struct_member(Type *ty, Token *tok); static Type *struct_decl(Token **rest, Token *tok); static Type *union_decl(Token **rest, Token *tok); static Node *postfix(Token **rest, Token *tok); +static Node *funcall(Token **rest, Token *tok, Node *node); static Node *unary(Token **rest, Token *tok); static Node *primary(Token **rest, Token *tok); +static Token *parse_typedef(Token *tok, Type *basety); +static bool is_function(Token *tok); +static Token *function(Token *tok, Type *basety, VarAttr *attr); +static Token *global_variable(Token *tok, Type *basety, VarAttr *attr); + +static int align_down(int n, int align) { + return align_to(n - align + 1, align); +} static void enter_scope(void) { Scope *sc = calloc(1, sizeof(Scope)); @@ -83,19 +171,21 @@ static void leave_scope(void) { } // Find a variable by name. -static Obj *find_var(Token *tok) { - for (Scope *sc = scope; sc; sc = sc->next) - for (VarScope *sc2 = sc->vars; sc2; sc2 = sc2->next) - if (equal(tok, sc2->name)) - return sc2->var; +static VarScope *find_var(Token *tok) { + for (Scope *sc = scope; sc; sc = sc->next) { + VarScope *sc2 = hashmap_get2(&sc->vars, tok->loc, tok->len); + if (sc2) + return sc2; + } return NULL; } static Type *find_tag(Token *tok) { - for (Scope *sc = scope; sc; sc = sc->next) - for (TagScope *sc2 = sc->tags; sc2; sc2 = sc2->next) - if (equal(tok, sc2->name)) - return sc2->ty; + for (Scope *sc = scope; sc; sc = sc->next) { + Type *ty = hashmap_get2(&sc->tags, tok->loc, tok->len); + if (ty) + return ty; + } return NULL; } @@ -125,26 +215,95 @@ static Node *new_num(int64_t val, Token *tok) { return node; } +static Node *new_long(int64_t val, Token *tok) { + Node *node = new_node(ND_NUM, tok); + node->val = val; + node->ty = ty_long; + return node; +} + +static Node *new_ulong(long val, Token *tok) { + Node *node = new_node(ND_NUM, tok); + node->val = val; + node->ty = ty_ulong; + return node; +} + static Node *new_var_node(Obj *var, Token *tok) { Node *node = new_node(ND_VAR, tok); node->var = var; return node; } -static VarScope *push_scope(char *name, Obj *var) { +static Node *new_vla_ptr(Obj *var, Token *tok) { + Node *node = new_node(ND_VLA_PTR, tok); + node->var = var; + return node; +} + +Node *new_cast(Node *expr, Type *ty) { + add_type(expr); + + Node *node = calloc(1, sizeof(Node)); + node->kind = ND_CAST; + node->tok = expr->tok; + node->lhs = expr; + node->ty = copy_type(ty); + return node; +} + +static VarScope *push_scope(char *name) { VarScope *sc = calloc(1, sizeof(VarScope)); - sc->name = name; - sc->var = var; - sc->next = scope->vars; - scope->vars = sc; + hashmap_put(&scope->vars, name, sc); return sc; } +static Initializer *new_initializer(Type *ty, bool is_flexible) { + Initializer *init = calloc(1, sizeof(Initializer)); + init->ty = ty; + + if (ty->kind == TY_ARRAY) { + if (is_flexible && ty->size < 0) { + init->is_flexible = true; + return init; + } + + init->children = calloc(ty->array_len, sizeof(Initializer *)); + for (int i = 0; i < ty->array_len; i++) + init->children[i] = new_initializer(ty->base, false); + return init; + } + + if (ty->kind == TY_STRUCT || ty->kind == TY_UNION) { + // Count the number of struct members. + int len = 0; + for (Member *mem = ty->members; mem; mem = mem->next) + len++; + + init->children = calloc(len, sizeof(Initializer *)); + + for (Member *mem = ty->members; mem; mem = mem->next) { + if (is_flexible && ty->is_flexible && !mem->next) { + Initializer *child = calloc(1, sizeof(Initializer)); + child->ty = mem->ty; + child->is_flexible = true; + init->children[mem->idx] = child; + } else { + init->children[mem->idx] = new_initializer(mem->ty, false); + } + } + return init; + } + + return init; +} + static Obj *new_var(char *name, Type *ty) { Obj *var = calloc(1, sizeof(Obj)); var->name = name; var->ty = ty; - push_scope(name, var); + var->align = ty->align; + push_scope(name)->var = var; return var; } @@ -159,6 +318,8 @@ static Obj *new_lvar(char *name, Type *ty) { static Obj *new_gvar(char *name, Type *ty) { Obj *var = new_var(name, ty); var->next = globals; + var->is_static = true; + var->is_definition = true; globals = var; return var; } @@ -184,230 +345,1473 @@ static char *get_ident(Token *tok) { return strndup(tok->loc, tok->len); } -static int get_number(Token *tok) { - if (tok->kind != TK_NUM) - error_tok(tok, "expected a number"); - return tok->val; +static Type *find_typedef(Token *tok) { + if (tok->kind == TK_IDENT) { + VarScope *sc = find_var(tok); + if (sc) + return sc->type_def; + } + return NULL; } static void push_tag_scope(Token *tok, Type *ty) { - TagScope *sc = calloc(1, sizeof(TagScope)); - sc->name = strndup(tok->loc, tok->len); - sc->ty = ty; - sc->next = scope->tags; - scope->tags = sc; + hashmap_put2(&scope->tags, tok->loc, tok->len, ty); } -// declspec = "char" | "short" | "int" | "long" | struct-decl | union-decl -static Type *declspec(Token **rest, Token *tok) { - if (equal(tok, "char")) { - *rest = tok->next; - return ty_char; - } +// declspec = ("void" | "_Bool" | "char" | "short" | "int" | "long" +// | "typedef" | "static" | "extern" | "inline" +// | "_Thread_local" | "__thread" +// | "signed" | "unsigned" +// | struct-decl | union-decl | typedef-name +// | enum-specifier | typeof-specifier +// | "const" | "volatile" | "auto" | "register" | "restrict" +// | "__restrict" | "__restrict__" | "_Noreturn")+ +// +// The order of typenames in a type-specifier doesn't matter. For +// example, `int long static` means the same as `static long int`. +// That can also be written as `static long` because you can omit +// `int` if `long` or `short` are specified. However, something like +// `char int` is not a valid type specifier. We have to accept only a +// limited combinations of the typenames. +// +// In this function, we count the number of occurrences of each typename +// while keeping the "current" type object that the typenames up +// until that point represent. When we reach a non-typename token, +// we returns the current type object. +static Type *declspec(Token **rest, Token *tok, VarAttr *attr) { + // We use a single integer as counters for all typenames. + // For example, bits 0 and 1 represents how many times we saw the + // keyword "void" so far. With this, we can use a switch statement + // as you can see below. + enum { + VOID = 1 << 0, + BOOL = 1 << 2, + CHAR = 1 << 4, + SHORT = 1 << 6, + INT = 1 << 8, + LONG = 1 << 10, + FLOAT = 1 << 12, + DOUBLE = 1 << 14, + OTHER = 1 << 16, + SIGNED = 1 << 17, + UNSIGNED = 1 << 18, + }; + + Type *ty = ty_int; + int counter = 0; + bool is_atomic = false; + + while (is_typename(tok)) { + // Handle storage class specifiers. + if (equal(tok, "typedef") || equal(tok, "static") || equal(tok, "extern") || + equal(tok, "inline") || equal(tok, "_Thread_local") || equal(tok, "__thread")) { + if (!attr) + error_tok(tok, "storage class specifier is not allowed in this context"); + + if (equal(tok, "typedef")) + attr->is_typedef = true; + else if (equal(tok, "static")) + attr->is_static = true; + else if (equal(tok, "extern")) + attr->is_extern = true; + else if (equal(tok, "inline")) + attr->is_inline = true; + else + attr->is_tls = true; + + if (attr->is_typedef && + attr->is_static + attr->is_extern + attr->is_inline + attr->is_tls > 1) + error_tok(tok, "typedef may not be used together with static," + " extern, inline, __thread or _Thread_local"); + tok = tok->next; + continue; + } - if (equal(tok, "short")) { - *rest = tok->next; - return ty_short; - } + // These keywords are recognized but ignored. + if (consume(&tok, tok, "const") || consume(&tok, tok, "volatile") || + consume(&tok, tok, "auto") || consume(&tok, tok, "register") || + consume(&tok, tok, "restrict") || consume(&tok, tok, "__restrict") || + consume(&tok, tok, "__restrict__") || consume(&tok, tok, "_Noreturn")) + continue; - if (equal(tok, "int")) { - *rest = tok->next; - return ty_int; - } + if (equal(tok, "_Atomic")) { + tok = tok->next; + if (equal(tok , "(")) { + ty = typename(&tok, tok->next); + tok = skip(tok, ")"); + } + is_atomic = true; + continue; + } - if (equal(tok, "long")) { - *rest = tok->next; - return ty_long; - } + if (equal(tok, "_Alignas")) { + if (!attr) + error_tok(tok, "_Alignas is not allowed in this context"); + tok = skip(tok->next, "("); + + if (is_typename(tok)) + attr->align = typename(&tok, tok)->align; + else + attr->align = const_expr(&tok, tok); + tok = skip(tok, ")"); + continue; + } + + // Handle user-defined types. + Type *ty2 = find_typedef(tok); + if (equal(tok, "struct") || equal(tok, "union") || equal(tok, "enum") || + equal(tok, "typeof") || ty2) { + if (counter) + break; + + if (equal(tok, "struct")) { + ty = struct_decl(&tok, tok->next); + } else if (equal(tok, "union")) { + ty = union_decl(&tok, tok->next); + } else if (equal(tok, "enum")) { + ty = enum_specifier(&tok, tok->next); + } else if (equal(tok, "typeof")) { + ty = typeof_specifier(&tok, tok->next); + } else { + ty = ty2; + tok = tok->next; + } + + counter += OTHER; + continue; + } - if (equal(tok, "struct")) - return struct_decl(rest, tok->next); + // Handle built-in types. + if (equal(tok, "void")) + counter += VOID; + else if (equal(tok, "_Bool")) + counter += BOOL; + else if (equal(tok, "char")) + counter += CHAR; + else if (equal(tok, "short")) + counter += SHORT; + else if (equal(tok, "int")) + counter += INT; + else if (equal(tok, "long")) + counter += LONG; + else if (equal(tok, "float")) + counter += FLOAT; + else if (equal(tok, "double")) + counter += DOUBLE; + else if (equal(tok, "signed")) + counter |= SIGNED; + else if (equal(tok, "unsigned")) + counter |= UNSIGNED; + else + unreachable(); + + switch (counter) { + case VOID: + ty = ty_void; + break; + case BOOL: + ty = ty_bool; + break; + case CHAR: + case SIGNED + CHAR: + ty = ty_char; + break; + case UNSIGNED + CHAR: + ty = ty_uchar; + break; + case SHORT: + case SHORT + INT: + case SIGNED + SHORT: + case SIGNED + SHORT + INT: + ty = ty_short; + break; + case UNSIGNED + SHORT: + case UNSIGNED + SHORT + INT: + ty = ty_ushort; + break; + case INT: + case SIGNED: + case SIGNED + INT: + ty = ty_int; + break; + case UNSIGNED: + case UNSIGNED + INT: + ty = ty_uint; + break; + case LONG: + case LONG + INT: + case LONG + LONG: + case LONG + LONG + INT: + case SIGNED + LONG: + case SIGNED + LONG + INT: + case SIGNED + LONG + LONG: + case SIGNED + LONG + LONG + INT: + ty = ty_long; + break; + case UNSIGNED + LONG: + case UNSIGNED + LONG + INT: + case UNSIGNED + LONG + LONG: + case UNSIGNED + LONG + LONG + INT: + ty = ty_ulong; + break; + case FLOAT: + ty = ty_float; + break; + case DOUBLE: + ty = ty_double; + break; + case LONG + DOUBLE: + ty = ty_ldouble; + break; + default: + error_tok(tok, "invalid type"); + } + + tok = tok->next; + } - if (equal(tok, "union")) - return union_decl(rest, tok->next); + if (is_atomic) { + ty = copy_type(ty); + ty->is_atomic = true; + } - error_tok(tok, "typename expected"); + *rest = tok; + return ty; } -// func-params = (param ("," param)*)? ")" +// func-params = ("void" | param ("," param)* ("," "...")?)? ")" // param = declspec declarator static Type *func_params(Token **rest, Token *tok, Type *ty) { + if (equal(tok, "void") && equal(tok->next, ")")) { + *rest = tok->next->next; + return func_type(ty); + } + Type head = {}; Type *cur = &head; + bool is_variadic = false; while (!equal(tok, ")")) { if (cur != &head) tok = skip(tok, ","); - Type *basety = declspec(&tok, tok); - Type *ty = declarator(&tok, tok, basety); - cur = cur->next = copy_type(ty); + + if (equal(tok, "...")) { + is_variadic = true; + tok = tok->next; + skip(tok, ")"); + break; + } + + Type *ty2 = declspec(&tok, tok, NULL); + ty2 = declarator(&tok, tok, ty2); + + Token *name = ty2->name; + + if (ty2->kind == TY_ARRAY) { + // "array of T" is converted to "pointer to T" only in the parameter + // context. For example, *argv[] is converted to **argv by this. + ty2 = pointer_to(ty2->base); + ty2->name = name; + } else if (ty2->kind == TY_FUNC) { + // Likewise, a function is converted to a pointer to a function + // only in the parameter context. + ty2 = pointer_to(ty2); + ty2->name = name; + } + + cur = cur->next = copy_type(ty2); } + if (cur == &head) + is_variadic = true; + ty = func_type(ty); ty->params = head.next; + ty->is_variadic = is_variadic; *rest = tok->next; return ty; } +// array-dimensions = ("static" | "restrict")* const-expr? "]" type-suffix +static Type *array_dimensions(Token **rest, Token *tok, Type *ty) { + while (equal(tok, "static") || equal(tok, "restrict")) + tok = tok->next; + + if (equal(tok, "]")) { + ty = type_suffix(rest, tok->next, ty); + return array_of(ty, -1); + } + + Node *expr = conditional(&tok, tok); + tok = skip(tok, "]"); + ty = type_suffix(rest, tok, ty); + + if (ty->kind == TY_VLA || !is_const_expr(expr)) + return vla_of(ty, expr); + return array_of(ty, eval(expr)); +} + // type-suffix = "(" func-params -// | "[" num "]" type-suffix +// | "[" array-dimensions // | ε static Type *type_suffix(Token **rest, Token *tok, Type *ty) { if (equal(tok, "(")) return func_params(rest, tok->next, ty); - if (equal(tok, "[")) { - int sz = get_number(tok->next); - tok = skip(tok->next->next, "]"); - ty = type_suffix(rest, tok, ty); - return array_of(ty, sz); - } + if (equal(tok, "[")) + return array_dimensions(rest, tok->next, ty); *rest = tok; return ty; } -// declarator = "*"* ident type-suffix -static Type *declarator(Token **rest, Token *tok, Type *ty) { - while (consume(&tok, tok, "*")) +// pointers = ("*" ("const" | "volatile" | "restrict")*)* +static Type *pointers(Token **rest, Token *tok, Type *ty) { + while (consume(&tok, tok, "*")) { ty = pointer_to(ty); - - if (tok->kind != TK_IDENT) - error_tok(tok, "expected a variable name"); - ty = type_suffix(rest, tok->next, ty); - ty->name = tok; + while (equal(tok, "const") || equal(tok, "volatile") || equal(tok, "restrict") || + equal(tok, "__restrict") || equal(tok, "__restrict__")) + tok = tok->next; + } + *rest = tok; return ty; } -// declaration = declspec (declarator ("=" expr)? ("," declarator ("=" expr)?)*)? ";" -static Node *declaration(Token **rest, Token *tok) { - Type *basety = declspec(&tok, tok); +// declarator = pointers ("(" ident ")" | "(" declarator ")" | ident) type-suffix +static Type *declarator(Token **rest, Token *tok, Type *ty) { + ty = pointers(&tok, tok, ty); - Node head = {}; - Node *cur = &head; - int i = 0; + if (equal(tok, "(")) { + Token *start = tok; + Type ignore = {}; + declarator(&tok, tok->next, &ignore); + tok = skip(tok, ")"); + ty = type_suffix(rest, tok, ty); + return declarator(&tok, start->next, ty); + } - while (!equal(tok, ";")) { - if (i++ > 0) - tok = skip(tok, ","); + Token *name = NULL; + Token *name_pos = tok; - Type *ty = declarator(&tok, tok, basety); - Obj *var = new_lvar(get_ident(ty->name), ty); + if (tok->kind == TK_IDENT) { + name = tok; + tok = tok->next; + } - if (!equal(tok, "=")) - continue; + ty = type_suffix(rest, tok, ty); + ty->name = name; + ty->name_pos = name_pos; + return ty; +} + +// abstract-declarator = pointers ("(" abstract-declarator ")")? type-suffix +static Type *abstract_declarator(Token **rest, Token *tok, Type *ty) { + ty = pointers(&tok, tok, ty); - Node *lhs = new_var_node(var, ty->name); - Node *rhs = assign(&tok, tok->next); - Node *node = new_binary(ND_ASSIGN, lhs, rhs, tok); - cur = cur->next = new_unary(ND_EXPR_STMT, node, tok); + if (equal(tok, "(")) { + Token *start = tok; + Type ignore = {}; + abstract_declarator(&tok, tok->next, &ignore); + tok = skip(tok, ")"); + ty = type_suffix(rest, tok, ty); + return abstract_declarator(&tok, start->next, ty); } - Node *node = new_node(ND_BLOCK, tok); - node->body = head.next; - *rest = tok->next; - return node; + return type_suffix(rest, tok, ty); } -// Returns true if a given token represents a type. -static bool is_typename(Token *tok) { - return equal(tok, "char") || equal(tok, "short") || equal(tok, "int") || - equal(tok, "long") || equal(tok, "struct") || equal(tok, "union"); +// type-name = declspec abstract-declarator +static Type *typename(Token **rest, Token *tok) { + Type *ty = declspec(&tok, tok, NULL); + return abstract_declarator(rest, tok, ty); } -// stmt = "return" expr ";" -// | "if" "(" expr ")" stmt ("else" stmt)? -// | "for" "(" expr-stmt expr? ";" expr? ")" stmt -// | "while" "(" expr ")" stmt -// | "{" compound-stmt -// | expr-stmt -static Node *stmt(Token **rest, Token *tok) { - if (equal(tok, "return")) { - Node *node = new_node(ND_RETURN, tok); - node->lhs = expr(&tok, tok->next); - *rest = skip(tok, ";"); - return node; +static bool is_end(Token *tok) { + return equal(tok, "}") || (equal(tok, ",") && equal(tok->next, "}")); +} + +static bool consume_end(Token **rest, Token *tok) { + if (equal(tok, "}")) { + *rest = tok->next; + return true; } - if (equal(tok, "if")) { - Node *node = new_node(ND_IF, tok); - tok = skip(tok->next, "("); - node->cond = expr(&tok, tok); - tok = skip(tok, ")"); - node->then = stmt(&tok, tok); - if (equal(tok, "else")) - node->els = stmt(&tok, tok->next); + if (equal(tok, ",") && equal(tok->next, "}")) { + *rest = tok->next->next; + return true; + } + + return false; +} + +// enum-specifier = ident? "{" enum-list? "}" +// | ident ("{" enum-list? "}")? +// +// enum-list = ident ("=" num)? ("," ident ("=" num)?)* ","? +static Type *enum_specifier(Token **rest, Token *tok) { + Type *ty = enum_type(); + + // Read a struct tag. + Token *tag = NULL; + if (tok->kind == TK_IDENT) { + tag = tok; + tok = tok->next; + } + + if (tag && !equal(tok, "{")) { + Type *ty = find_tag(tag); + if (!ty) + error_tok(tag, "unknown enum type"); + if (ty->kind != TY_ENUM) + error_tok(tag, "not an enum tag"); *rest = tok; - return node; + return ty; } - if (equal(tok, "for")) { - Node *node = new_node(ND_FOR, tok); - tok = skip(tok->next, "("); + tok = skip(tok, "{"); - node->init = expr_stmt(&tok, tok); + // Read an enum-list. + int i = 0; + int val = 0; + while (!consume_end(rest, tok)) { + if (i++ > 0) + tok = skip(tok, ","); - if (!equal(tok, ";")) - node->cond = expr(&tok, tok); - tok = skip(tok, ";"); + char *name = get_ident(tok); + tok = tok->next; - if (!equal(tok, ")")) - node->inc = expr(&tok, tok); - tok = skip(tok, ")"); + if (equal(tok, "=")) + val = const_expr(&tok, tok->next); - node->then = stmt(rest, tok); - return node; + VarScope *sc = push_scope(name); + sc->enum_ty = ty; + sc->enum_val = val++; } - if (equal(tok, "while")) { - Node *node = new_node(ND_FOR, tok); - tok = skip(tok->next, "("); - node->cond = expr(&tok, tok); - tok = skip(tok, ")"); - node->then = stmt(rest, tok); - return node; + if (tag) + push_tag_scope(tag, ty); + return ty; +} + +// typeof-specifier = "(" (expr | typename) ")" +static Type *typeof_specifier(Token **rest, Token *tok) { + tok = skip(tok, "("); + + Type *ty; + if (is_typename(tok)) { + ty = typename(&tok, tok); + } else { + Node *node = expr(&tok, tok); + add_type(node); + ty = node->ty; } + *rest = skip(tok, ")"); + return ty; +} - if (equal(tok, "{")) - return compound_stmt(rest, tok->next); +// Generate code for computing a VLA size. +static Node *compute_vla_size(Type *ty, Token *tok) { + Node *node = new_node(ND_NULL_EXPR, tok); + if (ty->base) + node = new_binary(ND_COMMA, node, compute_vla_size(ty->base, tok), tok); - return expr_stmt(rest, tok); + if (ty->kind != TY_VLA) + return node; + + Node *base_sz; + if (ty->base->kind == TY_VLA) + base_sz = new_var_node(ty->base->vla_size, tok); + else + base_sz = new_num(ty->base->size, tok); + + ty->vla_size = new_lvar("", ty_ulong); + Node *expr = new_binary(ND_ASSIGN, new_var_node(ty->vla_size, tok), + new_binary(ND_MUL, ty->vla_len, base_sz, tok), + tok); + return new_binary(ND_COMMA, node, expr, tok); } -// compound-stmt = (declaration | stmt)* "}" -static Node *compound_stmt(Token **rest, Token *tok) { - Node *node = new_node(ND_BLOCK, tok); +static Node *new_alloca(Node *sz) { + Node *node = new_unary(ND_FUNCALL, new_var_node(builtin_alloca, sz->tok), sz->tok); + node->func_ty = builtin_alloca->ty; + node->ty = builtin_alloca->ty->return_ty; + node->args = sz; + add_type(sz); + return node; +} + +// declaration = declspec (declarator ("=" expr)? ("," declarator ("=" expr)?)*)? ";" +static Node *declaration(Token **rest, Token *tok, Type *basety, VarAttr *attr) { Node head = {}; Node *cur = &head; + int i = 0; - enter_scope(); + while (!equal(tok, ";")) { + if (i++ > 0) + tok = skip(tok, ","); - while (!equal(tok, "}")) { - if (is_typename(tok)) - cur = cur->next = declaration(&tok, tok); - else - cur = cur->next = stmt(&tok, tok); - add_type(cur); - } + Type *ty = declarator(&tok, tok, basety); + if (ty->kind == TY_VOID) + error_tok(tok, "variable declared void"); + if (!ty->name) + error_tok(ty->name_pos, "variable name omitted"); + + if (attr && attr->is_static) { + // static local variable + Obj *var = new_anon_gvar(ty); + push_scope(get_ident(ty->name))->var = var; + if (equal(tok, "=")) + gvar_initializer(&tok, tok->next, var); + continue; + } - leave_scope(); + // Generate code for computing a VLA size. We need to do this + // even if ty is not VLA because ty may be a pointer to VLA + // (e.g. int (*foo)[n][m] where n and m are variables.) + cur = cur->next = new_unary(ND_EXPR_STMT, compute_vla_size(ty, tok), tok); + + if (ty->kind == TY_VLA) { + if (equal(tok, "=")) + error_tok(tok, "variable-sized object may not be initialized"); + + // Variable length arrays (VLAs) are translated to alloca() calls. + // For example, `int x[n+2]` is translated to `tmp = n + 2, + // x = alloca(tmp)`. + Obj *var = new_lvar(get_ident(ty->name), ty); + Token *tok = ty->name; + Node *expr = new_binary(ND_ASSIGN, new_vla_ptr(var, tok), + new_alloca(new_var_node(ty->vla_size, tok)), + tok); + + cur = cur->next = new_unary(ND_EXPR_STMT, expr, tok); + continue; + } + + Obj *var = new_lvar(get_ident(ty->name), ty); + if (attr && attr->align) + var->align = attr->align; + + if (equal(tok, "=")) { + Node *expr = lvar_initializer(&tok, tok->next, var); + cur = cur->next = new_unary(ND_EXPR_STMT, expr, tok); + } + + if (var->ty->size < 0) + error_tok(ty->name, "variable has incomplete type"); + if (var->ty->kind == TY_VOID) + error_tok(ty->name, "variable declared void"); + } + Node *node = new_node(ND_BLOCK, tok); node->body = head.next; *rest = tok->next; return node; } -// expr-stmt = expr? ";" -static Node *expr_stmt(Token **rest, Token *tok) { - if (equal(tok, ";")) { - *rest = tok->next; - return new_node(ND_BLOCK, tok); +static Token *skip_excess_element(Token *tok) { + if (equal(tok, "{")) { + tok = skip_excess_element(tok->next); + return skip(tok, "}"); } - Node *node = new_node(ND_EXPR_STMT, tok); - node->lhs = expr(&tok, tok); - *rest = skip(tok, ";"); - return node; + assign(&tok, tok); + return tok; +} + +// string-initializer = string-literal +static void string_initializer(Token **rest, Token *tok, Initializer *init) { + if (init->is_flexible) + *init = *new_initializer(array_of(init->ty->base, tok->ty->array_len), false); + + int len = MIN(init->ty->array_len, tok->ty->array_len); + + switch (init->ty->base->size) { + case 1: { + char *str = tok->str; + for (int i = 0; i < len; i++) + init->children[i]->expr = new_num(str[i], tok); + break; + } + case 2: { + uint16_t *str = (uint16_t *)tok->str; + for (int i = 0; i < len; i++) + init->children[i]->expr = new_num(str[i], tok); + break; + } + case 4: { + uint32_t *str = (uint32_t *)tok->str; + for (int i = 0; i < len; i++) + init->children[i]->expr = new_num(str[i], tok); + break; + } + default: + unreachable(); + } + + *rest = tok->next; +} + +// array-designator = "[" const-expr "]" +// +// C99 added the designated initializer to the language, which allows +// programmers to move the "cursor" of an initializer to any element. +// The syntax looks like this: +// +// int x[10] = { 1, 2, [5]=3, 4, 5, 6, 7 }; +// +// `[5]` moves the cursor to the 5th element, so the 5th element of x +// is set to 3. Initialization then continues forward in order, so +// 6th, 7th, 8th and 9th elements are initialized with 4, 5, 6 and 7, +// respectively. Unspecified elements (in this case, 3rd and 4th +// elements) are initialized with zero. +// +// Nesting is allowed, so the following initializer is valid: +// +// int x[5][10] = { [5][8]=1, 2, 3 }; +// +// It sets x[5][8], x[5][9] and x[6][0] to 1, 2 and 3, respectively. +// +// Use `.fieldname` to move the cursor for a struct initializer. E.g. +// +// struct { int a, b, c; } x = { .c=5 }; +// +// The above initializer sets x.c to 5. +static void array_designator(Token **rest, Token *tok, Type *ty, int *begin, int *end) { + *begin = const_expr(&tok, tok->next); + if (*begin >= ty->array_len) + error_tok(tok, "array designator index exceeds array bounds"); + + if (equal(tok, "...")) { + *end = const_expr(&tok, tok->next); + if (*end >= ty->array_len) + error_tok(tok, "array designator index exceeds array bounds"); + if (*end < *begin) + error_tok(tok, "array designator range [%d, %d] is empty", *begin, *end); + } else { + *end = *begin; + } + + *rest = skip(tok, "]"); +} + +// struct-designator = "." ident +static Member *struct_designator(Token **rest, Token *tok, Type *ty) { + Token *start = tok; + tok = skip(tok, "."); + if (tok->kind != TK_IDENT) + error_tok(tok, "expected a field designator"); + + for (Member *mem = ty->members; mem; mem = mem->next) { + // Anonymous struct member + if (mem->ty->kind == TY_STRUCT && !mem->name) { + if (get_struct_member(mem->ty, tok)) { + *rest = start; + return mem; + } + continue; + } + + // Regular struct member + if (mem->name->len == tok->len && !strncmp(mem->name->loc, tok->loc, tok->len)) { + *rest = tok->next; + return mem; + } + } + + error_tok(tok, "struct has no such member"); +} + +// designation = ("[" const-expr "]" | "." ident)* "="? initializer +static void designation(Token **rest, Token *tok, Initializer *init) { + if (equal(tok, "[")) { + if (init->ty->kind != TY_ARRAY) + error_tok(tok, "array index in non-array initializer"); + + int begin, end; + array_designator(&tok, tok, init->ty, &begin, &end); + + Token *tok2; + for (int i = begin; i <= end; i++) + designation(&tok2, tok, init->children[i]); + array_initializer2(rest, tok2, init, begin + 1); + return; + } + + if (equal(tok, ".") && init->ty->kind == TY_STRUCT) { + Member *mem = struct_designator(&tok, tok, init->ty); + designation(&tok, tok, init->children[mem->idx]); + init->expr = NULL; + struct_initializer2(rest, tok, init, mem->next); + return; + } + + if (equal(tok, ".") && init->ty->kind == TY_UNION) { + Member *mem = struct_designator(&tok, tok, init->ty); + init->mem = mem; + designation(rest, tok, init->children[mem->idx]); + return; + } + + if (equal(tok, ".")) + error_tok(tok, "field name not in struct or union initializer"); + + if (equal(tok, "=")) + tok = tok->next; + initializer2(rest, tok, init); +} + +// An array length can be omitted if an array has an initializer +// (e.g. `int x[] = {1,2,3}`). If it's omitted, count the number +// of initializer elements. +static int count_array_init_elements(Token *tok, Type *ty) { + bool first = true; + Initializer *dummy = new_initializer(ty->base, true); + + int i = 0, max = 0; + + while (!consume_end(&tok, tok)) { + if (!first) + tok = skip(tok, ","); + first = false; + + if (equal(tok, "[")) { + i = const_expr(&tok, tok->next); + if (equal(tok, "...")) + i = const_expr(&tok, tok->next); + tok = skip(tok, "]"); + designation(&tok, tok, dummy); + } else { + initializer2(&tok, tok, dummy); + } + + i++; + max = MAX(max, i); + } + return max; +} + +// array-initializer1 = "{" initializer ("," initializer)* ","? "}" +static void array_initializer1(Token **rest, Token *tok, Initializer *init) { + tok = skip(tok, "{"); + + if (init->is_flexible) { + int len = count_array_init_elements(tok, init->ty); + *init = *new_initializer(array_of(init->ty->base, len), false); + } + + bool first = true; + + if (init->is_flexible) { + int len = count_array_init_elements(tok, init->ty); + *init = *new_initializer(array_of(init->ty->base, len), false); + } + + for (int i = 0; !consume_end(rest, tok); i++) { + if (!first) + tok = skip(tok, ","); + first = false; + + if (equal(tok, "[")) { + int begin, end; + array_designator(&tok, tok, init->ty, &begin, &end); + + Token *tok2; + for (int j = begin; j <= end; j++) + designation(&tok2, tok, init->children[j]); + tok = tok2; + i = end; + continue; + } + + if (i < init->ty->array_len) + initializer2(&tok, tok, init->children[i]); + else + tok = skip_excess_element(tok); + } +} + +// array-initializer2 = initializer ("," initializer)* +static void array_initializer2(Token **rest, Token *tok, Initializer *init, int i) { + if (init->is_flexible) { + int len = count_array_init_elements(tok, init->ty); + *init = *new_initializer(array_of(init->ty->base, len), false); + } + + for (; i < init->ty->array_len && !is_end(tok); i++) { + Token *start = tok; + if (i > 0) + tok = skip(tok, ","); + + if (equal(tok, "[") || equal(tok, ".")) { + *rest = start; + return; + } + + initializer2(&tok, tok, init->children[i]); + } + *rest = tok; +} + +// struct-initializer1 = "{" initializer ("," initializer)* ","? "}" +static void struct_initializer1(Token **rest, Token *tok, Initializer *init) { + tok = skip(tok, "{"); + + Member *mem = init->ty->members; + bool first = true; + + while (!consume_end(rest, tok)) { + if (!first) + tok = skip(tok, ","); + first = false; + + if (equal(tok, ".")) { + mem = struct_designator(&tok, tok, init->ty); + designation(&tok, tok, init->children[mem->idx]); + mem = mem->next; + continue; + } + + if (mem) { + initializer2(&tok, tok, init->children[mem->idx]); + mem = mem->next; + } else { + tok = skip_excess_element(tok); + } + } +} + +// struct-initializer2 = initializer ("," initializer)* +static void struct_initializer2(Token **rest, Token *tok, Initializer *init, Member *mem) { + bool first = true; + + for (; mem && !is_end(tok); mem = mem->next) { + Token *start = tok; + + if (!first) + tok = skip(tok, ","); + first = false; + + if (equal(tok, "[") || equal(tok, ".")) { + *rest = start; + return; + } + + initializer2(&tok, tok, init->children[mem->idx]); + } + *rest = tok; +} + +static void union_initializer(Token **rest, Token *tok, Initializer *init) { + // Unlike structs, union initializers take only one initializer, + // and that initializes the first union member by default. + // You can initialize other member using a designated initializer. + if (equal(tok, "{") && equal(tok->next, ".")) { + Member *mem = struct_designator(&tok, tok->next, init->ty); + init->mem = mem; + designation(&tok, tok, init->children[mem->idx]); + *rest = skip(tok, "}"); + return; + } + + init->mem = init->ty->members; + + if (equal(tok, "{")) { + initializer2(&tok, tok->next, init->children[0]); + consume(&tok, tok, ","); + *rest = skip(tok, "}"); + } else { + initializer2(rest, tok, init->children[0]); + } +} + +// initializer = string-initializer | array-initializer +// | struct-initializer | union-initializer +// | assign +static void initializer2(Token **rest, Token *tok, Initializer *init) { + if (init->ty->kind == TY_ARRAY && tok->kind == TK_STR) { + string_initializer(rest, tok, init); + return; + } + + if (init->ty->kind == TY_ARRAY) { + if (equal(tok, "{")) + array_initializer1(rest, tok, init); + else + array_initializer2(rest, tok, init, 0); + return; + } + + if (init->ty->kind == TY_STRUCT) { + if (equal(tok, "{")) { + struct_initializer1(rest, tok, init); + return; + } + + // A struct can be initialized with another struct. E.g. + // `struct T x = y;` where y is a variable of type `struct T`. + // Handle that case first. + Node *expr = assign(rest, tok); + add_type(expr); + if (expr->ty->kind == TY_STRUCT) { + init->expr = expr; + return; + } + + struct_initializer2(rest, tok, init, init->ty->members); + return; + } + + if (init->ty->kind == TY_UNION) { + union_initializer(rest, tok, init); + return; + } + + if (equal(tok, "{")) { + // An initializer for a scalar variable can be surrounded by + // braces. E.g. `int x = {3};`. Handle that case. + initializer2(&tok, tok->next, init); + *rest = skip(tok, "}"); + return; + } + + init->expr = assign(rest, tok); +} + +static Type *copy_struct_type(Type *ty) { + ty = copy_type(ty); + + Member head = {}; + Member *cur = &head; + for (Member *mem = ty->members; mem; mem = mem->next) { + Member *m = calloc(1, sizeof(Member)); + *m = *mem; + cur = cur->next = m; + } + + ty->members = head.next; + return ty; +} + +static Initializer *initializer(Token **rest, Token *tok, Type *ty, Type **new_ty) { + Initializer *init = new_initializer(ty, true); + initializer2(rest, tok, init); + + if ((ty->kind == TY_STRUCT || ty->kind == TY_UNION) && ty->is_flexible) { + ty = copy_struct_type(ty); + + Member *mem = ty->members; + while (mem->next) + mem = mem->next; + mem->ty = init->children[mem->idx]->ty; + ty->size += mem->ty->size; + + *new_ty = ty; + return init; + } + + *new_ty = init->ty; + return init; +} + +static Node *init_desg_expr(InitDesg *desg, Token *tok) { + if (desg->var) + return new_var_node(desg->var, tok); + + if (desg->member) { + Node *node = new_unary(ND_MEMBER, init_desg_expr(desg->next, tok), tok); + node->member = desg->member; + return node; + } + + Node *lhs = init_desg_expr(desg->next, tok); + Node *rhs = new_num(desg->idx, tok); + return new_unary(ND_DEREF, new_add(lhs, rhs, tok), tok); +} + +static Node *create_lvar_init(Initializer *init, Type *ty, InitDesg *desg, Token *tok) { + if (ty->kind == TY_ARRAY) { + Node *node = new_node(ND_NULL_EXPR, tok); + for (int i = 0; i < ty->array_len; i++) { + InitDesg desg2 = {desg, i}; + Node *rhs = create_lvar_init(init->children[i], ty->base, &desg2, tok); + node = new_binary(ND_COMMA, node, rhs, tok); + } + return node; + } + + if (ty->kind == TY_STRUCT && !init->expr) { + Node *node = new_node(ND_NULL_EXPR, tok); + + for (Member *mem = ty->members; mem; mem = mem->next) { + InitDesg desg2 = {desg, 0, mem}; + Node *rhs = create_lvar_init(init->children[mem->idx], mem->ty, &desg2, tok); + node = new_binary(ND_COMMA, node, rhs, tok); + } + return node; + } + + if (ty->kind == TY_UNION) { + Member *mem = init->mem ? init->mem : ty->members; + InitDesg desg2 = {desg, 0, mem}; + return create_lvar_init(init->children[mem->idx], mem->ty, &desg2, tok); + } + + if (!init->expr) + return new_node(ND_NULL_EXPR, tok); + + Node *lhs = init_desg_expr(desg, tok); + return new_binary(ND_ASSIGN, lhs, init->expr, tok); +} + +// A variable definition with an initializer is a shorthand notation +// for a variable definition followed by assignments. This function +// generates assignment expressions for an initializer. For example, +// `int x[2][2] = {{6, 7}, {8, 9}}` is converted to the following +// expressions: +// +// x[0][0] = 6; +// x[0][1] = 7; +// x[1][0] = 8; +// x[1][1] = 9; +static Node *lvar_initializer(Token **rest, Token *tok, Obj *var) { + Initializer *init = initializer(rest, tok, var->ty, &var->ty); + InitDesg desg = {NULL, 0, NULL, var}; + + // If a partial initializer list is given, the standard requires + // that unspecified elements are set to 0. Here, we simply + // zero-initialize the entire memory region of a variable before + // initializing it with user-supplied values. + Node *lhs = new_node(ND_MEMZERO, tok); + lhs->var = var; + + Node *rhs = create_lvar_init(init, var->ty, &desg, tok); + return new_binary(ND_COMMA, lhs, rhs, tok); +} + +static uint64_t read_buf(char *buf, int sz) { + if (sz == 1) + return *buf; + if (sz == 2) + return *(uint16_t *)buf; + if (sz == 4) + return *(uint32_t *)buf; + if (sz == 8) + return *(uint64_t *)buf; + unreachable(); +} + +static void write_buf(char *buf, uint64_t val, int sz) { + if (sz == 1) + *buf = val; + else if (sz == 2) + *(uint16_t *)buf = val; + else if (sz == 4) + *(uint32_t *)buf = val; + else if (sz == 8) + *(uint64_t *)buf = val; + else + unreachable(); +} + +static Relocation * +write_gvar_data(Relocation *cur, Initializer *init, Type *ty, char *buf, int offset) { + if (ty->kind == TY_ARRAY) { + int sz = ty->base->size; + for (int i = 0; i < ty->array_len; i++) + cur = write_gvar_data(cur, init->children[i], ty->base, buf, offset + sz * i); + return cur; + } + + if (ty->kind == TY_STRUCT) { + for (Member *mem = ty->members; mem; mem = mem->next) { + if (mem->is_bitfield) { + Node *expr = init->children[mem->idx]->expr; + if (!expr) + break; + + char *loc = buf + offset + mem->offset; + uint64_t oldval = read_buf(loc, mem->ty->size); + uint64_t newval = eval(expr); + uint64_t mask = (1L << mem->bit_width) - 1; + uint64_t combined = oldval | ((newval & mask) << mem->bit_offset); + write_buf(loc, combined, mem->ty->size); + } else { + cur = write_gvar_data(cur, init->children[mem->idx], mem->ty, buf, + offset + mem->offset); + } + } + return cur; + } + + if (ty->kind == TY_UNION) { + if (!init->mem) + return cur; + return write_gvar_data(cur, init->children[init->mem->idx], + init->mem->ty, buf, offset); + } + + if (!init->expr) + return cur; + + if (ty->kind == TY_FLOAT) { + *(float *)(buf + offset) = eval_double(init->expr); + return cur; + } + + if (ty->kind == TY_DOUBLE) { + *(double *)(buf + offset) = eval_double(init->expr); + return cur; + } + + char **label = NULL; + uint64_t val = eval2(init->expr, &label); + + if (!label) { + write_buf(buf + offset, val, ty->size); + return cur; + } + + Relocation *rel = calloc(1, sizeof(Relocation)); + rel->offset = offset; + rel->label = label; + rel->addend = val; + cur->next = rel; + return cur->next; +} + +// Initializers for global variables are evaluated at compile-time and +// embedded to .data section. This function serializes Initializer +// objects to a flat byte array. It is a compile error if an +// initializer list contains a non-constant expression. +static void gvar_initializer(Token **rest, Token *tok, Obj *var) { + Initializer *init = initializer(rest, tok, var->ty, &var->ty); + + Relocation head = {}; + char *buf = calloc(1, var->ty->size); + write_gvar_data(&head, init, var->ty, buf, 0); + var->init_data = buf; + var->rel = head.next; +} + +// Returns true if a given token represents a type. +static bool is_typename(Token *tok) { + static HashMap map; + + if (map.capacity == 0) { + static char *kw[] = { + "void", "_Bool", "char", "short", "int", "long", "struct", "union", + "typedef", "enum", "static", "extern", "_Alignas", "signed", "unsigned", + "const", "volatile", "auto", "register", "restrict", "__restrict", + "__restrict__", "_Noreturn", "float", "double", "typeof", "inline", + "_Thread_local", "__thread", "_Atomic", + }; + + for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) + hashmap_put(&map, kw[i], (void *)1); + } + + return hashmap_get2(&map, tok->loc, tok->len) || find_typedef(tok); +} + +// asm-stmt = "asm" ("volatile" | "inline")* "(" string-literal ")" +static Node *asm_stmt(Token **rest, Token *tok) { + Node *node = new_node(ND_ASM, tok); + tok = tok->next; + + while (equal(tok, "volatile") || equal(tok, "inline")) + tok = tok->next; + + tok = skip(tok, "("); + if (tok->kind != TK_STR || tok->ty->base->kind != TY_CHAR) + error_tok(tok, "expected string literal"); + node->asm_str = tok->str; + *rest = skip(tok->next, ")"); + return node; +} + +// stmt = "return" expr? ";" +// | "if" "(" expr ")" stmt ("else" stmt)? +// | "switch" "(" expr ")" stmt +// | "case" const-expr ("..." const-expr)? ":" stmt +// | "default" ":" stmt +// | "for" "(" expr-stmt expr? ";" expr? ")" stmt +// | "while" "(" expr ")" stmt +// | "do" stmt "while" "(" expr ")" ";" +// | "asm" asm-stmt +// | "goto" (ident | "*" expr) ";" +// | "break" ";" +// | "continue" ";" +// | ident ":" stmt +// | "{" compound-stmt +// | expr-stmt +static Node *stmt(Token **rest, Token *tok) { + if (equal(tok, "return")) { + Node *node = new_node(ND_RETURN, tok); + if (consume(rest, tok->next, ";")) + return node; + + Node *exp = expr(&tok, tok->next); + *rest = skip(tok, ";"); + + add_type(exp); + Type *ty = current_fn->ty->return_ty; + if (ty->kind != TY_STRUCT && ty->kind != TY_UNION) + exp = new_cast(exp, current_fn->ty->return_ty); + + node->lhs = exp; + return node; + } + + if (equal(tok, "if")) { + Node *node = new_node(ND_IF, tok); + tok = skip(tok->next, "("); + node->cond = expr(&tok, tok); + tok = skip(tok, ")"); + node->then = stmt(&tok, tok); + if (equal(tok, "else")) + node->els = stmt(&tok, tok->next); + *rest = tok; + return node; + } + + if (equal(tok, "switch")) { + Node *node = new_node(ND_SWITCH, tok); + tok = skip(tok->next, "("); + node->cond = expr(&tok, tok); + tok = skip(tok, ")"); + + Node *sw = current_switch; + current_switch = node; + + char *brk = brk_label; + brk_label = node->brk_label = new_unique_name(); + + node->then = stmt(rest, tok); + + current_switch = sw; + brk_label = brk; + return node; + } + + if (equal(tok, "case")) { + if (!current_switch) + error_tok(tok, "stray case"); + + Node *node = new_node(ND_CASE, tok); + int begin = const_expr(&tok, tok->next); + int end; + + if (equal(tok, "...")) { + // [GNU] Case ranges, e.g. "case 1 ... 5:" + end = const_expr(&tok, tok->next); + if (end < begin) + error_tok(tok, "empty case range specified"); + } else { + end = begin; + } + + tok = skip(tok, ":"); + node->label = new_unique_name(); + node->lhs = stmt(rest, tok); + node->begin = begin; + node->end = end; + node->case_next = current_switch->case_next; + current_switch->case_next = node; + return node; + } + + if (equal(tok, "default")) { + if (!current_switch) + error_tok(tok, "stray default"); + + Node *node = new_node(ND_CASE, tok); + tok = skip(tok->next, ":"); + node->label = new_unique_name(); + node->lhs = stmt(rest, tok); + current_switch->default_case = node; + return node; + } + + if (equal(tok, "for")) { + Node *node = new_node(ND_FOR, tok); + tok = skip(tok->next, "("); + + enter_scope(); + + char *brk = brk_label; + char *cont = cont_label; + brk_label = node->brk_label = new_unique_name(); + cont_label = node->cont_label = new_unique_name(); + + if (is_typename(tok)) { + Type *basety = declspec(&tok, tok, NULL); + node->init = declaration(&tok, tok, basety, NULL); + } else { + node->init = expr_stmt(&tok, tok); + } + + if (!equal(tok, ";")) + node->cond = expr(&tok, tok); + tok = skip(tok, ";"); + + if (!equal(tok, ")")) + node->inc = expr(&tok, tok); + tok = skip(tok, ")"); + + node->then = stmt(rest, tok); + + leave_scope(); + brk_label = brk; + cont_label = cont; + return node; + } + + if (equal(tok, "while")) { + Node *node = new_node(ND_FOR, tok); + tok = skip(tok->next, "("); + node->cond = expr(&tok, tok); + tok = skip(tok, ")"); + + char *brk = brk_label; + char *cont = cont_label; + brk_label = node->brk_label = new_unique_name(); + cont_label = node->cont_label = new_unique_name(); + + node->then = stmt(rest, tok); + + brk_label = brk; + cont_label = cont; + return node; + } + + if (equal(tok, "do")) { + Node *node = new_node(ND_DO, tok); + + char *brk = brk_label; + char *cont = cont_label; + brk_label = node->brk_label = new_unique_name(); + cont_label = node->cont_label = new_unique_name(); + + node->then = stmt(&tok, tok->next); + + brk_label = brk; + cont_label = cont; + + tok = skip(tok, "while"); + tok = skip(tok, "("); + node->cond = expr(&tok, tok); + tok = skip(tok, ")"); + *rest = skip(tok, ";"); + return node; + } + + if (equal(tok, "asm")) + return asm_stmt(rest, tok); + + if (equal(tok, "goto")) { + if (equal(tok->next, "*")) { + // [GNU] `goto *ptr` jumps to the address specified by `ptr`. + Node *node = new_node(ND_GOTO_EXPR, tok); + node->lhs = expr(&tok, tok->next->next); + *rest = skip(tok, ";"); + return node; + } + + Node *node = new_node(ND_GOTO, tok); + node->label = get_ident(tok->next); + node->goto_next = gotos; + gotos = node; + *rest = skip(tok->next->next, ";"); + return node; + } + + if (equal(tok, "break")) { + if (!brk_label) + error_tok(tok, "stray break"); + Node *node = new_node(ND_GOTO, tok); + node->unique_label = brk_label; + *rest = skip(tok->next, ";"); + return node; + } + + if (equal(tok, "continue")) { + if (!cont_label) + error_tok(tok, "stray continue"); + Node *node = new_node(ND_GOTO, tok); + node->unique_label = cont_label; + *rest = skip(tok->next, ";"); + return node; + } + + if (tok->kind == TK_IDENT && equal(tok->next, ":")) { + Node *node = new_node(ND_LABEL, tok); + node->label = strndup(tok->loc, tok->len); + node->unique_label = new_unique_name(); + node->lhs = stmt(rest, tok->next->next); + node->goto_next = labels; + labels = node; + return node; + } + + if (equal(tok, "{")) + return compound_stmt(rest, tok->next); + + return expr_stmt(rest, tok); +} + +// compound-stmt = (typedef | declaration | stmt)* "}" +static Node *compound_stmt(Token **rest, Token *tok) { + Node *node = new_node(ND_BLOCK, tok); + Node head = {}; + Node *cur = &head; + + enter_scope(); + + while (!equal(tok, "}")) { + if (is_typename(tok) && !equal(tok->next, ":")) { + VarAttr attr = {}; + Type *basety = declspec(&tok, tok, &attr); + + if (attr.is_typedef) { + tok = parse_typedef(tok, basety); + continue; + } + + if (is_function(tok)) { + tok = function(tok, basety, &attr); + continue; + } + + if (attr.is_extern) { + tok = global_variable(tok, basety, &attr); + continue; + } + + cur = cur->next = declaration(&tok, tok, basety, &attr); + } else { + cur = cur->next = stmt(&tok, tok); + } + add_type(cur); + } + + leave_scope(); + + node->body = head.next; + *rest = tok->next; + return node; +} + +// expr-stmt = expr? ";" +static Node *expr_stmt(Token **rest, Token *tok) { + if (equal(tok, ";")) { + *rest = tok->next; + return new_node(ND_BLOCK, tok); + } + + Node *node = new_node(ND_EXPR_STMT, tok); + node->lhs = expr(&tok, tok); + *rest = skip(tok, ";"); + return node; } // expr = assign ("," expr)? @@ -421,13 +1825,443 @@ static Node *expr(Token **rest, Token *tok) { return node; } -// assign = equality ("=" assign)? -static Node *assign(Token **rest, Token *tok) { +static int64_t eval(Node *node) { + return eval2(node, NULL); +} + +// Evaluate a given node as a constant expression. +// +// A constant expression is either just a number or ptr+n where ptr +// is a pointer to a global variable and n is a postiive/negative +// number. The latter form is accepted only as an initialization +// expression for a global variable. +static int64_t eval2(Node *node, char ***label) { + add_type(node); + + if (is_flonum(node->ty)) + return eval_double(node); + + switch (node->kind) { + case ND_ADD: + return eval2(node->lhs, label) + eval(node->rhs); + case ND_SUB: + return eval2(node->lhs, label) - eval(node->rhs); + case ND_MUL: + return eval(node->lhs) * eval(node->rhs); + case ND_DIV: + if (node->ty->is_unsigned) + return (uint64_t)eval(node->lhs) / eval(node->rhs); + return eval(node->lhs) / eval(node->rhs); + case ND_NEG: + return -eval(node->lhs); + case ND_MOD: + if (node->ty->is_unsigned) + return (uint64_t)eval(node->lhs) % eval(node->rhs); + return eval(node->lhs) % eval(node->rhs); + case ND_BITAND: + return eval(node->lhs) & eval(node->rhs); + case ND_BITOR: + return eval(node->lhs) | eval(node->rhs); + case ND_BITXOR: + return eval(node->lhs) ^ eval(node->rhs); + case ND_SHL: + return eval(node->lhs) << eval(node->rhs); + case ND_SHR: + if (node->ty->is_unsigned && node->ty->size == 8) + return (uint64_t)eval(node->lhs) >> eval(node->rhs); + return eval(node->lhs) >> eval(node->rhs); + case ND_EQ: + return eval(node->lhs) == eval(node->rhs); + case ND_NE: + return eval(node->lhs) != eval(node->rhs); + case ND_LT: + if (node->lhs->ty->is_unsigned) + return (uint64_t)eval(node->lhs) < eval(node->rhs); + return eval(node->lhs) < eval(node->rhs); + case ND_LE: + if (node->lhs->ty->is_unsigned) + return (uint64_t)eval(node->lhs) <= eval(node->rhs); + return eval(node->lhs) <= eval(node->rhs); + case ND_COND: + return eval(node->cond) ? eval2(node->then, label) : eval2(node->els, label); + case ND_COMMA: + return eval2(node->rhs, label); + case ND_NOT: + return !eval(node->lhs); + case ND_BITNOT: + return ~eval(node->lhs); + case ND_LOGAND: + return eval(node->lhs) && eval(node->rhs); + case ND_LOGOR: + return eval(node->lhs) || eval(node->rhs); + case ND_CAST: { + int64_t val = eval2(node->lhs, label); + if (is_integer(node->ty)) { + switch (node->ty->size) { + case 1: return node->ty->is_unsigned ? (uint8_t)val : (int8_t)val; + case 2: return node->ty->is_unsigned ? (uint16_t)val : (int16_t)val; + case 4: return node->ty->is_unsigned ? (uint32_t)val : (int32_t)val; + } + } + return val; + } + case ND_ADDR: + return eval_rval(node->lhs, label); + case ND_LABEL_VAL: + *label = &node->unique_label; + return 0; + case ND_MEMBER: + if (!label) + error_tok(node->tok, "not a compile-time constant"); + if (node->ty->kind != TY_ARRAY) + error_tok(node->tok, "invalid initializer"); + return eval_rval(node->lhs, label) + node->member->offset; + case ND_VAR: + if (!label) + error_tok(node->tok, "not a compile-time constant"); + if (node->var->ty->kind != TY_ARRAY && node->var->ty->kind != TY_FUNC) + error_tok(node->tok, "invalid initializer"); + *label = &node->var->name; + return 0; + case ND_NUM: + return node->val; + } + + error_tok(node->tok, "not a compile-time constant"); +} + +static int64_t eval_rval(Node *node, char ***label) { + switch (node->kind) { + case ND_VAR: + if (node->var->is_local) + error_tok(node->tok, "not a compile-time constant"); + *label = &node->var->name; + return 0; + case ND_DEREF: + return eval2(node->lhs, label); + case ND_MEMBER: + return eval_rval(node->lhs, label) + node->member->offset; + } + + error_tok(node->tok, "invalid initializer"); +} + +static bool is_const_expr(Node *node) { + add_type(node); + + switch (node->kind) { + case ND_ADD: + case ND_SUB: + case ND_MUL: + case ND_DIV: + case ND_BITAND: + case ND_BITOR: + case ND_BITXOR: + case ND_SHL: + case ND_SHR: + case ND_EQ: + case ND_NE: + case ND_LT: + case ND_LE: + case ND_LOGAND: + case ND_LOGOR: + return is_const_expr(node->lhs) && is_const_expr(node->rhs); + case ND_COND: + if (!is_const_expr(node->cond)) + return false; + return is_const_expr(eval(node->cond) ? node->then : node->els); + case ND_COMMA: + return is_const_expr(node->rhs); + case ND_NEG: + case ND_NOT: + case ND_BITNOT: + case ND_CAST: + return is_const_expr(node->lhs); + case ND_NUM: + return true; + } + + return false; +} + +int64_t const_expr(Token **rest, Token *tok) { + Node *node = conditional(rest, tok); + return eval(node); +} + +static double eval_double(Node *node) { + add_type(node); + + if (is_integer(node->ty)) { + if (node->ty->is_unsigned) + return (unsigned long)eval(node); + return eval(node); + } + + switch (node->kind) { + case ND_ADD: + return eval_double(node->lhs) + eval_double(node->rhs); + case ND_SUB: + return eval_double(node->lhs) - eval_double(node->rhs); + case ND_MUL: + return eval_double(node->lhs) * eval_double(node->rhs); + case ND_DIV: + return eval_double(node->lhs) / eval_double(node->rhs); + case ND_NEG: + return -eval_double(node->lhs); + case ND_COND: + return eval_double(node->cond) ? eval_double(node->then) : eval_double(node->els); + case ND_COMMA: + return eval_double(node->rhs); + case ND_CAST: + if (is_flonum(node->lhs->ty)) + return eval_double(node->lhs); + return eval(node->lhs); + case ND_NUM: + return node->fval; + } + + error_tok(node->tok, "not a compile-time constant"); +} + +// Convert op= operators to expressions containing an assignment. +// +// In general, `A op= C` is converted to ``tmp = &A, *tmp = *tmp op B`. +// However, if a given expression is of form `A.x op= C`, the input is +// converted to `tmp = &A, (*tmp).x = (*tmp).x op C` to handle assignments +// to bitfields. +static Node *to_assign(Node *binary) { + add_type(binary->lhs); + add_type(binary->rhs); + Token *tok = binary->tok; + + // Convert `A.x op= C` to `tmp = &A, (*tmp).x = (*tmp).x op C`. + if (binary->lhs->kind == ND_MEMBER) { + Obj *var = new_lvar("", pointer_to(binary->lhs->lhs->ty)); + + Node *expr1 = new_binary(ND_ASSIGN, new_var_node(var, tok), + new_unary(ND_ADDR, binary->lhs->lhs, tok), tok); + + Node *expr2 = new_unary(ND_MEMBER, + new_unary(ND_DEREF, new_var_node(var, tok), tok), + tok); + expr2->member = binary->lhs->member; + + Node *expr3 = new_unary(ND_MEMBER, + new_unary(ND_DEREF, new_var_node(var, tok), tok), + tok); + expr3->member = binary->lhs->member; + + Node *expr4 = new_binary(ND_ASSIGN, expr2, + new_binary(binary->kind, expr3, binary->rhs, tok), + tok); + + return new_binary(ND_COMMA, expr1, expr4, tok); + } + + // If A is an atomic type, Convert `A op= B` to + // + // ({ + // T1 *addr = &A; T2 val = (B); T1 old = *addr; T1 new; + // do { + // new = old op val; + // } while (!atomic_compare_exchange_strong(addr, &old, new)); + // new; + // }) + if (binary->lhs->ty->is_atomic) { + Node head = {}; + Node *cur = &head; + + Obj *addr = new_lvar("", pointer_to(binary->lhs->ty)); + Obj *val = new_lvar("", binary->rhs->ty); + Obj *old = new_lvar("", binary->lhs->ty); + Obj *new = new_lvar("", binary->lhs->ty); + + cur = cur->next = + new_unary(ND_EXPR_STMT, + new_binary(ND_ASSIGN, new_var_node(addr, tok), + new_unary(ND_ADDR, binary->lhs, tok), tok), + tok); + + cur = cur->next = + new_unary(ND_EXPR_STMT, + new_binary(ND_ASSIGN, new_var_node(val, tok), binary->rhs, tok), + tok); + + cur = cur->next = + new_unary(ND_EXPR_STMT, + new_binary(ND_ASSIGN, new_var_node(old, tok), + new_unary(ND_DEREF, new_var_node(addr, tok), tok), tok), + tok); + + Node *loop = new_node(ND_DO, tok); + loop->brk_label = new_unique_name(); + loop->cont_label = new_unique_name(); + + Node *body = new_binary(ND_ASSIGN, + new_var_node(new, tok), + new_binary(binary->kind, new_var_node(old, tok), + new_var_node(val, tok), tok), + tok); + + loop->then = new_node(ND_BLOCK, tok); + loop->then->body = new_unary(ND_EXPR_STMT, body, tok); + + Node *cas = new_node(ND_CAS, tok); + cas->cas_addr = new_var_node(addr, tok); + cas->cas_old = new_unary(ND_ADDR, new_var_node(old, tok), tok); + cas->cas_new = new_var_node(new, tok); + loop->cond = new_unary(ND_NOT, cas, tok); + + cur = cur->next = loop; + cur = cur->next = new_unary(ND_EXPR_STMT, new_var_node(new, tok), tok); + + Node *node = new_node(ND_STMT_EXPR, tok); + node->body = head.next; + return node; + } + + // Convert `A op= B` to ``tmp = &A, *tmp = *tmp op B`. + Obj *var = new_lvar("", pointer_to(binary->lhs->ty)); + + Node *expr1 = new_binary(ND_ASSIGN, new_var_node(var, tok), + new_unary(ND_ADDR, binary->lhs, tok), tok); + + Node *expr2 = + new_binary(ND_ASSIGN, + new_unary(ND_DEREF, new_var_node(var, tok), tok), + new_binary(binary->kind, + new_unary(ND_DEREF, new_var_node(var, tok), tok), + binary->rhs, + tok), + tok); + + return new_binary(ND_COMMA, expr1, expr2, tok); +} + +// assign = conditional (assign-op assign)? +// assign-op = "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" +// | "<<=" | ">>=" +static Node *assign(Token **rest, Token *tok) { + Node *node = conditional(&tok, tok); + + if (equal(tok, "=")) + return new_binary(ND_ASSIGN, node, assign(rest, tok->next), tok); + + if (equal(tok, "+=")) + return to_assign(new_add(node, assign(rest, tok->next), tok)); + + if (equal(tok, "-=")) + return to_assign(new_sub(node, assign(rest, tok->next), tok)); + + if (equal(tok, "*=")) + return to_assign(new_binary(ND_MUL, node, assign(rest, tok->next), tok)); + + if (equal(tok, "/=")) + return to_assign(new_binary(ND_DIV, node, assign(rest, tok->next), tok)); + + if (equal(tok, "%=")) + return to_assign(new_binary(ND_MOD, node, assign(rest, tok->next), tok)); + + if (equal(tok, "&=")) + return to_assign(new_binary(ND_BITAND, node, assign(rest, tok->next), tok)); + + if (equal(tok, "|=")) + return to_assign(new_binary(ND_BITOR, node, assign(rest, tok->next), tok)); + + if (equal(tok, "^=")) + return to_assign(new_binary(ND_BITXOR, node, assign(rest, tok->next), tok)); + + if (equal(tok, "<<=")) + return to_assign(new_binary(ND_SHL, node, assign(rest, tok->next), tok)); + + if (equal(tok, ">>=")) + return to_assign(new_binary(ND_SHR, node, assign(rest, tok->next), tok)); + + *rest = tok; + return node; +} + +// conditional = logor ("?" expr? ":" conditional)? +static Node *conditional(Token **rest, Token *tok) { + Node *cond = logor(&tok, tok); + + if (!equal(tok, "?")) { + *rest = tok; + return cond; + } + + if (equal(tok->next, ":")) { + // [GNU] Compile `a ?: b` as `tmp = a, tmp ? tmp : b`. + add_type(cond); + Obj *var = new_lvar("", cond->ty); + Node *lhs = new_binary(ND_ASSIGN, new_var_node(var, tok), cond, tok); + Node *rhs = new_node(ND_COND, tok); + rhs->cond = new_var_node(var, tok); + rhs->then = new_var_node(var, tok); + rhs->els = conditional(rest, tok->next->next); + return new_binary(ND_COMMA, lhs, rhs, tok); + } + + Node *node = new_node(ND_COND, tok); + node->cond = cond; + node->then = expr(&tok, tok->next); + tok = skip(tok, ":"); + node->els = conditional(rest, tok); + return node; +} + +// logor = logand ("||" logand)* +static Node *logor(Token **rest, Token *tok) { + Node *node = logand(&tok, tok); + while (equal(tok, "||")) { + Token *start = tok; + node = new_binary(ND_LOGOR, node, logand(&tok, tok->next), start); + } + *rest = tok; + return node; +} + +// logand = bitor ("&&" bitor)* +static Node *logand(Token **rest, Token *tok) { + Node *node = bitor(&tok, tok); + while (equal(tok, "&&")) { + Token *start = tok; + node = new_binary(ND_LOGAND, node, bitor(&tok, tok->next), start); + } + *rest = tok; + return node; +} + +// bitor = bitxor ("|" bitxor)* +static Node *bitor(Token **rest, Token *tok) { + Node *node = bitxor(&tok, tok); + while (equal(tok, "|")) { + Token *start = tok; + node = new_binary(ND_BITOR, node, bitxor(&tok, tok->next), start); + } + *rest = tok; + return node; +} + +// bitxor = bitand ("^" bitand)* +static Node *bitxor(Token **rest, Token *tok) { + Node *node = bitand(&tok, tok); + while (equal(tok, "^")) { + Token *start = tok; + node = new_binary(ND_BITXOR, node, bitand(&tok, tok->next), start); + } + *rest = tok; + return node; +} + +// bitand = equality ("&" equality)* +static Node *bitand(Token **rest, Token *tok) { Node *node = equality(&tok, tok); - - if (equal(tok, "=")) - return new_binary(ND_ASSIGN, node, assign(rest, tok->next), tok); - + while (equal(tok, "&")) { + Token *start = tok; + node = new_binary(ND_BITAND, node, equality(&tok, tok->next), start); + } *rest = tok; return node; } @@ -454,30 +2288,52 @@ static Node *equality(Token **rest, Token *tok) { } } -// relational = add ("<" add | "<=" add | ">" add | ">=" add)* +// relational = shift ("<" shift | "<=" shift | ">" shift | ">=" shift)* static Node *relational(Token **rest, Token *tok) { - Node *node = add(&tok, tok); + Node *node = shift(&tok, tok); for (;;) { Token *start = tok; if (equal(tok, "<")) { - node = new_binary(ND_LT, node, add(&tok, tok->next), start); + node = new_binary(ND_LT, node, shift(&tok, tok->next), start); continue; } if (equal(tok, "<=")) { - node = new_binary(ND_LE, node, add(&tok, tok->next), start); + node = new_binary(ND_LE, node, shift(&tok, tok->next), start); continue; } if (equal(tok, ">")) { - node = new_binary(ND_LT, add(&tok, tok->next), node, start); + node = new_binary(ND_LT, shift(&tok, tok->next), node, start); continue; } if (equal(tok, ">=")) { - node = new_binary(ND_LE, add(&tok, tok->next), node, start); + node = new_binary(ND_LE, shift(&tok, tok->next), node, start); + continue; + } + + *rest = tok; + return node; + } +} + +// shift = add ("<<" add | ">>" add)* +static Node *shift(Token **rest, Token *tok) { + Node *node = add(&tok, tok); + + for (;;) { + Token *start = tok; + + if (equal(tok, "<<")) { + node = new_binary(ND_SHL, node, add(&tok, tok->next), start); + continue; + } + + if (equal(tok, ">>")) { + node = new_binary(ND_SHR, node, add(&tok, tok->next), start); continue; } @@ -496,7 +2352,7 @@ static Node *new_add(Node *lhs, Node *rhs, Token *tok) { add_type(rhs); // num + num - if (is_integer(lhs->ty) && is_integer(rhs->ty)) + if (is_numeric(lhs->ty) && is_numeric(rhs->ty)) return new_binary(ND_ADD, lhs, rhs, tok); if (lhs->ty->base && rhs->ty->base) @@ -509,8 +2365,14 @@ static Node *new_add(Node *lhs, Node *rhs, Token *tok) { rhs = tmp; } + // VLA + num + if (lhs->ty->base->kind == TY_VLA) { + rhs = new_binary(ND_MUL, rhs, new_var_node(lhs->ty->base->vla_size, tok), tok); + return new_binary(ND_ADD, lhs, rhs, tok); + } + // ptr + num - rhs = new_binary(ND_MUL, rhs, new_num(lhs->ty->base->size, tok), tok); + rhs = new_binary(ND_MUL, rhs, new_long(lhs->ty->base->size, tok), tok); return new_binary(ND_ADD, lhs, rhs, tok); } @@ -520,12 +2382,21 @@ static Node *new_sub(Node *lhs, Node *rhs, Token *tok) { add_type(rhs); // num - num - if (is_integer(lhs->ty) && is_integer(rhs->ty)) + if (is_numeric(lhs->ty) && is_numeric(rhs->ty)) return new_binary(ND_SUB, lhs, rhs, tok); + // VLA + num + if (lhs->ty->base->kind == TY_VLA) { + rhs = new_binary(ND_MUL, rhs, new_var_node(lhs->ty->base->vla_size, tok), tok); + add_type(rhs); + Node *node = new_binary(ND_SUB, lhs, rhs, tok); + node->ty = lhs->ty; + return node; + } + // ptr - num if (lhs->ty->base && is_integer(rhs->ty)) { - rhs = new_binary(ND_MUL, rhs, new_num(lhs->ty->base->size, tok), tok); + rhs = new_binary(ND_MUL, rhs, new_long(lhs->ty->base->size, tok), tok); add_type(rhs); Node *node = new_binary(ND_SUB, lhs, rhs, tok); node->ty = lhs->ty; @@ -535,7 +2406,7 @@ static Node *new_sub(Node *lhs, Node *rhs, Token *tok) { // ptr - ptr, which returns how many elements are between the two. if (lhs->ty->base && rhs->ty->base) { Node *node = new_binary(ND_SUB, lhs, rhs, tok); - node->ty = ty_int; + node->ty = ty_long; return new_binary(ND_DIV, node, new_num(lhs->ty->base->size, tok), tok); } @@ -564,20 +2435,25 @@ static Node *add(Token **rest, Token *tok) { } } -// mul = unary ("*" unary | "/" unary)* +// mul = cast ("*" cast | "/" cast | "%" cast)* static Node *mul(Token **rest, Token *tok) { - Node *node = unary(&tok, tok); + Node *node = cast(&tok, tok); for (;;) { Token *start = tok; if (equal(tok, "*")) { - node = new_binary(ND_MUL, node, unary(&tok, tok->next), start); + node = new_binary(ND_MUL, node, cast(&tok, tok->next), start); continue; } if (equal(tok, "/")) { - node = new_binary(ND_DIV, node, unary(&tok, tok->next), start); + node = new_binary(ND_DIV, node, cast(&tok, tok->next), start); + continue; + } + + if (equal(tok, "%")) { + node = new_binary(ND_MOD, node, cast(&tok, tok->next), start); continue; } @@ -586,20 +2462,80 @@ static Node *mul(Token **rest, Token *tok) { } } -// unary = ("+" | "-" | "*" | "&") unary +// cast = "(" type-name ")" cast | unary +static Node *cast(Token **rest, Token *tok) { + if (equal(tok, "(") && is_typename(tok->next)) { + Token *start = tok; + Type *ty = typename(&tok, tok->next); + tok = skip(tok, ")"); + + // compound literal + if (equal(tok, "{")) + return unary(rest, start); + + // type cast + Node *node = new_cast(cast(rest, tok), ty); + node->tok = start; + return node; + } + + return unary(rest, tok); +} + +// unary = ("+" | "-" | "*" | "&" | "!" | "~") cast +// | ("++" | "--") unary +// | "&&" ident // | postfix static Node *unary(Token **rest, Token *tok) { if (equal(tok, "+")) - return unary(rest, tok->next); + return cast(rest, tok->next); if (equal(tok, "-")) - return new_unary(ND_NEG, unary(rest, tok->next), tok); + return new_unary(ND_NEG, cast(rest, tok->next), tok); + + if (equal(tok, "&")) { + Node *lhs = cast(rest, tok->next); + add_type(lhs); + if (lhs->kind == ND_MEMBER && lhs->member->is_bitfield) + error_tok(tok, "cannot take address of bitfield"); + return new_unary(ND_ADDR, lhs, tok); + } + + if (equal(tok, "*")) { + // [https://www.sigbus.info/n1570#6.5.3.2p4] This is an oddity + // in the C spec, but dereferencing a function shouldn't do + // anything. If foo is a function, `*foo`, `**foo` or `*****foo` + // are all equivalent to just `foo`. + Node *node = cast(rest, tok->next); + add_type(node); + if (node->ty->kind == TY_FUNC) + return node; + return new_unary(ND_DEREF, node, tok); + } - if (equal(tok, "&")) - return new_unary(ND_ADDR, unary(rest, tok->next), tok); + if (equal(tok, "!")) + return new_unary(ND_NOT, cast(rest, tok->next), tok); - if (equal(tok, "*")) - return new_unary(ND_DEREF, unary(rest, tok->next), tok); + if (equal(tok, "~")) + return new_unary(ND_BITNOT, cast(rest, tok->next), tok); + + // Read ++i as i+=1 + if (equal(tok, "++")) + return to_assign(new_add(unary(rest, tok->next), new_num(1, tok), tok)); + + // Read --i as i-=1 + if (equal(tok, "--")) + return to_assign(new_sub(unary(rest, tok->next), new_num(1, tok), tok)); + + // [GNU] labels-as-values + if (equal(tok, "&&")) { + Node *node = new_node(ND_LABEL_VAL, tok); + node->label = get_ident(tok->next); + node->goto_next = gotos; + gotos = node; + *rest = tok->next->next; + return node; + } return postfix(rest, tok); } @@ -608,28 +2544,96 @@ static Node *unary(Token **rest, Token *tok) { static void struct_members(Token **rest, Token *tok, Type *ty) { Member head = {}; Member *cur = &head; + int idx = 0; while (!equal(tok, "}")) { - Type *basety = declspec(&tok, tok); - int i = 0; + VarAttr attr = {}; + Type *basety = declspec(&tok, tok, &attr); + bool first = true; + + // Anonymous struct member + if ((basety->kind == TY_STRUCT || basety->kind == TY_UNION) && + consume(&tok, tok, ";")) { + Member *mem = calloc(1, sizeof(Member)); + mem->ty = basety; + mem->idx = idx++; + mem->align = attr.align ? attr.align : mem->ty->align; + cur = cur->next = mem; + continue; + } + // Regular struct members while (!consume(&tok, tok, ";")) { - if (i++) + if (!first) tok = skip(tok, ","); + first = false; Member *mem = calloc(1, sizeof(Member)); mem->ty = declarator(&tok, tok, basety); mem->name = mem->ty->name; + mem->idx = idx++; + mem->align = attr.align ? attr.align : mem->ty->align; + + if (consume(&tok, tok, ":")) { + mem->is_bitfield = true; + mem->bit_width = const_expr(&tok, tok); + } + cur = cur->next = mem; } } + // If the last element is an array of incomplete type, it's + // called a "flexible array member". It should behave as if + // if were a zero-sized array. + if (cur != &head && cur->ty->kind == TY_ARRAY && cur->ty->array_len < 0) { + cur->ty = array_of(cur->ty->base, 0); + ty->is_flexible = true; + } + *rest = tok->next; ty->members = head.next; } -// struct-union-decl = ident? ("{" struct-members)? +// attribute = ("__attribute__" "(" "(" "packed" ")" ")")* +static Token *attribute_list(Token *tok, Type *ty) { + while (consume(&tok, tok, "__attribute__")) { + tok = skip(tok, "("); + tok = skip(tok, "("); + + bool first = true; + + while (!consume(&tok, tok, ")")) { + if (!first) + tok = skip(tok, ","); + first = false; + + if (consume(&tok, tok, "packed")) { + ty->is_packed = true; + continue; + } + + if (consume(&tok, tok, "aligned")) { + tok = skip(tok, "("); + ty->align = const_expr(&tok, tok); + tok = skip(tok, ")"); + continue; + } + + error_tok(tok, "unknown attribute"); + } + + tok = skip(tok, ")"); + } + + return tok; +} + +// struct-union-decl = attribute? ident? ("{" struct-members)? static Type *struct_union_decl(Token **rest, Token *tok) { + Type *ty = struct_type(); + tok = attribute_list(tok, ty); + // Read a tag. Token *tag = NULL; if (tok->kind == TK_IDENT) { @@ -638,22 +2642,35 @@ static Type *struct_union_decl(Token **rest, Token *tok) { } if (tag && !equal(tok, "{")) { - Type *ty = find_tag(tag); - if (!ty) - error_tok(tag, "unknown struct type"); *rest = tok; + + Type *ty2 = find_tag(tag); + if (ty2) + return ty2; + + ty->size = -1; + push_tag_scope(tag, ty); return ty; } + tok = skip(tok, "{"); + // Construct a struct object. - Type *ty = calloc(1, sizeof(Type)); - ty->kind = TY_STRUCT; - struct_members(rest, tok->next, ty); - ty->align = 1; + struct_members(&tok, tok, ty); + *rest = attribute_list(tok, ty); + + if (tag) { + // If this is a redefinition, overwrite a previous type. + // Otherwise, register the struct type. + Type *ty2 = hashmap_get2(&scope->tags, tag->loc, tag->len); + if (ty2) { + *ty2 = *ty; + return ty2; + } - // Register the struct type if a name was given. - if (tag) push_tag_scope(tag, ty); + } + return ty; } @@ -662,17 +2679,37 @@ static Type *struct_decl(Token **rest, Token *tok) { Type *ty = struct_union_decl(rest, tok); ty->kind = TY_STRUCT; + if (ty->size < 0) + return ty; + // Assign offsets within the struct to members. - int offset = 0; + int bits = 0; + for (Member *mem = ty->members; mem; mem = mem->next) { - offset = align_to(offset, mem->ty->align); - mem->offset = offset; - offset += mem->ty->size; + if (mem->is_bitfield && mem->bit_width == 0) { + // Zero-width anonymous bitfield has a special meaning. + // It affects only alignment. + bits = align_to(bits, mem->ty->size * 8); + } else if (mem->is_bitfield) { + int sz = mem->ty->size; + if (bits / (sz * 8) != (bits + mem->bit_width - 1) / (sz * 8)) + bits = align_to(bits, sz * 8); + + mem->offset = align_down(bits / 8, sz); + mem->bit_offset = bits % (sz * 8); + bits += mem->bit_width; + } else { + if (!ty->is_packed) + bits = align_to(bits, mem->align * 8); + mem->offset = bits / 8; + bits += mem->ty->size * 8; + } - if (ty->align < mem->ty->align) - ty->align = mem->ty->align; + if (!ty->is_packed && ty->align < mem->align) + ty->align = mem->align; } - ty->size = align_to(offset, ty->align); + + ty->size = align_to(bits, ty->align * 8) / 8; return ty; } @@ -681,12 +2718,15 @@ static Type *union_decl(Token **rest, Token *tok) { Type *ty = struct_union_decl(rest, tok); ty->kind = TY_UNION; + if (ty->size < 0) + return ty; + // If union, we don't have to assign offsets because they // are already initialized to zero. We need to compute the // alignment and the size though. for (Member *mem = ty->members; mem; mem = mem->next) { - if (ty->align < mem->ty->align) - ty->align = mem->ty->align; + if (ty->align < mem->align) + ty->align = mem->align; if (ty->size < mem->ty->size) ty->size = mem->ty->size; } @@ -694,29 +2734,103 @@ static Type *union_decl(Token **rest, Token *tok) { return ty; } +// Find a struct member by name. static Member *get_struct_member(Type *ty, Token *tok) { - for (Member *mem = ty->members; mem; mem = mem->next) + for (Member *mem = ty->members; mem; mem = mem->next) { + // Anonymous struct member + if ((mem->ty->kind == TY_STRUCT || mem->ty->kind == TY_UNION) && + !mem->name) { + if (get_struct_member(mem->ty, tok)) + return mem; + continue; + } + + // Regular struct member if (mem->name->len == tok->len && !strncmp(mem->name->loc, tok->loc, tok->len)) return mem; - error_tok(tok, "no such member"); + } + return NULL; } -static Node *struct_ref(Node *lhs, Token *tok) { - add_type(lhs); - if (lhs->ty->kind != TY_STRUCT && lhs->ty->kind != TY_UNION) - error_tok(lhs->tok, "not a struct nor a union"); +// Create a node representing a struct member access, such as foo.bar +// where foo is a struct and bar is a member name. +// +// C has a feature called "anonymous struct" which allows a struct to +// have another unnamed struct as a member like this: +// +// struct { struct { int a; }; int b; } x; +// +// The members of an anonymous struct belong to the outer struct's +// member namespace. Therefore, in the above example, you can access +// member "a" of the anonymous struct as "x.a". +// +// This function takes care of anonymous structs. +static Node *struct_ref(Node *node, Token *tok) { + add_type(node); + if (node->ty->kind != TY_STRUCT && node->ty->kind != TY_UNION) + error_tok(node->tok, "not a struct nor a union"); + + Type *ty = node->ty; - Node *node = new_unary(ND_MEMBER, lhs, tok); - node->member = get_struct_member(lhs->ty, tok); + for (;;) { + Member *mem = get_struct_member(ty, tok); + if (!mem) + error_tok(tok, "no such member"); + node = new_unary(ND_MEMBER, node, tok); + node->member = mem; + if (mem->name) + break; + ty = mem->ty; + } return node; } -// postfix = primary ("[" expr "]" | "." ident | "->" ident)* +// Convert A++ to `(typeof A)((A += 1) - 1)` +static Node *new_inc_dec(Node *node, Token *tok, int addend) { + add_type(node); + return new_cast(new_add(to_assign(new_add(node, new_num(addend, tok), tok)), + new_num(-addend, tok), tok), + node->ty); +} + +// postfix = "(" type-name ")" "{" initializer-list "}" +// = ident "(" func-args ")" postfix-tail* +// | primary postfix-tail* +// +// postfix-tail = "[" expr "]" +// | "(" func-args ")" +// | "." ident +// | "->" ident +// | "++" +// | "--" static Node *postfix(Token **rest, Token *tok) { + if (equal(tok, "(") && is_typename(tok->next)) { + // Compound literal + Token *start = tok; + Type *ty = typename(&tok, tok->next); + tok = skip(tok, ")"); + + if (scope->next == NULL) { + Obj *var = new_anon_gvar(ty); + gvar_initializer(rest, tok, var); + return new_var_node(var, start); + } + + Obj *var = new_lvar("", ty); + Node *lhs = lvar_initializer(rest, tok, var); + Node *rhs = new_var_node(var, tok); + return new_binary(ND_COMMA, lhs, rhs, start); + } + Node *node = primary(&tok, tok); for (;;) { + if (equal(tok, "(")) { + node = funcall(&tok, tok->next, node); + continue; + } + if (equal(tok, "[")) { // x[y] is short for *(x+y) Token *start = tok; @@ -740,15 +2854,33 @@ static Node *postfix(Token **rest, Token *tok) { continue; } + if (equal(tok, "++")) { + node = new_inc_dec(node, tok, 1); + tok = tok->next; + continue; + } + + if (equal(tok, "--")) { + node = new_inc_dec(node, tok, -1); + tok = tok->next; + continue; + } + *rest = tok; return node; } } -// funcall = ident "(" (assign ("," assign)*)? ")" -static Node *funcall(Token **rest, Token *tok) { - Token *start = tok; - tok = tok->next->next; +// funcall = (assign ("," assign)*)? ")" +static Node *funcall(Token **rest, Token *tok, Node *fn) { + add_type(fn); + + if (fn->ty->kind != TY_FUNC && + (fn->ty->kind != TY_PTR || fn->ty->base->kind != TY_FUNC)) + error_tok(fn->tok, "not a function"); + + Type *ty = (fn->ty->kind == TY_FUNC) ? fn->ty : fn->ty->base; + Type *param_ty = ty->params; Node head = {}; Node *cur = &head; @@ -756,24 +2888,101 @@ static Node *funcall(Token **rest, Token *tok) { while (!equal(tok, ")")) { if (cur != &head) tok = skip(tok, ","); - cur = cur->next = assign(&tok, tok); + + Node *arg = assign(&tok, tok); + add_type(arg); + + if (!param_ty && !ty->is_variadic) + error_tok(tok, "too many arguments"); + + if (param_ty) { + if (param_ty->kind != TY_STRUCT && param_ty->kind != TY_UNION) + arg = new_cast(arg, param_ty); + param_ty = param_ty->next; + } else if (arg->ty->kind == TY_FLOAT) { + // If parameter type is omitted (e.g. in "..."), float + // arguments are promoted to double. + arg = new_cast(arg, ty_double); + } + + cur = cur->next = arg; } + if (param_ty) + error_tok(tok, "too few arguments"); + *rest = skip(tok, ")"); - Node *node = new_node(ND_FUNCALL, start); - node->funcname = strndup(start->loc, start->len); + Node *node = new_unary(ND_FUNCALL, fn, tok); + node->func_ty = ty; + node->ty = ty->return_ty; node->args = head.next; + + // If a function returns a struct, it is caller's responsibility + // to allocate a space for the return value. + if (node->ty->kind == TY_STRUCT || node->ty->kind == TY_UNION) + node->ret_buffer = new_lvar("", node->ty); return node; } +// generic-selection = "(" assign "," generic-assoc ("," generic-assoc)* ")" +// +// generic-assoc = type-name ":" assign +// | "default" ":" assign +static Node *generic_selection(Token **rest, Token *tok) { + Token *start = tok; + tok = skip(tok, "("); + + Node *ctrl = assign(&tok, tok); + add_type(ctrl); + + Type *t1 = ctrl->ty; + if (t1->kind == TY_FUNC) + t1 = pointer_to(t1); + else if (t1->kind == TY_ARRAY) + t1 = pointer_to(t1->base); + + Node *ret = NULL; + + while (!consume(rest, tok, ")")) { + tok = skip(tok, ","); + + if (equal(tok, "default")) { + tok = skip(tok->next, ":"); + Node *node = assign(&tok, tok); + if (!ret) + ret = node; + continue; + } + + Type *t2 = typename(&tok, tok); + tok = skip(tok, ":"); + Node *node = assign(&tok, tok); + if (is_compatible(t1, t2)) + ret = node; + } + + if (!ret) + error_tok(start, "controlling expression type not compatible with" + " any generic association type"); + return ret; +} + // primary = "(" "{" stmt+ "}" ")" // | "(" expr ")" +// | "sizeof" "(" type-name ")" // | "sizeof" unary -// | ident func-args? +// | "_Alignof" "(" type-name ")" +// | "_Alignof" unary +// | "_Generic" generic-selection +// | "__builtin_types_compatible_p" "(" type-name, type-name, ")" +// | "__builtin_reg_class" "(" type-name ")" +// | ident // | str // | num static Node *primary(Token **rest, Token *tok) { + Token *start = tok; + if (equal(tok, "(") && equal(tok->next, "{")) { // This is a GNU statement expresssion. Node *node = new_node(ND_STMT_EXPR, tok); @@ -788,23 +2997,111 @@ static Node *primary(Token **rest, Token *tok) { return node; } + if (equal(tok, "sizeof") && equal(tok->next, "(") && is_typename(tok->next->next)) { + Type *ty = typename(&tok, tok->next->next); + *rest = skip(tok, ")"); + + if (ty->kind == TY_VLA) { + if (ty->vla_size) + return new_var_node(ty->vla_size, tok); + + Node *lhs = compute_vla_size(ty, tok); + Node *rhs = new_var_node(ty->vla_size, tok); + return new_binary(ND_COMMA, lhs, rhs, tok); + } + + return new_ulong(ty->size, start); + } + if (equal(tok, "sizeof")) { Node *node = unary(rest, tok->next); add_type(node); - return new_num(node->ty->size, tok); + if (node->ty->kind == TY_VLA) + return new_var_node(node->ty->vla_size, tok); + return new_ulong(node->ty->size, tok); } - if (tok->kind == TK_IDENT) { - // Function call - if (equal(tok->next, "(")) - return funcall(rest, tok); + if (equal(tok, "_Alignof") && equal(tok->next, "(") && is_typename(tok->next->next)) { + Type *ty = typename(&tok, tok->next->next); + *rest = skip(tok, ")"); + return new_ulong(ty->align, tok); + } + + if (equal(tok, "_Alignof")) { + Node *node = unary(rest, tok->next); + add_type(node); + return new_ulong(node->ty->align, tok); + } + + if (equal(tok, "_Generic")) + return generic_selection(rest, tok->next); + + if (equal(tok, "__builtin_types_compatible_p")) { + tok = skip(tok->next, "("); + Type *t1 = typename(&tok, tok); + tok = skip(tok, ","); + Type *t2 = typename(&tok, tok); + *rest = skip(tok, ")"); + return new_num(is_compatible(t1, t2), start); + } + + if (equal(tok, "__builtin_reg_class")) { + tok = skip(tok->next, "("); + Type *ty = typename(&tok, tok); + *rest = skip(tok, ")"); + + if (is_integer(ty) || ty->kind == TY_PTR) + return new_num(0, start); + if (is_flonum(ty)) + return new_num(1, start); + return new_num(2, start); + } + + if (equal(tok, "__builtin_compare_and_swap")) { + Node *node = new_node(ND_CAS, tok); + tok = skip(tok->next, "("); + node->cas_addr = assign(&tok, tok); + tok = skip(tok, ","); + node->cas_old = assign(&tok, tok); + tok = skip(tok, ","); + node->cas_new = assign(&tok, tok); + *rest = skip(tok, ")"); + return node; + } + + if (equal(tok, "__builtin_atomic_exchange")) { + Node *node = new_node(ND_EXCH, tok); + tok = skip(tok->next, "("); + node->lhs = assign(&tok, tok); + tok = skip(tok, ","); + node->rhs = assign(&tok, tok); + *rest = skip(tok, ")"); + return node; + } - // Variable - Obj *var = find_var(tok); - if (!var) - error_tok(tok, "undefined variable"); + if (tok->kind == TK_IDENT) { + // Variable or enum constant + VarScope *sc = find_var(tok); *rest = tok->next; - return new_var_node(var, tok); + + // For "static inline" function + if (sc && sc->var && sc->var->is_function) { + if (current_fn) + strarray_push(¤t_fn->refs, sc->var->name); + else + sc->var->is_root = true; + } + + if (sc) { + if (sc->var) + return new_var_node(sc->var, tok); + if (sc->enum_ty) + return new_num(sc->enum_val, tok); + } + + if (equal(tok->next, "(")) + error_tok(tok, "implicit declaration of a function"); + error_tok(tok, "undefined variable"); } if (tok->kind == TK_STR) { @@ -814,7 +3111,15 @@ static Node *primary(Token **rest, Token *tok) { } if (tok->kind == TK_NUM) { - Node *node = new_num(tok->val, tok); + Node *node; + if (is_flonum(tok->ty)) { + node = new_node(ND_NUM, tok); + node->fval = tok->fval; + } else { + node = new_num(tok->val, tok); + } + + node->ty = tok->ty; *rest = tok->next; return node; } @@ -822,32 +3127,141 @@ static Node *primary(Token **rest, Token *tok) { error_tok(tok, "expected an expression"); } +static Token *parse_typedef(Token *tok, Type *basety) { + bool first = true; + + while (!consume(&tok, tok, ";")) { + if (!first) + tok = skip(tok, ","); + first = false; + + Type *ty = declarator(&tok, tok, basety); + if (!ty->name) + error_tok(ty->name_pos, "typedef name omitted"); + push_scope(get_ident(ty->name))->type_def = ty; + } + return tok; +} + static void create_param_lvars(Type *param) { if (param) { create_param_lvars(param->next); + if (!param->name) + error_tok(param->name_pos, "parameter name omitted"); new_lvar(get_ident(param->name), param); } } -static Token *function(Token *tok, Type *basety) { +// This function matches gotos or labels-as-values with labels. +// +// We cannot resolve gotos as we parse a function because gotos +// can refer a label that appears later in the function. +// So, we need to do this after we parse the entire function. +static void resolve_goto_labels(void) { + for (Node *x = gotos; x; x = x->goto_next) { + for (Node *y = labels; y; y = y->goto_next) { + if (!strcmp(x->label, y->label)) { + x->unique_label = y->unique_label; + break; + } + } + + if (x->unique_label == NULL) + error_tok(x->tok->next, "use of undeclared label"); + } + + gotos = labels = NULL; +} + +static Obj *find_func(char *name) { + Scope *sc = scope; + while (sc->next) + sc = sc->next; + + VarScope *sc2 = hashmap_get(&sc->vars, name); + if (sc2 && sc2->var && sc2->var->is_function) + return sc2->var; + return NULL; +} + +static void mark_live(Obj *var) { + if (!var->is_function || var->is_live) + return; + var->is_live = true; + + for (int i = 0; i < var->refs.len; i++) { + Obj *fn = find_func(var->refs.data[i]); + if (fn) + mark_live(fn); + } +} + +static Token *function(Token *tok, Type *basety, VarAttr *attr) { Type *ty = declarator(&tok, tok, basety); + if (!ty->name) + error_tok(ty->name_pos, "function name omitted"); + char *name_str = get_ident(ty->name); + + Obj *fn = find_func(name_str); + if (fn) { + // Redeclaration + if (!fn->is_function) + error_tok(tok, "redeclared as a different kind of symbol"); + if (fn->is_definition && equal(tok, "{")) + error_tok(tok, "redefinition of %s", name_str); + if (!fn->is_static && attr->is_static) + error_tok(tok, "static declaration follows a non-static declaration"); + fn->is_definition = fn->is_definition || equal(tok, "{"); + } else { + fn = new_gvar(name_str, ty); + fn->is_function = true; + fn->is_definition = equal(tok, "{"); + fn->is_static = attr->is_static || (attr->is_inline && !attr->is_extern); + fn->is_inline = attr->is_inline; + } - Obj *fn = new_gvar(get_ident(ty->name), ty); - fn->is_function = true; + fn->is_root = !(fn->is_static && fn->is_inline); + if (consume(&tok, tok, ";")) + return tok; + + current_fn = fn; locals = NULL; enter_scope(); create_param_lvars(ty->params); + + // A buffer for a struct/union return value is passed + // as the hidden first parameter. + Type *rty = ty->return_ty; + if ((rty->kind == TY_STRUCT || rty->kind == TY_UNION) && rty->size > 16) + new_lvar("", pointer_to(rty)); + fn->params = locals; + if (ty->is_variadic) + fn->va_area = new_lvar("__va_area__", array_of(ty_char, 136)); + fn->alloca_bottom = new_lvar("__alloca_size__", pointer_to(ty_char)); + tok = skip(tok, "{"); + + // [https://www.sigbus.info/n1570#6.4.2.2p1] "__func__" is + // automatically defined as a local variable containing the + // current function name. + push_scope("__func__")->var = + new_string_literal(fn->name, array_of(ty_char, strlen(fn->name) + 1)); + + // [GNU] __FUNCTION__ is yet another name of __func__. + push_scope("__FUNCTION__")->var = + new_string_literal(fn->name, array_of(ty_char, strlen(fn->name) + 1)); + fn->body = compound_stmt(&tok, tok); fn->locals = locals; leave_scope(); + resolve_goto_labels(); return tok; } -static Token *global_variable(Token *tok, Type *basety) { +static Token *global_variable(Token *tok, Type *basety, VarAttr *attr) { bool first = true; while (!consume(&tok, tok, ";")) { @@ -856,7 +3270,20 @@ static Token *global_variable(Token *tok, Type *basety) { first = false; Type *ty = declarator(&tok, tok, basety); - new_gvar(get_ident(ty->name), ty); + if (!ty->name) + error_tok(ty->name_pos, "variable name omitted"); + + Obj *var = new_gvar(get_ident(ty->name), ty); + var->is_definition = !attr->is_extern; + var->is_static = attr->is_static; + var->is_tls = attr->is_tls; + if (attr->align) + var->align = attr->align; + + if (equal(tok, "=")) + gvar_initializer(&tok, tok->next, var); + else if (!attr->is_extern && !attr->is_tls) + var->is_tentative = true; } return tok; } @@ -872,22 +3299,70 @@ static bool is_function(Token *tok) { return ty->kind == TY_FUNC; } -// program = (function-definition | global-variable)* +// Remove redundant tentative definitions. +static void scan_globals(void) { + Obj head; + Obj *cur = &head; + + for (Obj *var = globals; var; var = var->next) { + if (!var->is_tentative) { + cur = cur->next = var; + continue; + } + + // Find another definition of the same identifier. + Obj *var2 = globals; + for (; var2; var2 = var2->next) + if (var != var2 && var2->is_definition && !strcmp(var->name, var2->name)) + break; + + // If there's another definition, the tentative definition + // is redundant + if (!var2) + cur = cur->next = var; + } + + cur->next = NULL; + globals = head.next; +} + +static void declare_builtin_functions(void) { + Type *ty = func_type(pointer_to(ty_void)); + ty->params = copy_type(ty_int); + builtin_alloca = new_gvar("alloca", ty); + builtin_alloca->is_definition = false; +} + +// program = (typedef | function-definition | global-variable)* Obj *parse(Token *tok) { + declare_builtin_functions(); globals = NULL; while (tok->kind != TK_EOF) { - Type *basety = declspec(&tok, tok); + VarAttr attr = {}; + Type *basety = declspec(&tok, tok, &attr); + + // Typedef + if (attr.is_typedef) { + tok = parse_typedef(tok, basety); + continue; + } // Function if (is_function(tok)) { - tok = function(tok, basety); + tok = function(tok, basety, &attr); continue; } // Global variable - tok = global_variable(tok, basety); - + tok = global_variable(tok, basety, &attr); } + + for (Obj *var = globals; var; var = var->next) + if (var->is_root) + mark_live(var); + + // Remove redundant tentative definitions. + scan_globals(); return globals; } diff --git a/preprocess.c b/preprocess.c new file mode 100644 index 00000000..2bef487c --- /dev/null +++ b/preprocess.c @@ -0,0 +1,1208 @@ +// This file implements the C preprocessor. +// +// The preprocessor takes a list of tokens as an input and returns a +// new list of tokens as an output. +// +// The preprocessing language is designed in such a way that that's +// guaranteed to stop even if there is a recursive macro. +// Informally speaking, a macro is applied only once for each token. +// That is, if a macro token T appears in a result of direct or +// indirect macro expansion of T, T won't be expanded any further. +// For example, if T is defined as U, and U is defined as T, then +// token T is expanded to U and then to T and the macro expansion +// stops at that point. +// +// To achieve the above behavior, we attach for each token a set of +// macro names from which the token is expanded. The set is called +// "hideset". Hideset is initially empty, and every time we expand a +// macro, the macro name is added to the resulting tokens' hidesets. +// +// The above macro expansion algorithm is explained in this document +// written by Dave Prossor, which is used as a basis for the +// standard's wording: +// https://github.com/rui314/chibicc/wiki/cpp.algo.pdf + +#include "chibicc.h" + +typedef struct MacroParam MacroParam; +struct MacroParam { + MacroParam *next; + char *name; +}; + +typedef struct MacroArg MacroArg; +struct MacroArg { + MacroArg *next; + char *name; + bool is_va_args; + Token *tok; +}; + +typedef Token *macro_handler_fn(Token *); + +typedef struct Macro Macro; +struct Macro { + char *name; + bool is_objlike; // Object-like or function-like + MacroParam *params; + char *va_args_name; + Token *body; + macro_handler_fn *handler; +}; + +// `#if` can be nested, so we use a stack to manage nested `#if`s. +typedef struct CondIncl CondIncl; +struct CondIncl { + CondIncl *next; + enum { IN_THEN, IN_ELIF, IN_ELSE } ctx; + Token *tok; + bool included; +}; + +typedef struct Hideset Hideset; +struct Hideset { + Hideset *next; + char *name; +}; + +static HashMap macros; +static CondIncl *cond_incl; +static HashMap pragma_once; +static int include_next_idx; + +static Token *preprocess2(Token *tok); +static Macro *find_macro(Token *tok); + +static bool is_hash(Token *tok) { + return tok->at_bol && equal(tok, "#"); +} + +// Some preprocessor directives such as #include allow extraneous +// tokens before newline. This function skips such tokens. +static Token *skip_line(Token *tok) { + if (tok->at_bol) + return tok; + warn_tok(tok, "extra token"); + while (tok->at_bol) + tok = tok->next; + return tok; +} + +static Token *copy_token(Token *tok) { + Token *t = calloc(1, sizeof(Token)); + *t = *tok; + t->next = NULL; + return t; +} + +static Token *new_eof(Token *tok) { + Token *t = copy_token(tok); + t->kind = TK_EOF; + t->len = 0; + return t; +} + +static Hideset *new_hideset(char *name) { + Hideset *hs = calloc(1, sizeof(Hideset)); + hs->name = name; + return hs; +} + +static Hideset *hideset_union(Hideset *hs1, Hideset *hs2) { + Hideset head = {}; + Hideset *cur = &head; + + for (; hs1; hs1 = hs1->next) + cur = cur->next = new_hideset(hs1->name); + cur->next = hs2; + return head.next; +} + +static bool hideset_contains(Hideset *hs, char *s, int len) { + for (; hs; hs = hs->next) + if (strlen(hs->name) == len && !strncmp(hs->name, s, len)) + return true; + return false; +} + +static Hideset *hideset_intersection(Hideset *hs1, Hideset *hs2) { + Hideset head = {}; + Hideset *cur = &head; + + for (; hs1; hs1 = hs1->next) + if (hideset_contains(hs2, hs1->name, strlen(hs1->name))) + cur = cur->next = new_hideset(hs1->name); + return head.next; +} + +static Token *add_hideset(Token *tok, Hideset *hs) { + Token head = {}; + Token *cur = &head; + + for (; tok; tok = tok->next) { + Token *t = copy_token(tok); + t->hideset = hideset_union(t->hideset, hs); + cur = cur->next = t; + } + return head.next; +} + +// Append tok2 to the end of tok1. +static Token *append(Token *tok1, Token *tok2) { + if (tok1->kind == TK_EOF) + return tok2; + + Token head = {}; + Token *cur = &head; + + for (; tok1->kind != TK_EOF; tok1 = tok1->next) + cur = cur->next = copy_token(tok1); + cur->next = tok2; + return head.next; +} + +static Token *skip_cond_incl2(Token *tok) { + while (tok->kind != TK_EOF) { + if (is_hash(tok) && + (equal(tok->next, "if") || equal(tok->next, "ifdef") || + equal(tok->next, "ifndef"))) { + tok = skip_cond_incl2(tok->next->next); + continue; + } + if (is_hash(tok) && equal(tok->next, "endif")) + return tok->next->next; + tok = tok->next; + } + return tok; +} + +// Skip until next `#else`, `#elif` or `#endif`. +// Nested `#if` and `#endif` are skipped. +static Token *skip_cond_incl(Token *tok) { + while (tok->kind != TK_EOF) { + if (is_hash(tok) && + (equal(tok->next, "if") || equal(tok->next, "ifdef") || + equal(tok->next, "ifndef"))) { + tok = skip_cond_incl2(tok->next->next); + continue; + } + + if (is_hash(tok) && + (equal(tok->next, "elif") || equal(tok->next, "else") || + equal(tok->next, "endif"))) + break; + tok = tok->next; + } + return tok; +} + +// Double-quote a given string and returns it. +static char *quote_string(char *str) { + int bufsize = 3; + for (int i = 0; str[i]; i++) { + if (str[i] == '\\' || str[i] == '"') + bufsize++; + bufsize++; + } + + char *buf = calloc(1, bufsize); + char *p = buf; + *p++ = '"'; + for (int i = 0; str[i]; i++) { + if (str[i] == '\\' || str[i] == '"') + *p++ = '\\'; + *p++ = str[i]; + } + *p++ = '"'; + *p++ = '\0'; + return buf; +} + +static Token *new_str_token(char *str, Token *tmpl) { + char *buf = quote_string(str); + return tokenize(new_file(tmpl->file->name, tmpl->file->file_no, buf)); +} + +// Copy all tokens until the next newline, terminate them with +// an EOF token and then returns them. This function is used to +// create a new list of tokens for `#if` arguments. +static Token *copy_line(Token **rest, Token *tok) { + Token head = {}; + Token *cur = &head; + + for (; !tok->at_bol; tok = tok->next) + cur = cur->next = copy_token(tok); + + cur->next = new_eof(tok); + *rest = tok; + return head.next; +} + +static Token *new_num_token(int val, Token *tmpl) { + char *buf = format("%d\n", val); + return tokenize(new_file(tmpl->file->name, tmpl->file->file_no, buf)); +} + +static Token *read_const_expr(Token **rest, Token *tok) { + tok = copy_line(rest, tok); + + Token head = {}; + Token *cur = &head; + + while (tok->kind != TK_EOF) { + // "defined(foo)" or "defined foo" becomes "1" if macro "foo" + // is defined. Otherwise "0". + if (equal(tok, "defined")) { + Token *start = tok; + bool has_paren = consume(&tok, tok->next, "("); + + if (tok->kind != TK_IDENT) + error_tok(start, "macro name must be an identifier"); + Macro *m = find_macro(tok); + tok = tok->next; + + if (has_paren) + tok = skip(tok, ")"); + + cur = cur->next = new_num_token(m ? 1 : 0, start); + continue; + } + + cur = cur->next = tok; + tok = tok->next; + } + + cur->next = tok; + return head.next; +} + +// Read and evaluate a constant expression. +static long eval_const_expr(Token **rest, Token *tok) { + Token *start = tok; + Token *expr = read_const_expr(rest, tok->next); + expr = preprocess2(expr); + + if (expr->kind == TK_EOF) + error_tok(start, "no expression"); + + // [https://www.sigbus.info/n1570#6.10.1p4] The standard requires + // we replace remaining non-macro identifiers with "0" before + // evaluating a constant expression. For example, `#if foo` is + // equivalent to `#if 0` if foo is not defined. + for (Token *t = expr; t->kind != TK_EOF; t = t->next) { + if (t->kind == TK_IDENT) { + Token *next = t->next; + *t = *new_num_token(0, t); + t->next = next; + } + } + + // Convert pp-numbers to regular numbers + convert_pp_tokens(expr); + + Token *rest2; + long val = const_expr(&rest2, expr); + if (rest2->kind != TK_EOF) + error_tok(rest2, "extra token"); + return val; +} + +static CondIncl *push_cond_incl(Token *tok, bool included) { + CondIncl *ci = calloc(1, sizeof(CondIncl)); + ci->next = cond_incl; + ci->ctx = IN_THEN; + ci->tok = tok; + ci->included = included; + cond_incl = ci; + return ci; +} + +static Macro *find_macro(Token *tok) { + if (tok->kind != TK_IDENT) + return NULL; + return hashmap_get2(¯os, tok->loc, tok->len); +} + +static Macro *add_macro(char *name, bool is_objlike, Token *body) { + Macro *m = calloc(1, sizeof(Macro)); + m->name = name; + m->is_objlike = is_objlike; + m->body = body; + hashmap_put(¯os, name, m); + return m; +} + +static MacroParam *read_macro_params(Token **rest, Token *tok, char **va_args_name) { + MacroParam head = {}; + MacroParam *cur = &head; + + while (!equal(tok, ")")) { + if (cur != &head) + tok = skip(tok, ","); + + if (equal(tok, "...")) { + *va_args_name = "__VA_ARGS__"; + *rest = skip(tok->next, ")"); + return head.next; + } + + if (tok->kind != TK_IDENT) + error_tok(tok, "expected an identifier"); + + if (equal(tok->next, "...")) { + *va_args_name = strndup(tok->loc, tok->len); + *rest = skip(tok->next->next, ")"); + return head.next; + } + + MacroParam *m = calloc(1, sizeof(MacroParam)); + m->name = strndup(tok->loc, tok->len); + cur = cur->next = m; + tok = tok->next; + } + + *rest = tok->next; + return head.next; +} + +static void read_macro_definition(Token **rest, Token *tok) { + if (tok->kind != TK_IDENT) + error_tok(tok, "macro name must be an identifier"); + char *name = strndup(tok->loc, tok->len); + tok = tok->next; + + if (!tok->has_space && equal(tok, "(")) { + // Function-like macro + char *va_args_name = NULL; + MacroParam *params = read_macro_params(&tok, tok->next, &va_args_name); + + Macro *m = add_macro(name, false, copy_line(rest, tok)); + m->params = params; + m->va_args_name = va_args_name; + } else { + // Object-like macro + add_macro(name, true, copy_line(rest, tok)); + } +} + +static MacroArg *read_macro_arg_one(Token **rest, Token *tok, bool read_rest) { + Token head = {}; + Token *cur = &head; + int level = 0; + + for (;;) { + if (level == 0 && equal(tok, ")")) + break; + if (level == 0 && !read_rest && equal(tok, ",")) + break; + + if (tok->kind == TK_EOF) + error_tok(tok, "premature end of input"); + + if (equal(tok, "(")) + level++; + else if (equal(tok, ")")) + level--; + + cur = cur->next = copy_token(tok); + tok = tok->next; + } + + cur->next = new_eof(tok); + + MacroArg *arg = calloc(1, sizeof(MacroArg)); + arg->tok = head.next; + *rest = tok; + return arg; +} + +static MacroArg * +read_macro_args(Token **rest, Token *tok, MacroParam *params, char *va_args_name) { + Token *start = tok; + tok = tok->next->next; + + MacroArg head = {}; + MacroArg *cur = &head; + + MacroParam *pp = params; + for (; pp; pp = pp->next) { + if (cur != &head) + tok = skip(tok, ","); + cur = cur->next = read_macro_arg_one(&tok, tok, false); + cur->name = pp->name; + } + + if (va_args_name) { + MacroArg *arg; + if (equal(tok, ")")) { + arg = calloc(1, sizeof(MacroArg)); + arg->tok = new_eof(tok); + } else { + if (pp != params) + tok = skip(tok, ","); + arg = read_macro_arg_one(&tok, tok, true); + } + arg->name = va_args_name;; + arg->is_va_args = true; + cur = cur->next = arg; + } else if (pp) { + error_tok(start, "too many arguments"); + } + + skip(tok, ")"); + *rest = tok; + return head.next; +} + +static MacroArg *find_arg(MacroArg *args, Token *tok) { + for (MacroArg *ap = args; ap; ap = ap->next) + if (tok->len == strlen(ap->name) && !strncmp(tok->loc, ap->name, tok->len)) + return ap; + return NULL; +} + +// Concatenates all tokens in `tok` and returns a new string. +static char *join_tokens(Token *tok, Token *end) { + // Compute the length of the resulting token. + int len = 1; + for (Token *t = tok; t != end && t->kind != TK_EOF; t = t->next) { + if (t != tok && t->has_space) + len++; + len += t->len; + } + + char *buf = calloc(1, len); + + // Copy token texts. + int pos = 0; + for (Token *t = tok; t != end && t->kind != TK_EOF; t = t->next) { + if (t != tok && t->has_space) + buf[pos++] = ' '; + strncpy(buf + pos, t->loc, t->len); + pos += t->len; + } + buf[pos] = '\0'; + return buf; +} + +// Concatenates all tokens in `arg` and returns a new string token. +// This function is used for the stringizing operator (#). +static Token *stringize(Token *hash, Token *arg) { + // Create a new string token. We need to set some value to its + // source location for error reporting function, so we use a macro + // name token as a template. + char *s = join_tokens(arg, NULL); + return new_str_token(s, hash); +} + +// Concatenate two tokens to create a new token. +static Token *paste(Token *lhs, Token *rhs) { + // Paste the two tokens. + char *buf = format("%.*s%.*s", lhs->len, lhs->loc, rhs->len, rhs->loc); + + // Tokenize the resulting string. + Token *tok = tokenize(new_file(lhs->file->name, lhs->file->file_no, buf)); + if (tok->next->kind != TK_EOF) + error_tok(lhs, "pasting forms '%s', an invalid token", buf); + return tok; +} + +static bool has_varargs(MacroArg *args) { + for (MacroArg *ap = args; ap; ap = ap->next) + if (!strcmp(ap->name, "__VA_ARGS__")) + return ap->tok->kind != TK_EOF; + return false; +} + +// Replace func-like macro parameters with given arguments. +static Token *subst(Token *tok, MacroArg *args) { + Token head = {}; + Token *cur = &head; + + while (tok->kind != TK_EOF) { + // "#" followed by a parameter is replaced with stringized actuals. + if (equal(tok, "#")) { + MacroArg *arg = find_arg(args, tok->next); + if (!arg) + error_tok(tok->next, "'#' is not followed by a macro parameter"); + cur = cur->next = stringize(tok, arg->tok); + tok = tok->next->next; + continue; + } + + // [GNU] If __VA_ARG__ is empty, `,##__VA_ARGS__` is expanded + // to the empty token list. Otherwise, its expaned to `,` and + // __VA_ARGS__. + if (equal(tok, ",") && equal(tok->next, "##")) { + MacroArg *arg = find_arg(args, tok->next->next); + if (arg && arg->is_va_args) { + if (arg->tok->kind == TK_EOF) { + tok = tok->next->next->next; + } else { + cur = cur->next = copy_token(tok); + tok = tok->next->next; + } + continue; + } + } + + if (equal(tok, "##")) { + if (cur == &head) + error_tok(tok, "'##' cannot appear at start of macro expansion"); + + if (tok->next->kind == TK_EOF) + error_tok(tok, "'##' cannot appear at end of macro expansion"); + + MacroArg *arg = find_arg(args, tok->next); + if (arg) { + if (arg->tok->kind != TK_EOF) { + *cur = *paste(cur, arg->tok); + for (Token *t = arg->tok->next; t->kind != TK_EOF; t = t->next) + cur = cur->next = copy_token(t); + } + tok = tok->next->next; + continue; + } + + *cur = *paste(cur, tok->next); + tok = tok->next->next; + continue; + } + + MacroArg *arg = find_arg(args, tok); + + if (arg && equal(tok->next, "##")) { + Token *rhs = tok->next->next; + + if (arg->tok->kind == TK_EOF) { + MacroArg *arg2 = find_arg(args, rhs); + if (arg2) { + for (Token *t = arg2->tok; t->kind != TK_EOF; t = t->next) + cur = cur->next = copy_token(t); + } else { + cur = cur->next = copy_token(rhs); + } + tok = rhs->next; + continue; + } + + for (Token *t = arg->tok; t->kind != TK_EOF; t = t->next) + cur = cur->next = copy_token(t); + tok = tok->next; + continue; + } + + // If __VA_ARG__ is empty, __VA_OPT__(x) is expanded to the + // empty token list. Otherwise, __VA_OPT__(x) is expanded to x. + if (equal(tok, "__VA_OPT__") && equal(tok->next, "(")) { + MacroArg *arg = read_macro_arg_one(&tok, tok->next->next, true); + if (has_varargs(args)) + for (Token *t = arg->tok; t->kind != TK_EOF; t = t->next) + cur = cur->next = t; + tok = skip(tok, ")"); + continue; + } + + // Handle a macro token. Macro arguments are completely macro-expanded + // before they are substituted into a macro body. + if (arg) { + Token *t = preprocess2(arg->tok); + t->at_bol = tok->at_bol; + t->has_space = tok->has_space; + for (; t->kind != TK_EOF; t = t->next) + cur = cur->next = copy_token(t); + tok = tok->next; + continue; + } + + // Handle a non-macro token. + cur = cur->next = copy_token(tok); + tok = tok->next; + continue; + } + + cur->next = tok; + return head.next; +} + +// If tok is a macro, expand it and return true. +// Otherwise, do nothing and return false. +static bool expand_macro(Token **rest, Token *tok) { + if (hideset_contains(tok->hideset, tok->loc, tok->len)) + return false; + + Macro *m = find_macro(tok); + if (!m) + return false; + + // Built-in dynamic macro application such as __LINE__ + if (m->handler) { + *rest = m->handler(tok); + (*rest)->next = tok->next; + return true; + } + + // Object-like macro application + if (m->is_objlike) { + Hideset *hs = hideset_union(tok->hideset, new_hideset(m->name)); + Token *body = add_hideset(m->body, hs); + for (Token *t = body; t->kind != TK_EOF; t = t->next) + t->origin = tok; + *rest = append(body, tok->next); + (*rest)->at_bol = tok->at_bol; + (*rest)->has_space = tok->has_space; + return true; + } + + // If a funclike macro token is not followed by an argument list, + // treat it as a normal identifier. + if (!equal(tok->next, "(")) + return false; + + // Function-like macro application + Token *macro_token = tok; + MacroArg *args = read_macro_args(&tok, tok, m->params, m->va_args_name); + Token *rparen = tok; + + // Tokens that consist a func-like macro invocation may have different + // hidesets, and if that's the case, it's not clear what the hideset + // for the new tokens should be. We take the interesection of the + // macro token and the closing parenthesis and use it as a new hideset + // as explained in the Dave Prossor's algorithm. + Hideset *hs = hideset_intersection(macro_token->hideset, rparen->hideset); + hs = hideset_union(hs, new_hideset(m->name)); + + Token *body = subst(m->body, args); + body = add_hideset(body, hs); + for (Token *t = body; t->kind != TK_EOF; t = t->next) + t->origin = macro_token; + *rest = append(body, tok->next); + (*rest)->at_bol = macro_token->at_bol; + (*rest)->has_space = macro_token->has_space; + return true; +} + +char *search_include_paths(char *filename) { + if (filename[0] == '/') + return filename; + + static HashMap cache; + char *cached = hashmap_get(&cache, filename); + if (cached) + return cached; + + // Search a file from the include paths. + for (int i = 0; i < include_paths.len; i++) { + char *path = format("%s/%s", include_paths.data[i], filename); + if (!file_exists(path)) + continue; + hashmap_put(&cache, filename, path); + include_next_idx = i + 1; + return path; + } + return NULL; +} + +static char *search_include_next(char *filename) { + for (; include_next_idx < include_paths.len; include_next_idx++) { + char *path = format("%s/%s", include_paths.data[include_next_idx], filename); + if (file_exists(path)) + return path; + } + return NULL; +} + +// Read an #include argument. +static char *read_include_filename(Token **rest, Token *tok, bool *is_dquote) { + // Pattern 1: #include "foo.h" + if (tok->kind == TK_STR) { + // A double-quoted filename for #include is a special kind of + // token, and we don't want to interpret any escape sequences in it. + // For example, "\f" in "C:\foo" is not a formfeed character but + // just two non-control characters, backslash and f. + // So we don't want to use token->str. + *is_dquote = true; + *rest = skip_line(tok->next); + return strndup(tok->loc + 1, tok->len - 2); + } + + // Pattern 2: #include + if (equal(tok, "<")) { + // Reconstruct a filename from a sequence of tokens between + // "<" and ">". + Token *start = tok; + + // Find closing ">". + for (; !equal(tok, ">"); tok = tok->next) + if (tok->at_bol || tok->kind == TK_EOF) + error_tok(tok, "expected '>'"); + + *is_dquote = false; + *rest = skip_line(tok->next); + return join_tokens(start->next, tok); + } + + // Pattern 3: #include FOO + // In this case FOO must be macro-expanded to either + // a single string token or a sequence of "<" ... ">". + if (tok->kind == TK_IDENT) { + Token *tok2 = preprocess2(copy_line(rest, tok)); + return read_include_filename(&tok2, tok2, is_dquote); + } + + error_tok(tok, "expected a filename"); +} + +// Detect the following "include guard" pattern. +// +// #ifndef FOO_H +// #define FOO_H +// ... +// #endif +static char *detect_include_guard(Token *tok) { + // Detect the first two lines. + if (!is_hash(tok) || !equal(tok->next, "ifndef")) + return NULL; + tok = tok->next->next; + + if (tok->kind != TK_IDENT) + return NULL; + + char *macro = strndup(tok->loc, tok->len); + tok = tok->next; + + if (!is_hash(tok) || !equal(tok->next, "define") || !equal(tok->next->next, macro)) + return NULL; + + // Read until the end of the file. + while (tok->kind != TK_EOF) { + if (!is_hash(tok)) { + tok = tok->next; + continue; + } + + if (equal(tok->next, "endif") && tok->next->next->kind == TK_EOF) + return macro; + + if (equal(tok, "if") || equal(tok, "ifdef") || equal(tok, "ifndef")) + tok = skip_cond_incl(tok->next); + else + tok = tok->next; + } + return NULL; +} + +static Token *include_file(Token *tok, char *path, Token *filename_tok) { + // Check for "#pragma once" + if (hashmap_get(&pragma_once, path)) + return tok; + + // If we read the same file before, and if the file was guarded + // by the usual #ifndef ... #endif pattern, we may be able to + // skip the file without opening it. + static HashMap include_guards; + char *guard_name = hashmap_get(&include_guards, path); + if (guard_name && hashmap_get(¯os, guard_name)) + return tok; + + Token *tok2 = tokenize_file(path); + if (!tok2) + error_tok(filename_tok, "%s: cannot open file: %s", path, strerror(errno)); + + guard_name = detect_include_guard(tok2); + if (guard_name) + hashmap_put(&include_guards, path, guard_name); + + return append(tok2, tok); +} + +// Read #line arguments +static void read_line_marker(Token **rest, Token *tok) { + Token *start = tok; + tok = preprocess(copy_line(rest, tok)); + + if (tok->kind != TK_NUM || tok->ty->kind != TY_INT) + error_tok(tok, "invalid line marker"); + start->file->line_delta = tok->val - start->line_no; + + tok = tok->next; + if (tok->kind == TK_EOF) + return; + + if (tok->kind != TK_STR) + error_tok(tok, "filename expected"); + start->file->display_name = tok->str; +} + +// Visit all tokens in `tok` while evaluating preprocessing +// macros and directives. +static Token *preprocess2(Token *tok) { + Token head = {}; + Token *cur = &head; + + while (tok->kind != TK_EOF) { + // If it is a macro, expand it. + if (expand_macro(&tok, tok)) + continue; + + // Pass through if it is not a "#". + if (!is_hash(tok)) { + tok->line_delta = tok->file->line_delta; + tok->filename = tok->file->display_name; + cur = cur->next = tok; + tok = tok->next; + continue; + } + + Token *start = tok; + tok = tok->next; + + if (equal(tok, "include")) { + bool is_dquote; + char *filename = read_include_filename(&tok, tok->next, &is_dquote); + + if (filename[0] != '/' && is_dquote) { + char *path = format("%s/%s", dirname(strdup(start->file->name)), filename); + if (file_exists(path)) { + tok = include_file(tok, path, start->next->next); + continue; + } + } + + char *path = search_include_paths(filename); + tok = include_file(tok, path ? path : filename, start->next->next); + continue; + } + + if (equal(tok, "include_next")) { + bool ignore; + char *filename = read_include_filename(&tok, tok->next, &ignore); + char *path = search_include_next(filename); + tok = include_file(tok, path ? path : filename, start->next->next); + continue; + } + + if (equal(tok, "define")) { + read_macro_definition(&tok, tok->next); + continue; + } + + if (equal(tok, "undef")) { + tok = tok->next; + if (tok->kind != TK_IDENT) + error_tok(tok, "macro name must be an identifier"); + undef_macro(strndup(tok->loc, tok->len)); + tok = skip_line(tok->next); + continue; + } + + if (equal(tok, "if")) { + long val = eval_const_expr(&tok, tok); + push_cond_incl(start, val); + if (!val) + tok = skip_cond_incl(tok); + continue; + } + + if (equal(tok, "ifdef")) { + bool defined = find_macro(tok->next); + push_cond_incl(tok, defined); + tok = skip_line(tok->next->next); + if (!defined) + tok = skip_cond_incl(tok); + continue; + } + + if (equal(tok, "ifndef")) { + bool defined = find_macro(tok->next); + push_cond_incl(tok, !defined); + tok = skip_line(tok->next->next); + if (defined) + tok = skip_cond_incl(tok); + continue; + } + + if (equal(tok, "elif")) { + if (!cond_incl || cond_incl->ctx == IN_ELSE) + error_tok(start, "stray #elif"); + cond_incl->ctx = IN_ELIF; + + if (!cond_incl->included && eval_const_expr(&tok, tok)) + cond_incl->included = true; + else + tok = skip_cond_incl(tok); + continue; + } + + if (equal(tok, "else")) { + if (!cond_incl || cond_incl->ctx == IN_ELSE) + error_tok(start, "stray #else"); + cond_incl->ctx = IN_ELSE; + tok = skip_line(tok->next); + + if (cond_incl->included) + tok = skip_cond_incl(tok); + continue; + } + + if (equal(tok, "endif")) { + if (!cond_incl) + error_tok(start, "stray #endif"); + cond_incl = cond_incl->next; + tok = skip_line(tok->next); + continue; + } + + if (equal(tok, "line")) { + read_line_marker(&tok, tok->next); + continue; + } + + if (tok->kind == TK_PP_NUM) { + read_line_marker(&tok, tok); + continue; + } + + if (equal(tok, "pragma") && equal(tok->next, "once")) { + hashmap_put(&pragma_once, tok->file->name, (void *)1); + tok = skip_line(tok->next->next); + continue; + } + + if (equal(tok, "pragma")) { + do { + tok = tok->next; + } while (!tok->at_bol); + continue; + } + + if (equal(tok, "error")) + error_tok(tok, "error"); + + // `#`-only line is legal. It's called a null directive. + if (tok->at_bol) + continue; + + error_tok(tok, "invalid preprocessor directive"); + } + + cur->next = tok; + return head.next; +} + +void define_macro(char *name, char *buf) { + Token *tok = tokenize(new_file("", 1, buf)); + add_macro(name, true, tok); +} + +void undef_macro(char *name) { + hashmap_delete(¯os, name); +} + +static Macro *add_builtin(char *name, macro_handler_fn *fn) { + Macro *m = add_macro(name, true, NULL); + m->handler = fn; + return m; +} + +static Token *file_macro(Token *tmpl) { + while (tmpl->origin) + tmpl = tmpl->origin; + return new_str_token(tmpl->file->display_name, tmpl); +} + +static Token *line_macro(Token *tmpl) { + while (tmpl->origin) + tmpl = tmpl->origin; + int i = tmpl->line_no + tmpl->file->line_delta; + return new_num_token(i, tmpl); +} + +// __COUNTER__ is expanded to serial values starting from 0. +static Token *counter_macro(Token *tmpl) { + static int i = 0; + return new_num_token(i++, tmpl); +} + +// __TIMESTAMP__ is expanded to a string describing the last +// modification time of the current file. E.g. +// "Fri Jul 24 01:32:50 2020" +static Token *timestamp_macro(Token *tmpl) { + struct stat st; + if (stat(tmpl->file->name, &st) != 0) + return new_str_token("??? ??? ?? ??:??:?? ????", tmpl); + + char buf[30]; + ctime_r(&st.st_mtime, buf); + buf[24] = '\0'; + return new_str_token(buf, tmpl); +} + +static Token *base_file_macro(Token *tmpl) { + return new_str_token(base_file, tmpl); +} + +// __DATE__ is expanded to the current date, e.g. "May 17 2020". +static char *format_date(struct tm *tm) { + static char mon[][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + + return format("\"%s %2d %d\"", mon[tm->tm_mon], tm->tm_mday, tm->tm_year + 1900); +} + +// __TIME__ is expanded to the current time, e.g. "13:34:03". +static char *format_time(struct tm *tm) { + return format("\"%02d:%02d:%02d\"", tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +void init_macros(void) { + // Define predefined macros + define_macro("_LP64", "1"); + define_macro("__C99_MACRO_WITH_VA_ARGS", "1"); + define_macro("__ELF__", "1"); + define_macro("__LP64__", "1"); + define_macro("__SIZEOF_DOUBLE__", "8"); + define_macro("__SIZEOF_FLOAT__", "4"); + define_macro("__SIZEOF_INT__", "4"); + define_macro("__SIZEOF_LONG_DOUBLE__", "8"); + define_macro("__SIZEOF_LONG_LONG__", "8"); + define_macro("__SIZEOF_LONG__", "8"); + define_macro("__SIZEOF_POINTER__", "8"); + define_macro("__SIZEOF_PTRDIFF_T__", "8"); + define_macro("__SIZEOF_SHORT__", "2"); + define_macro("__SIZEOF_SIZE_T__", "8"); + define_macro("__SIZE_TYPE__", "unsigned long"); + define_macro("__STDC_HOSTED__", "1"); + define_macro("__STDC_NO_COMPLEX__", "1"); + define_macro("__STDC_UTF_16__", "1"); + define_macro("__STDC_UTF_32__", "1"); + define_macro("__STDC_VERSION__", "201112L"); + define_macro("__STDC__", "1"); + define_macro("__USER_LABEL_PREFIX__", ""); + define_macro("__alignof__", "_Alignof"); + define_macro("__amd64", "1"); + define_macro("__amd64__", "1"); + define_macro("__chibicc__", "1"); + define_macro("__const__", "const"); + define_macro("__gnu_linux__", "1"); + define_macro("__inline__", "inline"); + define_macro("__linux", "1"); + define_macro("__linux__", "1"); + define_macro("__signed__", "signed"); + define_macro("__typeof__", "typeof"); + define_macro("__unix", "1"); + define_macro("__unix__", "1"); + define_macro("__volatile__", "volatile"); + define_macro("__x86_64", "1"); + define_macro("__x86_64__", "1"); + define_macro("linux", "1"); + define_macro("unix", "1"); + + add_builtin("__FILE__", file_macro); + add_builtin("__LINE__", line_macro); + add_builtin("__COUNTER__", counter_macro); + add_builtin("__TIMESTAMP__", timestamp_macro); + add_builtin("__BASE_FILE__", base_file_macro); + + time_t now = time(NULL); + struct tm *tm = localtime(&now); + define_macro("__DATE__", format_date(tm)); + define_macro("__TIME__", format_time(tm)); +} + +typedef enum { + STR_NONE, STR_UTF8, STR_UTF16, STR_UTF32, STR_WIDE, +} StringKind; + +static StringKind getStringKind(Token *tok) { + if (!strcmp(tok->loc, "u8")) + return STR_UTF8; + + switch (tok->loc[0]) { + case '"': return STR_NONE; + case 'u': return STR_UTF16; + case 'U': return STR_UTF32; + case 'L': return STR_WIDE; + } + unreachable(); +} + +// Concatenate adjacent string literals into a single string literal +// as per the C spec. +static void join_adjacent_string_literals(Token *tok) { + // First pass: If regular string literals are adjacent to wide + // string literals, regular string literals are converted to a wide + // type before concatenation. In this pass, we do the conversion. + for (Token *tok1 = tok; tok1->kind != TK_EOF;) { + if (tok1->kind != TK_STR || tok1->next->kind != TK_STR) { + tok1 = tok1->next; + continue; + } + + StringKind kind = getStringKind(tok1); + Type *basety = tok1->ty->base; + + for (Token *t = tok1->next; t->kind == TK_STR; t = t->next) { + StringKind k = getStringKind(t); + if (kind == STR_NONE) { + kind = k; + basety = t->ty->base; + } else if (k != STR_NONE && kind != k) { + error_tok(t, "unsupported non-standard concatenation of string literals"); + } + } + + if (basety->size > 1) + for (Token *t = tok1; t->kind == TK_STR; t = t->next) + if (t->ty->base->size == 1) + *t = *tokenize_string_literal(t, basety); + + while (tok1->kind == TK_STR) + tok1 = tok1->next; + } + + // Second pass: concatenate adjacent string literals. + for (Token *tok1 = tok; tok1->kind != TK_EOF;) { + if (tok1->kind != TK_STR || tok1->next->kind != TK_STR) { + tok1 = tok1->next; + continue; + } + + Token *tok2 = tok1->next; + while (tok2->kind == TK_STR) + tok2 = tok2->next; + + int len = tok1->ty->array_len; + for (Token *t = tok1->next; t != tok2; t = t->next) + len = len + t->ty->array_len - 1; + + char *buf = calloc(tok1->ty->base->size, len); + + int i = 0; + for (Token *t = tok1; t != tok2; t = t->next) { + memcpy(buf + i, t->str, t->ty->size); + i = i + t->ty->size - t->ty->base->size; + } + + *tok1 = *copy_token(tok1); + tok1->ty = array_of(tok1->ty->base, len); + tok1->str = buf; + tok1->next = tok2; + tok1 = tok2; + } +} + +// Entry point function of the preprocessor. +Token *preprocess(Token *tok) { + tok = preprocess2(tok); + if (cond_incl) + error_tok(cond_incl->tok, "unterminated conditional directive"); + convert_pp_tokens(tok); + join_adjacent_string_literals(tok); + + for (Token *t = tok; t; t = t->next) + t->line_no += t->line_delta; + return tok; +} diff --git a/strings.c b/strings.c index 485d033b..9e674286 100644 --- a/strings.c +++ b/strings.c @@ -1,5 +1,21 @@ #include "chibicc.h" +void strarray_push(StringArray *arr, char *s) { + if (!arr->data) { + arr->data = calloc(8, sizeof(char *)); + arr->capacity = 8; + } + + if (arr->capacity == arr->len) { + arr->data = realloc(arr->data, sizeof(char *) * arr->capacity * 2); + arr->capacity *= 2; + for (int i = arr->len; i < arr->capacity; i++) + arr->data[i] = NULL; + } + + arr->data[arr->len++] = s; +} + // Takes a printf-style format string and returns a formatted string. char *format(char *fmt, ...) { char *buf; diff --git a/test/alignof.c b/test/alignof.c new file mode 100644 index 00000000..d5c2594b --- /dev/null +++ b/test/alignof.c @@ -0,0 +1,49 @@ +#include "test.h" + +int _Alignas(512) g1; +int _Alignas(512) g2; +char g3; +int g4; +long g5; +char g6; + +int main() { + ASSERT(1, _Alignof(char)); + ASSERT(2, _Alignof(short)); + ASSERT(4, _Alignof(int)); + ASSERT(8, _Alignof(long)); + ASSERT(8, _Alignof(long long)); + ASSERT(1, _Alignof(char[3])); + ASSERT(4, _Alignof(int[3])); + ASSERT(1, _Alignof(struct {char a; char b;}[2])); + ASSERT(8, _Alignof(struct {char a; long b;}[2])); + + ASSERT(1, ({ _Alignas(char) char x, y; &y-&x; })); + ASSERT(8, ({ _Alignas(long) char x, y; &y-&x; })); + ASSERT(32, ({ _Alignas(32) char x, y; &y-&x; })); + ASSERT(32, ({ _Alignas(32) int *x, *y; ((char *)&y)-((char *)&x); })); + ASSERT(16, ({ struct { _Alignas(16) char x, y; } a; &a.y-&a.x; })); + ASSERT(8, ({ struct T { _Alignas(8) char a; }; _Alignof(struct T); })); + + ASSERT(0, (long)(char *)&g1 % 512); + ASSERT(0, (long)(char *)&g2 % 512); + ASSERT(0, (long)(char *)&g4 % 4); + ASSERT(0, (long)(char *)&g5 % 8); + + ASSERT(1, ({ char x; _Alignof(x); })); + ASSERT(4, ({ int x; _Alignof(x); })); + ASSERT(1, ({ char x; _Alignof x; })); + ASSERT(4, ({ int x; _Alignof x; })); + + ASSERT(1, _Alignof(char) << 31 >> 31); + ASSERT(1, _Alignof(char) << 63 >> 63); + ASSERT(1, ({ char x; _Alignof(x) << 63 >> 63; })); + + ASSERT(0, ({ char x[16]; (unsigned long)&x % 16; })); + ASSERT(0, ({ char x[17]; (unsigned long)&x % 16; })); + ASSERT(0, ({ char x[100]; (unsigned long)&x % 16; })); + ASSERT(0, ({ char x[101]; (unsigned long)&x % 16; })); + + printf("OK\n"); + return 0; +} diff --git a/test/alloca.c b/test/alloca.c new file mode 100644 index 00000000..ed6ffbc4 --- /dev/null +++ b/test/alloca.c @@ -0,0 +1,28 @@ +#include "test.h" + +void *fn(int x, void *p, int y) { return p; } + +int main() { + int i = 0; + + char *p1 = alloca(16); + char *p2 = alloca(16); + char *p3 = 1 + (char *)alloca(3) + 1; + p3 -= 2; + char *p4 = fn(1, alloca(16), 3); + + ASSERT(16, p1 - p2); + ASSERT(16, p2 - p3); + ASSERT(16, p3 - p4); + + memcpy(p1, "0123456789abcdef", 16); + memcpy(p2, "ghijklmnopqrstuv", 16); + memcpy(p3, "wxy", 3); + + ASSERT(0, memcmp(p1, "0123456789abcdef", 16)); + ASSERT(0, memcmp(p2, "ghijklmnopqrstuv", 16)); + ASSERT(0, memcmp(p3, "wxy", 3)); + + printf("OK\n"); + return 0; +} diff --git a/test/arith.c b/test/arith.c index 44b699ca..f98b6808 100644 --- a/test/arith.c +++ b/test/arith.c @@ -31,6 +31,114 @@ int main() { ASSERT(1, 1>=1); ASSERT(0, 1>=2); + ASSERT(0, 1073741824 * 100 / 100); + + ASSERT(7, ({ int i=2; i+=5; i; })); + ASSERT(7, ({ int i=2; i+=5; })); + ASSERT(3, ({ int i=5; i-=2; i; })); + ASSERT(3, ({ int i=5; i-=2; })); + ASSERT(6, ({ int i=3; i*=2; i; })); + ASSERT(6, ({ int i=3; i*=2; })); + ASSERT(3, ({ int i=6; i/=2; i; })); + ASSERT(3, ({ int i=6; i/=2; })); + + ASSERT(3, ({ int i=2; ++i; })); + ASSERT(2, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; ++*p; })); + ASSERT(0, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; --*p; })); + + ASSERT(2, ({ int i=2; i++; })); + ASSERT(2, ({ int i=2; i--; })); + ASSERT(3, ({ int i=2; i++; i; })); + ASSERT(1, ({ int i=2; i--; i; })); + ASSERT(1, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; *p++; })); + ASSERT(1, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; *p--; })); + + ASSERT(0, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p++)--; a[0]; })); + ASSERT(0, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*(p--))--; a[1]; })); + ASSERT(2, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p)--; a[2]; })); + ASSERT(2, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p)--; p++; *p; })); + + ASSERT(0, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p++)--; a[0]; })); + ASSERT(0, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p++)--; a[1]; })); + ASSERT(2, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p++)--; a[2]; })); + ASSERT(2, ({ int a[3]; a[0]=0; a[1]=1; a[2]=2; int *p=a+1; (*p++)--; *p; })); + + ASSERT(0, !1); + ASSERT(0, !2); + ASSERT(1, !0); + ASSERT(1, !(char)0); + ASSERT(0, !(long)3); + ASSERT(4, sizeof(!(char)0)); + ASSERT(4, sizeof(!(long)0)); + + ASSERT(-1, ~0); + ASSERT(0, ~-1); + + ASSERT(5, 17%6); + ASSERT(5, ((long)17)%6); + ASSERT(2, ({ int i=10; i%=4; i; })); + ASSERT(2, ({ long i=10; i%=4; i; })); + + ASSERT(0, 0&1); + ASSERT(1, 3&1); + ASSERT(3, 7&3); + ASSERT(10, -1&10); + + ASSERT(1, 0|1); + ASSERT(0b10011, 0b10000|0b00011); + + ASSERT(0, 0^0); + ASSERT(0, 0b1111^0b1111); + ASSERT(0b110100, 0b111000^0b001100); + + ASSERT(2, ({ int i=6; i&=3; i; })); + ASSERT(7, ({ int i=6; i|=3; i; })); + ASSERT(10, ({ int i=15; i^=5; i; })); + + ASSERT(1, 1<<0); + ASSERT(8, 1<<3); + ASSERT(10, 5<<1); + ASSERT(2, 5>>1); + ASSERT(-1, -1>>1); + ASSERT(1, ({ int i=1; i<<=0; i; })); + ASSERT(8, ({ int i=1; i<<=3; i; })); + ASSERT(10, ({ int i=5; i<<=1; i; })); + ASSERT(2, ({ int i=5; i>>=1; i; })); + ASSERT(-1, -1); + ASSERT(-1, ({ int i=-1; i; })); + ASSERT(-1, ({ int i=-1; i>>=1; i; })); + + ASSERT(2, 0?1:2); + ASSERT(1, 1?1:2); + ASSERT(-1, 0?-2:-1); + ASSERT(-2, 1?-2:-1); + ASSERT(4, sizeof(0?1:2)); + ASSERT(8, sizeof(0?(long)1:(long)2)); + ASSERT(-1, 0?(long)-2:-1); + ASSERT(-1, 0?-2:(long)-1); + ASSERT(-2, 1?(long)-2:-1); + ASSERT(-2, 1?-2:(long)-1); + + 1 ? -2 : (void)-1; + + ASSERT(20, ({ int x; int *p=&x; p+20-p; })); + ASSERT(1, ({ int x; int *p=&x; p+20-p>0; })); + ASSERT(-20, ({ int x; int *p=&x; p-20-p; })); + ASSERT(1, ({ int x; int *p=&x; p-20-p<0; })); + + ASSERT(15, (char *)0xffffffffffffffff - (char *)0xfffffffffffffff0); + ASSERT(-15, (char *)0xfffffffffffffff0 - (char *)0xffffffffffffffff); + ASSERT(1, (void *)0xffffffffffffffff > (void *)0); + + ASSERT(3, 3?:5); + ASSERT(5, 0?:5); + ASSERT(4, ({ int i = 3; ++i?:10; })); + + ASSERT(3, (long double)3); + ASSERT(5, (long double)3+2); + ASSERT(6, (long double)3*2); + ASSERT(5, (long double)3+2.0); + printf("OK\n"); return 0; } diff --git a/test/asm.c b/test/asm.c new file mode 100644 index 00000000..e0667842 --- /dev/null +++ b/test/asm.c @@ -0,0 +1,23 @@ +#include "test.h" + +char *asm_fn1(void) { + asm("mov $50, %rax\n\t" + "mov %rbp, %rsp\n\t" + "pop %rbp\n\t" + "ret"); +} + +char *asm_fn2(void) { + asm inline volatile("mov $55, %rax\n\t" + "mov %rbp, %rsp\n\t" + "pop %rbp\n\t" + "ret"); +} + +int main() { + ASSERT(50, asm_fn1()); + ASSERT(55, asm_fn2()); + + printf("OK\n"); + return 0; +} diff --git a/test/atomic.c b/test/atomic.c new file mode 100644 index 00000000..476bef6c --- /dev/null +++ b/test/atomic.c @@ -0,0 +1,63 @@ +#include "test.h" +#include +#include + +static int incr(_Atomic int *p) { + int oldval = *p; + int newval; + do { + newval = oldval + 1; + } while (!atomic_compare_exchange_weak(p, &oldval, newval)); + return newval; +} + +static int add1(void *arg) { + _Atomic int *x = arg; + for (int i = 0; i < 1000*1000; i++) + incr(x); + return 0; +} + +static int add2(void *arg) { + _Atomic int *x = arg; + for (int i = 0; i < 1000*1000; i++) + (*x)++; + return 0; +} + +static int add3(void *arg) { + _Atomic int *x = arg; + for (int i = 0; i < 1000*1000; i++) + *x += 5; + return 0; +} + +static int add_millions(void) { + _Atomic int x = 0; + + pthread_t thr1; + pthread_t thr2; + pthread_t thr3; + + pthread_create(&thr1, NULL, add1, &x); + pthread_create(&thr2, NULL, add2, &x); + pthread_create(&thr3, NULL, add3, &x); + + for (int i = 0; i < 1000*1000; i++) + x--; + + pthread_join(thr1, NULL); + pthread_join(thr2, NULL); + pthread_join(thr3, NULL); + return x; +} + +int main() { + ASSERT(6*1000*1000, add_millions()); + + ASSERT(3, ({ int x=3; atomic_exchange(&x, 5); })); + ASSERT(5, ({ int x=3; atomic_exchange(&x, 5); x; })); + + printf("OK\n"); + return 0; +} diff --git a/test/attribute.c b/test/attribute.c new file mode 100644 index 00000000..9195d3bb --- /dev/null +++ b/test/attribute.c @@ -0,0 +1,39 @@ +#include "test.h" +#include "stddef.h" + +int main() { + ASSERT(5, ({ struct { char a; int b; } __attribute__((packed)) x; sizeof(x); })); + ASSERT(0, offsetof(struct __attribute__((packed)) { char a; int b; }, a)); + ASSERT(1, offsetof(struct __attribute__((packed)) { char a; int b; }, b)); + + ASSERT(5, ({ struct __attribute__((packed)) { char a; int b; } x; sizeof(x); })); + ASSERT(0, offsetof(struct { char a; int b; } __attribute__((packed)), a)); + ASSERT(1, offsetof(struct { char a; int b; } __attribute__((packed)), b)); + + ASSERT(9, ({ typedef struct { char a; int b[2]; } __attribute__((packed)) T; sizeof(T); })); + ASSERT(9, ({ typedef struct __attribute__((packed)) { char a; int b[2]; } T; sizeof(T); })); + + ASSERT(1, offsetof(struct __attribute__((packed)) T { char a; int b[2]; }, b)); + ASSERT(1, _Alignof(struct __attribute__((packed)) { char a; int b[2]; })); + + ASSERT(8, ({ struct __attribute__((aligned(8))) { int a; } x; _Alignof(x); })); + ASSERT(8, ({ struct { int a; } __attribute__((aligned(8))) x; _Alignof(x); })); + + ASSERT(8, ({ struct __attribute__((aligned(8), packed)) { char a; int b; } x; _Alignof(x); })); + ASSERT(8, ({ struct { char a; int b; } __attribute__((aligned(8), packed)) x; _Alignof(x); })); + ASSERT(1, offsetof(struct __attribute__((aligned(8), packed)) { char a; int b; }, b)); + ASSERT(1, offsetof(struct { char a; int b; } __attribute__((aligned(8), packed)), b)); + + ASSERT(8, ({ struct __attribute__((aligned(8))) __attribute__((packed)) { char a; int b; } x; _Alignof(x); })); + ASSERT(8, ({ struct { char a; int b; } __attribute__((aligned(8))) __attribute__((packed)) x; _Alignof(x); })); + ASSERT(1, offsetof(struct __attribute__((aligned(8))) __attribute__((packed)) { char a; int b; }, b)); + ASSERT(1, offsetof(struct { char a; int b; } __attribute__((aligned(8))) __attribute__((packed)), b)); + + ASSERT(8, ({ struct __attribute__((aligned(8))) { char a; int b; } __attribute__((packed)) x; _Alignof(x); })); + ASSERT(1, offsetof(struct __attribute__((aligned(8))) { char a; int b; } __attribute__((packed)), b)); + + ASSERT(16, ({ struct __attribute__((aligned(8+8))) { char a; int b; } x; _Alignof(x); })); + + printf("OK\n"); + return 0; +} diff --git a/test/bitfield.c b/test/bitfield.c new file mode 100644 index 00000000..169081c4 --- /dev/null +++ b/test/bitfield.c @@ -0,0 +1,57 @@ +#include "test.h" + +struct { + char a; + int b : 5; + int c : 10; +} g45 = {1, 2, 3}, g46={}; + +int main() { + ASSERT(4, sizeof(struct {int x:1; })); + ASSERT(8, sizeof(struct {long x:1; })); + + struct bit1 { + short a; + char b; + int c : 2; + int d : 3; + int e : 3; + }; + + ASSERT(4, sizeof(struct bit1)); + ASSERT(1, ({ struct bit1 x; x.a=1; x.b=2; x.c=3; x.d=4; x.e=5; x.a; })); + ASSERT(1, ({ struct bit1 x={1,2,3,4,5}; x.a; })); + ASSERT(2, ({ struct bit1 x={1,2,3,4,5}; x.b; })); + ASSERT(-1, ({ struct bit1 x={1,2,3,4,5}; x.c; })); + ASSERT(-4, ({ struct bit1 x={1,2,3,4,5}; x.d; })); + ASSERT(-3, ({ struct bit1 x={1,2,3,4,5}; x.e; })); + + ASSERT(1, g45.a); + ASSERT(2, g45.b); + ASSERT(3, g45.c); + + ASSERT(0, g46.a); + ASSERT(0, g46.b); + ASSERT(0, g46.c); + + typedef struct { + int a : 10; + int b : 10; + int c : 10; + } T3; + + ASSERT(1, ({ T3 x={1,2,3}; x.a++; })); + ASSERT(2, ({ T3 x={1,2,3}; x.b++; })); + ASSERT(3, ({ T3 x={1,2,3}; x.c++; })); + + ASSERT(2, ({ T3 x={1,2,3}; ++x.a; })); + ASSERT(3, ({ T3 x={1,2,3}; ++x.b; })); + ASSERT(4, ({ T3 x={1,2,3}; ++x.c; })); + + ASSERT(4, sizeof(struct {int a:3; int c:1; int c:5;})); + ASSERT(8, sizeof(struct {int a:3; int:0; int c:5;})); + ASSERT(4, sizeof(struct {int a:3; int:0;})); + + printf("OK\n"); + return 0; +} diff --git a/test/builtin.c b/test/builtin.c new file mode 100644 index 00000000..178c555f --- /dev/null +++ b/test/builtin.c @@ -0,0 +1,33 @@ +#include "test.h" + +int main() { + ASSERT(1, __builtin_types_compatible_p(int, int)); + ASSERT(1, __builtin_types_compatible_p(double, double)); + ASSERT(0, __builtin_types_compatible_p(int, long)); + ASSERT(0, __builtin_types_compatible_p(long, float)); + ASSERT(1, __builtin_types_compatible_p(int *, int *)); + ASSERT(0, __builtin_types_compatible_p(short *, int *)); + ASSERT(0, __builtin_types_compatible_p(int **, int *)); + ASSERT(1, __builtin_types_compatible_p(const int, int)); + ASSERT(0, __builtin_types_compatible_p(unsigned, int)); + ASSERT(1, __builtin_types_compatible_p(signed, int)); + ASSERT(0, __builtin_types_compatible_p(struct {int a;}, struct {int a;})); + + ASSERT(1, __builtin_types_compatible_p(int (*)(void), int (*)(void))); + ASSERT(1, __builtin_types_compatible_p(void (*)(int), void (*)(int))); + ASSERT(1, __builtin_types_compatible_p(void (*)(int, double), void (*)(int, double))); + ASSERT(1, __builtin_types_compatible_p(int (*)(float, double), int (*)(float, double))); + ASSERT(0, __builtin_types_compatible_p(int (*)(float, double), int)); + ASSERT(0, __builtin_types_compatible_p(int (*)(float, double), int (*)(float))); + ASSERT(0, __builtin_types_compatible_p(int (*)(float, double), int (*)(float, double, int))); + ASSERT(1, __builtin_types_compatible_p(double (*)(...), double (*)(...))); + ASSERT(0, __builtin_types_compatible_p(double (*)(...), double (*)(void))); + + ASSERT(1, ({ typedef struct {int a;} T; __builtin_types_compatible_p(T, T); })); + ASSERT(1, ({ typedef struct {int a;} T; __builtin_types_compatible_p(T, const T); })); + + ASSERT(1, ({ struct {int a; int b;} x; __builtin_types_compatible_p(typeof(x.a), typeof(x.b)); })); + + printf("OK\n"); + return 0; +} diff --git a/test/cast.c b/test/cast.c new file mode 100644 index 00000000..2ce8d3ed --- /dev/null +++ b/test/cast.c @@ -0,0 +1,59 @@ +#include "test.h" + +int main() { + ASSERT(131585, (int)8590066177); + ASSERT(513, (short)8590066177); + ASSERT(1, (char)8590066177); + ASSERT(1, (long)1); + ASSERT(0, (long)&*(int *)0); + ASSERT(513, ({ int x=512; *(char *)&x=1; x; })); + ASSERT(5, ({ int x=5; long y=(long)&x; *(int*)y; })); + + (void)1; + + ASSERT(-1, (char)255); + ASSERT(-1, (signed char)255); + ASSERT(255, (unsigned char)255); + ASSERT(-1, (short)65535); + ASSERT(65535, (unsigned short)65535); + ASSERT(-1, (int)0xffffffff); + ASSERT(0xffffffff, (unsigned)0xffffffff); + + ASSERT(1, -1<1); + ASSERT(0, -1<(unsigned)1); + ASSERT(254, (char)127+(char)127); + ASSERT(65534, (short)32767+(short)32767); + ASSERT(-1, -1>>1); + ASSERT(-1, (unsigned long)-1); + ASSERT(2147483647, ((unsigned)-1)>>1); + ASSERT(-50, (-100)/2); + ASSERT(2147483598, ((unsigned)-100)/2); + ASSERT(9223372036854775758, ((unsigned long)-100)/2); + ASSERT(0, ((long)-1)/(unsigned)100); + ASSERT(-2, (-100)%7); + ASSERT(2, ((unsigned)-100)%7); + ASSERT(6, ((unsigned long)-100)%9); + + ASSERT(65535, (int)(unsigned short)65535); + ASSERT(65535, ({ unsigned short x = 65535; x; })); + ASSERT(65535, ({ unsigned short x = 65535; (int)x; })); + + ASSERT(-1, ({ typedef short T; T x = 65535; (int)x; })); + ASSERT(65535, ({ typedef unsigned short T; T x = 65535; (int)x; })); + + ASSERT(0, (_Bool)0.0); + ASSERT(1, (_Bool)0.1); + ASSERT(3, (char)3.0); + ASSERT(1000, (short)1000.3); + ASSERT(3, (int)3.99); + ASSERT(2000000000000000, (long)2e15); + ASSERT(3, (float)3.5); + ASSERT(5, (double)(float)5.5); + ASSERT(3, (float)3); + ASSERT(3, (double)3); + ASSERT(3, (float)3L); + ASSERT(3, (double)3L); + + printf("OK\n"); + return 0; +} diff --git a/test/common b/test/common index 9d164961..9676755a 100644 --- a/test/common +++ b/test/common @@ -1,3 +1,4 @@ +#include #include #include @@ -9,3 +10,110 @@ void assert(int expected, int actual, char *code) { exit(1); } } + +static int static_fn() { return 5; } +int ext1 = 5; +int *ext2 = &ext1; +int ext3 = 7; +int ext_fn1(int x) { return x; } +int ext_fn2(int x) { return x; } +int common_ext2 = 3; +static int common_local; + +int false_fn() { return 512; } +int true_fn() { return 513; } +int char_fn() { return (2<<8)+3; } +int short_fn() { return (2<<16)+5; } + +int uchar_fn() { return (2<<10)-1-4; } +int ushort_fn() { return (2<<20)-1-7; } + +int schar_fn() { return (2<<10)-1-4; } +int sshort_fn() { return (2<<20)-1-7; } + +int add_all(int n, ...) { + va_list ap; + va_start(ap, n); + + int sum = 0; + for (int i = 0; i < n; i++) + sum += va_arg(ap, int); + return sum; +} + +float add_float(float x, float y) { + return x + y; +} + +double add_double(double x, double y) { + return x + y; +} + +int add10_int(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9, int x10) { + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10; +} + +float add10_float(float x1, float x2, float x3, float x4, float x5, float x6, float x7, float x8, float x9, float x10) { + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10; +} + +double add10_double(double x1, double x2, double x3, double x4, double x5, double x6, double x7, double x8, double x9, double x10) { + return x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10; +} + +typedef struct { int a,b; short c; char d; } Ty4; +typedef struct { int a; float b; double c; } Ty5; +typedef struct { unsigned char a[3]; } Ty6; +typedef struct { long a, b, c; } Ty7; + +int struct_test4(Ty4 x, int n) { + switch (n) { + case 0: return x.a; + case 1: return x.b; + case 2: return x.c; + default: return x.d; + } +} + +int struct_test5(Ty5 x, int n) { + switch (n) { + case 0: return x.a; + case 1: return x.b; + default: return x.c; + } +} + +int struct_test6(Ty6 x, int n) { + return x.a[n]; +} + +int struct_test7(Ty7 x, int n) { + switch (n) { + case 0: return x.a; + case 1: return x.b; + default: return x.c; + } +} + +Ty4 struct_test24(void) { + return (Ty4){10, 20, 30, 40}; +} + +Ty5 struct_test25(void) { + return (Ty5){10, 20, 30}; +} + +Ty6 struct_test26(void) { + return (Ty6){10, 20, 30}; +} + +typedef struct { unsigned char a[10]; } Ty20; +typedef struct { unsigned char a[20]; } Ty21; + +Ty20 struct_test27(void) { + return (Ty20){10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; +} + +Ty21 struct_test28(void) { + return (Ty21){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; +} diff --git a/test/commonsym.c b/test/commonsym.c new file mode 100644 index 00000000..75995278 --- /dev/null +++ b/test/commonsym.c @@ -0,0 +1,19 @@ +#include "test.h" + +int x; +int x = 5; +int y = 7; +int y; +int common_ext1; +int common_ext2; +static int common_local; + +int main() { + ASSERT(5, x); + ASSERT(7, y); + ASSERT(0, common_ext1); + ASSERT(3, common_ext2); + + printf("OK\n"); + return 0; +} diff --git a/test/compat.c b/test/compat.c new file mode 100644 index 00000000..d0857298 --- /dev/null +++ b/test/compat.c @@ -0,0 +1,19 @@ +#include "test.h" + +_Noreturn noreturn_fn(int restrict x) { + exit(0); +} + +void funcy_type(int arg[restrict static 3]) {} + +int main() { + { volatile x; } + { int volatile x; } + { volatile int x; } + { volatile int volatile volatile x; } + { int volatile * volatile volatile x; } + { auto ** restrict __restrict __restrict__ const volatile *x; } + + printf("OK\n"); + return 0; +} diff --git a/test/complit.c b/test/complit.c new file mode 100644 index 00000000..6277f503 --- /dev/null +++ b/test/complit.c @@ -0,0 +1,33 @@ +#include "test.h" + +typedef struct Tree { + int val; + struct Tree *lhs; + struct Tree *rhs; +} Tree; + +Tree *tree = &(Tree){ + 1, + &(Tree){ + 2, + &(Tree){ 3, 0, 0 }, + &(Tree){ 4, 0, 0 } + }, + 0 +}; + +int main() { + ASSERT(1, (int){1}); + ASSERT(2, ((int[]){0,1,2})[2]); + ASSERT('a', ((struct {char a; int b;}){'a', 3}).a); + ASSERT(3, ({ int x=3; (int){x}; })); + (int){3} = 5; + + ASSERT(1, tree->val); + ASSERT(2, tree->lhs->val); + ASSERT(3, tree->lhs->lhs->val); + ASSERT(4, tree->lhs->rhs->val); + + printf("OK\n"); + return 0; +} diff --git a/test/const.c b/test/const.c new file mode 100644 index 00000000..a45478be --- /dev/null +++ b/test/const.c @@ -0,0 +1,14 @@ +#include "test.h" + +int main() { + { const x; } + { int const x; } + { const int x; } + { const int const const x; } + ASSERT(5, ({ const x = 5; x; })); + ASSERT(8, ({ const x = 8; int *const y=&x; *y; })); + ASSERT(6, ({ const x = 6; *(const * const)&x; })); + + printf("OK\n"); + return 0; +} diff --git a/test/constexpr.c b/test/constexpr.c new file mode 100644 index 00000000..3678e249 --- /dev/null +++ b/test/constexpr.c @@ -0,0 +1,55 @@ +#include "test.h" + +float g40 = 1.5; +double g41 = 0.0 ? 55 : (0, 1 + 1 * 5.0 / 2 * (double)2 * (int)2.0); + +int main() { + ASSERT(10, ({ enum { ten=1+2+3+4 }; ten; })); + ASSERT(1, ({ int i=0; switch(3) { case 5-2+0*3: i++; } i; })); + ASSERT(8, ({ int x[1+1]; sizeof(x); })); + ASSERT(6, ({ char x[8-2]; sizeof(x); })); + ASSERT(6, ({ char x[2*3]; sizeof(x); })); + ASSERT(3, ({ char x[12/4]; sizeof(x); })); + ASSERT(2, ({ char x[12%10]; sizeof(x); })); + ASSERT(0b100, ({ char x[0b110&0b101]; sizeof(x); })); + ASSERT(0b111, ({ char x[0b110|0b101]; sizeof(x); })); + ASSERT(0b110, ({ char x[0b111^0b001]; sizeof(x); })); + ASSERT(4, ({ char x[1<<2]; sizeof(x); })); + ASSERT(2, ({ char x[4>>1]; sizeof(x); })); + ASSERT(2, ({ char x[(1==1)+1]; sizeof(x); })); + ASSERT(1, ({ char x[(1!=1)+1]; sizeof(x); })); + ASSERT(1, ({ char x[(1<1)+1]; sizeof(x); })); + ASSERT(2, ({ char x[(1<=1)+1]; sizeof(x); })); + ASSERT(2, ({ char x[1?2:3]; sizeof(x); })); + ASSERT(3, ({ char x[0?2:3]; sizeof(x); })); + ASSERT(3, ({ char x[(1,3)]; sizeof(x); })); + ASSERT(2, ({ char x[!0+1]; sizeof(x); })); + ASSERT(1, ({ char x[!1+1]; sizeof(x); })); + ASSERT(2, ({ char x[~-3]; sizeof(x); })); + ASSERT(2, ({ char x[(5||6)+1]; sizeof(x); })); + ASSERT(1, ({ char x[(0||0)+1]; sizeof(x); })); + ASSERT(2, ({ char x[(1&&1)+1]; sizeof(x); })); + ASSERT(1, ({ char x[(1&&0)+1]; sizeof(x); })); + ASSERT(3, ({ char x[(int)3]; sizeof(x); })); + ASSERT(15, ({ char x[(char)0xffffff0f]; sizeof(x); })); + ASSERT(0x10f, ({ char x[(short)0xffff010f]; sizeof(x); })); + ASSERT(4, ({ char x[(int)0xfffffffffff+5]; sizeof(x); })); + ASSERT(8, ({ char x[(int*)0+2]; sizeof(x); })); + ASSERT(12, ({ char x[(int*)16-1]; sizeof(x); })); + ASSERT(3, ({ char x[(int*)16-(int*)4]; sizeof(x); })); + + ASSERT(4, ({ char x[(-1>>31)+5]; sizeof(x); })); + ASSERT(255, ({ char x[(unsigned char)0xffffffff]; sizeof(x); })); + ASSERT(0x800f, ({ char x[(unsigned short)0xffff800f]; sizeof(x); })); + ASSERT(1, ({ char x[(unsigned int)0xfffffffffff>>31]; sizeof(x); })); + ASSERT(1, ({ char x[(long)-1/((long)1<<62)+1]; sizeof(x); })); + ASSERT(4, ({ char x[(unsigned long)-1/((long)1<<62)+1]; sizeof(x); })); + ASSERT(1, ({ char x[(unsigned)1<-1]; sizeof(x); })); + ASSERT(1, ({ char x[(unsigned)1<=-1]; sizeof(x); })); + + ASSERT(1, g40==1.5); + ASSERT(1, g41==11); + + printf("OK\n"); + return 0; +} diff --git a/test/control.c b/test/control.c index d7221e2d..0a89e3dd 100644 --- a/test/control.c +++ b/test/control.c @@ -24,6 +24,77 @@ int main() { ASSERT(5, ({ int i=2, j=3; (i=5,j)=6; i; })); ASSERT(6, ({ int i=2, j=3; (i=5,j)=6; j; })); + ASSERT(55, ({ int j=0; for (int i=0; i<=10; i=i+1) j=j+i; j; })); + ASSERT(3, ({ int i=3; int j=0; for (int i=0; i<=10; i=i+1) j=j+i; i; })); + + ASSERT(1, 0||1); + ASSERT(1, 0||(2-2)||5); + ASSERT(0, 0||0); + ASSERT(0, 0||(2-2)); + + ASSERT(0, 0&&1); + ASSERT(0, (2-2)&&5); + ASSERT(1, 1&&5); + + ASSERT(3, ({ int i=0; goto a; a: i++; b: i++; c: i++; i; })); + ASSERT(2, ({ int i=0; goto e; d: i++; e: i++; f: i++; i; })); + ASSERT(1, ({ int i=0; goto i; g: i++; h: i++; i: i++; i; })); + + ASSERT(1, ({ typedef int foo; goto foo; foo:; 1; })); + + ASSERT(3, ({ int i=0; for(;i<10;i++) { if (i == 3) break; } i; })); + ASSERT(4, ({ int i=0; while (1) { if (i++ == 3) break; } i; })); + ASSERT(3, ({ int i=0; for(;i<10;i++) { for (;;) break; if (i == 3) break; } i; })); + ASSERT(4, ({ int i=0; while (1) { while(1) break; if (i++ == 3) break; } i; })); + + ASSERT(10, ({ int i=0; int j=0; for (;i<10;i++) { if (i>5) continue; j++; } i; })); + ASSERT(6, ({ int i=0; int j=0; for (;i<10;i++) { if (i>5) continue; j++; } j; })); + ASSERT(10, ({ int i=0; int j=0; for(;!i;) { for (;j!=10;j++) continue; break; } j; })); + ASSERT(11, ({ int i=0; int j=0; while (i++<10) { if (i>5) continue; j++; } i; })); + ASSERT(5, ({ int i=0; int j=0; while (i++<10) { if (i>5) continue; j++; } j; })); + ASSERT(11, ({ int i=0; int j=0; while(!i) { while (j++!=10) continue; break; } j; })); + + ASSERT(5, ({ int i=0; switch(0) { case 0:i=5;break; case 1:i=6;break; case 2:i=7;break; } i; })); + ASSERT(6, ({ int i=0; switch(1) { case 0:i=5;break; case 1:i=6;break; case 2:i=7;break; } i; })); + ASSERT(7, ({ int i=0; switch(2) { case 0:i=5;break; case 1:i=6;break; case 2:i=7;break; } i; })); + ASSERT(0, ({ int i=0; switch(3) { case 0:i=5;break; case 1:i=6;break; case 2:i=7;break; } i; })); + ASSERT(5, ({ int i=0; switch(0) { case 0:i=5;break; default:i=7; } i; })); + ASSERT(7, ({ int i=0; switch(1) { case 0:i=5;break; default:i=7; } i; })); + ASSERT(2, ({ int i=0; switch(1) { case 0: 0; case 1: 0; case 2: 0; i=2; } i; })); + ASSERT(0, ({ int i=0; switch(3) { case 0: 0; case 1: 0; case 2: 0; i=2; } i; })); + + ASSERT(3, ({ int i=0; switch(-1) { case 0xffffffff: i=3; break; } i; })); + + ASSERT(7, ({ int i=0; int j=0; do { j++; } while (i++ < 6); j; })); + ASSERT(4, ({ int i=0; int j=0; int k=0; do { if (++j > 3) break; continue; k++; } while (1); j; })); + + ASSERT(0, 0.0 && 0.0); + ASSERT(0, 0.0 && 0.1); + ASSERT(0, 0.3 && 0.0); + ASSERT(1, 0.3 && 0.5); + ASSERT(0, 0.0 || 0.0); + ASSERT(1, 0.0 || 0.1); + ASSERT(1, 0.3 || 0.0); + ASSERT(1, 0.3 || 0.5); + ASSERT(5, ({ int x; if (0.0) x=3; else x=5; x; })); + ASSERT(3, ({ int x; if (0.1) x=3; else x=5; x; })); + ASSERT(5, ({ int x=5; if (0.0) x=3; x; })); + ASSERT(3, ({ int x=5; if (0.1) x=3; x; })); + ASSERT(10, ({ double i=10.0; int j=0; for (; i; i--, j++); j; })); + ASSERT(10, ({ double i=10.0; int j=0; do j++; while(--i); j; })); + + ASSERT(2, ({ int i=0; switch(7) { case 0 ... 5: i=1; break; case 6 ... 20: i=2; break; } i; })); + ASSERT(1, ({ int i=0; switch(7) { case 0 ... 7: i=1; break; case 8 ... 10: i=2; break; } i; })); + ASSERT(1, ({ int i=0; switch(7) { case 0: i=1; break; case 7 ... 7: i=1; break; } i; })); + + ASSERT(3, ({ void *p = &&v11; int i=0; goto *p; v11:i++; v12:i++; v13:i++; i; })); + ASSERT(2, ({ void *p = &&v22; int i=0; goto *p; v21:i++; v22:i++; v23:i++; i; })); + ASSERT(1, ({ void *p = &&v33; int i=0; goto *p; v31:i++; v32:i++; v33:i++; i; })); + + ASSERT(3, ({ static void *p[]={&&v41,&&v42,&&v43}; int i=0; goto *p[0]; v41:i++; v42:i++; v43:i++; i; })); + ASSERT(2, ({ static void *p[]={&&v52,&&v52,&&v53}; int i=0; goto *p[1]; v51:i++; v52:i++; v53:i++; i; })); + ASSERT(1, ({ static void *p[]={&&v62,&&v62,&&v63}; int i=0; goto *p[2]; v61:i++; v62:i++; v63:i++; i; })); + printf("OK\n"); return 0; } diff --git a/test/decl.c b/test/decl.c new file mode 100644 index 00000000..a8fd0ddf --- /dev/null +++ b/test/decl.c @@ -0,0 +1,22 @@ +#include "test.h" + +int main() { + ASSERT(1, ({ char x; sizeof(x); })); + ASSERT(2, ({ short int x; sizeof(x); })); + ASSERT(2, ({ int short x; sizeof(x); })); + ASSERT(4, ({ int x; sizeof(x); })); + ASSERT(8, ({ long int x; sizeof(x); })); + ASSERT(8, ({ int long x; sizeof(x); })); + + ASSERT(8, ({ long long x; sizeof(x); })); + + ASSERT(0, ({ _Bool x=0; x; })); + ASSERT(1, ({ _Bool x=1; x; })); + ASSERT(1, ({ _Bool x=2; x; })); + ASSERT(1, (_Bool)1); + ASSERT(1, (_Bool)2); + ASSERT(0, (_Bool)(char)256); + + printf("OK\n"); + return 0; +} diff --git a/test/driver.sh b/test/driver.sh index 5f919fa8..c9a7b837 100755 --- a/test/driver.sh +++ b/test/driver.sh @@ -1,4 +1,6 @@ #!/bin/bash +chibicc=$1 + tmp=`mktemp -d /tmp/chibicc-test-XXXXXX` trap 'rm -rf $tmp' INT TERM HUP EXIT echo > $tmp/empty.c @@ -14,12 +16,297 @@ check() { # -o rm -f $tmp/out -./chibicc -o $tmp/out $tmp/empty.c +./chibicc -c -o $tmp/out $tmp/empty.c [ -f $tmp/out ] check -o # --help -./chibicc --help 2>&1 | grep -q chibicc +$chibicc --help 2>&1 | grep -q chibicc check --help +# -S +echo 'int main() {}' | $chibicc -S -o- -xc - | grep -q 'main:' +check -S + +# Default output file +rm -f $tmp/out.o $tmp/out.s +echo 'int main() {}' > $tmp/out.c +(cd $tmp; $OLDPWD/$chibicc -c out.c) +[ -f $tmp/out.o ] +check 'default output file' + +(cd $tmp; $OLDPWD/$chibicc -c -S out.c) +[ -f $tmp/out.s ] +check 'default output file' + +# Multiple input files +rm -f $tmp/foo.o $tmp/bar.o +echo 'int x;' > $tmp/foo.c +echo 'int y;' > $tmp/bar.c +(cd $tmp; $OLDPWD/$chibicc -c $tmp/foo.c $tmp/bar.c) +[ -f $tmp/foo.o ] && [ -f $tmp/bar.o ] +check 'multiple input files' + +rm -f $tmp/foo.s $tmp/bar.s +echo 'int x;' > $tmp/foo.c +echo 'int y;' > $tmp/bar.c +(cd $tmp; $OLDPWD/$chibicc -c -S $tmp/foo.c $tmp/bar.c) +[ -f $tmp/foo.s ] && [ -f $tmp/bar.s ] +check 'multiple input files' + +# Run linker +rm -f $tmp/foo +echo 'int main() { return 0; }' | $chibicc -o $tmp/foo -xc -xc - +$tmp/foo +check linker + +rm -f $tmp/foo +echo 'int bar(); int main() { return bar(); }' > $tmp/foo.c +echo 'int bar() { return 42; }' > $tmp/bar.c +$chibicc -o $tmp/foo $tmp/foo.c $tmp/bar.c +$tmp/foo +[ "$?" = 42 ] +check linker + +# a.out +rm -f $tmp/a.out +echo 'int main() {}' > $tmp/foo.c +(cd $tmp; $OLDPWD/$chibicc foo.c) +[ -f $tmp/a.out ] +check a.out + +# -E +echo foo > $tmp/out +echo "#include \"$tmp/out\"" | $chibicc -E -xc - | grep -q foo +check -E + +echo foo > $tmp/out1 +echo "#include \"$tmp/out1\"" | $chibicc -E -o $tmp/out2 -xc - +cat $tmp/out2 | grep -q foo +check '-E and -o' + +# -I +mkdir $tmp/dir +echo foo > $tmp/dir/i-option-test +echo "#include \"i-option-test\"" | $chibicc -I$tmp/dir -E -xc - | grep -q foo +check -I + +# -D +echo foo | $chibicc -Dfoo -E -xc - | grep -q 1 +check -D + +# -D +echo foo | $chibicc -Dfoo=bar -E -xc - | grep -q bar +check -D + +# -U +echo foo | $chibicc -Dfoo=bar -Ufoo -E -xc - | grep -q foo +check -U + +# ignored options +$chibicc -c -O -Wall -g -std=c11 -ffreestanding -fno-builtin \ + -fno-omit-frame-pointer -fno-stack-protector -fno-strict-aliasing \ + -m64 -mno-red-zone -w -o /dev/null $tmp/empty.c +check 'ignored options' + +# BOM marker +printf '\xef\xbb\xbfxyz\n' | $chibicc -E -o- -xc - | grep -q '^xyz' +check 'BOM marker' + +# Inline functions +echo 'inline void foo() {}' > $tmp/inline1.c +echo 'inline void foo() {}' > $tmp/inline2.c +echo 'int main() { return 0; }' > $tmp/inline3.c +$chibicc -o /dev/null $tmp/inline1.c $tmp/inline2.c $tmp/inline3.c +check inline + +echo 'extern inline void foo() {}' > $tmp/inline1.c +echo 'int foo(); int main() { foo(); }' > $tmp/inline2.c +$chibicc -o /dev/null $tmp/inline1.c $tmp/inline2.c +check inline + +echo 'static inline void f1() {}' | $chibicc -o- -S -xc - | grep -v -q f1: +check inline + +echo 'static inline void f1() {} void foo() { f1(); }' | $chibicc -o- -S -xc - | grep -q f1: +check inline + +echo 'static inline void f1() {} static inline void f2() { f1(); } void foo() { f1(); }' | $chibicc -o- -S -xc - | grep -q f1: +check inline + +echo 'static inline void f1() {} static inline void f2() { f1(); } void foo() { f1(); }' | $chibicc -o- -S -xc - | grep -v -q f2: +check inline + +echo 'static inline void f1() {} static inline void f2() { f1(); } void foo() { f2(); }' | $chibicc -o- -S -xc - | grep -q f1: +check inline + +echo 'static inline void f1() {} static inline void f2() { f1(); } void foo() { f2(); }' | $chibicc -o- -S -xc - | grep -q f2: +check inline + +echo 'static inline void f2(); static inline void f1() { f2(); } static inline void f2() { f1(); } void foo() {}' | $chibicc -o- -S -xc - | grep -v -q f1: +check inline + +echo 'static inline void f2(); static inline void f1() { f2(); } static inline void f2() { f1(); } void foo() {}' | $chibicc -o- -S -xc - | grep -v -q f2: +check inline + +echo 'static inline void f2(); static inline void f1() { f2(); } static inline void f2() { f1(); } void foo() { f1(); }' | $chibicc -o- -S -xc - | grep -q f1: +check inline + +echo 'static inline void f2(); static inline void f1() { f2(); } static inline void f2() { f1(); } void foo() { f1(); }' | $chibicc -o- -S -xc - | grep -q f2: +check inline + +echo 'static inline void f2(); static inline void f1() { f2(); } static inline void f2() { f1(); } void foo() { f2(); }' | $chibicc -o- -S -xc - | grep -q f1: +check inline + +echo 'static inline void f2(); static inline void f1() { f2(); } static inline void f2() { f1(); } void foo() { f2(); }' | $chibicc -o- -S -xc - | grep -q f2: +check inline + +# -idirafter +mkdir -p $tmp/dir1 $tmp/dir2 +echo foo > $tmp/dir1/idirafter +echo bar > $tmp/dir2/idirafter +echo "#include \"idirafter\"" | $chibicc -I$tmp/dir1 -I$tmp/dir2 -E -xc - | grep -q foo +check -idirafter +echo "#include \"idirafter\"" | $chibicc -idirafter $tmp/dir1 -I$tmp/dir2 -E -xc - | grep -q bar +check -idirafter + +# -fcommon +echo 'int foo;' | $chibicc -S -o- -xc - | grep -q '\.comm foo' +check '-fcommon (default)' + +echo 'int foo;' | $chibicc -fcommon -S -o- -xc - | grep -q '\.comm foo' +check '-fcommon' + +# -fno-common +echo 'int foo;' | $chibicc -fno-common -S -o- -xc - | grep -q '^foo:' +check '-fno-common' + +# -include +echo foo > $tmp/out.h +echo bar | $chibicc -include $tmp/out.h -E -o- -xc - | grep -q -z 'foo.*bar' +check -include +echo NULL | $chibicc -Iinclude -include stdio.h -E -o- -xc - | grep -q 0 +check -include + +# -x +echo 'int x;' | $chibicc -c -xc -o $tmp/foo.o - +check -xc +echo 'x:' | $chibicc -c -x assembler -o $tmp/foo.o - +check '-x assembler' + +echo 'int x;' > $tmp/foo.c +$chibicc -c -x assembler -x none -o $tmp/foo.o $tmp/foo.c +check '-x none' + +# -E +echo foo | $chibicc -E - | grep -q foo +check -E + +# .a file +echo 'void foo() {}' | $chibicc -c -xc -o $tmp/foo.o - +echo 'void bar() {}' | $chibicc -c -xc -o $tmp/bar.o - +ar rcs $tmp/foo.a $tmp/foo.o $tmp/bar.o +echo 'void foo(); void bar(); int main() { foo(); bar(); }' > $tmp/main.c +$chibicc -o $tmp/foo $tmp/main.c $tmp/foo.a +check '.a' + +# .so file +echo 'void foo() {}' | cc -fPIC -c -xc -o $tmp/foo.o - +echo 'void bar() {}' | cc -fPIC -c -xc -o $tmp/bar.o - +cc -shared -o $tmp/foo.so $tmp/foo.o $tmp/bar.o +echo 'void foo(); void bar(); int main() { foo(); bar(); }' > $tmp/main.c +$chibicc -o $tmp/foo $tmp/main.c $tmp/foo.so +check '.so' + +$chibicc -hashmap-test +check 'hashmap' + +# -M +echo '#include "out2.h"' > $tmp/out.c +echo '#include "out3.h"' >> $tmp/out.c +touch $tmp/out2.h $tmp/out3.h +$chibicc -M -I$tmp $tmp/out.c | grep -q -z '^out.o: .*/out\.c .*/out2\.h .*/out3\.h' +check -M + +# -MF +$chibicc -MF $tmp/mf -M -I$tmp $tmp/out.c +grep -q -z '^out.o: .*/out\.c .*/out2\.h .*/out3\.h' $tmp/mf +check -MF + +# -MP +$chibicc -MF $tmp/mp -MP -M -I$tmp $tmp/out.c +grep -q '^.*/out2.h:' $tmp/mp +check -MP +grep -q '^.*/out3.h:' $tmp/mp +check -MP + +# -MT +$chibicc -MT foo -M -I$tmp $tmp/out.c | grep -q '^foo:' +check -MT +$chibicc -MT foo -MT bar -M -I$tmp $tmp/out.c | grep -q '^foo bar:' +check -MT + +# -MD +echo '#include "out2.h"' > $tmp/md2.c +echo '#include "out3.h"' > $tmp/md3.c +(cd $tmp; $OLDPWD/$chibicc -c -MD -I. md2.c md3.c) +grep -q -z '^md2.o:.* md2\.c .* ./out2\.h' $tmp/md2.d +check -MD +grep -q -z '^md3.o:.* md3\.c .* ./out3\.h' $tmp/md3.d +check -MD + +$chibicc -c -MD -MF $tmp/md-mf.d -I. $tmp/md2.c +grep -q -z '^md2.o:.*md2\.c .*/out2\.h' $tmp/md-mf.d +check -MD + +echo 'extern int bar; int foo() { return bar; }' | $chibicc -fPIC -xc -c -o $tmp/foo.o - +cc -shared -o $tmp/foo.so $tmp/foo.o +echo 'int foo(); int bar=3; int main() { foo(); }' > $tmp/main.c +$chibicc -o $tmp/foo $tmp/main.c $tmp/foo.so +check -fPIC + +# #include_next +mkdir -p $tmp/next1 $tmp/next2 $tmp/next3 +echo '#include "file1.h"' > $tmp/file.c +echo '#include_next "file1.h"' > $tmp/next1/file1.h +echo '#include_next "file2.h"' > $tmp/next2/file1.h +echo 'foo' > $tmp/next3/file2.h +$chibicc -I$tmp/next1 -I$tmp/next2 -I$tmp/next3 -E $tmp/file.c | grep -q foo +check '#include_next' + +# -static +echo 'extern int bar; int foo() { return bar; }' > $tmp/foo.c +echo 'int foo(); int bar=3; int main() { foo(); }' > $tmp/bar.c +$chibicc -static -o $tmp/foo $tmp/foo.c $tmp/bar.c +check -static +file $tmp/foo | grep -q 'statically linked' +check -static + +# -shared +echo 'extern int bar; int foo() { return bar; }' > $tmp/foo.c +echo 'int foo(); int bar=3; int main() { foo(); }' > $tmp/bar.c +$chibicc -fPIC -shared -o $tmp/foo.so $tmp/foo.c $tmp/bar.c +check -shared + +# -L +echo 'extern int bar; int foo() { return bar; }' > $tmp/foo.c +$chibicc -fPIC -shared -o $tmp/libfoobar.so $tmp/foo.c +echo 'int foo(); int bar=3; int main() { foo(); }' > $tmp/bar.c +$chibicc -o $tmp/foo $tmp/bar.c -L$tmp -lfoobar +check -L + +# -Wl, +echo 'int foo() {}' | $chibicc -c -o $tmp/foo.o -xc - +echo 'int foo() {}' | $chibicc -c -o $tmp/bar.o -xc - +echo 'int main() {}' | $chibicc -c -o $tmp/baz.o -xc - +cc -Wl,-z,muldefs,--gc-sections -o $tmp/foo $tmp/foo.o $tmp/bar.o $tmp/baz.o +check -Wl, + +# -Xlinker +echo 'int foo() {}' | $chibicc -c -o $tmp/foo.o -xc - +echo 'int foo() {}' | $chibicc -c -o $tmp/bar.o -xc - +echo 'int main() {}' | $chibicc -c -o $tmp/baz.o -xc - +cc -Xlinker -z -Xlinker muldefs -Xlinker --gc-sections -o $tmp/foo $tmp/foo.o $tmp/bar.o $tmp/baz.o +check -Xlinker + echo OK diff --git a/test/enum.c b/test/enum.c new file mode 100644 index 00000000..ba580be3 --- /dev/null +++ b/test/enum.c @@ -0,0 +1,18 @@ +#include "test.h" + +int main() { + ASSERT(0, ({ enum { zero, one, two }; zero; })); + ASSERT(1, ({ enum { zero, one, two }; one; })); + ASSERT(2, ({ enum { zero, one, two }; two; })); + ASSERT(5, ({ enum { five=5, six, seven }; five; })); + ASSERT(6, ({ enum { five=5, six, seven }; six; })); + ASSERT(0, ({ enum { zero, five=5, three=3, four }; zero; })); + ASSERT(5, ({ enum { zero, five=5, three=3, four }; five; })); + ASSERT(3, ({ enum { zero, five=5, three=3, four }; three; })); + ASSERT(4, ({ enum { zero, five=5, three=3, four }; four; })); + ASSERT(4, ({ enum { zero, one, two } x; sizeof(x); })); + ASSERT(4, ({ enum t { zero, one, two }; enum t y; sizeof(y); })); + + printf("OK\n"); + return 0; +} diff --git a/test/extern.c b/test/extern.c new file mode 100644 index 00000000..4a1dac97 --- /dev/null +++ b/test/extern.c @@ -0,0 +1,25 @@ +#include "test.h" + +extern int ext1; +extern int *ext2; + +inline int inline_fn(void) { + return 3; +} + +int main() { + ASSERT(5, ext1); + ASSERT(5, *ext2); + + extern int ext3; + ASSERT(7, ext3); + + int ext_fn1(int x); + ASSERT(5, ext_fn1(5)); + + extern int ext_fn2(int x); + ASSERT(8, ext_fn2(8)); + + printf("OK\n"); + return 0; +} diff --git a/test/float.c b/test/float.c new file mode 100644 index 00000000..1ee27252 --- /dev/null +++ b/test/float.c @@ -0,0 +1,93 @@ +#include "test.h" + +int main() { + ASSERT(35, (float)(char)35); + ASSERT(35, (float)(short)35); + ASSERT(35, (float)(int)35); + ASSERT(35, (float)(long)35); + ASSERT(35, (float)(unsigned char)35); + ASSERT(35, (float)(unsigned short)35); + ASSERT(35, (float)(unsigned int)35); + ASSERT(35, (float)(unsigned long)35); + + ASSERT(35, (double)(char)35); + ASSERT(35, (double)(short)35); + ASSERT(35, (double)(int)35); + ASSERT(35, (double)(long)35); + ASSERT(35, (double)(unsigned char)35); + ASSERT(35, (double)(unsigned short)35); + ASSERT(35, (double)(unsigned int)35); + ASSERT(35, (double)(unsigned long)35); + + ASSERT(35, (char)(float)35); + ASSERT(35, (short)(float)35); + ASSERT(35, (int)(float)35); + ASSERT(35, (long)(float)35); + ASSERT(35, (unsigned char)(float)35); + ASSERT(35, (unsigned short)(float)35); + ASSERT(35, (unsigned int)(float)35); + ASSERT(35, (unsigned long)(float)35); + + ASSERT(35, (char)(double)35); + ASSERT(35, (short)(double)35); + ASSERT(35, (int)(double)35); + ASSERT(35, (long)(double)35); + ASSERT(35, (unsigned char)(double)35); + ASSERT(35, (unsigned short)(double)35); + ASSERT(35, (unsigned int)(double)35); + ASSERT(35, (unsigned long)(double)35); + + ASSERT(-2147483648, (double)(unsigned long)(long)-1); + + ASSERT(1, 2e3==2e3); + ASSERT(0, 2e3==2e5); + ASSERT(1, 2.0==2); + ASSERT(0, 5.1<5); + ASSERT(0, 5.0<5); + ASSERT(1, 4.9<5); + ASSERT(0, 5.1<=5); + ASSERT(1, 5.0<=5); + ASSERT(1, 4.9<=5); + + ASSERT(1, 2e3f==2e3); + ASSERT(0, 2e3f==2e5); + ASSERT(1, 2.0f==2); + ASSERT(0, 5.1f<5); + ASSERT(0, 5.0f<5); + ASSERT(1, 4.9f<5); + ASSERT(0, 5.1f<=5); + ASSERT(1, 5.0f<=5); + ASSERT(1, 4.9f<=5); + + ASSERT(6, 2.3+3.8); + ASSERT(-1, 2.3-3.8); + ASSERT(-3, -3.8); + ASSERT(13, 3.3*4); + ASSERT(2, 5.0/2); + + ASSERT(6, 2.3f+3.8f); + ASSERT(6, 2.3f+3.8); + ASSERT(-1, 2.3f-3.8); + ASSERT(-3, -3.8f); + ASSERT(13, 3.3f*4); + ASSERT(2, 5.0f/2); + + ASSERT(0, 0.0/0.0 == 0.0/0.0); + ASSERT(1, 0.0/0.0 != 0.0/0.0); + + ASSERT(0, 0.0/0.0 < 0); + ASSERT(0, 0.0/0.0 <= 0); + ASSERT(0, 0.0/0.0 > 0); + ASSERT(0, 0.0/0.0 >= 0); + + ASSERT(0, !3.); + ASSERT(1, !0.); + ASSERT(0, !3.f); + ASSERT(1, !0.f); + + ASSERT(5, 0.0 ? 3 : 5); + ASSERT(3, 1.2 ? 3 : 5); + + printf("OK\n"); + return 0; +} diff --git a/test/function.c b/test/function.c index ece913a8..eb6ad3a1 100644 --- a/test/function.c +++ b/test/function.c @@ -1,6 +1,6 @@ #include "test.h" -int ret3() { +int ret3(void) { return 3; return 5; } @@ -39,6 +39,176 @@ int sub_short(short a, short b, short c) { return a - b - c; } +int g1; + +int *g1_ptr(void) { return &g1; } +char int_to_char(int x) { return x; } + +int div_long(long a, long b) { + return a / b; +} + +_Bool bool_fn_add(_Bool x) { return x + 1; } +_Bool bool_fn_sub(_Bool x) { return x - 1; } + +static int static_fn(void) { return 3; } + +int param_decay(int x[]) { return x[0]; } + +int counter() { + static int i; + static int j = 1+1; + return i++ + j++; +} + +void ret_none() { + return; +} + +_Bool true_fn(); +_Bool false_fn(); +char char_fn(); +short short_fn(); + +unsigned char uchar_fn(); +unsigned short ushort_fn(); + +char schar_fn(); +short sshort_fn(); + +int add_all(int n, ...); + +typedef struct { + int gp_offset; + int fp_offset; + void *overflow_arg_area; + void *reg_save_area; +} __va_elem; + +typedef __va_elem va_list[1]; + +int add_all(int n, ...); +int sprintf(char *buf, char *fmt, ...); +int vsprintf(char *buf, char *fmt, va_list ap); + +char *fmt(char *buf, char *fmt, ...) { + va_list ap; + *ap = *(__va_elem *)__va_area__; + vsprintf(buf, fmt, ap); +} + +double add_double(double x, double y); +float add_float(float x, float y); + +float add_float3(float x, float y, float z) { + return x + y + z; +} + +double add_double3(double x, double y, double z) { + return x + y + z; +} + +int (*fnptr(int (*fn)(int n, ...)))(int, ...) { + return fn; +} + +int param_decay2(int x()) { return x(); } + +char *func_fn(void) { + return __func__; +} + +char *function_fn(void) { + return __FUNCTION__; +} + +int add10_int(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9, int x10); +float add10_float(float x1, float x2, float x3, float x4, float x5, float x6, float x7, float x8, float x9, float x10); +double add10_double(double x1, double x2, double x3, double x4, double x5, double x6, double x7, double x8, double x9, double x10); + +int many_args1(int a, int b, int c, int d, int e, int f, int g, int h) { + return g / h; +} + +double many_args2(double a, double b, double c, double d, double e, + double f, double g, double h, double i, double j) { + return i / j; +} + +int many_args3(int a, double b, int c, int d, double e, int f, + double g, int h, double i, double j, double k, + double l, double m, int n, int o, double p) { + return o / p; +} + +typedef struct { int a,b; short c; char d; } Ty4; +typedef struct { int a; float b; double c; } Ty5; +typedef struct { unsigned char a[3]; } Ty6; +typedef struct { long a, b, c; } Ty7; + +int struct_test5(Ty5 x, int n); +int struct_test4(Ty4 x, int n); +int struct_test6(Ty6 x, int n); +int struct_test7(Ty7 x, int n); + +int struct_test14(Ty4 x, int n) { + switch (n) { + case 0: return x.a; + case 1: return x.b; + case 2: return x.c; + default: return x.d; + } +} + +int struct_test15(Ty5 x, int n) { + switch (n) { + case 0: return x.a; + case 1: return x.b; + default: return x.c; + } +} + +typedef struct { unsigned char a[10]; } Ty20; +typedef struct { unsigned char a[20]; } Ty21; + +Ty4 struct_test24(void); +Ty5 struct_test25(void); +Ty6 struct_test26(void); +Ty20 struct_test27(void); +Ty21 struct_test28(void); + +Ty4 struct_test34(void) { + return (Ty4){10, 20, 30, 40}; +} + +Ty5 struct_test35(void) { + return (Ty5){10, 20, 30}; +} + +Ty6 struct_test36(void) { + return (Ty6){10, 20, 30}; +} + +Ty20 struct_test37(void) { + return (Ty20){10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; +} + +Ty21 struct_test38(void) { + return (Ty21){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; +} + +inline int inline_fn(void) { + return 3; +} + +double to_double(long double x) { + return x; +} + +long double to_ldouble(int x) { + return x; +} + int main() { ASSERT(3, ret3()); ASSERT(8, add2(3, 5)); @@ -56,6 +226,169 @@ int main() { ASSERT(1, sub_long(7, 3, 3)); ASSERT(1, sub_short(7, 3, 3)); + g1 = 3; + + ASSERT(3, *g1_ptr()); + ASSERT(5, int_to_char(261)); + ASSERT(5, int_to_char(261)); + ASSERT(-5, div_long(-10, 2)); + + ASSERT(1, bool_fn_add(3)); + ASSERT(0, bool_fn_sub(3)); + ASSERT(1, bool_fn_add(-3)); + ASSERT(0, bool_fn_sub(-3)); + ASSERT(1, bool_fn_add(0)); + ASSERT(1, bool_fn_sub(0)); + + ASSERT(3, static_fn()); + + ASSERT(3, ({ int x[2]; x[0]=3; param_decay(x); })); + + ASSERT(2, counter()); + ASSERT(4, counter()); + ASSERT(6, counter()); + + ret_none(); + + ASSERT(1, true_fn()); + ASSERT(0, false_fn()); + ASSERT(3, char_fn()); + ASSERT(5, short_fn()); + + ASSERT(6, add_all(3,1,2,3)); + ASSERT(5, add_all(4,1,2,3,-1)); + + { char buf[100]; fmt(buf, "%d %d %s", 1, 2, "foo"); printf("%s\n", buf); } + + ASSERT(0, ({ char buf[100]; sprintf(buf, "%d %d %s", 1, 2, "foo"); strcmp("1 2 foo", buf); })); + + ASSERT(0, ({ char buf[100]; fmt(buf, "%d %d %s", 1, 2, "foo"); strcmp("1 2 foo", buf); })); + + ASSERT(251, uchar_fn()); + ASSERT(65528, ushort_fn()); + ASSERT(-5, schar_fn()); + ASSERT(-8, sshort_fn()); + + ASSERT(6, add_float(2.3, 3.8)); + ASSERT(6, add_double(2.3, 3.8)); + + ASSERT(7, add_float3(2.5, 2.5, 2.5)); + ASSERT(7, add_double3(2.5, 2.5, 2.5)); + + ASSERT(0, ({ char buf[100]; sprintf(buf, "%.1f", (float)3.5); strcmp(buf, "3.5"); })); + + ASSERT(0, ({ char buf[100]; fmt(buf, "%.1f", (float)3.5); strcmp(buf, "3.5"); })); + + ASSERT(5, (add2)(2,3)); + ASSERT(5, (&add2)(2,3)); + ASSERT(7, ({ int (*fn)(int,int) = add2; fn(2,5); })); + ASSERT(6, fnptr(add_all)(3, 1, 2, 3)); + + ASSERT(3, param_decay2(ret3)); + + ASSERT(5, sizeof(__func__)); + ASSERT(0, strcmp("main", __func__)); + ASSERT(0, strcmp("func_fn", func_fn())); + ASSERT(0, strcmp("main", __FUNCTION__)); + ASSERT(0, strcmp("function_fn", function_fn())); + + + ASSERT(55, add10_int(1,2,3,4,5,6,7,8,9,10)); + ASSERT(55, add10_float(1,2,3,4,5,6,7,8,9,10)); + ASSERT(55, add10_double(1,2,3,4,5,6,7,8,9,10)); + + ASSERT(0, ({ char buf[200]; sprintf(buf, "%d %.1f %.1f %.1f %d %d %.1f %d %d %d %d %.1f %d %d %.1f %.1f %.1f %.1f %d", 1, 1.0, 1.0, 1.0, 1, 1, 1.0, 1, 1, 1, 1, 1.0, 1, 1, 1.0, 1.0, 1.0, 1.0, 1); strcmp("1 1.0 1.0 1.0 1 1 1.0 1 1 1 1 1.0 1 1 1.0 1.0 1.0 1.0 1", buf); })); + + ASSERT(4, many_args1(1,2,3,4,5,6,40,10)); + ASSERT(4, many_args2(1,2,3,4,5,6,7,8,40,10)); + ASSERT(8, many_args3(1,2,3,4,5,6,7,8,9,10,11,12,13,14,80,10)); + + ASSERT(10, ({ Ty4 x={10,20,30,40}; struct_test4(x, 0); })); + ASSERT(20, ({ Ty4 x={10,20,30,40}; struct_test4(x, 1); })); + ASSERT(30, ({ Ty4 x={10,20,30,40}; struct_test4(x, 2); })); + ASSERT(40, ({ Ty4 x={10,20,30,40}; struct_test4(x, 3); })); + + ASSERT(10, ({ Ty5 x={10,20,30}; struct_test5(x, 0); })); + ASSERT(20, ({ Ty5 x={10,20,30}; struct_test5(x, 1); })); + ASSERT(30, ({ Ty5 x={10,20,30}; struct_test5(x, 2); })); + + ASSERT(10, ({ Ty6 x={10,20,30}; struct_test6(x, 0); })); + ASSERT(20, ({ Ty6 x={10,20,30}; struct_test6(x, 1); })); + ASSERT(30, ({ Ty6 x={10,20,30}; struct_test6(x, 2); })); + + ASSERT(10, ({ Ty7 x={10,20,30}; struct_test7(x, 0); })); + ASSERT(20, ({ Ty7 x={10,20,30}; struct_test7(x, 1); })); + ASSERT(30, ({ Ty7 x={10,20,30}; struct_test7(x, 2); })); + + ASSERT(10, ({ Ty4 x={10,20,30,40}; struct_test14(x, 0); })); + ASSERT(20, ({ Ty4 x={10,20,30,40}; struct_test14(x, 1); })); + ASSERT(30, ({ Ty4 x={10,20,30,40}; struct_test14(x, 2); })); + ASSERT(40, ({ Ty4 x={10,20,30,40}; struct_test14(x, 3); })); + + ASSERT(10, ({ Ty5 x={10,20,30}; struct_test15(x, 0); })); + ASSERT(20, ({ Ty5 x={10,20,30}; struct_test15(x, 1); })); + ASSERT(30, ({ Ty5 x={10,20,30}; struct_test15(x, 2); })); + + ASSERT(10, struct_test24().a); + ASSERT(20, struct_test24().b); + ASSERT(30, struct_test24().c); + ASSERT(40, struct_test24().d); + + ASSERT(10, struct_test25().a); + ASSERT(20, struct_test25().b); + ASSERT(30, struct_test25().c); + + ASSERT(10, struct_test26().a[0]); + ASSERT(20, struct_test26().a[1]); + ASSERT(30, struct_test26().a[2]); + + ASSERT(10, struct_test27().a[0]); + ASSERT(60, struct_test27().a[5]); + ASSERT(100, struct_test27().a[9]); + + ASSERT(1, struct_test28().a[0]); + ASSERT(5, struct_test28().a[4]); + ASSERT(10, struct_test28().a[9]); + ASSERT(15, struct_test28().a[14]); + ASSERT(20, struct_test28().a[19]); + + ASSERT(10, struct_test34().a); + ASSERT(20, struct_test34().b); + ASSERT(30, struct_test34().c); + ASSERT(40, struct_test34().d); + + ASSERT(10, struct_test35().a); + ASSERT(20, struct_test35().b); + ASSERT(30, struct_test35().c); + + ASSERT(10, struct_test36().a[0]); + ASSERT(20, struct_test36().a[1]); + ASSERT(30, struct_test36().a[2]); + + ASSERT(10, struct_test37().a[0]); + ASSERT(60, struct_test37().a[5]); + ASSERT(100, struct_test37().a[9]); + + ASSERT(1, struct_test38().a[0]); + ASSERT(5, struct_test38().a[4]); + ASSERT(10, struct_test38().a[9]); + ASSERT(15, struct_test38().a[14]); + ASSERT(20, struct_test38().a[19]); + + ASSERT(5, (***add2)(2,3)); + + ASSERT(3, inline_fn()); + + ASSERT(0, ({ char buf[100]; sprintf(buf, "%Lf", (long double)12.3); strncmp(buf, "12.3", 4); })); + + ASSERT(1, to_double(3.5) == 3.5); + ASSERT(0, to_double(3.5) == 3); + + ASSERT(1, (long double)5.0 == (long double)5.0); + ASSERT(0, (long double)5.0 == (long double)5.2); + + ASSERT(1, to_ldouble(5.0) == 5.0); + ASSERT(0, to_ldouble(5.0) == 5.2); + printf("OK\n"); - return 0; } diff --git a/test/generic.c b/test/generic.c new file mode 100644 index 00000000..ac1fb9c3 --- /dev/null +++ b/test/generic.c @@ -0,0 +1,12 @@ +#include "test.h" + +int main() { + ASSERT(1, _Generic(100.0, double: 1, int *: 2, int: 3, float: 4)); + ASSERT(2, _Generic((int *)0, double: 1, int *: 2, int: 3, float: 4)); + ASSERT(2, _Generic((int[3]){}, double: 1, int *: 2, int: 3, float: 4)); + ASSERT(3, _Generic(100, double: 1, int *: 2, int: 3, float: 4)); + ASSERT(4, _Generic(100f, double: 1, int *: 2, int: 3, float: 4)); + + printf("OK\n"); + return 0; +} diff --git a/test/include1.h b/test/include1.h new file mode 100644 index 00000000..9381511f --- /dev/null +++ b/test/include1.h @@ -0,0 +1,6 @@ +#include "include2.h" + +char *include1_filename = __FILE__; +int include1_line = __LINE__; + +int include1 = 5; diff --git a/test/include2.h b/test/include2.h new file mode 100644 index 00000000..1a5ee9dc --- /dev/null +++ b/test/include2.h @@ -0,0 +1 @@ +int include2 = 7; diff --git a/test/include3.h b/test/include3.h new file mode 100644 index 00000000..820d4a6a --- /dev/null +++ b/test/include3.h @@ -0,0 +1 @@ +#define foo 3 diff --git a/test/include4.h b/test/include4.h new file mode 100644 index 00000000..1fea1a23 --- /dev/null +++ b/test/include4.h @@ -0,0 +1 @@ +#define foo 4 diff --git a/test/initializer.c b/test/initializer.c new file mode 100644 index 00000000..9df2ccdd --- /dev/null +++ b/test/initializer.c @@ -0,0 +1,267 @@ +#include "test.h" + +char g3 = 3; +short g4 = 4; +int g5 = 5; +long g6 = 6; +int g9[3] = {0, 1, 2}; +struct {char a; int b;} g11[2] = {{1, 2}, {3, 4}}; +struct {int a[2];} g12[2] = {{{1, 2}}}; +union { int a; char b[8]; } g13[2] = {0x01020304, 0x05060708}; +char g17[] = "foobar"; +char g18[10] = "foobar"; +char g19[3] = "foobar"; +char *g20 = g17+0; +char *g21 = g17+3; +char *g22 = &g17-3; +char *g23[] = {g17+0, g17+3, g17-3}; +int g24=3; +int *g25=&g24; +int g26[3] = {1, 2, 3}; +int *g27 = g26 + 1; +int *g28 = &g11[1].a; +long g29 = (long)(long)g26; +struct { struct { int a[3]; } a; } g30 = {{{1,2,3}}}; +int *g31=g30.a.a; +struct {int a[2];} g40[2] = {{1, 2}, 3, 4}; +struct {int a[2];} g41[2] = {1, 2, 3, 4}; +char g43[][4] = {'f', 'o', 'o', 0, 'b', 'a', 'r', 0}; +char *g44 = {"foo"}; +union { int a; char b[4]; } g50 = {.b[2]=0x12}; +union { int a; } g51[2] = {}; + +typedef char T60[]; +T60 g60 = {1, 2, 3}; +T60 g61 = {1, 2, 3, 4, 5, 6}; + +typedef struct { char a, b[]; } T65; +T65 g65 = {'f','o','o',0}; +T65 g66 = {'f','o','o','b','a','r',0}; + +int main() { + ASSERT(1, ({ int x[3]={1,2,3}; x[0]; })); + ASSERT(2, ({ int x[3]={1,2,3}; x[1]; })); + ASSERT(3, ({ int x[3]={1,2,3}; x[2]; })); + ASSERT(3, ({ int x[3]={1,2,3}; x[2]; })); + + ASSERT(2, ({ int x[2][3]={{1,2,3},{4,5,6}}; x[0][1]; })); + ASSERT(4, ({ int x[2][3]={{1,2,3},{4,5,6}}; x[1][0]; })); + ASSERT(6, ({ int x[2][3]={{1,2,3},{4,5,6}}; x[1][2]; })); + + ASSERT(0, ({ int x[3]={}; x[0]; })); + ASSERT(0, ({ int x[3]={}; x[1]; })); + ASSERT(0, ({ int x[3]={}; x[2]; })); + + ASSERT(2, ({ int x[2][3]={{1,2}}; x[0][1]; })); + ASSERT(0, ({ int x[2][3]={{1,2}}; x[1][0]; })); + ASSERT(0, ({ int x[2][3]={{1,2}}; x[1][2]; })); + + ASSERT('a', ({ char x[4]="abc"; x[0]; })); + ASSERT('c', ({ char x[4]="abc"; x[2]; })); + ASSERT(0, ({ char x[4]="abc"; x[3]; })); + ASSERT('a', ({ char x[2][4]={"abc","def"}; x[0][0]; })); + ASSERT(0, ({ char x[2][4]={"abc","def"}; x[0][3]; })); + ASSERT('d', ({ char x[2][4]={"abc","def"}; x[1][0]; })); + ASSERT('f', ({ char x[2][4]={"abc","def"}; x[1][2]; })); + + ASSERT(4, ({ int x[]={1,2,3,4}; x[3]; })); + ASSERT(16, ({ int x[]={1,2,3,4}; sizeof(x); })); + ASSERT(4, ({ char x[]="foo"; sizeof(x); })); + + ASSERT(4, ({ typedef char T[]; T x="foo"; T y="x"; sizeof(x); })); + ASSERT(2, ({ typedef char T[]; T x="foo"; T y="x"; sizeof(y); })); + ASSERT(2, ({ typedef char T[]; T x="x"; T y="foo"; sizeof(x); })); + ASSERT(4, ({ typedef char T[]; T x="x"; T y="foo"; sizeof(y); })); + + ASSERT(1, ({ struct {int a; int b; int c;} x={1,2,3}; x.a; })); + ASSERT(2, ({ struct {int a; int b; int c;} x={1,2,3}; x.b; })); + ASSERT(3, ({ struct {int a; int b; int c;} x={1,2,3}; x.c; })); + ASSERT(1, ({ struct {int a; int b; int c;} x={1}; x.a; })); + ASSERT(0, ({ struct {int a; int b; int c;} x={1}; x.b; })); + ASSERT(0, ({ struct {int a; int b; int c;} x={1}; x.c; })); + + ASSERT(1, ({ struct {int a; int b;} x[2]={{1,2},{3,4}}; x[0].a; })); + ASSERT(2, ({ struct {int a; int b;} x[2]={{1,2},{3,4}}; x[0].b; })); + ASSERT(3, ({ struct {int a; int b;} x[2]={{1,2},{3,4}}; x[1].a; })); + ASSERT(4, ({ struct {int a; int b;} x[2]={{1,2},{3,4}}; x[1].b; })); + + ASSERT(0, ({ struct {int a; int b;} x[2]={{1,2}}; x[1].b; })); + + ASSERT(0, ({ struct {int a; int b;} x={}; x.a; })); + ASSERT(0, ({ struct {int a; int b;} x={}; x.b; })); + + ASSERT(5, ({ typedef struct {int a,b,c,d,e,f;} T; T x={1,2,3,4,5,6}; T y; y=x; y.e; })); + ASSERT(2, ({ typedef struct {int a,b;} T; T x={1,2}; T y, z; z=y=x; z.b; })); + + ASSERT(1, ({ typedef struct {int a,b;} T; T x={1,2}; T y=x; y.a; })); + + ASSERT(4, ({ union { int a; char b[4]; } x={0x01020304}; x.b[0]; })); + ASSERT(3, ({ union { int a; char b[4]; } x={0x01020304}; x.b[1]; })); + + ASSERT(0x01020304, ({ union { struct { char a,b,c,d; } e; int f; } x={{4,3,2,1}}; x.f; })); + + ASSERT(3, g3); + ASSERT(4, g4); + ASSERT(5, g5); + ASSERT(6, g6); + + ASSERT(0, g9[0]); + ASSERT(1, g9[1]); + ASSERT(2, g9[2]); + + ASSERT(1, g11[0].a); + ASSERT(2, g11[0].b); + ASSERT(3, g11[1].a); + ASSERT(4, g11[1].b); + + ASSERT(1, g12[0].a[0]); + ASSERT(2, g12[0].a[1]); + ASSERT(0, g12[1].a[0]); + ASSERT(0, g12[1].a[1]); + + ASSERT(4, g13[0].b[0]); + ASSERT(3, g13[0].b[1]); + ASSERT(8, g13[1].b[0]); + ASSERT(7, g13[1].b[1]); + + ASSERT(7, sizeof(g17)); + ASSERT(10, sizeof(g18)); + ASSERT(3, sizeof(g19)); + + ASSERT(0, memcmp(g17, "foobar", 7)); + ASSERT(0, memcmp(g18, "foobar\0\0\0", 10)); + ASSERT(0, memcmp(g19, "foo", 3)); + + ASSERT(0, strcmp(g20, "foobar")); + ASSERT(0, strcmp(g21, "bar")); + ASSERT(0, strcmp(g22+3, "foobar")); + + ASSERT(0, strcmp(g23[0], "foobar")); + ASSERT(0, strcmp(g23[1], "bar")); + ASSERT(0, strcmp(g23[2]+3, "foobar")); + + ASSERT(3, g24); + ASSERT(3, *g25); + ASSERT(2, *g27); + ASSERT(3, *g28); + ASSERT(1, *(int *)g29); + + ASSERT(1, g31[0]); + ASSERT(2, g31[1]); + ASSERT(3, g31[2]); + + ASSERT(1, g40[0].a[0]); + ASSERT(2, g40[0].a[1]); + ASSERT(3, g40[1].a[0]); + ASSERT(4, g40[1].a[1]); + + ASSERT(1, g41[0].a[0]); + ASSERT(2, g41[0].a[1]); + ASSERT(3, g41[1].a[0]); + ASSERT(4, g41[1].a[1]); + + ASSERT(0, ({ int x[2][3]={0,1,2,3,4,5}; x[0][0]; })); + ASSERT(3, ({ int x[2][3]={0,1,2,3,4,5}; x[1][0]; })); + + ASSERT(0, ({ struct {int a; int b;} x[2]={0,1,2,3}; x[0].a; })); + ASSERT(2, ({ struct {int a; int b;} x[2]={0,1,2,3}; x[1].a; })); + + ASSERT(0, strcmp(g43[0], "foo")); + ASSERT(0, strcmp(g43[1], "bar")); + ASSERT(0, strcmp(g44, "foo")); + + ASSERT(3, ({ int a[]={1,2,3,}; a[2]; })); + ASSERT(1, ({ struct {int a,b,c;} x={1,2,3,}; x.a; })); + ASSERT(1, ({ union {int a; char b;} x={1,}; x.a; })); + ASSERT(2, ({ enum {x,y,z,}; z; })); + + ASSERT(3, sizeof(g60)); + ASSERT(6, sizeof(g61)); + + ASSERT(4, sizeof(g65)); + ASSERT(7, sizeof(g66)); + ASSERT(0, strcmp(g65.b, "oo")); + ASSERT(0, strcmp(g66.b, "oobar")); + + ASSERT(4, ({ int x[3]={1, 2, 3, [0]=4, 5}; x[0]; })); + ASSERT(5, ({ int x[3]={1, 2, 3, [0]=4, 5}; x[1]; })); + ASSERT(3, ({ int x[3]={1, 2, 3, [0]=4, 5}; x[2]; })); + + ASSERT(10, ({ int x[2][3]={1,2,3,4,5,6,[0][1]=7,8,[0]=9,[0]=10,11,[1][0]=12}; x[0][0]; })); + ASSERT(11, ({ int x[2][3]={1,2,3,4,5,6,[0][1]=7,8,[0]=9,[0]=10,11,[1][0]=12}; x[0][1]; })); + ASSERT(8, ({ int x[2][3]={1,2,3,4,5,6,[0][1]=7,8,[0]=9,[0]=10,11,[1][0]=12}; x[0][2]; })); + ASSERT(12, ({ int x[2][3]={1,2,3,4,5,6,[0][1]=7,8,[0]=9,[0]=10,11,[1][0]=12}; x[1][0]; })); + ASSERT(5, ({ int x[2][3]={1,2,3,4,5,6,[0][1]=7,8,[0]=9,[0]=10,11,[1][0]=12}; x[1][1]; })); + ASSERT(6, ({ int x[2][3]={1,2,3,4,5,6,[0][1]=7,8,[0]=9,[0]=10,11,[1][0]=12}; x[1][2]; })); + + ASSERT(7, ({ int x[2][3]={1,2,3,4,5,6,[0]={7,8},9,10}; x[0][0]; })); + ASSERT(8, ({ int x[2][3]={1,2,3,4,5,6,[0]={7,8},9,10}; x[0][1]; })); + ASSERT(3, ({ int x[2][3]={1,2,3,4,5,6,[0]={7,8},9,10}; x[0][2]; })); + ASSERT(9, ({ int x[2][3]={1,2,3,4,5,6,[0]={7,8},9,10}; x[1][0]; })); + ASSERT(10, ({ int x[2][3]={1,2,3,4,5,6,[0]={7,8},9,10}; x[1][1]; })); + ASSERT(6, ({ int x[2][3]={1,2,3,4,5,6,[0]={7,8},9,10}; x[1][2]; })); + + ASSERT(7, ((int[10]){ [3]=7 })[3]); + ASSERT(0, ((int[10]){ [3]=7 })[4]); + + ASSERT(10, ({ char x[]={[10-3]=1,2,3}; sizeof(x); })); + ASSERT(20, ({ char x[][2]={[8][1]=1,2}; sizeof(x); })); + + ASSERT(3, sizeof(g60)); + ASSERT(6, sizeof(g61)); + + ASSERT(4, sizeof(g65)); + ASSERT(7, sizeof(g66)); + ASSERT(0, strcmp(g65.b, "oo")); + ASSERT(0, strcmp(g66.b, "oobar")); + + ASSERT(7, ((int[10]){ [3] 7 })[3]); + ASSERT(0, ((int[10]){ [3] 7 })[4]); + + ASSERT(4, ({ struct { int a,b; } x={1,2,.b=3,.a=4}; x.a; })); + ASSERT(3, ({ struct { int a,b; } x={1,2,.b=3,.a=4}; x.b; })); + + ASSERT(1, ({ struct { struct { int a,b; } c; } x={.c=1,2}; x.c.a; })); + ASSERT(2, ({ struct { struct { int a,b; } c; } x={.c=1,2}; x.c.b; })); + + ASSERT(0, ({ struct { struct { int a,b; } c; } x={.c.b=1}; x.c.a; })); + ASSERT(1, ({ struct { struct { int a,b; } c; } x={.c.b=1}; x.c.b; })); + + ASSERT(1, ({ struct { int a[2]; } x={.a=1,2}; x.a[0]; })); + ASSERT(2, ({ struct { int a[2]; } x={.a=1,2}; x.a[1]; })); + + ASSERT(0, ({ struct { int a[2]; } x={.a[1]=1}; x.a[0]; })); + ASSERT(1, ({ struct { int a[2]; } x={.a[1]=1}; x.a[1]; })); + + ASSERT(3, ({ struct { int a,b; } x[]={[1].b=1,2,[0]=3,4,}; x[0].a; })); + ASSERT(4, ({ struct { int a,b; } x[]={[1].b=1,2,[0]=3,4,}; x[0].b; })); + ASSERT(0, ({ struct { int a,b; } x[]={[1].b=1,2,[0]=3,4,}; x[1].a; })); + ASSERT(1, ({ struct { int a,b; } x[]={[1].b=1,2,[0]=3,4,}; x[1].b; })); + ASSERT(2, ({ struct { int a,b; } x[]={[1].b=1,2,[0]=3,4,}; x[2].a; })); + ASSERT(0, ({ struct { int a,b; } x[]={[1].b=1,2,[0]=3,4,}; x[2].b; })); + + ASSERT(1, ({ typedef struct { int a,b; } T; T x={1,2}; T y[]={x}; y[0].a; })); + ASSERT(2, ({ typedef struct { int a,b; } T; T x={1,2}; T y[]={x}; y[0].b; })); + ASSERT(0, ({ typedef struct { int a,b; } T; T x={1,2}; T y[]={x, [0].b=3}; y[0].a; })); + ASSERT(3, ({ typedef struct { int a,b; } T; T x={1,2}; T y[]={x, [0].b=3}; y[0].b; })); + + ASSERT(5, ((struct { int a,b,c; }){ .c=5 }).c); + ASSERT(0, ((struct { int a,b,c; }){ .c=5 }).a); + + ASSERT(0x00ff, ({ union { unsigned short a; char b[2]; } x={.b[0]=0xff}; x.a; })); + ASSERT(0xff00, ({ union { unsigned short a; char b[2]; } x={.b[1]=0xff}; x.a; })); + + ASSERT(0x00120000, g50.a); + ASSERT(0, g51[0].a); + ASSERT(0, g51[1].a); + + ASSERT(1, ({ struct { struct { int a; struct { int b; }; }; int c; } x={1,2,3,.b=4,5}; x.a; })); + ASSERT(4, ({ struct { struct { int a; struct { int b; }; }; int c; } x={1,2,3,.b=4,5}; x.b; })); + ASSERT(5, ({ struct { struct { int a; struct { int b; }; }; int c; } x={1,2,3,.b=4,5}; x.c; })); + + ASSERT(16, ({ char x[]={[2 ... 10]='a', [7]='b', [15 ... 15]='c', [3 ... 5]='d'}; sizeof(x); })); + ASSERT(0, ({ char x[]={[2 ... 10]='a', [7]='b', [15 ... 15]='c', [3 ... 5]='d'}; memcmp(x, "\0\0adddabaaa\0\0\0\0c", 16); })); + + printf("OK\n"); + return 0; +} diff --git a/test/line.c b/test/line.c new file mode 100644 index 00000000..d480d98d --- /dev/null +++ b/test/line.c @@ -0,0 +1,21 @@ +#include "test.h" + +int main() { +#line 500 "foo" + ASSERT(501, __LINE__); + ASSERT(0, strcmp(__FILE__, "foo")); + +#line 800 "bar" + ASSERT(801, __LINE__); + ASSERT(0, strcmp(__FILE__, "bar")); + +#line 1 + ASSERT(2, __LINE__); + +# 200 "xyz" 2 3 + ASSERT(201, __LINE__); + ASSERT(0, strcmp(__FILE__, "xyz")); + + printf("OK\n"); + return 0; +} diff --git a/test/literal.c b/test/literal.c new file mode 100644 index 00000000..45d06c94 --- /dev/null +++ b/test/literal.c @@ -0,0 +1,103 @@ +#include "test.h" + +int main() { + ASSERT(97, 'a'); + ASSERT(10, '\n'); + ASSERT(-128, '\x80'); + + ASSERT(511, 0777); + ASSERT(0, 0x0); + ASSERT(10, 0xa); + ASSERT(10, 0XA); + ASSERT(48879, 0xbeef); + ASSERT(48879, 0xBEEF); + ASSERT(48879, 0XBEEF); + ASSERT(0, 0b0); + ASSERT(1, 0b1); + ASSERT(47, 0b101111); + ASSERT(47, 0B101111); + + ASSERT(4, sizeof(0)); + ASSERT(8, sizeof(0L)); + ASSERT(8, sizeof(0LU)); + ASSERT(8, sizeof(0UL)); + ASSERT(8, sizeof(0LL)); + ASSERT(8, sizeof(0LLU)); + ASSERT(8, sizeof(0Ull)); + ASSERT(8, sizeof(0l)); + ASSERT(8, sizeof(0ll)); + ASSERT(8, sizeof(0x0L)); + ASSERT(8, sizeof(0b0L)); + ASSERT(4, sizeof(2147483647)); + ASSERT(8, sizeof(2147483648)); + ASSERT(-1, 0xffffffffffffffff); + ASSERT(8, sizeof(0xffffffffffffffff)); + ASSERT(4, sizeof(4294967295U)); + ASSERT(8, sizeof(4294967296U)); + + ASSERT(3, -1U>>30); + ASSERT(3, -1Ul>>62); + ASSERT(3, -1ull>>62); + + ASSERT(1, 0xffffffffffffffffl>>63); + ASSERT(1, 0xffffffffffffffffll>>63); + + ASSERT(-1, 18446744073709551615); + ASSERT(8, sizeof(18446744073709551615)); + ASSERT(-1, 18446744073709551615>>63); + + ASSERT(-1, 0xffffffffffffffff); + ASSERT(8, sizeof(0xffffffffffffffff)); + ASSERT(1, 0xffffffffffffffff>>63); + + ASSERT(-1, 01777777777777777777777); + ASSERT(8, sizeof(01777777777777777777777)); + ASSERT(1, 01777777777777777777777>>63); + + ASSERT(-1, 0b1111111111111111111111111111111111111111111111111111111111111111); + ASSERT(8, sizeof(0b1111111111111111111111111111111111111111111111111111111111111111)); + ASSERT(1, 0b1111111111111111111111111111111111111111111111111111111111111111>>63); + + ASSERT(8, sizeof(2147483648)); + ASSERT(4, sizeof(2147483647)); + + ASSERT(8, sizeof(0x1ffffffff)); + ASSERT(4, sizeof(0xffffffff)); + ASSERT(1, 0xffffffff>>31); + + ASSERT(8, sizeof(040000000000)); + ASSERT(4, sizeof(037777777777)); + ASSERT(1, 037777777777>>31); + + ASSERT(8, sizeof(0b111111111111111111111111111111111)); + ASSERT(4, sizeof(0b11111111111111111111111111111111)); + ASSERT(1, 0b11111111111111111111111111111111>>31); + + ASSERT(-1, 1 << 31 >> 31); + ASSERT(-1, 01 << 31 >> 31); + ASSERT(-1, 0x1 << 31 >> 31); + ASSERT(-1, 0b1 << 31 >> 31); + + 0.0; + 1.0; + 3e+8; + 0x10.1p0; + .1E4f; + + ASSERT(4, sizeof(8f)); + ASSERT(4, sizeof(0.3F)); + ASSERT(8, sizeof(0.)); + ASSERT(8, sizeof(.0)); + ASSERT(16, sizeof(5.l)); + ASSERT(16, sizeof(2.0L)); + + assert(1, size\ +of(char), \ + "sizeof(char)"); + + ASSERT(4, sizeof(L'\0')); + ASSERT(97, L'a'); + + printf("OK\n"); + return 0; +} diff --git a/test/macro.c b/test/macro.c new file mode 100644 index 00000000..cbcfc2d6 --- /dev/null +++ b/test/macro.c @@ -0,0 +1,414 @@ +#include "test.h" +#include "include1.h" + +char *main_filename1 = __FILE__; +int main_line1 = __LINE__; +#define LINE() __LINE__ +int main_line2 = LINE(); + +# + +/* */ # + +int ret3(void) { return 3; } +int dbl(int x) { return x*x; } + +int add2(int x, int y) { + return x + y; +} + +int add6(int a, int b, int c, int d, int e, int f) { + return a + b + c + d + e + f; +} + +int main() { + ASSERT(5, include1); + ASSERT(7, include2); + +#if 0 +#include "/no/such/file" + ASSERT(0, 1); +#if nested +#endif +#endif + + int m = 0; + +#if 1 + m = 5; +#endif + ASSERT(5, m); + +#if 1 +# if 0 +# if 1 + foo bar +# endif +# endif + m = 3; +#endif + ASSERT(3, m); + +#if 1-1 +# if 1 +# endif +# if 1 +# else +# endif +# if 0 +# else +# endif + m = 2; +#else +# if 1 + m = 3; +# endif +#endif + ASSERT(3, m); + +#if 1 + m = 2; +#else + m = 3; +#endif + ASSERT(2, m); + +#if 1 + m = 2; +#else + m = 3; +#endif + ASSERT(2, m); + +#if 0 + m = 1; +#elif 0 + m = 2; +#elif 3+5 + m = 3; +#elif 1*5 + m = 4; +#endif + ASSERT(3, m); + +#if 1+5 + m = 1; +#elif 1 + m = 2; +#elif 3 + m = 2; +#endif + ASSERT(1, m); + +#if 0 + m = 1; +#elif 1 +# if 1 + m = 2; +# else + m = 3; +# endif +#else + m = 5; +#endif + ASSERT(2, m); + + int M1 = 5; + +#define M1 3 + ASSERT(3, M1); +#define M1 4 + ASSERT(4, M1); + +#define M1 3+4+ + ASSERT(12, M1 5); + +#define M1 3+4 + ASSERT(23, M1*5); + +#define ASSERT_ assert( +#define if 5 +#define five "5" +#define END ) + ASSERT_ 5, if, five END; + +#undef ASSERT_ +#undef if +#undef five +#undef END + + if (0); + +#define M 5 +#if M + m = 5; +#else + m = 6; +#endif + ASSERT(5, m); + +#define M 5 +#if M-5 + m = 6; +#elif M + m = 5; +#endif + ASSERT(5, m); + + int M2 = 6; +#define M2 M2 + 3 + ASSERT(9, M2); + +#define M3 M2 + 3 + ASSERT(12, M3); + + int M4 = 3; +#define M4 M5 * 5 +#define M5 M4 + 2 + ASSERT(13, M4); + +#ifdef M6 + m = 5; +#else + m = 3; +#endif + ASSERT(3, m); + +#define M6 +#ifdef M6 + m = 5; +#else + m = 3; +#endif + ASSERT(5, m); + +#ifndef M7 + m = 3; +#else + m = 5; +#endif + ASSERT(3, m); + +#define M7 +#ifndef M7 + m = 3; +#else + m = 5; +#endif + ASSERT(5, m); + +#if 0 +#ifdef NO_SUCH_MACRO +#endif +#ifndef NO_SUCH_MACRO +#endif +#else +#endif + +#define M7() 1 + int M7 = 5; + ASSERT(1, M7()); + ASSERT(5, M7); + +#define M7 () + ASSERT(3, ret3 M7); + +#define M8(x,y) x+y + ASSERT(7, M8(3, 4)); + +#define M8(x,y) x*y + ASSERT(24, M8(3+4, 4+5)); + +#define M8(x,y) (x)*(y) + ASSERT(63, M8(3+4, 4+5)); + +#define M8(x,y) x y + ASSERT(9, M8(, 4+5)); + +#define M8(x,y) x*y + ASSERT(20, M8((2+3), 4)); + +#define M8(x,y) x*y + ASSERT(12, M8((2,3), 4)); + +#define dbl(x) M10(x) * x +#define M10(x) dbl(x) + 3 + ASSERT(10, dbl(2)); + +#define M11(x) #x + ASSERT('a', M11( a!b `""c)[0]); + ASSERT('!', M11( a!b `""c)[1]); + ASSERT('b', M11( a!b `""c)[2]); + ASSERT(' ', M11( a!b `""c)[3]); + ASSERT('`', M11( a!b `""c)[4]); + ASSERT('"', M11( a!b `""c)[5]); + ASSERT('"', M11( a!b `""c)[6]); + ASSERT('c', M11( a!b `""c)[7]); + ASSERT(0, M11( a!b `""c)[8]); + +#define paste(x,y) x##y + ASSERT(15, paste(1,5)); + ASSERT(255, paste(0,xff)); + ASSERT(3, ({ int foobar=3; paste(foo,bar); })); + ASSERT(5, paste(5,)); + ASSERT(5, paste(,5)); + +#define i 5 + ASSERT(101, ({ int i3=100; paste(1+i,3); })); +#undef i + +#define paste2(x) x##5 + ASSERT(26, paste2(1+2)); + +#define paste3(x) 2##x + ASSERT(23, paste3(1+2)); + +#define paste4(x, y, z) x##y##z + ASSERT(123, paste4(1,2,3)); + +#define M12 +#if defined(M12) + m = 3; +#else + m = 4; +#endif + ASSERT(3, m); + +#define M12 +#if defined M12 + m = 3; +#else + m = 4; +#endif + ASSERT(3, m); + +#if defined(M12) - 1 + m = 3; +#else + m = 4; +#endif + ASSERT(4, m); + +#if defined(NO_SUCH_MACRO) + m = 3; +#else + m = 4; +#endif + ASSERT(4, m); + +#if no_such_symbol == 0 + m = 5; +#else + m = 6; +#endif + ASSERT(5, m); + +#define STR(x) #x +#define M12(x) STR(x) +#define M13(x) M12(foo.x) + ASSERT(0, strcmp(M13(bar), "foo.bar")); + +#define M13(x) M12(foo. x) + ASSERT(0, strcmp(M13(bar), "foo. bar")); + +#define M12 foo +#define M13(x) STR(x) +#define M14(x) M13(x.M12) + ASSERT(0, strcmp(M14(bar), "bar.foo")); + +#define M14(x) M13(x. M12) + ASSERT(0, strcmp(M14(bar), "bar. foo")); + +#include "include3.h" + ASSERT(3, foo); + +#include "include4.h" + ASSERT(4, foo); + +#define M13 "include3.h" +#include M13 + ASSERT(3, foo); + +#define M13 < include4.h +#include M13 > + ASSERT(4, foo); + +#undef foo + + ASSERT(1, __STDC__); + + ASSERT(0, strcmp(main_filename1, "test/macro.c")); + ASSERT(5, main_line1); + ASSERT(7, main_line2); + ASSERT(0, strcmp(include1_filename, "test/include1.h")); + ASSERT(4, include1_line); + +#define M14(...) 3 + ASSERT(3, M14()); + +#define M14(...) __VA_ARGS__ + ASSERT(2, M14() 2); + ASSERT(5, M14(5)); + +#define M14(...) add2(__VA_ARGS__) + ASSERT(8, M14(2, 6)); + +#define M14(...) add6(1,2,__VA_ARGS__,6) + ASSERT(21, M14(3,4,5)); + +#define M14(x, ...) add6(1,2,x,__VA_ARGS__,6) + ASSERT(21, M14(3,4,5)); + +#define M14(args...) 3 + ASSERT(3, M14()); + +#define M14(x, ...) x + ASSERT(5, M14(5)); + +#define M14(args...) args + ASSERT(2, M14() 2); + ASSERT(5, M14(5)); + +#define M14(args...) add2(args) + ASSERT(8, M14(2, 6)); + +#define M14(args...) add6(1,2,args,6) + ASSERT(21, M14(3,4,5)); + +#define M14(x, args...) add6(1,2,x,args,6) + ASSERT(21, M14(3,4,5)); + +#define M14(x, args...) x + ASSERT(5, M14(5)); + +#define CONCAT(x,y) x##y + ASSERT(5, ({ int f0zz=5; CONCAT(f,0zz); })); + ASSERT(5, ({ CONCAT(4,.57) + 0.5; })); + + ASSERT(11, strlen(__DATE__)); + ASSERT(8, strlen(__TIME__)); + + ASSERT(0, __COUNTER__); + ASSERT(1, __COUNTER__); + ASSERT(2, __COUNTER__); + + ASSERT(24, strlen(__TIMESTAMP__)); + + ASSERT(0, strcmp(__BASE_FILE__, "test/macro.c")); + +#define M30(buf, fmt, ...) sprintf(buf, fmt __VA_OPT__(,) __VA_ARGS__) + ASSERT(0, ({ char buf[100]; M30(buf, "foo"); strcmp(buf, "foo"); })); + ASSERT(0, ({ char buf[100]; M30(buf, "foo%d", 3); strcmp(buf, "foo3"); })); + ASSERT(0, ({ char buf[100]; M30(buf, "foo%d%d", 3, 5); strcmp(buf, "foo35"); })); + +#define M31(buf, fmt, ...) sprintf(buf, fmt, ## __VA_ARGS__) + ASSERT(0, ({ char buf[100]; M31(buf, "foo"); strcmp(buf, "foo"); })); + ASSERT(0, ({ char buf[100]; M31(buf, "foo%d", 3); strcmp(buf, "foo3"); })); + ASSERT(0, ({ char buf[100]; M31(buf, "foo%d%d", 3, 5); strcmp(buf, "foo35"); })); + +#define M31(x, y) (1, ##x y) + ASSERT(3, M31(, 3)); + + printf("OK\n"); + return 0; +} diff --git a/test/offsetof.c b/test/offsetof.c new file mode 100644 index 00000000..7f16ec92 --- /dev/null +++ b/test/offsetof.c @@ -0,0 +1,19 @@ +#include "test.h" +#include + +typedef struct { + int a; + char b; + int c; + double d; +} T; + +int main() { + ASSERT(0, offsetof(T, a)); + ASSERT(4, offsetof(T, b)); + ASSERT(8, offsetof(T, c)); + ASSERT(16, offsetof(T, d)); + + printf("OK\n"); + return 0; +} diff --git a/test/pragma-once.c b/test/pragma-once.c new file mode 100644 index 00000000..263a2eb4 --- /dev/null +++ b/test/pragma-once.c @@ -0,0 +1,10 @@ +#include "test.h" + +#pragma once + +#include "test/pragma-once.c" + +int main() { + printf("OK\n"); + return 0; +} diff --git a/test/sizeof.c b/test/sizeof.c new file mode 100644 index 00000000..5b4398de --- /dev/null +++ b/test/sizeof.c @@ -0,0 +1,105 @@ +#include "test.h" + +int main() { + ASSERT(1, sizeof(char)); + ASSERT(2, sizeof(short)); + ASSERT(2, sizeof(short int)); + ASSERT(2, sizeof(int short)); + ASSERT(4, sizeof(int)); + ASSERT(8, sizeof(long)); + ASSERT(8, sizeof(long int)); + ASSERT(8, sizeof(long int)); + ASSERT(8, sizeof(char *)); + ASSERT(8, sizeof(int *)); + ASSERT(8, sizeof(long *)); + ASSERT(8, sizeof(int **)); + ASSERT(8, sizeof(int(*)[4])); + ASSERT(32, sizeof(int*[4])); + ASSERT(16, sizeof(int[4])); + ASSERT(48, sizeof(int[3][4])); + ASSERT(8, sizeof(struct {int a; int b;})); + + ASSERT(8, sizeof(-10 + (long)5)); + ASSERT(8, sizeof(-10 - (long)5)); + ASSERT(8, sizeof(-10 * (long)5)); + ASSERT(8, sizeof(-10 / (long)5)); + ASSERT(8, sizeof((long)-10 + 5)); + ASSERT(8, sizeof((long)-10 - 5)); + ASSERT(8, sizeof((long)-10 * 5)); + ASSERT(8, sizeof((long)-10 / 5)); + + ASSERT(1, ({ char i; sizeof(++i); })); + ASSERT(1, ({ char i; sizeof(i++); })); + + ASSERT(8, sizeof(int(*)[10])); + ASSERT(8, sizeof(int(*)[][10])); + + ASSERT(4, sizeof(struct { int x, y[]; })); + + ASSERT(1, sizeof(char)); + ASSERT(1, sizeof(signed char)); + ASSERT(1, sizeof(signed char signed)); + ASSERT(1, sizeof(unsigned char)); + ASSERT(1, sizeof(unsigned char unsigned)); + + ASSERT(2, sizeof(short)); + ASSERT(2, sizeof(int short)); + ASSERT(2, sizeof(short int)); + ASSERT(2, sizeof(signed short)); + ASSERT(2, sizeof(int short signed)); + ASSERT(2, sizeof(unsigned short)); + ASSERT(2, sizeof(int short unsigned)); + + ASSERT(4, sizeof(int)); + ASSERT(4, sizeof(signed int)); + ASSERT(4, sizeof(signed)); + ASSERT(4, sizeof(signed signed)); + ASSERT(4, sizeof(unsigned int)); + ASSERT(4, sizeof(unsigned)); + ASSERT(4, sizeof(unsigned unsigned)); + + ASSERT(8, sizeof(long)); + ASSERT(8, sizeof(signed long)); + ASSERT(8, sizeof(signed long int)); + ASSERT(8, sizeof(unsigned long)); + ASSERT(8, sizeof(unsigned long int)); + + ASSERT(8, sizeof(long long)); + ASSERT(8, sizeof(signed long long)); + ASSERT(8, sizeof(signed long long int)); + ASSERT(8, sizeof(unsigned long long)); + ASSERT(8, sizeof(unsigned long long int)); + + ASSERT(1, sizeof((char)1)); + ASSERT(2, sizeof((short)1)); + ASSERT(4, sizeof((int)1)); + ASSERT(8, sizeof((long)1)); + + ASSERT(4, sizeof((char)1 + (char)1)); + ASSERT(4, sizeof((short)1 + (short)1)); + ASSERT(4, sizeof(1?2:3)); + ASSERT(4, sizeof(1?(short)2:(char)3)); + ASSERT(8, sizeof(1?(long)2:(char)3)); + + ASSERT(1, sizeof(char) << 31 >> 31); + ASSERT(1, sizeof(char) << 63 >> 63); + + ASSERT(4, sizeof(float)); + ASSERT(8, sizeof(double)); + + ASSERT(4, sizeof(1f+2)); + ASSERT(8, sizeof(1.0+2)); + ASSERT(4, sizeof(1f-2)); + ASSERT(8, sizeof(1.0-2)); + ASSERT(4, sizeof(1f*2)); + ASSERT(8, sizeof(1.0*2)); + ASSERT(4, sizeof(1f/2)); + ASSERT(8, sizeof(1.0/2)); + + ASSERT(16, sizeof(long double)); + + ASSERT(1, sizeof(main)); + + printf("OK\n"); + return 0; +} diff --git a/test/stdhdr.c b/test/stdhdr.c new file mode 100644 index 00000000..9c5e72b3 --- /dev/null +++ b/test/stdhdr.c @@ -0,0 +1,12 @@ +#include "test.h" +#include +#include +#include +#include +#include +#include + +int main() { + printf("OK\n"); + return 0; +} diff --git a/test/string.c b/test/string.c index 303e72b2..04af2e3a 100644 --- a/test/string.c +++ b/test/string.c @@ -35,6 +35,38 @@ int main() { ASSERT(0, "\x00"[0]); ASSERT(119, "\x77"[0]); + ASSERT(7, sizeof("abc" "def")); + ASSERT(9, sizeof("abc" "d" "efgh")); + ASSERT(0, strcmp("abc" "d" "\nefgh", "abcd\nefgh")); + ASSERT(0, !strcmp("abc" "d", "abcd\nefgh")); + ASSERT(0, strcmp("\x9" "0", "\t0")); + + ASSERT(16, sizeof(L"abc" "")); + + ASSERT(28, sizeof(L"abc" "def")); + ASSERT(28, sizeof(L"abc" L"def")); + ASSERT(14, sizeof(u"abc" "def")); + ASSERT(14, sizeof(u"abc" u"def")); + + ASSERT(L'a', (L"abc" "def")[0]); + ASSERT(L'd', (L"abc" "def")[3]); + ASSERT(L'\0', (L"abc" "def")[6]); + + ASSERT(u'a', (u"abc" "def")[0]); + ASSERT(u'd', (u"abc" "def")[3]); + ASSERT(u'\0', (u"abc" "def")[6]); + + ASSERT(L'あ', ("あ" L"")[0]); + ASSERT(0343, ("\343\201\202" L"")[0]); + ASSERT(0201, ("\343\201\202" L"")[1]); + ASSERT(0202, ("\343\201\202" L"")[2]); + ASSERT(0, ("\343\201\202" L"")[3]); + + ASSERT(L'a', ("a" "b" L"c")[0]); + ASSERT(L'b', ("a" "b" L"c")[1]); + ASSERT(L'c', ("a" "b" L"c")[2]); + ASSERT(0, ("a" "b" L"c")[3]); + printf("OK\n"); return 0; } diff --git a/test/struct.c b/test/struct.c index cbd43433..64fb03ff 100644 --- a/test/struct.c +++ b/test/struct.c @@ -52,6 +52,11 @@ int main() { ASSERT(16, ({ struct {char a; long b;} x; sizeof(x); })); ASSERT(4, ({ struct {char a; short b;} x; sizeof(x); })); + ASSERT(8, ({ struct foo *bar; sizeof(bar); })); + ASSERT(4, ({ struct T *foo; struct T {int x;}; sizeof(struct T); })); + ASSERT(1, ({ struct T { struct T *next; int x; } a; struct T b; b.x=1; a.next=&b; a.next->x; })); + ASSERT(4, ({ typedef struct T T; struct T { int x; }; sizeof(T); })); + printf("OK\n"); return 0; } diff --git a/test/test.h b/test/test.h index bdbd4741..61fe2605 100644 --- a/test/test.h +++ b/test/test.h @@ -1 +1,14 @@ #define ASSERT(x, y) assert(x, y, #y) + +void assert(int expected, int actual, char *code); +int printf(char *fmt, ...); +int sprintf(char *buf, char *fmt, ...); +int vsprintf(char *buf, char *fmt, void *ap); +int strcmp(char *p, char *q); +int strncmp(char *p, char *q, long n); +int memcmp(char *p, char *q, long n); +void exit(int n); +int vsprintf(); +long strlen(char *s); +void *memcpy(void *dest, void *src, long n); +void *memset(void *s, int c, long n); diff --git a/test/thirdparty/common b/test/thirdparty/common new file mode 100644 index 00000000..5c4ba8a0 --- /dev/null +++ b/test/thirdparty/common @@ -0,0 +1,11 @@ +make="make -j$(nproc)" +chibicc=`pwd`/chibicc + +dir=$(basename -s .git $repo) + +set -e -x + +mkdir -p thirdparty +cd thirdparty +[ -d $dir ] || git clone $repo +cd $dir diff --git a/test/thirdparty/cpython.sh b/test/thirdparty/cpython.sh new file mode 100755 index 00000000..3ff06cb0 --- /dev/null +++ b/test/thirdparty/cpython.sh @@ -0,0 +1,15 @@ +#!/bin/bash +repo='git@github.com:python/cpython.git' +. test/thirdparty/common +git reset --hard c75330605d4795850ec74fdc4d69aa5d92f76c00 + +# Python's './configure' command misidentifies chibicc as icc +# (Intel C Compiler) because icc is a substring of chibicc. +# Modify the configure file as a workaround. +sed -i -e 1996,2011d configure.ac +autoreconf + +CC=$chibicc ./configure +$make clean +$make +$make test diff --git a/test/thirdparty/git.sh b/test/thirdparty/git.sh new file mode 100755 index 00000000..d20d1e19 --- /dev/null +++ b/test/thirdparty/git.sh @@ -0,0 +1,7 @@ +#!/bin/bash +repo='git@github.com:git/git.git' +. test/thirdparty/common +git reset --hard 54e85e7af1ac9e9a92888060d6811ae767fea1bc + +$make clean +$make V=1 CC=$chibicc test diff --git a/test/thirdparty/libpng.sh b/test/thirdparty/libpng.sh new file mode 100755 index 00000000..8146eb58 --- /dev/null +++ b/test/thirdparty/libpng.sh @@ -0,0 +1,10 @@ +#!/bin/bash +repo='git@github.com:rui314/libpng.git' +. test/thirdparty/common +git reset --hard dbe3e0c43e549a1602286144d94b0666549b18e6 + +CC=$chibicc ./configure +sed -i 's/^wl=.*/wl=-Wl,/; s/^pic_flag=.*/pic_flag=-fPIC/' libtool +$make clean +$make +$make test diff --git a/test/thirdparty/sqlite.sh b/test/thirdparty/sqlite.sh new file mode 100755 index 00000000..d59def4a --- /dev/null +++ b/test/thirdparty/sqlite.sh @@ -0,0 +1,10 @@ +#!/bin/bash +repo='git@github.com:sqlite/sqlite.git' +. test/thirdparty/common +git reset --hard 86f477edaa17767b39c7bae5b67cac8580f7a8c1 + +CC=$chibicc CFLAGS=-D_GNU_SOURCE ./configure +sed -i 's/^wl=.*/wl=-Wl,/; s/^pic_flag=.*/pic_flag=-fPIC/' libtool +$make clean +$make +$make test diff --git a/test/thirdparty/tinycc.sh b/test/thirdparty/tinycc.sh new file mode 100755 index 00000000..0228ebfe --- /dev/null +++ b/test/thirdparty/tinycc.sh @@ -0,0 +1,9 @@ +#!/bin/bash +repo='git@github.com:TinyCC/tinycc.git' +. test/thirdparty/common +git reset --hard df67d8617b7d1d03a480a28f9f901848ffbfb7ec + +./configure --cc=$chibicc +$make clean +$make +$make CC=cc test diff --git a/test/tls.c b/test/tls.c new file mode 100644 index 00000000..337b366a --- /dev/null +++ b/test/tls.c @@ -0,0 +1,41 @@ +#include "test.h" +#include +#include + +_Thread_local int v1; +_Thread_local int v2 = 5; +int v3 = 7; + +int thread_main(void *unused) { + ASSERT(0, v1); + ASSERT(5, v2); + ASSERT(7, v3); + + v1 = 1; + v2 = 2; + v3 = 3; + + ASSERT(1, v1); + ASSERT(2, v2); + ASSERT(3, v3); + + return 0; +} + +int main() { + pthread_t thr; + + ASSERT(0, v1); + ASSERT(5, v2); + ASSERT(7, v3); + + ASSERT(0, pthread_create(&thr, NULL, thread_main, NULL)); + ASSERT(0, pthread_join(thr, NULL)); + + ASSERT(0, v1); + ASSERT(5, v2); + ASSERT(3, v3); + + printf("OK\n"); + return 0; +} diff --git a/test/typedef.c b/test/typedef.c new file mode 100644 index 00000000..24cf4489 --- /dev/null +++ b/test/typedef.c @@ -0,0 +1,17 @@ +#include "test.h" + +typedef int MyInt, MyInt2[4]; +typedef int; + +int main() { + ASSERT(1, ({ typedef int t; t x=1; x; })); + ASSERT(1, ({ typedef struct {int a;} t; t x; x.a=1; x.a; })); + ASSERT(1, ({ typedef int t; t t=1; t; })); + ASSERT(2, ({ typedef struct {int a;} t; { typedef int t; } t x; x.a=2; x.a; })); + ASSERT(4, ({ typedef t; t x; sizeof(x); })); + ASSERT(3, ({ MyInt x=3; x; })); + ASSERT(16, ({ MyInt2 x; sizeof(x); })); + + printf("OK\n"); + return 0; +} diff --git a/test/typeof.c b/test/typeof.c new file mode 100644 index 00000000..a2141659 --- /dev/null +++ b/test/typeof.c @@ -0,0 +1,13 @@ +#include "test.h" + +int main() { + ASSERT(3, ({ typeof(int) x=3; x; })); + ASSERT(3, ({ typeof(1) x=3; x; })); + ASSERT(4, ({ int x; typeof(x) y; sizeof(y); })); + ASSERT(8, ({ int x; typeof(&x) y; sizeof(y); })); + ASSERT(4, ({ typeof("foo") x; sizeof(x); })); + ASSERT(12, sizeof(typeof(struct { int a,b,c; }))); + + printf("OK\n"); + return 0; +} diff --git a/test/unicode.c b/test/unicode.c new file mode 100644 index 00000000..2ee574af --- /dev/null +++ b/test/unicode.c @@ -0,0 +1,104 @@ +#include "test.h" + +#define STR(x) #x + +typedef unsigned short char16_t; +typedef unsigned int char32_t; +typedef int wchar_t; + +int π = 3; + +int main() { + ASSERT(4, sizeof(L'\0')); + ASSERT(97, L'a'); + + ASSERT(0, strcmp("αβγ", "\u03B1\u03B2\u03B3")); + ASSERT(0, strcmp("日本語", "\u65E5\u672C\u8A9E")); + ASSERT(0, strcmp("日本語", "\U000065E5\U0000672C\U00008A9E")); + ASSERT(0, strcmp("🌮", "\U0001F32E")); + + ASSERT(-1, L'\xffffffff'>>31); + ASSERT(946, L'β'); + ASSERT(12354, L'あ'); + ASSERT(127843, L'🍣'); + + ASSERT(2, sizeof(u'\0')); + ASSERT(1, u'\xffff'>>15); + ASSERT(97, u'a'); + ASSERT(946, u'β'); + ASSERT(12354, u'あ'); + ASSERT(62307, u'🍣'); + + ASSERT(0, strcmp(STR(u'a'), "u'a'")); + + ASSERT(4, sizeof(U'\0')); + ASSERT(1, U'\xffffffff'>>31); + ASSERT(97, U'a'); + ASSERT(946, U'β'); + ASSERT(12354, U'あ'); + ASSERT(127843, U'🍣'); + + ASSERT(0, strcmp(STR(U'a'), "U'a'")); + + ASSERT(4, sizeof(u8"abc")); + ASSERT(0, strcmp(u8"abc", "abc")); + + ASSERT(0, strcmp(STR(u8"a"), "u8\"a\"")); + + ASSERT(2, sizeof(u"")); + ASSERT(10, sizeof(u"\xffzzz")); + ASSERT(0, memcmp(u"", "\0\0", 2)); + ASSERT(0, memcmp(u"abc", "a\0b\0c\0\0\0", 8)); + ASSERT(0, memcmp(u"日本語", "\345e,g\236\212\0\0", 8)); + ASSERT(0, memcmp(u"🍣", "<\330c\337\0\0", 6)); + ASSERT(u'β', u"βb"[0]); + ASSERT(u'b', u"βb"[1]); + ASSERT(0, u"βb"[2]); + + ASSERT(0, strcmp(STR(u"a"), "u\"a\"")); + + ASSERT(4, sizeof(U"")); + ASSERT(20, sizeof(U"\xffzzz")); + ASSERT(0, memcmp(U"", "\0\0\0\0", 4)); + ASSERT(0, memcmp(U"abc", "a\0\0\0b\0\0\0c\0\0\0\0\0\0\0", 16)); + ASSERT(0, memcmp(U"日本語", "\345e\0\0,g\0\0\236\212\0\0\0\0\0\0", 16)); + ASSERT(0, memcmp(U"🍣", "c\363\001\0\0\0\0\0", 8)); + ASSERT(u'β', U"βb"[0]); + ASSERT(u'b', U"βb"[1]); + ASSERT(0, U"βb"[2]); + ASSERT(1, U"\xffffffff"[0] >> 31); + + ASSERT(0, strcmp(STR(U"a"), "U\"a\"")); + + ASSERT(4, sizeof(L"")); + ASSERT(20, sizeof(L"\xffzzz")); + ASSERT(0, memcmp(L"", "\0\0\0\0", 4)); + ASSERT(0, memcmp(L"abc", "a\0\0\0b\0\0\0c\0\0\0\0\0\0\0", 16)); + ASSERT(0, memcmp(L"日本語", "\345e\0\0,g\0\0\236\212\0\0\0\0\0\0", 16)); + ASSERT(0, memcmp(L"🍣", "c\363\001\0\0\0\0\0", 8)); + ASSERT(u'β', L"βb"[0]); + ASSERT(u'b', L"βb"[1]); + ASSERT(0, L"βb"[2]); + ASSERT(-1, L"\xffffffff"[0] >> 31); + + ASSERT(0, strcmp(STR(L"a"), "L\"a\"")); + + ASSERT(u'α', ({ char16_t x[] = u"αβ"; x[0]; })); + ASSERT(u'β', ({ char16_t x[] = u"αβ"; x[1]; })); + ASSERT(6, ({ char16_t x[] = u"αβ"; sizeof(x); })); + + ASSERT(U'🤔', ({ char32_t x[] = U"🤔x"; x[0]; })); + ASSERT(U'x', ({ char32_t x[] = U"🤔x"; x[1]; })); + ASSERT(12, ({ char32_t x[] = U"🤔x"; sizeof(x); })); + + ASSERT(L'🤔', ({ wchar_t x[] = L"🤔x"; x[0]; })); + ASSERT(L'x', ({ wchar_t x[] = L"🤔x"; x[1]; })); + ASSERT(12, ({ wchar_t x[] = L"🤔x"; sizeof(x); })); + + ASSERT(3, π); + ASSERT(3, ({ int あβ0¾=3; あβ0¾; })); + ASSERT(5, ({ int $$$=5; $$$; })); + + printf("OK\n"); + return 0; +} diff --git a/test/union.c b/test/union.c index c25a621e..4d8e984c 100644 --- a/test/union.c +++ b/test/union.c @@ -10,6 +10,14 @@ int main() { ASSERT(3, ({ union {int a,b;} x,y; x.a=3; y.a=5; y=x; y.a; })); ASSERT(3, ({ union {struct {int a,b;} c;} x,y; x.c.b=3; y.c.b=5; y=x; y.c.b; })); + ASSERT(0xef, ({ union { struct { unsigned char a,b,c,d; }; long e; } x; x.e=0xdeadbeef; x.a; })); + ASSERT(0xbe, ({ union { struct { unsigned char a,b,c,d; }; long e; } x; x.e=0xdeadbeef; x.b; })); + ASSERT(0xad, ({ union { struct { unsigned char a,b,c,d; }; long e; } x; x.e=0xdeadbeef; x.c; })); + ASSERT(0xde, ({ union { struct { unsigned char a,b,c,d; }; long e; } x; x.e=0xdeadbeef; x.d; })); + + ASSERT(3, ({struct { union { int a,b; }; union { int c,d; }; } x; x.a=3; x.b; })); + ASSERT(5, ({struct { union { int a,b; }; union { int c,d; }; } x; x.d=5; x.c; })); + printf("OK\n"); return 0; } diff --git a/test/usualconv.c b/test/usualconv.c new file mode 100644 index 00000000..b173fe18 --- /dev/null +++ b/test/usualconv.c @@ -0,0 +1,32 @@ +#include "test.h" + +static int ret10(void) { return 10; } + +int main() { + ASSERT((long)-5, -10 + (long)5); + ASSERT((long)-15, -10 - (long)5); + ASSERT((long)-50, -10 * (long)5); + ASSERT((long)-2, -10 / (long)5); + + ASSERT(1, -2 < (long)-1); + ASSERT(1, -2 <= (long)-1); + ASSERT(0, -2 > (long)-1); + ASSERT(0, -2 >= (long)-1); + + ASSERT(1, (long)-2 < -1); + ASSERT(1, (long)-2 <= -1); + ASSERT(0, (long)-2 > -1); + ASSERT(0, (long)-2 >= -1); + + ASSERT(0, 2147483647 + 2147483647 + 2); + ASSERT((long)-1, ({ long x; x=-1; x; })); + + ASSERT(1, ({ char x[3]; x[0]=0; x[1]=1; x[2]=2; char *y=x+1; y[0]; })); + ASSERT(0, ({ char x[3]; x[0]=0; x[1]=1; x[2]=2; char *y=x+1; y[-1]; })); + ASSERT(5, ({ struct t {char a;} x, y; x.a=5; y=x; y.a; })); + + ASSERT(10, (1 ? ret10 : (void *)0)()); + + printf("OK\n"); + return 0; +} diff --git a/test/varargs.c b/test/varargs.c new file mode 100644 index 00000000..2468fec5 --- /dev/null +++ b/test/varargs.c @@ -0,0 +1,51 @@ +#include "test.h" +#include + +int sum1(int x, ...) { + va_list ap; + va_start(ap, x); + + for (;;) { + int y = va_arg(ap, int); + if (y == 0) + return x; + x += y; + } +} + +int sum2(int x, ...) { + va_list ap; + va_start(ap, x); + + for (;;) { + double y = va_arg(ap, double); + x += y; + + int z = va_arg(ap, int); + if (z == 0) + return x; + x += z; + } +} + +void fmt(char *buf, char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + + va_list ap2; + va_copy(ap2, ap); + vsprintf(buf, fmt, ap2); + va_end(buf); +} + +int main() { + ASSERT(6, sum1(1, 2, 3, 0)); + ASSERT(55, sum1(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0)); + ASSERT(21, sum2(1, 2.0, 3, 4.0, 5, 6.0, 0)); + ASSERT(21, sum2(1, 2.0, 3, 4.0, 5, 6.0, 0)); + ASSERT(210, sum2(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13, 14.0, 15, 16.0, 17, 18.0, 19, 20.0, 0)); + ASSERT(0, ({ char buf[100]; fmt(buf, "%d %d", 2, 3); strcmp(buf, "2 3"); })); + + printf("OK\n"); + return 0; +} diff --git a/test/variable.c b/test/variable.c index 98ef696e..ae2ff13c 100644 --- a/test/variable.c +++ b/test/variable.c @@ -1,6 +1,7 @@ #include "test.h" int g1, g2[4]; +static int g3 = 3; int main() { ASSERT(3, ({ int a; a=3; a; })); @@ -53,6 +54,19 @@ int main() { ASSERT(8, ({ long x; sizeof(x); })); ASSERT(2, ({ short x; sizeof(x); })); + ASSERT(24, ({ char *x[3]; sizeof(x); })); + ASSERT(8, ({ char (*x)[3]; sizeof(x); })); + ASSERT(1, ({ char (x); sizeof(x); })); + ASSERT(3, ({ char (x)[3]; sizeof(x); })); + ASSERT(12, ({ char (x[3])[4]; sizeof(x); })); + ASSERT(4, ({ char (x[3])[4]; sizeof(x[0]); })); + ASSERT(3, ({ char *x[3]; char y; x[0]=&y; y=3; x[0][0]; })); + ASSERT(4, ({ char x[3]; char (*y)[3]=x; y[0][0]=4; y[0][0]; })); + + { void *x; } + + ASSERT(3, g3); + printf("OK\n"); return 0; } diff --git a/test/vla.c b/test/vla.c new file mode 100644 index 00000000..b0efd4a6 --- /dev/null +++ b/test/vla.c @@ -0,0 +1,26 @@ +#include "test.h" + +int main() { + ASSERT(20, ({ int n=5; int x[n]; sizeof(x); })); + ASSERT((5+1)*(8*2)*4, ({ int m=5, n=8; int x[m+1][n*2]; sizeof(x); })); + + ASSERT(8, ({ char n=10; int (*x)[n][n+2]; sizeof(x); })); + ASSERT(480, ({ char n=10; int (*x)[n][n+2]; sizeof(*x); })); + ASSERT(48, ({ char n=10; int (*x)[n][n+2]; sizeof(**x); })); + ASSERT(4, ({ char n=10; int (*x)[n][n+2]; sizeof(***x); })); + + ASSERT(60, ({ char n=3; int x[5][n]; sizeof(x); })); + ASSERT(12, ({ char n=3; int x[5][n]; sizeof(*x); })); + + ASSERT(60, ({ char n=3; int x[n][5]; sizeof(x); })); + ASSERT(20, ({ char n=3; int x[n][5]; sizeof(*x); })); + + ASSERT(0, ({ int n=10; int x[n+1][n+6]; int *p=x; for (int i = 0; i -static void verror_at(int line_no, char *loc, char *fmt, va_list ap) { +static void verror_at(char *filename, char *input, int line_no, + char *loc, char *fmt, va_list ap) { // Find a line containing `loc`. char *line = loc; - while (current_input < line && line[-1] != '\n') + while (input < line && line[-1] != '\n') line--; char *end = loc; - while (*end != '\n') + while (*end && *end != '\n') end++; // Print out the line. - int indent = fprintf(stderr, "%s:%d: ", current_filename, line_no); + int indent = fprintf(stderr, "%s:%d: ", filename, line_no); fprintf(stderr, "%.*s\n", (int)(end - line), line); // Show the error message. - int pos = loc - line + indent; + int pos = display_width(line, loc - line) + indent; fprintf(stderr, "%*s", pos, ""); // print pos spaces. fprintf(stderr, "^ "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); - exit(1); } void error_at(char *loc, char *fmt, ...) { int line_no = 1; - for (char *p = current_input; p < loc; p++) + for (char *p = current_file->contents; p < loc; p++) if (*p == '\n') line_no++; va_list ap; va_start(ap, fmt); - verror_at(line_no, loc, fmt, ap); + verror_at(current_file->name, current_file->contents, line_no, loc, fmt, ap); + exit(1); } void error_tok(Token *tok, char *fmt, ...) { va_list ap; va_start(ap, fmt); - verror_at(tok->line_no, tok->loc, fmt, ap); + verror_at(tok->file->name, tok->file->contents, tok->line_no, tok->loc, fmt, ap); + exit(1); +} + +void warn_tok(Token *tok, char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + verror_at(tok->file->name, tok->file->contents, tok->line_no, tok->loc, fmt, ap); + va_end(ap); } // Consumes the current token if it matches `op`. @@ -87,6 +102,12 @@ static Token *new_token(TokenKind kind, char *start, char *end) { tok->kind = kind; tok->loc = start; tok->len = end - start; + tok->file = current_file; + tok->filename = current_file->display_name; + tok->at_bol = at_bol; + tok->has_space = has_space; + + at_bol = has_space = false; return tok; } @@ -94,14 +115,21 @@ static bool startswith(char *p, char *q) { return strncmp(p, q, strlen(q)) == 0; } -// Returns true if c is valid as the first character of an identifier. -static bool is_ident1(char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; -} +// Read an identifier and returns the length of it. +// If p does not point to a valid identifier, 0 is returned. +static int read_ident(char *start) { + char *p = start; + uint32_t c = decode_utf8(&p, p); + if (!is_ident1(c)) + return 0; -// Returns true if c is valid as a non-first character of an identifier. -static bool is_ident2(char c) { - return is_ident1(c) || ('0' <= c && c <= '9'); + for (;;) { + char *q; + c = decode_utf8(&q, p); + if (!is_ident2(c)) + return p - start; + p = q; + } } static int from_hex(char c) { @@ -114,7 +142,11 @@ static int from_hex(char c) { // Read a punctuator token from p and returns its length. static int read_punct(char *p) { - static char *kw[] = {"==", "!=", "<=", ">=", "->"}; + static char *kw[] = { + "<<=", ">>=", "...", "==", "!=", "<=", ">=", "->", "+=", + "-=", "*=", "/=", "++", "--", "%=", "&=", "|=", "^=", "&&", + "||", "<<", ">>", "##", + }; for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) if (startswith(p, kw[i])) @@ -124,15 +156,25 @@ static int read_punct(char *p) { } static bool is_keyword(Token *tok) { - static char *kw[] = { - "return", "if", "else", "for", "while", "int", "sizeof", "char", - "struct", "union", "short", "long", - }; + static HashMap map; + + if (map.capacity == 0) { + static char *kw[] = { + "return", "if", "else", "for", "while", "int", "sizeof", "char", + "struct", "union", "short", "long", "void", "typedef", "_Bool", + "enum", "static", "goto", "break", "continue", "switch", "case", + "default", "extern", "_Alignof", "_Alignas", "do", "signed", + "unsigned", "const", "volatile", "auto", "register", "restrict", + "__restrict", "__restrict__", "_Noreturn", "float", "double", + "typeof", "asm", "_Thread_local", "__thread", "_Atomic", + "__attribute__", + }; + + for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) + hashmap_put(&map, kw[i], (void *)1); + } - for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) - if (equal(tok, kw[i])) - return true; - return false; + return hashmap_get2(&map, tok->loc, tok->len); } static int read_escaped_char(char **new_pos, char *p) { @@ -200,12 +242,12 @@ static char *string_literal_end(char *p) { return p; } -static Token *read_string_literal(char *start) { - char *end = string_literal_end(start + 1); - char *buf = calloc(1, end - start); +static Token *read_string_literal(char *start, char *quote) { + char *end = string_literal_end(quote + 1); + char *buf = calloc(1, end - quote); int len = 0; - for (char *p = start + 1; p < end;) { + for (char *p = quote + 1; p < end;) { if (*p == '\\') buf[len++] = read_escaped_char(&p, p + 1); else @@ -218,15 +260,210 @@ static Token *read_string_literal(char *start) { return tok; } -static void convert_keywords(Token *tok) { - for (Token *t = tok; t->kind != TK_EOF; t = t->next) +// Read a UTF-8-encoded string literal and transcode it in UTF-16. +// +// UTF-16 is yet another variable-width encoding for Unicode. Code +// points smaller than U+10000 are encoded in 2 bytes. Code points +// equal to or larger than that are encoded in 4 bytes. Each 2 bytes +// in the 4 byte sequence is called "surrogate", and a 4 byte sequence +// is called a "surrogate pair". +static Token *read_utf16_string_literal(char *start, char *quote) { + char *end = string_literal_end(quote + 1); + uint16_t *buf = calloc(2, end - start); + int len = 0; + + for (char *p = quote + 1; p < end;) { + if (*p == '\\') { + buf[len++] = read_escaped_char(&p, p + 1); + continue; + } + + uint32_t c = decode_utf8(&p, p); + if (c < 0x10000) { + // Encode a code point in 2 bytes. + buf[len++] = c; + } else { + // Encode a code point in 4 bytes. + c -= 0x10000; + buf[len++] = 0xd800 + ((c >> 10) & 0x3ff); + buf[len++] = 0xdc00 + (c & 0x3ff); + } + } + + Token *tok = new_token(TK_STR, start, end + 1); + tok->ty = array_of(ty_ushort, len + 1); + tok->str = (char *)buf; + return tok; +} + +// Read a UTF-8-encoded string literal and transcode it in UTF-32. +// +// UTF-32 is a fixed-width encoding for Unicode. Each code point is +// encoded in 4 bytes. +static Token *read_utf32_string_literal(char *start, char *quote, Type *ty) { + char *end = string_literal_end(quote + 1); + uint32_t *buf = calloc(4, end - quote); + int len = 0; + + for (char *p = quote + 1; p < end;) { + if (*p == '\\') + buf[len++] = read_escaped_char(&p, p + 1); + else + buf[len++] = decode_utf8(&p, p); + } + + Token *tok = new_token(TK_STR, start, end + 1); + tok->ty = array_of(ty, len + 1); + tok->str = (char *)buf; + return tok; +} + +static Token *read_char_literal(char *start, char *quote, Type *ty) { + char *p = quote + 1; + if (*p == '\0') + error_at(start, "unclosed char literal"); + + int c; + if (*p == '\\') + c = read_escaped_char(&p, p + 1); + else + c = decode_utf8(&p, p); + + char *end = strchr(p, '\''); + if (!end) + error_at(p, "unclosed char literal"); + + Token *tok = new_token(TK_NUM, start, end + 1); + tok->val = c; + tok->ty = ty; + return tok; +} + +static bool convert_pp_int(Token *tok) { + char *p = tok->loc; + + // Read a binary, octal, decimal or hexadecimal number. + int base = 10; + if (!strncasecmp(p, "0x", 2) && isxdigit(p[2])) { + p += 2; + base = 16; + } else if (!strncasecmp(p, "0b", 2) && (p[2] == '0' || p[2] == '1')) { + p += 2; + base = 2; + } else if (*p == '0') { + base = 8; + } + + int64_t val = strtoul(p, &p, base); + + // Read U, L or LL suffixes. + bool l = false; + bool u = false; + + if (startswith(p, "LLU") || startswith(p, "LLu") || + startswith(p, "llU") || startswith(p, "llu") || + startswith(p, "ULL") || startswith(p, "Ull") || + startswith(p, "uLL") || startswith(p, "ull")) { + p += 3; + l = u = true; + } else if (!strncasecmp(p, "lu", 2) || !strncasecmp(p, "ul", 2)) { + p += 2; + l = u = true; + } else if (startswith(p, "LL") || startswith(p, "ll")) { + p += 2; + l = true; + } else if (*p == 'L' || *p == 'l') { + p++; + l = true; + } else if (*p == 'U' || *p == 'u') { + p++; + u = true; + } + + if (p != tok->loc + tok->len) + return false; + + // Infer a type. + Type *ty; + if (base == 10) { + if (l && u) + ty = ty_ulong; + else if (l) + ty = ty_long; + else if (u) + ty = (val >> 32) ? ty_ulong : ty_uint; + else + ty = (val >> 31) ? ty_long : ty_int; + } else { + if (l && u) + ty = ty_ulong; + else if (l) + ty = (val >> 63) ? ty_ulong : ty_long; + else if (u) + ty = (val >> 32) ? ty_ulong : ty_uint; + else if (val >> 63) + ty = ty_ulong; + else if (val >> 32) + ty = ty_long; + else if (val >> 31) + ty = ty_uint; + else + ty = ty_int; + } + + tok->kind = TK_NUM; + tok->val = val; + tok->ty = ty; + return true; +} + +// The definition of the numeric literal at the preprocessing stage +// is more relaxed than the definition of that at the later stages. +// In order to handle that, a numeric literal is tokenized as a +// "pp-number" token first and then converted to a regular number +// token after preprocessing. +// +// This function converts a pp-number token to a regular number token. +static void convert_pp_number(Token *tok) { + // Try to parse as an integer constant. + if (convert_pp_int(tok)) + return; + + // If it's not an integer, it must be a floating point constant. + char *end; + long double val = strtold(tok->loc, &end); + + Type *ty; + if (*end == 'f' || *end == 'F') { + ty = ty_float; + end++; + } else if (*end == 'l' || *end == 'L') { + ty = ty_ldouble; + end++; + } else { + ty = ty_double; + } + + if (tok->loc + tok->len != end) + error_tok(tok, "invalid numeric constant"); + + tok->kind = TK_NUM; + tok->fval = val; + tok->ty = ty; +} + +void convert_pp_tokens(Token *tok) { + for (Token *t = tok; t->kind != TK_EOF; t = t->next) { if (is_keyword(t)) t->kind = TK_KEYWORD; + else if (t->kind == TK_PP_NUM) + convert_pp_number(t); + } } // Initialize line info for all tokens. static void add_line_numbers(Token *tok) { - char *p = current_input; + char *p = current_file->contents; int n = 1; do { @@ -239,19 +476,34 @@ static void add_line_numbers(Token *tok) { } while (*p++); } +Token *tokenize_string_literal(Token *tok, Type *basety) { + Token *t; + if (basety->size == 2) + t = read_utf16_string_literal(tok->loc, tok->loc); + else + t = read_utf32_string_literal(tok->loc, tok->loc, basety); + t->next = tok->next; + return t; +} + // Tokenize a given string and returns new tokens. -static Token *tokenize(char *filename, char *p) { - current_filename = filename; - current_input = p; +Token *tokenize(File *file) { + current_file = file; + + char *p = file->contents; Token head = {}; Token *cur = &head; + at_bol = true; + has_space = false; + while (*p) { // Skip line comments. if (startswith(p, "//")) { p += 2; while (*p != '\n') p++; + has_space = true; continue; } @@ -261,38 +513,110 @@ static Token *tokenize(char *filename, char *p) { if (!q) error_at(p, "unclosed block comment"); p = q + 2; + has_space = true; + continue; + } + + // Skip newline. + if (*p == '\n') { + p++; + at_bol = true; + has_space = false; continue; } // Skip whitespace characters. if (isspace(*p)) { p++; + has_space = true; continue; } // Numeric literal - if (isdigit(*p)) { - cur = cur->next = new_token(TK_NUM, p, p); - char *q = p; - cur->val = strtoul(p, &p, 10); - cur->len = p - q; + if (isdigit(*p) || (*p == '.' && isdigit(p[1]))) { + char *q = p++; + for (;;) { + if (p[0] && p[1] && strchr("eEpP", p[0]) && strchr("+-", p[1])) + p += 2; + else if (isalnum(*p) || *p == '.') + p++; + else + break; + } + cur = cur->next = new_token(TK_PP_NUM, q, p); continue; } // String literal if (*p == '"') { - cur = cur->next = read_string_literal(p); + cur = cur->next = read_string_literal(p, p); + p += cur->len; + continue; + } + + // UTF-8 string literal + if (startswith(p, "u8\"")) { + cur = cur->next = read_string_literal(p, p + 2); + p += cur->len; + continue; + } + + // UTF-16 string literal + if (startswith(p, "u\"")) { + cur = cur->next = read_utf16_string_literal(p, p + 1); + p += cur->len; + continue; + } + + // Wide string literal + if (startswith(p, "L\"")) { + cur = cur->next = read_utf32_string_literal(p, p + 1, ty_int); + p += cur->len; + continue; + } + + // UTF-32 string literal + if (startswith(p, "U\"")) { + cur = cur->next = read_utf32_string_literal(p, p + 1, ty_uint); + p += cur->len; + continue; + } + + // Character literal + if (*p == '\'') { + cur = cur->next = read_char_literal(p, p, ty_int); + cur->val = (char)cur->val; + p += cur->len; + continue; + } + + // UTF-16 character literal + if (startswith(p, "u'")) { + cur = cur->next = read_char_literal(p, p + 1, ty_ushort); + cur->val &= 0xffff; + p += cur->len; + continue; + } + + // Wide character literal + if (startswith(p, "L'")) { + cur = cur->next = read_char_literal(p, p + 1, ty_int); + p += cur->len; + continue; + } + + // UTF-32 character literal + if (startswith(p, "U'")) { + cur = cur->next = read_char_literal(p, p + 1, ty_uint); p += cur->len; continue; } // Identifier or keyword - if (is_ident1(*p)) { - char *start = p; - do { - p++; - } while (is_ident2(*p)); - cur = cur->next = new_token(TK_IDENT, start, p); + int ident_len = read_ident(p); + if (ident_len) { + cur = cur->next = new_token(TK_IDENT, p, p + ident_len); + p += cur->len; continue; } @@ -309,7 +633,6 @@ static Token *tokenize(char *filename, char *p) { cur = cur->next = new_token(TK_EOF, p, p); add_line_numbers(head.next); - convert_keywords(head.next); return head.next; } @@ -323,7 +646,7 @@ static char *read_file(char *path) { } else { fp = fopen(path, "r"); if (!fp) - error("cannot open %s: %s", path, strerror(errno)); + return NULL; } char *buf; @@ -351,6 +674,132 @@ static char *read_file(char *path) { return buf; } +File **get_input_files(void) { + return input_files; +} + +File *new_file(char *name, int file_no, char *contents) { + File *file = calloc(1, sizeof(File)); + file->name = name; + file->display_name = name; + file->file_no = file_no; + file->contents = contents; + return file; +} + +// Replaces \r or \r\n with \n. +static void canonicalize_newline(char *p) { + int i = 0, j = 0; + + while (p[i]) { + if (p[i] == '\r' && p[i + 1] == '\n') { + i += 2; + p[j++] = '\n'; + } else if (p[i] == '\r') { + i++; + p[j++] = '\n'; + } else { + p[j++] = p[i++]; + } + } + + p[j] = '\0'; +} + +// Removes backslashes followed by a newline. +static void remove_backslash_newline(char *p) { + int i = 0, j = 0; + + // We want to keep the number of newline characters so that + // the logical line number matches the physical one. + // This counter maintain the number of newlines we have removed. + int n = 0; + + while (p[i]) { + if (p[i] == '\\' && p[i + 1] == '\n') { + i += 2; + n++; + } else if (p[i] == '\n') { + p[j++] = p[i++]; + for (; n > 0; n--) + p[j++] = '\n'; + } else { + p[j++] = p[i++]; + } + } + + for (; n > 0; n--) + p[j++] = '\n'; + p[j] = '\0'; +} + +static uint32_t read_universal_char(char *p, int len) { + uint32_t c = 0; + for (int i = 0; i < len; i++) { + if (!isxdigit(p[i])) + return 0; + c = (c << 4) | from_hex(p[i]); + } + return c; +} + +// Replace \u or \U escape sequences with corresponding UTF-8 bytes. +static void convert_universal_chars(char *p) { + char *q = p; + + while (*p) { + if (startswith(p, "\\u")) { + uint32_t c = read_universal_char(p + 2, 4); + if (c) { + p += 6; + q += encode_utf8(q, c); + } else { + *q++ = *p++; + } + } else if (startswith(p, "\\U")) { + uint32_t c = read_universal_char(p + 2, 8); + if (c) { + p += 10; + q += encode_utf8(q, c); + } else { + *q++ = *p++; + } + } else if (p[0] == '\\') { + *q++ = *p++; + *q++ = *p++; + } else { + *q++ = *p++; + } + } + + *q = '\0'; +} + Token *tokenize_file(char *path) { - return tokenize(path, read_file(path)); + char *p = read_file(path); + if (!p) + return NULL; + + // UTF-8 texts may start with a 3-byte "BOM" marker sequence. + // If exists, just skip them because they are useless bytes. + // (It is actually not recommended to add BOM markers to UTF-8 + // texts, but it's not uncommon particularly on Windows.) + if (!memcmp(p, "\xef\xbb\xbf", 3)) + p += 3; + + canonicalize_newline(p); + remove_backslash_newline(p); + convert_universal_chars(p); + + // Save the filename for assembler .file directive. + static int file_no; + File *file = new_file(path, file_no + 1, p); + + // Save the filename for assembler .file directive. + input_files = realloc(input_files, sizeof(char *) * (file_no + 2)); + input_files[file_no] = file; + input_files[file_no + 1] = NULL; + file_no++; + + return tokenize(file); } diff --git a/type.c b/type.c index 847b543e..02ade592 100644 --- a/type.c +++ b/type.c @@ -1,10 +1,22 @@ #include "chibicc.h" +Type *ty_void = &(Type){TY_VOID, 1, 1}; +Type *ty_bool = &(Type){TY_BOOL, 1, 1}; + Type *ty_char = &(Type){TY_CHAR, 1, 1}; Type *ty_short = &(Type){TY_SHORT, 2, 2}; Type *ty_int = &(Type){TY_INT, 4, 4}; Type *ty_long = &(Type){TY_LONG, 8, 8}; +Type *ty_uchar = &(Type){TY_CHAR, 1, 1, true}; +Type *ty_ushort = &(Type){TY_SHORT, 2, 2, true}; +Type *ty_uint = &(Type){TY_INT, 4, 4, true}; +Type *ty_ulong = &(Type){TY_LONG, 8, 8, true}; + +Type *ty_float = &(Type){TY_FLOAT, 4, 4}; +Type *ty_double = &(Type){TY_DOUBLE, 8, 8}; +Type *ty_ldouble = &(Type){TY_LDOUBLE, 16, 16}; + static Type *new_type(TypeKind kind, int size, int align) { Type *ty = calloc(1, sizeof(Type)); ty->kind = kind; @@ -15,25 +27,84 @@ static Type *new_type(TypeKind kind, int size, int align) { bool is_integer(Type *ty) { TypeKind k = ty->kind; - return k == TY_CHAR || k == TY_SHORT || k == TY_INT || - k == TY_LONG; + return k == TY_BOOL || k == TY_CHAR || k == TY_SHORT || + k == TY_INT || k == TY_LONG || k == TY_ENUM; +} + +bool is_flonum(Type *ty) { + return ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE || + ty->kind == TY_LDOUBLE; +} + +bool is_numeric(Type *ty) { + return is_integer(ty) || is_flonum(ty); +} + +bool is_compatible(Type *t1, Type *t2) { + if (t1 == t2) + return true; + + if (t1->origin) + return is_compatible(t1->origin, t2); + + if (t2->origin) + return is_compatible(t1, t2->origin); + + if (t1->kind != t2->kind) + return false; + + switch (t1->kind) { + case TY_CHAR: + case TY_SHORT: + case TY_INT: + case TY_LONG: + return t1->is_unsigned == t2->is_unsigned; + case TY_FLOAT: + case TY_DOUBLE: + case TY_LDOUBLE: + return true; + case TY_PTR: + return is_compatible(t1->base, t2->base); + case TY_FUNC: { + if (!is_compatible(t1->return_ty, t2->return_ty)) + return false; + if (t1->is_variadic != t2->is_variadic) + return false; + + Type *p1 = t1->params; + Type *p2 = t2->params; + for (; p1 && p2; p1 = p1->next, p2 = p2->next) + if (!is_compatible(p1, p2)) + return false; + return p1 == NULL && p2 == NULL; + } + case TY_ARRAY: + if (!is_compatible(t1->base, t2->base)) + return false; + return t1->array_len < 0 && t2->array_len < 0 && + t1->array_len == t2->array_len; + } + return false; } Type *copy_type(Type *ty) { Type *ret = calloc(1, sizeof(Type)); *ret = *ty; + ret->origin = ty; return ret; } Type *pointer_to(Type *base) { Type *ty = new_type(TY_PTR, 8, 8); ty->base = base; + ty->is_unsigned = true; return ty; } Type *func_type(Type *return_ty) { - Type *ty = calloc(1, sizeof(Type)); - ty->kind = TY_FUNC; + // The C spec disallows sizeof(), but + // GCC allows that and the expression is evaluated to 1. + Type *ty = new_type(TY_FUNC, 1, 1); ty->return_ty = return_ty; return ty; } @@ -45,6 +116,63 @@ Type *array_of(Type *base, int len) { return ty; } +Type *vla_of(Type *base, Node *len) { + Type *ty = new_type(TY_VLA, 8, 8); + ty->base = base; + ty->vla_len = len; + return ty; +} + +Type *enum_type(void) { + return new_type(TY_ENUM, 4, 4); +} + +Type *struct_type(void) { + return new_type(TY_STRUCT, 0, 1); +} + +static Type *get_common_type(Type *ty1, Type *ty2) { + if (ty1->base) + return pointer_to(ty1->base); + + if (ty1->kind == TY_FUNC) + return pointer_to(ty1); + if (ty2->kind == TY_FUNC) + return pointer_to(ty2); + + if (ty1->kind == TY_LDOUBLE || ty2->kind == TY_LDOUBLE) + return ty_ldouble; + if (ty1->kind == TY_DOUBLE || ty2->kind == TY_DOUBLE) + return ty_double; + if (ty1->kind == TY_FLOAT || ty2->kind == TY_FLOAT) + return ty_float; + + if (ty1->size < 4) + ty1 = ty_int; + if (ty2->size < 4) + ty2 = ty_int; + + if (ty1->size != ty2->size) + return (ty1->size < ty2->size) ? ty2 : ty1; + + if (ty2->is_unsigned) + return ty2; + return ty1; +} + +// For many binary operators, we implicitly promote operands so that +// both operands have the same type. Any integral type smaller than +// int is always promoted to int. If the type of one operand is larger +// than the other's (e.g. "long" vs. "int"), the smaller operand will +// be promoted to match with the other. +// +// This operation is called the "usual arithmetic conversion". +static void usual_arith_conv(Node **lhs, Node **rhs) { + Type *ty = get_common_type((*lhs)->ty, (*rhs)->ty); + *lhs = new_cast(*lhs, ty); + *rhs = new_cast(*rhs, ty); +} + void add_type(Node *node) { if (!node || node->ty) return; @@ -63,44 +191,85 @@ void add_type(Node *node) { add_type(n); switch (node->kind) { + case ND_NUM: + node->ty = ty_int; + return; case ND_ADD: case ND_SUB: case ND_MUL: case ND_DIV: - case ND_NEG: + case ND_MOD: + case ND_BITAND: + case ND_BITOR: + case ND_BITXOR: + usual_arith_conv(&node->lhs, &node->rhs); node->ty = node->lhs->ty; return; + case ND_NEG: { + Type *ty = get_common_type(ty_int, node->lhs->ty); + node->lhs = new_cast(node->lhs, ty); + node->ty = ty; + return; + } case ND_ASSIGN: if (node->lhs->ty->kind == TY_ARRAY) error_tok(node->lhs->tok, "not an lvalue"); + if (node->lhs->ty->kind != TY_STRUCT) + node->rhs = new_cast(node->rhs, node->lhs->ty); node->ty = node->lhs->ty; return; case ND_EQ: case ND_NE: case ND_LT: case ND_LE: - case ND_NUM: + usual_arith_conv(&node->lhs, &node->rhs); + node->ty = ty_int; + return; case ND_FUNCALL: - node->ty = ty_long; + node->ty = node->func_ty->return_ty; + return; + case ND_NOT: + case ND_LOGOR: + case ND_LOGAND: + node->ty = ty_int; + return; + case ND_BITNOT: + case ND_SHL: + case ND_SHR: + node->ty = node->lhs->ty; return; case ND_VAR: + case ND_VLA_PTR: node->ty = node->var->ty; return; + case ND_COND: + if (node->then->ty->kind == TY_VOID || node->els->ty->kind == TY_VOID) { + node->ty = ty_void; + } else { + usual_arith_conv(&node->then, &node->els); + node->ty = node->then->ty; + } + return; case ND_COMMA: node->ty = node->rhs->ty; return; case ND_MEMBER: node->ty = node->member->ty; return; - case ND_ADDR: - if (node->lhs->ty->kind == TY_ARRAY) - node->ty = pointer_to(node->lhs->ty->base); + case ND_ADDR: { + Type *ty = node->lhs->ty; + if (ty->kind == TY_ARRAY) + node->ty = pointer_to(ty->base); else - node->ty = pointer_to(node->lhs->ty); + node->ty = pointer_to(ty); return; + } case ND_DEREF: if (!node->lhs->ty->base) error_tok(node->tok, "invalid pointer dereference"); + if (node->lhs->ty->base->kind == TY_VOID) + error_tok(node->tok, "dereferencing a void pointer"); + node->ty = node->lhs->ty->base; return; case ND_STMT_EXPR: @@ -115,5 +284,24 @@ void add_type(Node *node) { } error_tok(node->tok, "statement expression returning void is not supported"); return; + case ND_LABEL_VAL: + node->ty = pointer_to(ty_void); + return; + case ND_CAS: + add_type(node->cas_addr); + add_type(node->cas_old); + add_type(node->cas_new); + node->ty = ty_bool; + + if (node->cas_addr->ty->kind != TY_PTR) + error_tok(node->cas_addr->tok, "pointer expected"); + if (node->cas_old->ty->kind != TY_PTR) + error_tok(node->cas_old->tok, "pointer expected"); + return; + case ND_EXCH: + if (node->lhs->ty->kind != TY_PTR) + error_tok(node->cas_addr->tok, "pointer expected"); + node->ty = node->lhs->ty->base; + return; } } diff --git a/unicode.c b/unicode.c new file mode 100644 index 00000000..2eeeeed9 --- /dev/null +++ b/unicode.c @@ -0,0 +1,189 @@ +#include "chibicc.h" + +// Encode a given character in UTF-8. +int encode_utf8(char *buf, uint32_t c) { + if (c <= 0x7F) { + buf[0] = c; + return 1; + } + + if (c <= 0x7FF) { + buf[0] = 0b11000000 | (c >> 6); + buf[1] = 0b10000000 | (c & 0b00111111); + return 2; + } + + if (c <= 0xFFFF) { + buf[0] = 0b11100000 | (c >> 12); + buf[1] = 0b10000000 | ((c >> 6) & 0b00111111); + buf[2] = 0b10000000 | (c & 0b00111111); + return 3; + } + + buf[0] = 0b11110000 | (c >> 18); + buf[1] = 0b10000000 | ((c >> 12) & 0b00111111); + buf[2] = 0b10000000 | ((c >> 6) & 0b00111111); + buf[3] = 0b10000000 | (c & 0b00111111); + return 4; +} + +// Read a UTF-8-encoded Unicode code point from a source file. +// We assume that source files are always in UTF-8. +// +// UTF-8 is a variable-width encoding in which one code point is +// encoded in one to four bytes. One byte UTF-8 code points are +// identical to ASCII. Non-ASCII characters are encoded using more +// than one byte. +uint32_t decode_utf8(char **new_pos, char *p) { + if ((unsigned char)*p < 128) { + *new_pos = p + 1; + return *p; + } + + char *start = p; + int len; + uint32_t c; + + if ((unsigned char)*p >= 0b11110000) { + len = 4; + c = *p & 0b111; + } else if ((unsigned char)*p >= 0b11100000) { + len = 3; + c = *p & 0b1111; + } else if ((unsigned char)*p >= 0b11000000) { + len = 2; + c = *p & 0b11111; + } else { + error_at(start, "invalid UTF-8 sequence"); + } + + for (int i = 1; i < len; i++) { + if ((unsigned char)p[i] >> 6 != 0b10) + error_at(start, "invalid UTF-8 sequence"); + c = (c << 6) | (p[i] & 0b111111); + } + + *new_pos = p + len; + return c; +} + +static bool in_range(uint32_t *range, uint32_t c) { + for (int i = 0; range[i] != -1; i += 2) + if (range[i] <= c && c <= range[i + 1]) + return true; + return false; +} + +// [https://www.sigbus.info/n1570#D] C11 allows not only ASCII but +// some multibyte characters in certan Unicode ranges to be used in an +// identifier. +// +// This function returns true if a given character is acceptable as +// the first character of an identifier. +// +// For example, ¾ (U+00BE) is a valid identifier because characters in +// 0x00BE-0x00C0 are allowed, while neither ⟘ (U+27D8) nor ' ' +// (U+3000, full-width space) are allowed because they are out of range. +bool is_ident1(uint32_t c) { + static uint32_t range[] = { + '_', '_', 'a', 'z', 'A', 'Z', '$', '$', + 0x00A8, 0x00A8, 0x00AA, 0x00AA, 0x00AD, 0x00AD, 0x00AF, 0x00AF, + 0x00B2, 0x00B5, 0x00B7, 0x00BA, 0x00BC, 0x00BE, 0x00C0, 0x00D6, + 0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x02FF, 0x0370, 0x167F, + 0x1681, 0x180D, 0x180F, 0x1DBF, 0x1E00, 0x1FFF, 0x200B, 0x200D, + 0x202A, 0x202E, 0x203F, 0x2040, 0x2054, 0x2054, 0x2060, 0x206F, + 0x2070, 0x20CF, 0x2100, 0x218F, 0x2460, 0x24FF, 0x2776, 0x2793, + 0x2C00, 0x2DFF, 0x2E80, 0x2FFF, 0x3004, 0x3007, 0x3021, 0x302F, + 0x3031, 0x303F, 0x3040, 0xD7FF, 0xF900, 0xFD3D, 0xFD40, 0xFDCF, + 0xFDF0, 0xFE1F, 0xFE30, 0xFE44, 0xFE47, 0xFFFD, + 0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, + 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, + 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, + 0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD, -1, + }; + + return in_range(range, c); +} + +// Returns true if a given character is acceptable as a non-first +// character of an identifier. +bool is_ident2(uint32_t c) { + static uint32_t range[] = { + '0', '9', '$', '$', 0x0300, 0x036F, 0x1DC0, 0x1DFF, 0x20D0, 0x20FF, + 0xFE20, 0xFE2F, -1, + }; + + return is_ident1(c) || in_range(range, c); +} + +// Returns the number of columns needed to display a given +// character in a fixed-width font. +// +// Based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +static int char_width(uint32_t c) { + static uint32_t range1[] = { + 0x0000, 0x001F, 0x007f, 0x00a0, 0x0300, 0x036F, 0x0483, 0x0486, + 0x0488, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, + 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0600, 0x0603, 0x0610, 0x0615, + 0x064B, 0x065E, 0x0670, 0x0670, 0x06D6, 0x06E4, 0x06E7, 0x06E8, + 0x06EA, 0x06ED, 0x070F, 0x070F, 0x0711, 0x0711, 0x0730, 0x074A, + 0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x0901, 0x0902, 0x093C, 0x093C, + 0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0954, 0x0962, 0x0963, + 0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, 0x09CD, 0x09CD, + 0x09E2, 0x09E3, 0x0A01, 0x0A02, 0x0A3C, 0x0A3C, 0x0A41, 0x0A42, + 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A70, 0x0A71, 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, 0x0B3F, 0x0B3F, + 0x0B41, 0x0B43, 0x0B4D, 0x0B4D, 0x0B56, 0x0B56, 0x0B82, 0x0B82, + 0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, 0x0C3E, 0x0C40, 0x0C46, 0x0C48, + 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF, + 0x0CC6, 0x0CC6, 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D41, 0x0D43, + 0x0D4D, 0x0D4D, 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6, + 0x0E31, 0x0E31, 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, + 0x0EB4, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19, + 0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E, + 0x0F80, 0x0F84, 0x0F86, 0x0F87, 0x0F90, 0x0F97, 0x0F99, 0x0FBC, + 0x0FC6, 0x0FC6, 0x102D, 0x1030, 0x1032, 0x1032, 0x1036, 0x1037, + 0x1039, 0x1039, 0x1058, 0x1059, 0x1160, 0x11FF, 0x135F, 0x135F, + 0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, + 0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, 0x17C9, 0x17D3, + 0x17DD, 0x17DD, 0x180B, 0x180D, 0x18A9, 0x18A9, 0x1920, 0x1922, + 0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18, + 0x1B00, 0x1B03, 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C, + 0x1B42, 0x1B42, 0x1B6B, 0x1B73, 0x1DC0, 0x1DCA, 0x1DFE, 0x1DFF, + 0x200B, 0x200F, 0x202A, 0x202E, 0x2060, 0x2063, 0x206A, 0x206F, + 0x20D0, 0x20EF, 0x302A, 0x302F, 0x3099, 0x309A, 0xA806, 0xA806, + 0xA80B, 0xA80B, 0xA825, 0xA826, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, + 0xFE20, 0xFE23, 0xFEFF, 0xFEFF, 0xFFF9, 0xFFFB, 0x10A01, 0x10A03, + 0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, + 0x1D167, 0x1D169, 0x1D173, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, 0xE0001, 0xE0001, 0xE0020, 0xE007F, 0xE0100, 0xE01EF, + -1, + }; + + if (in_range(range1, c)) + return 0; + + static uint32_t range2[] = { + 0x1100, 0x115F, 0x2329, 0x2329, 0x232A, 0x232A, 0x2E80, 0x303E, + 0x3040, 0xA4CF, 0xAC00, 0xD7A3, 0xF900, 0xFAFF, 0xFE10, 0xFE19, + 0xFE30, 0xFE6F, 0xFF00, 0xFF60, 0xFFE0, 0xFFE6, 0x1F000, 0x1F644, + 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, -1, + }; + + if (in_range(range2, c)) + return 2; + return 1; +} + +// Returns the number of columns needed to display a given +// string in a fixed-width font. +int display_width(char *p, int len) { + char *start = p; + int w = 0; + while (p - start < len) { + uint32_t c = decode_utf8(&p, p); + w += char_width(c); + } + return w; +}