diff options
44 files changed, 9716 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1853630 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +**/*~ +**/\#* +**/*.o +**/*.s +**/a.out +/tmp* +/thirdparty +/chibicc +/test/*.exe +/stage2 +build +chibicc +.*.sw* +dietc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..79e825b --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +CFLAGS=-std=c11 -g -fno-common -Wall -Wno-switch +CFLAGS+=-DDIETC_ROOT="\"$(PWD)\"" +# CFLAGS=$(CFLAGS) -fsanitize=address + +SRCS=$(wildcard *.c) +OBJS=$(addprefix build/,$(SRCS:.c=.o)) + +dietc: $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +build/%.o: %.c chibicc.h + @mkdir -p build + $(CC) $(CFLAGS) -c $(word 1,$^) -o $@ + +# Misc. + +clean: + rm -rf dietc build + +.PHONY: clean diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..ca59161 --- /dev/null +++ b/README.txt @@ -0,0 +1,76 @@ +# dietC: a C backend for chibicc + +What: desugar C code + +Why: easier program analysis and transformation + +How: fork of Rui Ueyama's great chibicc compiler, replacing the x86 assembly +backend with a C backend + +Quick Example: + + $ cat tests/super_simple.c + int main(void) { + int x = 1; + int y = 2; + return y; + } + $ make + ... + $ ./dietc tests/super_simple.c + #include "/home/matthew/repos/dietc/scripts/dietc_helpers.h" + typedef int Type_1 ; + ... + extern Type_5 main ; + ... + Type_1 main ( ) { + Type_1 t1 ; + ... + t3 = 1 ; + * t5 = t3 ; + ... + return t1 ; + } + +Selling points: + + - Guaranteed space-separated tokens + - Extremely restricted grammar, super easy to parse; see docs/LANGUAGE.txt + - Maintains all type information + - Comes with a wrapper to replace GCC in existing build scripts; see + docs/DIETCC.txt + +Known to be unsupported: + +- dietC is ... optimistic about identifier conflicts with its generated + labels. It likely will not work if you run it on its own output. This is very + fixable, will do soon. +- chibicc does not have good support for long double; specifically, long + doubles cannot be used in constant expressions. This is fixable, but requires + non-trivial modifications to the chibicc side of the code. +- chibicc does not parse const, volatile, etc. type modifiers. it would not be + too hard to support this if desired. + +Why not cilly? + + - cilly performs both not enough and too much lowering. + - it often compiles a struct field acces a->foo into a literal offset like + *(a + 16). + - even after simplification it has multiple control flow constructs: loop, + etc. + - it's written in OCAML, and hard to parse the result outside of OCAML + +Why not C--, assembly, QBE? + + - all these are strictly lower level than cilly. They preserve almost no type + information at all. + +Why not LLVM? + + - LLVM has breaking changes approximately every 5ns and preservation of + high-level type information is not guaranteed. + - mostly forced to use LLVM's C++ interfaces and then compile with LLVM + +License: + - the underlying chibicc code is licensed under the MIT license + - my modifications are licensed under the AGPLv3 diff --git a/chibicc.h b/chibicc.h new file mode 100644 index 0000000..397c4fb --- /dev/null +++ b/chibicc.h @@ -0,0 +1,472 @@ +#define _POSIX_C_SOURCE 200809L +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <glob.h> +#include <libgen.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#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 +// + +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 +// + +// Token +typedef enum { + TK_IDENT, // Identifiers + TK_PUNCT, // Punctuators + 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 + 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 +}; + +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__) + +// +// parse.c +// + +// Variable or function +typedef struct Obj Obj; +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; + + // helper + bool is_param; +}; + +// 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 +struct Node { + NodeKind kind; // Node kind + Node *next; // Next node + Type *ty; // Type, e.g. int or pointer to int + Token *tok; // Representative token + + Node *lhs; // Left-hand side + Node *rhs; // Right-hand side + + // "if" or "for" statement + Node *cond; + Node *then; + Node *els; + Node *init; + Node *inc; + + // "break" and "continue" labels + char *brk_label; + char *cont_label; + + // Block or statement expression + Node *body; + + // Struct member access + Member *member; + + // Function call + 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; + + // 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); + +// +// type.c +// + +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 + 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. + // + // In many contexts in which a pointer is expected, we examine this + // member instead of "kind" member to determine whether a type is a + // pointer or not. That means in many contexts "array of T" is + // naturally handled as if it were "pointer to T", as required by + // the C spec. + Type *base; + + // 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; + + // ID for printing + int id; + int hashing; + int typedecling; + Type *pointer_type; +}; + +// Struct member +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); + +// +// codegen.c +// + +void codegen(Obj *prog, FILE *out); +int align_to(int n, int align); + +// +// typegen.c +// + +void typegen(Obj *prog, FILE *out); + +// +// type_helpers.c +// + +int definitely_same_type(Type *type1, Type *type2); +unsigned long hash_type(Type *type); + +// +// 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 +// + +extern bool opt_fpic; +extern bool opt_fcommon; +extern char *base_file; + +// +// preprocess.c +// + +Token *preprocess(Token *tok); diff --git a/codegen.c b/codegen.c new file mode 100644 index 0000000..9eaac4a --- /dev/null +++ b/codegen.c @@ -0,0 +1,771 @@ +#include "chibicc.h" +#include <unistd.h> + +#define GP_MAX 6 +#define FP_MAX 8 + +int is_bad_number(double x) { + return (x != x) || (x == 1./0.) || (-x == 1./0.); +} + +static FILE *output_file; +static int RETURN_TMP; + +static void printnoln(char *fmt, ...); +static void println(char *fmt, ...); +static void print_tok(Token *tok); + +FILE *BUFFER; +int DO_BUFFER; + +static void flush_buffer() { + rewind(BUFFER); + int c; + while ((c = fgetc(BUFFER)) != EOF) + fputc(c, output_file); + rewind(BUFFER); + ftruncate(fileno(BUFFER), 0); +} + +void print_label(char *label) { + printnoln("_l"); + for (; *label; label++) { + if (*label == '.') printnoln("_"); + else printnoln("%c", *label); + } +} + +static void print_tok(Token *tok) { + if (tok->str) printnoln("%s", tok->str); + else { + assert(tok->loc); + for (int i = 0; i < tok->len; i++) + printnoln("%c", tok->loc[i]); + } +} + +void print_obj(Obj *obj) { + assert(obj->offset >= 0); + if (obj->is_local || strchr(obj->name, '.')) { + printnoln("_"); + for (char *c = obj->name; *c; c++) + if (*c == '.') printnoln("_"); + else printnoln("%c", *c); + printnoln("_%d", obj->offset); + } else { + printnoln("%s", obj->name); + } +} + +const void print_type(Type *type) { + if (type->kind == TY_FUNC) { + assert(type->pointer_type); + print_type(type->pointer_type); + } else { + assert(type->id); + printnoln("Type_%d", type->id); + } +} +static int depth; +static Obj *current_fn; + +static void gen_expr(Node *node, int to_tmp); +static void gen_stmt(Node *node); + +__attribute__((format(printf, 1, 2))) +static void println(char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (DO_BUFFER) + vfprintf(BUFFER, fmt, ap); + else + vfprintf(output_file, fmt, ap); + va_end(ap); + if (DO_BUFFER) + fprintf(BUFFER, "\n"); + else + fprintf(output_file, "\n"); +} + +__attribute__((format(printf, 1, 2))) +static void printnoln(char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (DO_BUFFER) + vfprintf(BUFFER, fmt, ap); + else + vfprintf(output_file, fmt, ap); + va_end(ap); +} + +static int count(void) { + static int i = 1; + return i++; +} + +// 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; +} + +void decltmp(Type *type, int c) { + DO_BUFFER = 0; + printnoln("\t"); + print_type(type); + println(" t%d ;", c); + DO_BUFFER = 1; +} + +void decltmpptr(Type *type, int c) { + assert(type->pointer_type); + decltmp(type->pointer_type, c); +} + +// 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, int to_tmp) { + switch (node->kind) { + case ND_VAR: + decltmpptr(node->ty, to_tmp); + printnoln("\tt%d = & ", to_tmp); + print_obj(node->var); + println(" ;"); + return; + case ND_DEREF: + gen_expr(node->lhs, to_tmp); + return; + case ND_COMMA: + gen_expr(node->lhs, count()); + gen_addr(node->rhs, to_tmp); + return; + case ND_MEMBER: { + int s = count(); + gen_addr(node->lhs, s); + decltmpptr(node->ty, to_tmp); + printnoln("\tt%d = FIELDPTR ( t%d, ", to_tmp, s); + if (node->member->name) + print_tok(node->member->name); + else + printnoln("___dietc_f%d", node->member->idx); + println(" ) ;"); + return; + } + case ND_ASSIGN: + case ND_COND: + if (node->ty->kind == TY_STRUCT || node->ty->kind == TY_UNION) { + gen_expr(node, to_tmp); + return; + } + break; + case ND_VLA_PTR: assert(0); + } + + error_tok(node->tok, "not an lvalue"); +} + +// Generate code for a given node. +static void gen_expr(Node *node, int to_tmp) { + // println(" .loc %d %d", node->tok->file->file_no, node->tok->line_no); + + switch (node->kind) { + case ND_NULL_EXPR: + return; + case ND_NUM: { + decltmp(node->ty, to_tmp); + switch (node->ty->kind) { + case TY_FLOAT: + case TY_DOUBLE: + case TY_LDOUBLE: + println("\tt%d = %Lf ;", to_tmp, node->fval); + return; + default: + println("\tt%d = %ld ;", to_tmp, node->val); + return; + } + } + case ND_NEG: { + int c = count(); + gen_expr(node->lhs, c); + decltmp(node->ty, to_tmp); + println("\tt%d = - t%d ;", to_tmp, c); + return; + } + case ND_VAR: { + // we inline the *getaddr(node) + if (node->ty->kind == TY_ARRAY) { + decltmpptr(node->ty->base, to_tmp); + printnoln("\tt%d = ", to_tmp); + print_obj(node->var); + println(" ;"); + } else { + decltmp(node->ty, to_tmp); + printnoln("\tt%d = ", to_tmp); + print_obj(node->var); + println(" ;"); + } + return; + } + case ND_MEMBER: { + int c = count(); + gen_addr(node, c); + // TODO: what to do in this case? + if (node->ty->kind == TY_ARRAY) { + decltmpptr(node->ty->base, to_tmp); + println("\tt%d = * t%d ;", to_tmp, c); + } else { + decltmp(node->ty, to_tmp); + println("\tt%d = * t%d ;", to_tmp, c); + } + + if (node->member->is_bitfield) + fprintf(stderr, "WARNING: bitfields ignored"); + return; + } + case ND_DEREF: { + int c = count(); + gen_expr(node->lhs, c); + // TODO: deref a pointer to an array; is the temporary a pointer, or an + // array?? + if (node->ty->kind == TY_ARRAY) { + decltmpptr(node->ty->base, to_tmp); + println("\tt%d = * t%d ;", to_tmp, c); + } else { + decltmp(node->ty, to_tmp); + println("\tt%d = * t%d ;", to_tmp, c); + } + return; + } + case ND_ADDR: + gen_addr(node->lhs, to_tmp); + return; + case ND_ASSIGN: { + int lhsa = count(); + gen_addr(node->lhs, lhsa); + gen_expr(node->rhs, to_tmp); + + if (node->lhs->kind == ND_MEMBER && node->lhs->member->is_bitfield) + fprintf(stderr, "WARNING: bitfields ignored"); + + println("\t* t%d = t%d ;", lhsa, to_tmp); + return; + } + case ND_STMT_EXPR: + for (Node *n = node->body; n; n = n->next) { + if (n->next || n->kind != ND_EXPR_STMT) + gen_stmt(n); + else + gen_expr(n->lhs, to_tmp); + } + return; + case ND_COMMA: + gen_expr(node->lhs, count()); + gen_expr(node->rhs, to_tmp); + return; + case ND_CAST: { + if (definitely_same_type(node->lhs->ty, node->ty)) + return gen_expr(node->lhs, to_tmp); + int c = count(); + if (node->ty->kind == TY_VOID) { + gen_expr(node->lhs, c); + println("\t( Type_%d ) t%d ;", node->ty->id, c); + } else if (node->lhs->ty->kind == TY_UNION) { + // union *tuptr = &union + int uptr = count(); + gen_addr(node->lhs, uptr); + // T * tc = ( T * ) tuptr + decltmpptr(node->ty, c); + println("\tt%d = ( Type_%d ) t%d ;", + c, node->ty->pointer_type->id, uptr); + // dereference it + decltmp(node->ty, to_tmp); + println("\tt%d = * t%d ;", to_tmp, c); + } else { + gen_expr(node->lhs, c); + decltmp(node->ty, to_tmp); + println("\tt%d = ( Type_%d ) t%d ;", to_tmp, node->ty->id, c); + } + return; + } + case ND_MEMZERO: + printnoln("\tMEMZERO ( "); print_obj(node->var); println(" ) ;"); + return; + case ND_COND: { + int c = count(), cond = count(), condfalse = count(), + tmp1 = count(), tmp2 = count(); + gen_expr(node->cond, cond); + decltmp(ty_int, condfalse); + if (node->ty->kind != TY_VOID) + decltmp(node->ty, to_tmp); + println("\tt%d = ! t%d ;", condfalse, cond); + println("\tif ( t%d ) goto _L_else_%d ;", condfalse, c); + gen_expr(node->then, tmp1); + if (node->ty->kind != TY_VOID) + println("\tt%d = t%d ;", to_tmp, tmp1); + println("\tgoto _L_end_%d ;", c); + println("\t_L_else_%d :", c); + gen_expr(node->els, tmp2); + if (node->ty->kind != TY_VOID) + println("\tt%d = t%d ;", to_tmp, tmp2); + println("\t_L_end_%d :", c); + return; + } + case ND_NOT: { + int c = count(); + gen_expr(node->lhs, c); + decltmp(ty_int, to_tmp); + println("\tt%d = ! t%d ;", to_tmp, c); + return; + } + case ND_BITNOT: { + int c = count(); + gen_expr(node->lhs, c); + decltmp(node->ty, to_tmp); + println("\tt%d = ~ t%d ;", to_tmp, c); + return; + } + case ND_LOGAND: { + int c = count(), lhs = count(), lhsfalse = count(), + rhs = count(), rhsfalse = count(); + decltmp(ty_int, to_tmp); + decltmp(ty_int, lhsfalse); + decltmp(ty_int, rhsfalse); + gen_expr(node->lhs, lhs); + println("\tt%d = 0 ;", to_tmp); + println("\tt%d = ! t%d ;", lhsfalse, lhs); + println("\tif ( t%d ) goto _L_false_%d ;", lhsfalse, c); + gen_expr(node->rhs, rhs); + println("\tt%d = ! t%d ;", rhsfalse, rhs); + println("\tif ( t%d ) goto _L_false_%d ;", rhsfalse, c); + println("\tt%d = 1 ;", to_tmp); + println("\tgoto _L_end_%d;", c); + println("\t_L_false_%d :", c); + println("\t_L_end_%d :", c); + return; + } + case ND_LOGOR: { + int c = count(), lhs = count(), rhs = count(); + decltmp(ty_int, to_tmp); + println("\tt%d = 0 ;", to_tmp); + gen_expr(node->lhs, lhs); + println("\tif ( t%d ) goto _L_true_%d ;", lhs, c); + gen_expr(node->rhs, rhs); + println("\tif ( t%d ) goto _L_true_%d ;", rhs, c); + println("\tgoto _L_end_%d ;", c); + println("\t_L_true_%d :", c); + println("\tt%d = 1 ;", to_tmp); + println("\t_L_end_%d :", c); + return; + } + case ND_FUNCALL: { + int fnc = count(); + gen_expr(node->lhs, fnc); + + int n_args = 0; + for (Node *arg = node->args; arg; arg = arg->next) n_args++; + + int *args = calloc(n_args, sizeof(int)), + arg_i = 0; + for (Node *arg = node->args; arg; arg = arg->next, arg_i++) { + args[arg_i] = count(); + gen_expr(arg, args[arg_i]); + } + if (node->ty->kind != TY_VOID) { + decltmp(node->ty, to_tmp); + printnoln("\tt%d = ", to_tmp); + } else { + printnoln("\t"); + } + printnoln("t%d ( ", fnc); + for (int i = 0; i < n_args; i++) { + if (i) printnoln(", "); + printnoln("t%d ", args[i]); + } + println(") ;"); + return; + } + case ND_LABEL_VAL: assert(0); + case ND_CAS: assert(0); + case ND_EXCH: assert(0); + } + + int rhsc = count(), lhsc = count(); + gen_expr(node->rhs, rhsc); + gen_expr(node->lhs, lhsc); + + char *op = NULL; + switch (node->kind) { + case ND_ADD: op = "+"; break; + case ND_SUB: op = "-"; break; + case ND_MUL: op = "*"; break; + case ND_DIV: op = "/"; break; + case ND_MOD: op = "%"; break; + case ND_BITAND: op = "&"; break; + case ND_BITOR: op = "|"; break; + case ND_BITXOR: op = "^"; break; + case ND_EQ: op = "=="; break; + case ND_NE: op = "!="; break; + case ND_LT: op = "<"; break; + case ND_LE: op = "<="; break; + case ND_SHL: op = "<<"; break; + case ND_SHR: op = ">>"; break; + } + + decltmp(node->ty, to_tmp); + if (node->ty->base) { + println("\tt%d = PTR_BINARY ( t%d , %s , t%d ) ;", to_tmp, lhsc, op, rhsc); + } else { + println("\tt%d = BINARY ( t%d , %s , t%d ) ;", to_tmp, lhsc, op, rhsc); + } +} + +static void gen_stmt(Node *node) { + switch (node->kind) { + case ND_IF: { + int cond = count(), condfalse = count(), c = count(); + gen_expr(node->cond, cond); + decltmp(ty_int, condfalse); + println("\tt%d = ! t%d ;", condfalse, cond); + println("\tif ( t%d ) goto _L_else_%d ;", condfalse, c); + gen_stmt(node->then); + println("\tgoto _L_end_%d ;", c); + println("\t_L_else_%d :", c); + if (node->els) + gen_stmt(node->els); + println("\t_L_end_%d :", c); + return; + } + case ND_FOR: { + int c = count(), cond = count(), condfalse = count(); + if (node->init) + gen_stmt(node->init); + println("\t_L_begin_%d :", c); + if (node->cond) { + gen_expr(node->cond, cond); + decltmp(ty_int, condfalse); + println("\tt%d = ! t%d ;", condfalse, cond); + printnoln("\tif ( t%d ) goto ", condfalse); + print_label(node->brk_label); + println(" ;"); + } + gen_stmt(node->then); + printnoln("\t"); print_label(node->cont_label); println(" :"); + if (node->inc) + gen_expr(node->inc, count()); + println("\tgoto _L_begin_%d ;", c); + printnoln("\t"); print_label(node->brk_label); println(" :"); + return; + } + case ND_DO: { + int c = count(), cond = count(); + println("\t_L_begin_%d :", c); + gen_stmt(node->then); + printnoln("\t"); print_label(node->cont_label); println(" :"); + gen_expr(node->cond, cond); + println("\tif ( t%d ) goto _L_begin_%d ;", cond, c); + printnoln("\t"); print_label(node->brk_label); println(" :"); + return; + } + case ND_SWITCH: { + int cond = count(), eqtmp = count(); + gen_expr(node->cond, cond); + decltmp(ty_int, eqtmp); + + for (Node *n = node->case_next; n; n = n->case_next) { + if (n->begin == n->end) { + println("\tt%d = BINARY ( t%d , == , %ld ) ;", eqtmp, cond, n->begin); + printnoln("\tif ( t%d ) goto ", eqtmp); + print_label(n->label); println(" ;"); + continue; + } + + // [GNU] Case ranges + assert(!"unimplemented"); + } + + if (node->default_case) { + printnoln("\tgoto "); print_label(node->default_case->label); println(" ;"); + } + + printnoln("\tgoto "); print_label(node->brk_label); println(" ;"); + gen_stmt(node->then); + printnoln("\t"); print_label(node->brk_label); println(" :"); + return; + } + case ND_CASE: + printnoln("\t"); print_label(node->label); println(" :"); + gen_stmt(node->lhs); + return; + case ND_BLOCK: + for (Node *n = node->body; n; n = n->next) + gen_stmt(n); + return; + case ND_GOTO: + printnoln("\tgoto "); print_label(node->unique_label); println(" ;"); + return; + case ND_GOTO_EXPR: assert(0); return; + case ND_LABEL: + printnoln("\t"); print_label(node->unique_label); println(" :"); + gen_stmt(node->lhs); + return; + case ND_RETURN: + if (node->lhs) { + int expr = count(); + gen_expr(node->lhs, expr); + println("\tt%d = t%d ;", RETURN_TMP, expr); + } + println("\tgoto _L_RETURN ;"); + return; + case ND_EXPR_STMT: + gen_expr(node->lhs, count()); + return; + case ND_ASM: assert(0); return; + } + + error_tok(node->tok, "invalid statement"); +} + +// Assign offsets to local variables. +static void assign_lvar_offsets(Obj *prog) { + for (Obj *fn = prog; fn; fn = fn->next) { + if (!fn->is_function) + continue; + + // 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 c = 1; + + for (Obj *var = fn->params; var; var = var->next) + var->offset = c++; + for (Obj *var = fn->locals; var; var = var->next) + if (!(var->offset)) + var->offset = c++; + } +} + +void emit_constant(int pos, char *data, Relocation **rel, Type *type) { + switch (type->kind) { + case TY_STRUCT: { + int i = 0; + printnoln("{ "); + for (struct Member *m = type->members; m; m = m->next, i++) { + if (i) printnoln(", "); + assert(!m->is_bitfield); + emit_constant(pos + m->offset, data, rel, m->ty); + } + printnoln("} "); + break; + } + case TY_UNION: { + struct Member *biggest = type->members; + for (struct Member *m = type->members; m; m = m->next) + if (m->ty->size > biggest->ty->size) biggest = m; + assert(biggest); + printnoln("{ "); + emit_constant(pos, data, rel, biggest->ty); + printnoln("} "); + break; + } + case TY_ARRAY: { + printnoln("{ "); + for (int i = 0; i < type->array_len; i++) { + if (i) printnoln(", "); + emit_constant(pos, data, rel, type->base); + pos += type->base->size; + } + printnoln("} "); + break; + } + case TY_CHAR: + assert(type->size == 1); + // we don't output ' ' so lexing can be done by splitting on whitespace + if (data[pos] > ' ' && data[pos] <= '~' && data[pos] != '\'' && data[pos] != '\\') + printnoln("'%c' ", data[pos++]); + else + printnoln("%d ", (int)data[pos++]); + break; + case TY_BOOL: + assert(type->size == 1); + printnoln("%d ", *((char*)(data+pos))); + break; + case TY_SHORT: + assert(type->size == sizeof(short)); + printnoln("%d ", *((short*)(data+pos))); + break; + case TY_INT: + assert(type->size == sizeof(int)); + printnoln("%d ", *((int*)(data+pos))); + break; + case TY_LONG: + assert(type->size == sizeof(long)); + printnoln("%ld ", *((long*)(data+pos))); + break; + case TY_FLOAT: { + assert(type->size == sizeof(float)); + float v = *((float*)(data + pos)); + if (v != v) printnoln("0./0. "); + else if (v == 1./0.) printnoln("1./0. "); + else if (-v == 1./0.) printnoln("-1./0. "); + else printnoln("%f ", v); + break; + } case TY_DOUBLE: { + assert(type->size == sizeof(double)); + double v = *((double*)(data + pos)); + if (v != v) printnoln("0./0. "); + else if (v == 1./0.) printnoln("1./0. "); + else if (-v == 1./0.) printnoln("-1./0. "); + else printnoln("%lf ", v); + break; + } case TY_LDOUBLE: { + assert(type->size == sizeof(long double)); + long double v = *((long double*)(data + pos)); + if (v != v) printnoln("0./0. "); + else if (v == 1./0.) printnoln("1./0. "); + else if (-v == 1./0.) printnoln("-1./0. "); + else printnoln("%Lf ", v); + break; + } case TY_PTR: + assert(type->size == 8); + if (*rel && (*rel)->offset == pos) { + // TODO: should addend be divided by (*rel)->ty->size? + printnoln("( void * ) ( & "); + if (strchr(*((*rel)->label), '.')) { + printnoln("_"); + for (char *c = *((*rel)->label); *c; c++) { + if (*c == '.') printnoln("_"); + else printnoln("%c", *c); + } + printnoln("_0 ) + %ld ", (*rel)->addend); + } else { + printnoln("%s ) + %ld ", *((*rel)->label), (*rel)->addend); + } + *rel = (*rel)->next; + } else { + // TODO: actually read the absolute address out + printnoln("( void * ) ( %ld ) + 0 ", *((uint64_t*)(data+pos))); + } + break; + case TY_ENUM: + switch (type->size) { + case sizeof(int): + printnoln("%d ", *((int*)(data+pos))); + break; + case sizeof(long): + printnoln("%ld ", *((long*)(data+pos))); + break; + default: assert(0); + } + break; + default: fprintf(stderr, "Got: %d\n", type->kind); assert(0); + } +} + +static void emit_data(Obj *prog) { + for (Obj *var = prog; var; var = var->next) { + if (var->is_static && var->is_function) { + printnoln("static Type_%d ", var->ty->id); + print_obj(var); println(" ;"); + } else if (var->is_static) { + printnoln("static Type_%d ", var->ty->id); + print_obj(var); println(" ;"); + } else { + printnoln("extern Type_%d ", var->ty->id); + print_obj(var); println(" ;"); + } + } + + for (Obj *var = prog; var; var = var->next) { + if (var->is_function || !var->is_definition) + continue; + + if (var->is_static) printnoln("static "); + print_type(var->ty); printnoln(" "); print_obj(var); + + // Common symbol + assert(!(opt_fcommon && var->is_tentative)); + + // .data or .tdata + if (var->init_data) { + printnoln(" = "); + Relocation *rel = var->rel; + emit_constant(0, var->init_data, &rel, var->ty); + } + println(";"); + } +} + +static void emit_text(Obj *prog) { + for (Obj *fn = prog; fn; fn = fn->next) { + 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; + + current_fn = fn; + + if (fn->is_static) + printnoln("static "); + + print_type(fn->ty->return_ty); printnoln(" "); + printnoln("%s ( ", fn->name); + int p = 0; + for (Obj *param = fn->params; param; param = param->next, p++) { + if (p) printnoln(", "); + print_type(param->ty); printnoln(" "); print_obj(param); + param->is_param = 1; + printnoln(" "); + } + if (fn->va_area) { + if (p) printnoln(", ... "); + // else printnoln("... "); + } + println(") {"); + + RETURN_TMP = 0; + if (fn->ty->return_ty->kind != TY_VOID) { + RETURN_TMP = count(); + decltmp(fn->ty->return_ty, RETURN_TMP); + } + + for (Obj *var = fn->locals; var; var = var->next) { + if (!(var->ty->id)) continue; + if (var->is_param) continue; + if (var == fn->va_area) continue; + if (var == fn->alloca_bottom) continue; + printnoln("\t"); + print_type(var->ty); + printnoln(" "); + print_obj(var); + println(" ;"); + } + + // Emit code + DO_BUFFER = 1; + gen_stmt(fn->body); + assert(depth == 0); + flush_buffer(); + DO_BUFFER = 0; + + println("\t_L_RETURN :"); + if (fn->ty->return_ty->kind != TY_VOID) { + println("\treturn t%d ;", RETURN_TMP); + } else { + println("\treturn ;"); + } + + println("}"); + } +} + +void codegen(Obj *prog, FILE *out) { + output_file = out; + + BUFFER = tmpfile(); + assign_lvar_offsets(prog); + emit_data(prog); + emit_text(prog); +} diff --git a/docs/BUILDING_COREUTILS_WITH_DIETCC.txt b/docs/BUILDING_COREUTILS_WITH_DIETCC.txt new file mode 100644 index 0000000..79fa2ea --- /dev/null +++ b/docs/BUILDING_COREUTILS_WITH_DIETCC.txt @@ -0,0 +1,18 @@ +As a test for dietc, we've ensured that we can build a mostly-working version +of GNU coreutils. Note that this has only been checked on GCC-on-Debian; likely +won't work on other platforms. + +$ cd /dietc/root/directory +$ make +$ cd /tmp/ +$ curl -OL https://ftp.gnu.org/gnu/coreutils/coreutils-9.3.tar.xz +$ tar -xvf coreutils-9.3.tar.xz +$ cd coreutils-9.3 +$ vim lib/config.hin + rewrite "# define _GL_INLINE inline" + to "# define _GL_INLINE extern inline" +$ vim lib/malloc/scratch_buffer.h + add "#include <lib/scratch_buffer.h>" +$ CC=/home/matthew/repos/dietc/scripts/dietcc ./configure +... +$ make -j8 diff --git a/docs/CHIBICC_MODS.txt b/docs/CHIBICC_MODS.txt new file mode 100644 index 0000000..ab733ba --- /dev/null +++ b/docs/CHIBICC_MODS.txt @@ -0,0 +1,14 @@ +we're attempting to stay as close to mainline chibicc as possible + +at a high level, the major changes made are: +- strip out most of proprocess.c, as we're OK with using GCC's preprocessor + (still need preprocess.c, as it handles parsing of literals) +- add a typegen.c, which runs before codegen & outputs the typedefs +- replace codegen.c to output C instead of assembly + +to support these, ended up making some changes to the rest of the files. these +include: + - in type.c:usual_arith_conv, + - don't cast if they're already the right type + - for pointer arithmetic, leave it as pointer + long + - in parse.c:is_const_expr, also allow modulos to be const diff --git a/docs/DIETCC.txt b/docs/DIETCC.txt new file mode 100644 index 0000000..2b3762f --- /dev/null +++ b/docs/DIETCC.txt @@ -0,0 +1,19 @@ +The basic idea is to replace + + gcc ... file.c ... + +with + + gcc -E ... file.c ... -o preproc.c + dietc preproc.c > diet.c + pass1 diet.c > diet.c + pass2 diet.c > diet.c + ... + gcc ... diet.c ... + +To assist with this, we provide `scripts/dietcc`, which provides a drop-in +replacement for `gcc` that can run your patches. In your build scripts, simply +replace `gcc` with the path of `dietcc`. + +You can insert your own compilation pass into dietcc; read its Python source +for examples. diff --git a/docs/LANGUAGE.txt b/docs/LANGUAGE.txt new file mode 100644 index 0000000..124e100 --- /dev/null +++ b/docs/LANGUAGE.txt @@ -0,0 +1,88 @@ +# Full language reference for dietC + +(Some of these may be a bit out-of-date; let me know if you find something +inaccurate!) + +Literals: + ints & floats as output by printf + chars, but no escaping (special characters are encoded as ints) + +File layout: + #include "/path/to/dietc/scripts/dietc_helpers.h" + [type defining lines] + [global declarations & definitions] + +Type defining lines: + struct Struct_%d { Type_%d [ident] ; Type_%d [ident] ; [...] } ; + union Union_%d { Type_%d [ident] ; Type_%d [ident] ; [...] } ; + typedef basic_type Type_%d ; + typedef Type_%d * Type_%d ; + typedef Type_%d Type_%d [ %d ] ; + typedef struct Struct_%d Type_%d ; + typedef union Union_%d Type_%d ; + typedef Type_%d Type_%d ( Type_%d , Type_%d , [...] ) ; + typedef Type_%d Type_%d ( Type_%d , Type_%d , ... ) ; + +global declarations & definitions: + extern Type_%d [ident] ; + static Type_%d [ident] = [globlit] ; + Type_%d [ident] = [globlit] ; + Type_%d [ident] ( Type_%d , [...] ) {\n[body]\n} + Type_%d [ident] ( Type_%d , ... ) {\n[body]\n} + +globlit: + [literal] + { [globlit] , [...] } + ( void * ) ( & [ident] ) + [literal] + ( void * ) [int literal] + +body: + [declaration] + [declaration] + [...] + [instruction] + [instruction] + [...] + [return] + +return: + _L_RETURN : + return [ident] ; + or + _L_RETURN : + return ; + +declaration: + Type_%d [ident] ; + +instruction: + MEMZERO ( [ident] ) ; + MEMCPY ( [ident] , [ident] ) ; + [ident] : + if ( [ident] ) goto [ident] ; + goto [ident] ; + [ident] = [ident] ; + [ident] = [literal] ; + * [ident] = [ident] ; + [ident] = [preop] [ident] ; + [ident] = ( Type_%d ) [ident] ; + ( Type_%d ) [ident] ; + // ^ only when (void)x; left in for autoconf + [ident] = BINARY ( [ident] , [binop] , [ident] ) ; + [ident] = [ident] ( [ident] , [...] ) ; + [ident] ( [ident] , [...] ) ; + [ident] = FIELDPTR ( [ident] , [fname] ) ; + +preop: + !, ~, *, & + +binop: + +, -, *, /, %, &, |, ^, ==, !=, <, <=, <<, >> + +Other guarantees: + instructions in function bodies, and *only* those instructions, are indented + with one tab character + all tokens are space-separated; you can tokenize by splitting each line on + space characters + all instructions are one-line + MEMZERO and MEMCPY are always called on globals & locals; never the heap diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..ea94cc9 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,592 @@ +license for chibicc: + +MIT License + +Copyright (c) 2019 Rui Ueyama + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +license for the dietC modifications to chibicc: + +GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is +permitted to copy and distribute verbatim copies of this license document, but +changing it is not allowed. Preamble + +The GNU Affero General Public License is a free, copyleft license for software +and other kinds of works, specifically designed to ensure cooperation with the +community in the case of network server software. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, our General +Public Licenses are intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for them if you wish), that you +receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs, and that you know you can do +these things. + +Developers that use our General Public Licenses protect your rights with two +steps: (1) assert copyright on the software, and (2) offer you this License +which gives you legal permission to copy, distribute and/or modify the +software. + +A secondary benefit of defending all users' freedom is that improvements made +in alternate versions of the program, if they receive widespread use, become +available for other developers to incorporate. Many developers of free software +are heartened and encouraged by the resulting cooperation. However, in the case +of software used on network servers, this result may fail to come about. The +GNU General Public License permits making a modified version and letting the +public access it on a server without ever releasing its source code to the +public. + +The GNU Affero General Public License is designed specifically to ensure that, +in such cases, the modified source code becomes available to the community. It +requires the operator of a network server to provide the source code of the +modified version running there to the users of that server. Therefore, public +use of a modified version, on a publicly accessible server, gives the public +access to the source code of the modified version. + +An older license, called the Affero General Public License and published by +Affero, was designed to accomplish similar goals. This is a different license, +not a version of the Affero GPL, but Affero has released a new version of the +Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification +follow. TERMS AND CONDITIONS 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. +Each licensee is addressed as "you". "Licensees" and "recipients" may be +individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a +fashion requiring copyright permission, other than the making of an exact copy. +The resulting work is called a "modified version" of the earlier work or a work +"based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the +Program. + +To "propagate" a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under applicable +copyright law, except executing it on a computer or modifying a private copy. +Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to +make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the +extent that it includes a convenient and prominently visible feature that (1) +displays an appropriate copyright notice, and (2) tells the user that there is +no warranty for the work (except to the extent that warranties are provided), +that licensees may convey the work under this License, and how to view a copy +of this License. If the interface presents a list of user commands or options, +such as a menu, a prominent item in the list meets this criterion. 1. Source +Code. + +The "source code" for a work means the preferred form of the work for making +modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard +defined by a recognized standards body, or, in the case of interfaces specified +for a particular programming language, one that is widely used among developers +working in that language. + +The "System Libraries" of an executable work include anything, other than the +work as a whole, that (a) is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and (b) serves only +to enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code +form. A "Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on +which the executable work runs, or a compiler used to produce the work, or an +object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source +code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. +However, it does not include the work's System Libraries, or general-purpose +tools or generally available free programs which are used unmodified in +performing those activities but which are not part of the work. For example, +Corresponding Source includes interface definition files associated with source +files for the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, such as +by intimate data communication or control flow between those subprograms and +other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. 2. +Basic Permissions. + +All rights granted under this License are granted for the term of copyright on +the Program, and are irrevocable provided the stated conditions are met. This +License explicitly affirms your unlimited permission to run the unmodified +Program. The output from running a covered work is covered by this License only +if the output, given its content, constitutes a covered work. This License +acknowledges your rights of fair use or other equivalent, as provided by +copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey +covered works to others for the sole purpose of having them make modifications +exclusively for you, or provide you with facilities for running those works, +provided that you comply with the terms of this License in conveying all +material for which you do not control copyright. Those thus making or running +the covered works for you must do so exclusively on your behalf, under your +direction and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes it +unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure +under any applicable law fulfilling obligations under article 11 of the WIPO +copyright treaty adopted on 20 December 1996, or similar laws prohibiting or +restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention is +effected by exercising rights under this License with respect to the covered +work, and you disclaim any intention to limit operation or modification of the +work as a means of enforcing, against the work's users, your or third parties' +legal rights to forbid circumvention of technological measures. 4. Conveying +Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, +in any medium, provided that you conspicuously and appropriately publish on +each copy an appropriate copyright notice; keep intact all notices stating that +this License and any non-permissive terms added in accord with section 7 apply +to the code; keep intact all notices of the absence of any warranty; and give +all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may +offer support or warranty protection for a fee. 5. Conveying Modified Source +Versions. + +You may convey a work based on the Program, or the modifications to produce it +from the Program, in the form of source code under the terms of section 4, +provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and + giving a relevant date. b) The work must carry prominent notices stating + that it is released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 to "keep + intact all notices". c) You must license the entire work, as a whole, + under this License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, regardless + of how they are packaged. This License gives no permission to license the + work in any other way, but it does not invalidate such permission if you + have separately received it. d) If the work has interactive user + interfaces, each must display Appropriate Legal Notices; however, if the + Program has interactive interfaces that do not display Appropriate Legal + Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, +which are not by their nature extensions of the covered work, and which are not +combined with it such as to form a larger program, in or on a volume of a +storage or distribution medium, is called an "aggregate" if the compilation and +its resulting copyright are not used to limit the access or legal rights of the +compilation's users beyond what the individual works permit. Inclusion of a +covered work in an aggregate does not cause this License to apply to the other +parts of the aggregate. 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 +and 5, provided that you also convey the machine-readable Corresponding Source +under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including + a physical distribution medium), accompanied by the Corresponding Source + fixed on a durable physical medium customarily used for software + interchange. b) Convey the object code in, or embodied in, a physical + product (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as long as you + offer spare parts or customer support for that product model, to give + anyone who possesses the object code either (1) a copy of the Corresponding + Source for all the software in the product that is covered by this License, + on a durable physical medium customarily used for software interchange, for + a price no more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding Source from a + network server at no charge. c) Convey individual copies of the object + code with a copy of the written offer to provide the Corresponding Source. + This alternative is allowed only occasionally and noncommercially, and only + if you received the object code with such an offer, in accord with + subsection 6b. d) Convey the object code by offering access from a + designated place (gratis or for a charge), and offer equivalent access to + the Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the Corresponding + Source along with the object code. If the place to copy the object code is + a network server, the Corresponding Source may be on a different server + (operated by you or a third party) that supports equivalent copying + facilities, provided you maintain clear directions next to the object code + saying where to find the Corresponding Source. Regardless of what server + hosts the Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. e) Convey + the object code using peer-to-peer transmission, provided you inform other + peers where the object code and Corresponding Source of the work are being + offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible +personal property which is normally used for personal, family, or household +purposes, or (2) anything designed or sold for incorporation into a dwelling. +In determining whether a product is a consumer product, doubtful cases shall be +resolved in favor of coverage. For a particular product received by a +particular user, "normally used" refers to a typical or common use of that +class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, +the product. A product is a consumer product regardless of whether the product +has substantial commercial, industrial or non-consumer uses, unless such uses +represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, +authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified +version of its Corresponding Source. The information must suffice to ensure +that the continued functioning of the modified object code is in no case +prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as part of a +transaction in which the right of possession and use of the User Product is +transferred to the recipient in perpetuity or for a fixed term (regardless of +how the transaction is characterized), the Corresponding Source conveyed under +this section must be accompanied by the Installation Information. But this +requirement does not apply if neither you nor any third party retains the +ability to install modified object code on the User Product (for example, the +work has been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates for a +work that has been modified or installed by the recipient, or for the User +Product in which it has been modified or installed. Access to a network may be +denied when the modification itself materially and adversely affects the +operation of the network or violates the rules and protocols for communication +across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord +with this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require +no special password or key for unpacking, reading or copying. 7. Additional +Terms. + +"Additional permissions" are terms that supplement the terms of this License by +making exceptions from one or more of its conditions. Additional permissions +that are applicable to the entire Program shall be treated as though they were +included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may +be used separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added +by you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of + sections 15 and 16 of this License; or b) Requiring preservation of + specified reasonable legal notices or author attributions in that material + or in the Appropriate Legal Notices displayed by works containing it; or c) + Prohibiting misrepresentation of the origin of that material, or requiring + that modified versions of such material be marked in reasonable ways as + different from the original version; or d) Limiting the use for publicity + purposes of names of licensors or authors of the material; or e) Declining + to grant rights under trademark law for use of some trade names, + trademarks, or service marks; or f) Requiring indemnification of licensors + and authors of that material by anyone who conveys the material (or + modified versions of it) with contractual assumptions of liability to the + recipient, for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" +within the meaning of section 10. If the Program as you received it, or any +part of it, contains a notice stating that it is governed by this License along +with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or +conveying under this License, you may add to a covered work material governed +by the terms of that license document, provided that the further restriction +does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, +in the relevant source files, a statement of the additional terms that apply to +those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements +apply either way. 8. Termination. + +You may not propagate or modify a covered work except as expressly provided +under this License. Any attempt otherwise to propagate or modify it is void, +and will automatically terminate your rights under this License (including any +patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated (a) provisionally, unless and until +the copyright holder explicitly and finally terminates your license, and (b) +permanently, if the copyright holder fails to notify you of the violation by +some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated +permanently if the copyright holder notifies you of the violation by some +reasonable means, this is the first time you have received notice of violation +of this License (for any work) from that copyright holder, and you cure the +violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. If +your rights have been terminated and not permanently reinstated, you do not +qualify to receive new licenses for the same material under section 10. 9. +Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy +of the Program. Ancillary propagation of a covered work occurring solely as a +consequence of using peer-to-peer transmission to receive a copy likewise does +not require acceptance. However, nothing other than this License grants you +permission to propagate or modify any covered work. These actions infringe +copyright if you do not accept this License. Therefore, by modifying or +propagating a covered work, you indicate your acceptance of this License to do +so. 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a +license from the original licensors, to run, modify and propagate that work, +subject to this License. You are not responsible for enforcing compliance by +third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered work +results from an entity transaction, each party to that transaction who receives +a copy of the work also receives whatever licenses to the work the party's +predecessor in interest had or could give under the previous paragraph, plus a +right to possession of the Corresponding Source of the work from the +predecessor in interest, if the predecessor has it or can get it with +reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights +granted or affirmed under this License. For example, you may not impose a +license fee, royalty, or other charge for exercise of rights granted under this +License, and you may not initiate litigation (including a cross-claim or +counterclaim in a lawsuit) alleging that any patent claim is infringed by +making, using, selling, offering for sale, or importing the Program or any +portion of it. 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of +the Program or a work on which the Program is based. The work thus licensed is +called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, +that would be infringed by some manner, permitted by this License, of making, +using, or selling its contributor version, but do not include claims that would +be infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of this +License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent +license under the contributor's essential patent claims, to make, use, sell, +offer for sale, import and otherwise run, modify and propagate the contents of +its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement +or commitment, however denominated, not to enforce a patent (such as an express +permission to practice a patent or covenant not to sue for patent +infringement). To "grant" such a patent license to a party means to make such +an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of +charge and under the terms of this License, through a publicly available +network server or other readily accessible means, then you must either (1) +cause the Corresponding Source to be so available, or (2) arrange to deprive +yourself of the benefit of the patent license for this particular work, or (3) +arrange, in a manner consistent with the requirements of this License, to +extend the patent license to downstream recipients. "Knowingly relying" means +you have actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work in a +country, would infringe one or more identifiable patents in that country that +you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a +patent license to some of the parties receiving the covered work authorizing +them to use, propagate, modify or convey a specific copy of the covered work, +then the patent license you grant is automatically extended to all recipients +of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of +its coverage, prohibits the exercise of, or is conditioned on the non-exercise +of one or more of the rights that are specifically granted under this License. +You may not convey a covered work if you are a party to an arrangement with a +third party that is in the business of distributing software, under which you +make payment to the third party based on the extent of your activity of +conveying the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by you (or +copies made from those copies), or (b) primarily for and in connection with +specific products or compilations that contain the covered work, unless you +entered into that arrangement, or that patent license was granted, prior to 28 +March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to +you under applicable patent law. 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not excuse +you from the conditions of this License. If you cannot convey a covered work so +as to satisfy simultaneously your obligations under this License and any other +pertinent obligations, then as a consequence you may not convey it at all. For +example, if you agree to terms that obligate you to collect a royalty for +further conveying from those to whom you convey the Program, the only way you +could satisfy both those terms and this License would be to refrain entirely +from conveying the Program. 13. Remote Network Interaction; Use with the GNU +General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, +your modified version must prominently offer all users interacting with it +remotely through a computer network (if your version supports such interaction) +an opportunity to receive the Corresponding Source of your version by providing +access to the Corresponding Source from a network server at no charge, through +some standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any work +covered by version 3 of the GNU General Public License that is incorporated +pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to +link or combine any covered work with a work licensed under version 3 of the +GNU General Public License into a single combined work, and to convey the +resulting work. The terms of this License will continue to apply to the part +which is the covered work, but the work with which it is combined will remain +governed by version 3 of the GNU General Public License. 14. Revised Versions +of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU +Affero General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address +new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +that a certain numbered version of the GNU Affero General Public License "or +any later version" applies to it, you have the option of following the terms +and conditions either of that numbered version or of any later version +published by the Free Software Foundation. If the Program does not specify a +version number of the GNU Affero General Public License, you may choose any +version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the +GNU Affero General Public License can be used, that proxy's public statement of +acceptance of a version permanently authorizes you to choose that version for +the Program. + +Later license versions may give you additional or different permissions. +However, no additional obligations are imposed on any author or copyright +holder as a result of your choosing to follow a later version. 15. Disclaimer +of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE +LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER +PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE +THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of +Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot +be given local legal effect according to their terms, reviewing courts shall +apply local law that most closely approximates an absolute waiver of all civil +liability in connection with the Program, unless a warranty or assumption of +liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively state the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, +you should also make sure that it provides a way for users to get its source. +For example, if your program is a web application, its interface could display +a "Source" link that leads users to an archive of the code. There are many ways +you could offer source, and different solutions will be better for different +programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if +any, to sign a "copyright disclaimer" for the program, if necessary. For more +information on this, and how to apply and follow the GNU AGPL, see +<https://www.gnu.org/licenses/>. diff --git a/hashmap.c b/hashmap.c new file mode 100644 index 0000000..a83934d --- /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"); +} @@ -0,0 +1,72 @@ +#include "chibicc.h" + +typedef enum { + FILE_NONE, FILE_C, FILE_ASM, FILE_OBJ, FILE_AR, FILE_DSO, +} FileType; + +char *base_file; + +StringArray include_paths; +bool opt_fcommon = false; +bool opt_fpic; + +static FILE *open_file(char *path) { + if (!path || strcmp(path, "-") == 0) + return stdout; + + FILE *out = fopen(path, "w"); + if (!out) + error("cannot open output file: %s: %s", path, strerror(errno)); + return out; +} + +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(char *base_file) { + Token *tok = NULL; + + // Tokenize and parse. + Token *tok2 = must_tokenize_file(base_file); + tok = append_tokens(tok, tok2); + tok = preprocess(tok); + + 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. + typegen(prog, output_buf); + codegen(prog, output_buf); + fclose(output_buf); + + // Write the asembly text to a file. + FILE *out = open_file("/dev/stdout"); + fwrite(buf, buflen, 1, out); + fclose(out); +} + +int main(int argc, char **argv) { + assert(argc == 2); + base_file = argv[1]; + cc1(argv[1]); + return 0; +} @@ -0,0 +1,3378 @@ +// This file contains a recursive descent parser for C. +// +// Most functions in this file are named after the symbols they are +// supposed to read from an input token list. For example, stmt() is +// responsible for reading a statement from a token list. The function +// then construct an AST node representing a statement. +// +// Each function conceptually returns two values, an AST node and +// remaining part of the input tokens. Since C doesn't support +// multiple return values, the remaining tokens are returned to the +// caller via a pointer argument. +// +// Input tokens are represented by a linked list. Unlike many recursive +// descent parsers, we don't have the notion of the "input token stream". +// Most parsing functions don't change the global state of the parser. +// So it is very easy to lookahead arbitrary number of tokens in this +// parser. + +#include "chibicc.h" + +// Scope for local variables, global variables, typedefs +// or enum constants +typedef struct { + Obj *var; + 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/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 +// accumulated to this list. +static Obj *locals; + +// Likewise, global variables are accumulated to this list. +static Obj *globals; + +static Scope *scope = &(Scope){}; + +// 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, 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)); + sc->next = scope; + scope = sc; +} + +static void leave_scope(void) { + scope = scope->next; +} + +// Find a variable by name. +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) { + Type *ty = hashmap_get2(&sc->tags, tok->loc, tok->len); + if (ty) + return ty; + } + return NULL; +} + +static Node *new_node(NodeKind kind, Token *tok) { + Node *node = calloc(1, sizeof(Node)); + node->kind = kind; + node->tok = tok; + return node; +} + +static Node *new_binary(NodeKind kind, Node *lhs, Node *rhs, Token *tok) { + Node *node = new_node(kind, tok); + node->lhs = lhs; + node->rhs = rhs; + return node; +} + +static Node *new_unary(NodeKind kind, Node *expr, Token *tok) { + Node *node = new_node(kind, tok); + node->lhs = expr; + return node; +} + +static Node *new_num(int64_t val, Token *tok) { + Node *node = new_node(ND_NUM, tok); + node->val = val; + 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 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)); + 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; + var->align = ty->align; + push_scope(name)->var = var; + return var; +} + +static Obj *new_lvar(char *name, Type *ty) { + Obj *var = new_var(name, ty); + var->is_local = true; + var->next = locals; + locals = var; + return var; +} + +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; +} + +static char *new_unique_name(void) { + static int id = 0; + return format(".L..%d", id++); +} + +static Obj *new_anon_gvar(Type *ty) { + return new_gvar(new_unique_name(), ty); +} + +static Obj *new_string_literal(char *p, Type *ty) { + Obj *var = new_anon_gvar(ty); + var->init_data = p; + return var; +} + +static char *get_ident(Token *tok) { + if (tok->kind != TK_IDENT) + error_tok(tok, "expected an identifier"); + return strndup(tok->loc, tok->len); +} + +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) { + hashmap_put2(&scope->tags, tok->loc, tok->len, ty); +} + +// 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; + } + + // 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, "_Atomic")) { + tok = tok->next; + if (equal(tok , "(")) { + ty = typename(&tok, tok->next); + tok = skip(tok, ")"); + } + is_atomic = true; + continue; + } + + 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; + } + + // 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 (is_atomic) { + ty = copy_type(ty); + ty->is_atomic = true; + } + + *rest = tok; + return ty; +} + +// 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 (tok && tok->str) { + printf("TOKSTR: %s\n", tok->str); + } + if (cur != &head) + tok = skip(tok, ","); + + 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)) { + error_tok(tok, "VLA found!"); + return vla_of(ty, expr); + } + int len = eval(expr); + if (len < 0) error_tok(tok, "negative array length"); + return array_of(ty, len); +} + +// type-suffix = "(" func-params +// | "[" 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, "[")) + return array_dimensions(rest, tok->next, ty); + + *rest = tok; + return ty; +} + +// pointers = ("*" ("const" | "volatile" | "restrict")*)* +static Type *pointers(Token **rest, Token *tok, Type *ty) { + while (consume(&tok, tok, "*")) { + ty = pointer_to(ty); + while (equal(tok, "const") || equal(tok, "volatile") || equal(tok, "restrict") || + equal(tok, "__restrict") || equal(tok, "__restrict__")) + tok = tok->next; + } + *rest = tok; + return ty; +} + +// declarator = pointers ("(" ident ")" | "(" declarator ")" | ident) type-suffix +static Type *declarator(Token **rest, Token *tok, Type *ty) { + ty = pointers(&tok, tok, ty); + + if (equal(tok, "(")) { + Token *start = tok; + Type dummy = {}; + declarator(&tok, start->next, &dummy); + tok = skip(tok, ")"); + ty = type_suffix(rest, tok, ty); + return declarator(&tok, start->next, ty); + } + + Token *name = NULL; + Token *name_pos = tok; + + if (tok->kind == TK_IDENT) { + name = tok; + tok = tok->next; + } + + 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); + + if (equal(tok, "(")) { + Token *start = tok; + Type dummy = {}; + abstract_declarator(&tok, start->next, &dummy); + tok = skip(tok, ")"); + ty = type_suffix(rest, tok, ty); + return abstract_declarator(&tok, start->next, ty); + } + + return type_suffix(rest, tok, ty); +} + +// type-name = declspec abstract-declarator +static Type *typename(Token **rest, Token *tok) { + Type *ty = declspec(&tok, tok, NULL); + return abstract_declarator(rest, tok, ty); +} + +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, ",") && 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 ty; + } + + tok = skip(tok, "{"); + + // Read an enum-list. + int i = 0; + int val = 0; + while (!consume_end(rest, tok)) { + if (i++ > 0) + tok = skip(tok, ","); + + char *name = get_ident(tok); + tok = tok->next; + + if (equal(tok, "=")) + val = const_expr(&tok, tok->next); + + VarScope *sc = push_scope(name); + sc->enum_ty = ty; + sc->enum_val = val++; + } + + 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; +} + +// 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); + + 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); +} + +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; + + while (!equal(tok, ";")) { + if (i++ > 0) + tok = skip(tok, ","); + + 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; + } + + // 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; +} + +static Token *skip_excess_element(Token *tok) { + if (equal(tok, "{")) { + tok = skip_excess_element(tok->next); + return skip(tok, "}"); + } + + 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)? +static Node *expr(Token **rest, Token *tok) { + Node *node = assign(&tok, tok); + + if (equal(tok, ",")) + return new_binary(ND_COMMA, node, expr(rest, tok->next), tok); + + *rest = tok; + return node; +} + +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_MOD: + 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); + while (equal(tok, "&")) { + Token *start = tok; + node = new_binary(ND_BITAND, node, equality(&tok, tok->next), start); + } + *rest = tok; + return node; +} + +// equality = relational ("==" relational | "!=" relational)* +static Node *equality(Token **rest, Token *tok) { + Node *node = relational(&tok, tok); + + for (;;) { + Token *start = tok; + + if (equal(tok, "==")) { + node = new_binary(ND_EQ, node, relational(&tok, tok->next), start); + continue; + } + + if (equal(tok, "!=")) { + node = new_binary(ND_NE, node, relational(&tok, tok->next), start); + continue; + } + + *rest = tok; + return node; + } +} + +// relational = shift ("<" shift | "<=" shift | ">" shift | ">=" shift)* +static Node *relational(Token **rest, Token *tok) { + Node *node = shift(&tok, tok); + + for (;;) { + Token *start = tok; + + if (equal(tok, "<")) { + node = new_binary(ND_LT, node, shift(&tok, tok->next), start); + continue; + } + + if (equal(tok, "<=")) { + node = new_binary(ND_LE, node, shift(&tok, tok->next), start); + continue; + } + + if (equal(tok, ">")) { + node = new_binary(ND_LT, shift(&tok, tok->next), node, start); + continue; + } + + if (equal(tok, ">=")) { + 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; + } + + *rest = tok; + return node; + } +} + +// In C, `+` operator is overloaded to perform the pointer arithmetic. +// If p is a pointer, p+n adds not n but sizeof(*p)*n to the value of p, +// so that p+n points to the location n elements (not bytes) ahead of p. +// In other words, we need to scale an integer value before adding to a +// pointer value. This function takes care of the scaling. +static Node *new_add(Node *lhs, Node *rhs, Token *tok) { + add_type(lhs); + add_type(rhs); + + // num + num + if (is_numeric(lhs->ty) && is_numeric(rhs->ty)) + return new_binary(ND_ADD, lhs, rhs, tok); + + if (lhs->ty->base && rhs->ty->base) + error_tok(tok, "invalid operands"); + + // Canonicalize `num + ptr` to `ptr + num`. + if (!lhs->ty->base && rhs->ty->base) { + Node *tmp = lhs; + lhs = rhs; + 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_long(lhs->ty->base->size, tok), tok); + return new_binary(ND_ADD, lhs, rhs, tok); +} + +// Like `+`, `-` is overloaded for the pointer type. +static Node *new_sub(Node *lhs, Node *rhs, Token *tok) { + add_type(lhs); + add_type(rhs); + + // num - num + 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_long(lhs->ty->base->size, tok), tok); + add_type(rhs); + Node *node = new_binary(ND_SUB, lhs, rhs, tok); + node->ty = lhs->ty; + return node; + } + + // 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_long; + return new_binary(ND_DIV, node, new_num(lhs->ty->base->size, tok), tok); + } + + error_tok(tok, "invalid operands"); +} + +// add = mul ("+" mul | "-" mul)* +static Node *add(Token **rest, Token *tok) { + Node *node = mul(&tok, tok); + + for (;;) { + Token *start = tok; + + if (equal(tok, "+")) { + node = new_add(node, mul(&tok, tok->next), start); + continue; + } + + if (equal(tok, "-")) { + node = new_sub(node, mul(&tok, tok->next), start); + continue; + } + + *rest = tok; + return node; + } +} + +// mul = cast ("*" cast | "/" cast | "%" cast)* +static Node *mul(Token **rest, Token *tok) { + Node *node = cast(&tok, tok); + + for (;;) { + Token *start = tok; + + if (equal(tok, "*")) { + node = new_binary(ND_MUL, node, cast(&tok, tok->next), start); + continue; + } + + if (equal(tok, "/")) { + 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; + } + + *rest = tok; + return node; + } +} + +// 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 cast(rest, tok->next); + + if (equal(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_NOT, cast(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); +} + +// struct-members = (declspec declarator ("," declarator)* ";")* +static void struct_members(Token **rest, Token *tok, Type *ty) { + Member head = {}; + Member *cur = &head; + int idx = 0; + + while (!equal(tok, "}")) { + 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 (!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; +} + +// 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) { + tag = tok; + tok = tok->next; + } + + if (tag && !equal(tok, "{")) { + *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. + 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; + } + + push_tag_scope(tag, ty); + } + + return ty; +} + +// struct-decl = struct-union-decl +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 bits = 0; + + for (Member *mem = ty->members; mem; mem = mem->next) { + 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->is_packed && ty->align < mem->align) + ty->align = mem->align; + } + + ty->size = align_to(bits, ty->align * 8) / 8; + return ty; +} + +// union-decl = struct-union-decl +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->align) + ty->align = mem->align; + if (ty->size < mem->ty->size) + ty->size = mem->ty->size; + } + ty->size = align_to(ty->size, ty->align); + 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) { + // 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; + } + return NULL; +} + +// 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; + + 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; +} + +// 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; + Node *idx = expr(&tok, tok->next); + tok = skip(tok, "]"); + node = new_unary(ND_DEREF, new_add(node, idx, start), start); + continue; + } + + if (equal(tok, ".")) { + node = struct_ref(node, tok->next); + tok = tok->next->next; + continue; + } + + if (equal(tok, "->")) { + // x->y is short for (*x).y + node = new_unary(ND_DEREF, node, tok); + node = struct_ref(node, tok->next); + tok = tok->next->next; + 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 = (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; + + while (!equal(tok, ")")) { + if (cur != &head) + tok = skip(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_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 +// | "_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); + node->body = compound_stmt(&tok, tok->next->next)->body; + *rest = skip(tok, ")"); + return node; + } + + if (equal(tok, "(")) { + Node *node = expr(&tok, tok->next); + *rest = skip(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); + if (node->ty->kind == TY_VLA) + return new_var_node(node->ty->vla_size, tok); + return new_ulong(node->ty->size, 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; + } + + if (tok->kind == TK_IDENT) { + // Variable or enum constant + VarScope *sc = find_var(tok); + *rest = tok->next; + + // 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) { + Obj *var = new_string_literal(tok->str, tok->ty); + *rest = tok->next; + return new_var_node(var, tok); + } + + if (tok->kind == TK_NUM) { + 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; + } + + 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); + } +} + +// 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, "{")) + /* MASOT 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, "{"); + fn->is_static = attr->is_static || (attr->is_inline && attr->is_extern); + } 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_static = attr->is_static || (attr->is_inline && attr->is_extern); + fn->is_inline = attr->is_inline; + } + + 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, VarAttr *attr) { + 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, "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; +} + +// Lookahead tokens and returns true if a given token is a start +// of a function definition or declaration. +static bool is_function(Token *tok) { + if (equal(tok, ";")) + return false; + + Type dummy = {}; + Type *ty = declarator(&tok, tok, &dummy); + return ty->kind == TY_FUNC; +} + +// 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) { + 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, &attr); + continue; + } + + // Global variable + 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 0000000..abc095f --- /dev/null +++ b/preprocess.c @@ -0,0 +1,1151 @@ +// 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 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 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; + + return NULL; +} + +static char *search_include_next(char *filename) { + 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 <foo.h> + 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")) { + assert(0); + } + + 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")) { + assert(0); + } + + if (equal(tok, "if")) { + assert(0); + } + + if (equal(tok, "ifdef")) { + assert(0); + } + + if (equal(tok, "ifndef")) { + assert(0); + } + + 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("<built-in>", 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/scripts/dietc_helpers.h b/scripts/dietc_helpers.h new file mode 100644 index 0000000..a5e00ac --- /dev/null +++ b/scripts/dietc_helpers.h @@ -0,0 +1,5 @@ +extern void *memset(void *, int, long unsigned int); +#define BINARY(a, op, b) a op b +#define PTR_BINARY(a, op, b) ((void*)(a) op (unsigned long)(b)) +#define MEMZERO(obj) memset(&obj, 0, sizeof(obj)) +#define FIELDPTR(sptr, field) (&(sptr -> field)) diff --git a/scripts/dietcc b/scripts/dietcc new file mode 100755 index 0000000..8898df2 --- /dev/null +++ b/scripts/dietcc @@ -0,0 +1,171 @@ +#!/bin/python3 +import subprocess +import sys +import re +import tempfile +import os + +if sys.argv[1:] == ["--version"]: + print("DIETC") + sys.exit(0) + +# Have to do this because ./configure seems to do some nasty stuff +# https://lists.gnu.org/archive/html/bug-make/2022-11/msg00041.html +if "-E" in sys.argv[1:]: + os.system(f"gcc {' '.join(sys.argv[1:])}") + sys.exit(0) + +# https://stackoverflow.com/questions/595305 +dietc_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + +def check_result(spr): + if not spr.returncode: return spr.stdout + print("Failed to run gcc wrapper:", ' '.join(sys.argv), file=sys.stderr) + if spr.stderr: + print(spr.stderr.decode("utf-8"), file=sys.stderr) + assert False + +def preprocess_pass(c, meta): + with tempfile.TemporaryDirectory() as tmpdir: + t_file = open(f"{tmpdir}/file.c", "wb") + t_file.write(f"#include \"{dietc_dir}/scripts/stdincludes/dietc_defines.h\"\n".encode("utf-8")) + t_file.write(c) + t_file.flush() + dirname = os.path.dirname(os.path.realpath(meta["c_path"])) + return check_result( + subprocess.run(f"gcc -I{dirname} -E {meta['argstr']} {t_file.name}", + shell=True, capture_output=True)) + +def strip_after_preprocess_pass(c, meta): + c = re.sub(rb"\[\[[^\]]*\]\]", b"", c) + c = re.sub(rb"__asm__", b"", c) + return c + +def dietc_pass(c, meta): + with tempfile.TemporaryDirectory() as tmpdir: + t_file = open(f"{tmpdir}/file.c", "wb") + t_file.write(c) + t_file.flush() + dirname = os.path.dirname(os.path.realpath(meta["c_path"])) + return check_result( + subprocess.run(f"timeout 5s {dietc_dir}/dietc {t_file.name}", + shell=True, capture_output=True)) + +def final_cleanup_pass(dietc, meta): + # (1) treat builtins as builtins + patterns = { + rb"extern Type_[0-9]* __builtin_[^ ]* ;\n": b"", + rb"extern Type_[0-9]* __(div|mul)[ds]c3 ;\n": b"", + rb"extern Type_[0-9]* __btowc_alias ;\n": b"", + } + for pattern, replace in patterns.items(): + dietc = re.sub(pattern, b"", dietc) + + # (2) builtins must be called directly + # NOTE: if passes mess this up, e.g., by doing optimizations, we could be in + # trouble here + pattern = rb"\t(t[0-9]*) = (__builtin_[^ ]*|alloca) ;\n" + match_ = re.search(pattern, dietc) + while match_ is not None: + temp = match_.groups()[0] + builtin = match_.groups()[1] + # (2a) delete the match + dietc = dietc.replace(b"\t" + temp + b" = " + builtin + b" ;\n", b"") + # (2b) delete the declaration of the builtin + dietc = re.sub(b"\tType_[0-9]* " + temp + b" ;\n", b"", dietc) + # (2c) replace uses of the temp with the builtin + dietc = dietc.replace(b" " + temp + b" ", b" " + builtin + b" ") + dietc = dietc.replace(b"\t" + temp + b" ", b"\t" + builtin + b" ") + + match_ = re.search(pattern, dietc) + + # (3) __builtin_va_arg_pack* must be called *super* directly, and must be in + # an always-inline method + # NOTE: if passes mess this up, e.g., by doing optimizations, we could be in + # trouble here + pattern = rb"\t(t[0-9]*) = (__builtin_va_arg_pack[^ ]*) \( \) ;\n" + match_ = re.search(pattern, dietc) + any_arg_pack = (match_ is not None) + while match_ is not None: + temp = match_.groups()[0] + builtin = match_.groups()[1] + # (2a) delete the match + dietc = dietc.replace(b"\t" + temp + b" = " + builtin + b" ( ) ;\n", b"") + # (2b) delete the declaration of the result + dietc = re.sub(b"\tType_[0-9]* " + temp + b" ;\n", b"", dietc) + # (2c) replace uses of the temp with the builtin + dietc = dietc.replace(b" " + temp + b" ", b" " + builtin + b" ( ) ") + + match_ = re.search(pattern, dietc) + + # (3b) any functions using __builtin_va_arg_pack* must be inlined + if any_arg_pack or b"\t__builtin_va_start " in dietc: + lines = dietc.split(b"\n") + inline_is = set() + last_decl_i = 0 + for i, line in enumerate(lines): + if not line.startswith(b"\t"): + last_decl_i = i + elif b"__builtin_va_arg_pack" in line: + inline_is.add(last_decl_i) + elif b"\t__builtin_va_start " in line: + # second argument of __builtin_va_start must be last named argument to + # function + fn, _po, arg1, _c, arg2, _pc, _sc = line.split() + # {, ), ..., <,>, [arg] + real_arg2 = lines[last_decl_i].split()[-5] + lines[i] = b"\t" + b" ".join([fn, _po, arg1, _c, real_arg2, _pc, _sc]) + for i in sorted(inline_is): + if "static" in lines[i]: + lines[i] = b"inline __attribute__ ((__gnu_inline__)) " + lines[i] + else: + lines[i] = b"extern inline __attribute__ ((__gnu_inline__)) " + lines[i] + dietc = b"\n".join(lines) + + return dietc + +PASSES = [preprocess_pass, + strip_after_preprocess_pass, + dietc_pass, + final_cleanup_pass] + +def main(): + args = sys.argv[1:] + args.insert(0, f"-I{dietc_dir}/scripts/stdincludes") + # first, parse out any DietC passes + dietc_passes = [] + while "--dietc-pass" in args: + i = args.index("--dietc-pass") + args.pop(i) + dietc_passes.append(args.pop(i)) + + # then process all of the C files + out_dir = tempfile.TemporaryDirectory() + c_files = [arg for arg in args if arg.endswith(".c")] + diet_files = [] + for i, c_file in enumerate(c_files): + subargs = [arg for arg in args if not arg.endswith(".c")] + if "-o" in subargs: + i = subargs.index("-o") + subargs.pop(i) + subargs.pop(i) + argstr = " ".join(subargs) + + last = open(c_file, "rb").read() + for dietpass in PASSES: + last = dietpass(last, {"c_path": c_file, "argstr": argstr}) + + diet_files.append(f"{out_dir.name}/file{i}.c") + with open(diet_files[-1], "wb") as f: + f.write(last) + + # then reconstruct the arguments, using the processed C files + renaming = dict({c: diet for c, diet in zip(c_files, diet_files)}) + args = [renaming.get(arg, arg) for arg in args] + if c_files and "-o" not in args: + if "-c" in args: + args += ["-o", c_files[0].replace(".c", ".o")] + gcc = subprocess.run(f"gcc {' '.join(args)} -Wno-int-to-pointer-cast -Wno-builtin-declaration-mismatch", shell=True) + check_result(gcc) + +main() diff --git a/scripts/stdincludes/alloca.h b/scripts/stdincludes/alloca.h new file mode 100644 index 0000000..cb1abd8 --- /dev/null +++ b/scripts/stdincludes/alloca.h @@ -0,0 +1,4 @@ +extern void *__builtin_alloca(unsigned long long); +#define alloca __builtin_alloca + +#include_next <alloca.h> diff --git a/scripts/stdincludes/dietc_defines.h b/scripts/stdincludes/dietc_defines.h new file mode 100644 index 0000000..2ca19c9 --- /dev/null +++ b/scripts/stdincludes/dietc_defines.h @@ -0,0 +1,24 @@ +#define __attribute__(...) +#define __attribute(...) +#undef __GNUC__ +#undef __GNUC__ +#undef __has_builtin + +#define __signed__ signed +#define __typeof__ typeof +#define __typeof typeof + +#define __asm__(...) +#define __asm(...) +#define __extension__ +#define __inline inline +#define __extern_always_inline extern inline +#define _FORTIFY_SOURCE 0 +#define __restrict +#define __restrict__ +#define _Restrict +#define __PRETTY_FUNCTION__ __FUNCTION__ +#define volatile(...) +#define __volatile__(...) + +#define _Static_assert(...) diff --git a/scripts/stdincludes/regex.h b/scripts/stdincludes/regex.h new file mode 100644 index 0000000..1930c74 --- /dev/null +++ b/scripts/stdincludes/regex.h @@ -0,0 +1,698 @@ +/* Definitions for data structures and routines for the regular + expression library. + Copyright (C) 1985, 1989-2022 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +#ifndef _REGEX_H +#define _REGEX_H 1 + +#include <sys/types.h> + +/* Allow the use in C++ code. */ +#ifdef __cplusplus +extern "C" { +#endif + +/* Define __USE_GNU to declare GNU extensions that violate the + POSIX name space rules. */ +#ifdef _GNU_SOURCE +# define __USE_GNU 1 +#endif + +#ifdef _REGEX_LARGE_OFFSETS + +/* Use types and values that are wide enough to represent signed and + unsigned byte offsets in memory. This currently works only when + the regex code is used outside of the GNU C library; it is not yet + supported within glibc itself, and glibc users should not define + _REGEX_LARGE_OFFSETS. */ + +/* The type of object sizes. */ +typedef size_t __re_size_t; + +/* The type of object sizes, in places where the traditional code + uses unsigned long int. */ +typedef size_t __re_long_size_t; + +#else + +/* The traditional GNU regex implementation mishandles strings longer + than INT_MAX. */ +typedef unsigned int __re_size_t; +typedef unsigned long int __re_long_size_t; + +#endif + +/* The following two types have to be signed and unsigned integer type + wide enough to hold a value of a pointer. For most ANSI compilers + ptrdiff_t and size_t should be likely OK. Still size of these two + types is 2 for Microsoft C. Ugh... */ +typedef long int s_reg_t; +typedef unsigned long int active_reg_t; + +/* The following bits are used to determine the regexp syntax we + recognize. The set/not-set meanings are chosen so that Emacs syntax + remains the value 0. The bits are given in alphabetical order, and + the definitions shifted by one from the previous bit; thus, when we + add or remove a bit, only one other definition need change. */ +typedef unsigned long int reg_syntax_t; + +#ifdef __USE_GNU +/* If this bit is not set, then \ inside a bracket expression is literal. + If set, then such a \ quotes the following character. */ +# define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1) + +/* If this bit is not set, then + and ? are operators, and \+ and \? are + literals. + If set, then \+ and \? are operators and + and ? are literals. */ +# define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1) + +/* If this bit is set, then character classes are supported. They are: + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:], + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:]. + If not set, then character classes are not supported. */ +# define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1) + +/* If this bit is set, then ^ and $ are always anchors (outside bracket + expressions, of course). + If this bit is not set, then it depends: + ^ is an anchor if it is at the beginning of a regular + expression or after an open-group or an alternation operator; + $ is an anchor if it is at the end of a regular expression, or + before a close-group or an alternation operator. + + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because + POSIX draft 11.2 says that * etc. in leading positions is undefined. + We already implemented a previous draft which made those constructs + invalid, though, so we haven't changed the code back. */ +# define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1) + +/* If this bit is set, then special characters are always special + regardless of where they are in the pattern. + If this bit is not set, then special characters are special only in + some contexts; otherwise they are ordinary. Specifically, + * + ? and intervals are only special when not after the beginning, + open-group, or alternation operator. */ +# define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1) + +/* If this bit is set, then *, +, ?, and { cannot be first in an re or + immediately after an alternation or begin-group operator. */ +# define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1) + +/* If this bit is set, then . matches newline. + If not set, then it doesn't. */ +# define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1) + +/* If this bit is set, then . doesn't match NUL. + If not set, then it does. */ +# define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1) + +/* If this bit is set, nonmatching lists [^...] do not match newline. + If not set, they do. */ +# define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1) + +/* If this bit is set, either \{...\} or {...} defines an + interval, depending on RE_NO_BK_BRACES. + If not set, \{, \}, {, and } are literals. */ +# define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1) + +/* If this bit is set, +, ? and | aren't recognized as operators. + If not set, they are. */ +# define RE_LIMITED_OPS (RE_INTERVALS << 1) + +/* If this bit is set, newline is an alternation operator. + If not set, newline is literal. */ +# define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1) + +/* If this bit is set, then '{...}' defines an interval, and \{ and \} + are literals. + If not set, then '\{...\}' defines an interval. */ +# define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1) + +/* If this bit is set, (...) defines a group, and \( and \) are literals. + If not set, \(...\) defines a group, and ( and ) are literals. */ +# define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1) + +/* If this bit is set, then \<digit> matches <digit>. + If not set, then \<digit> is a back-reference. */ +# define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1) + +/* If this bit is set, then | is an alternation operator, and \| is literal. + If not set, then \| is an alternation operator, and | is literal. */ +# define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1) + +/* If this bit is set, then an ending range point collating higher + than the starting range point, as in [z-a], is invalid. + If not set, then when ending range point collates higher than the + starting range point, the range is ignored. */ +# define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1) + +/* If this bit is set, then an unmatched ) is ordinary. + If not set, then an unmatched ) is invalid. */ +# define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1) + +/* If this bit is set, succeed as soon as we match the whole pattern, + without further backtracking. */ +# define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1) + +/* If this bit is set, do not process the GNU regex operators. + If not set, then the GNU regex operators are recognized. */ +# define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1) + +/* If this bit is set, turn on internal regex debugging. + If not set, and debugging was on, turn it off. + This only works if regex.c is compiled -DDEBUG. + We define this bit always, so that all that's needed to turn on + debugging is to recompile regex.c; the calling code can always have + this bit set, and it won't affect anything in the normal case. */ +# define RE_DEBUG (RE_NO_GNU_OPS << 1) + +/* If this bit is set, a syntactically invalid interval is treated as + a string of ordinary characters. For example, the ERE 'a{1' is + treated as 'a\{1'. */ +# define RE_INVALID_INTERVAL_ORD (RE_DEBUG << 1) + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +# define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1) + +/* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only + for ^, because it is difficult to scan the regex backwards to find + whether ^ should be special. */ +# define RE_CARET_ANCHORS_HERE (RE_ICASE << 1) + +/* If this bit is set, then \{ cannot be first in a regex or + immediately after an alternation, open-group or \} operator. */ +# define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1) + +/* If this bit is set, then no_sub will be set to 1 during + re_compile_pattern. */ +# define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1) +#endif + +/* This global variable defines the particular regexp syntax to use (for + some interfaces). When a regexp is compiled, the syntax used is + stored in the pattern buffer, so changing this does not affect + already-compiled regexps. */ +extern reg_syntax_t re_syntax_options; + +#ifdef __USE_GNU +/* Define combinations of the above bits for the standard possibilities. + (The [[[ comments delimit what gets put into the Texinfo file, so + don't delete them!) */ +/* [[[begin syntaxes]]] */ +# define RE_SYNTAX_EMACS 0 + +# define RE_SYNTAX_AWK \ + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \ + | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CHAR_CLASSES \ + | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS) + +# define RE_SYNTAX_GNU_AWK \ + ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ + | RE_INVALID_INTERVAL_ORD) \ + & ~(RE_DOT_NOT_NULL | RE_CONTEXT_INDEP_OPS \ + | RE_CONTEXT_INVALID_OPS )) + +# define RE_SYNTAX_POSIX_AWK \ + (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \ + | RE_INTERVALS | RE_NO_GNU_OPS \ + | RE_INVALID_INTERVAL_ORD) + +# define RE_SYNTAX_GREP \ + ((RE_SYNTAX_POSIX_BASIC | RE_NEWLINE_ALT) \ + & ~(RE_CONTEXT_INVALID_DUP | RE_DOT_NOT_NULL)) + +# define RE_SYNTAX_EGREP \ + ((RE_SYNTAX_POSIX_EXTENDED | RE_INVALID_INTERVAL_ORD | RE_NEWLINE_ALT) \ + & ~(RE_CONTEXT_INVALID_OPS | RE_DOT_NOT_NULL)) + +/* POSIX grep -E behavior is no longer incompatible with GNU. */ +# define RE_SYNTAX_POSIX_EGREP \ + RE_SYNTAX_EGREP + +/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ +# define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC + +# define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC + +/* Syntax bits common to both basic and extended POSIX regex syntax. */ +# define _RE_SYNTAX_POSIX_COMMON \ + (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \ + | RE_INTERVALS | RE_NO_EMPTY_RANGES) + +# define RE_SYNTAX_POSIX_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP) + +/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this + isn't minimal, since other operators, such as \`, aren't disabled. */ +# define RE_SYNTAX_POSIX_MINIMAL_BASIC \ + (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS) + +# define RE_SYNTAX_POSIX_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_VBAR \ + | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD) + +/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is + removed and RE_NO_BK_REFS is added. */ +# define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \ + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \ + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \ + | RE_NO_BK_PARENS | RE_NO_BK_REFS \ + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD) +/* [[[end syntaxes]]] */ + +/* Maximum number of duplicates an interval can allow. POSIX-conforming + systems might define this in <limits.h>, but we want our + value, so remove any previous define. */ +# ifdef _REGEX_INCLUDE_LIMITS_H +# include <limits.h> +# endif +# ifdef RE_DUP_MAX +# undef RE_DUP_MAX +# endif + +/* RE_DUP_MAX is 2**15 - 1 because an earlier implementation stored + the counter as a 2-byte signed integer. This is no longer true, so + RE_DUP_MAX could be increased to (INT_MAX / 10 - 1), or to + ((SIZE_MAX - 9) / 10) if _REGEX_LARGE_OFFSETS is defined. + However, there would be a huge performance problem if someone + actually used a pattern like a\{214748363\}, so RE_DUP_MAX retains + its historical value. */ +# define RE_DUP_MAX (0x7fff) +#endif + + +/* POSIX 'cflags' bits (i.e., information for 'regcomp'). */ + +/* If this bit is set, then use extended regular expression syntax. + If not set, then use basic regular expression syntax. */ +#define REG_EXTENDED 1 + +/* If this bit is set, then ignore case when matching. + If not set, then case is significant. */ +#define REG_ICASE (1 << 1) + +/* If this bit is set, then anchors do not match at newline + characters in the string. + If not set, then anchors do match at newlines. */ +#define REG_NEWLINE (1 << 2) + +/* If this bit is set, then report only success or fail in regexec. + If not set, then returns differ between not matching and errors. */ +#define REG_NOSUB (1 << 3) + + +/* POSIX 'eflags' bits (i.e., information for regexec). */ + +/* If this bit is set, then the beginning-of-line operator doesn't match + the beginning of the string (presumably because it's not the + beginning of a line). + If not set, then the beginning-of-line operator does match the + beginning of the string. */ +#define REG_NOTBOL 1 + +/* Like REG_NOTBOL, except for the end-of-line. */ +#define REG_NOTEOL (1 << 1) + +/* Use PMATCH[0] to delimit the start and end of the search in the + buffer. */ +#define REG_STARTEND (1 << 2) + + +/* If any error codes are removed, changed, or added, update the + '__re_error_msgid' table in regcomp.c. */ + +typedef enum +{ + _REG_ENOSYS = -1, /* This will never happen for this implementation. */ + _REG_NOERROR = 0, /* Success. */ + _REG_NOMATCH, /* Didn't find a match (for regexec). */ + + /* POSIX regcomp return error codes. (In the order listed in the + standard.) */ + _REG_BADPAT, /* Invalid pattern. */ + _REG_ECOLLATE, /* Invalid collating element. */ + _REG_ECTYPE, /* Invalid character class name. */ + _REG_EESCAPE, /* Trailing backslash. */ + _REG_ESUBREG, /* Invalid back reference. */ + _REG_EBRACK, /* Unmatched left bracket. */ + _REG_EPAREN, /* Parenthesis imbalance. */ + _REG_EBRACE, /* Unmatched \{. */ + _REG_BADBR, /* Invalid contents of \{\}. */ + _REG_ERANGE, /* Invalid range end. */ + _REG_ESPACE, /* Ran out of memory. */ + _REG_BADRPT, /* No preceding re for repetition op. */ + + /* Error codes we've added. */ + _REG_EEND, /* Premature end. */ + _REG_ESIZE, /* Too large (e.g., repeat count too large). */ + _REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */ +} reg_errcode_t; + +#if defined _XOPEN_SOURCE || defined __USE_XOPEN2K +# define REG_ENOSYS _REG_ENOSYS +#endif +#define REG_NOERROR _REG_NOERROR +#define REG_NOMATCH _REG_NOMATCH +#define REG_BADPAT _REG_BADPAT +#define REG_ECOLLATE _REG_ECOLLATE +#define REG_ECTYPE _REG_ECTYPE +#define REG_EESCAPE _REG_EESCAPE +#define REG_ESUBREG _REG_ESUBREG +#define REG_EBRACK _REG_EBRACK +#define REG_EPAREN _REG_EPAREN +#define REG_EBRACE _REG_EBRACE +#define REG_BADBR _REG_BADBR +#define REG_ERANGE _REG_ERANGE +#define REG_ESPACE _REG_ESPACE +#define REG_BADRPT _REG_BADRPT +#define REG_EEND _REG_EEND +#define REG_ESIZE _REG_ESIZE +#define REG_ERPAREN _REG_ERPAREN + +/* This data structure represents a compiled pattern. Before calling + the pattern compiler, the fields 'buffer', 'allocated', 'fastmap', + and 'translate' can be set. After the pattern has been compiled, + the fields 're_nsub', 'not_bol' and 'not_eol' are available. All + other fields are private to the regex routines. */ + +#ifndef RE_TRANSLATE_TYPE +# define __RE_TRANSLATE_TYPE unsigned char * +# ifdef __USE_GNU +# define RE_TRANSLATE_TYPE __RE_TRANSLATE_TYPE +# endif +#endif + +#ifdef __USE_GNU +# define __REPB_PREFIX(name) name +#else +# define __REPB_PREFIX(name) __##name +#endif + +struct re_pattern_buffer +{ + /* Space that holds the compiled pattern. The type + 'struct re_dfa_t' is private and is not declared here. */ + struct re_dfa_t *__REPB_PREFIX(buffer); + + /* Number of bytes to which 'buffer' points. */ + __re_long_size_t __REPB_PREFIX(allocated); + + /* Number of bytes actually used in 'buffer'. */ + __re_long_size_t __REPB_PREFIX(used); + + /* Syntax setting with which the pattern was compiled. */ + reg_syntax_t __REPB_PREFIX(syntax); + + /* Pointer to a fastmap, if any, otherwise zero. re_search uses the + fastmap, if there is one, to skip over impossible starting points + for matches. */ + char *__REPB_PREFIX(fastmap); + + /* Either a translate table to apply to all characters before + comparing them, or zero for no translation. The translation is + applied to a pattern when it is compiled and to a string when it + is matched. */ + __RE_TRANSLATE_TYPE __REPB_PREFIX(translate); + + /* Number of subexpressions found by the compiler. */ + size_t re_nsub; + + /* Zero if this pattern cannot match the empty string, one else. + Well, in truth it's used only in 're_search_2', to see whether or + not we should use the fastmap, so we don't set this absolutely + perfectly; see 're_compile_fastmap' (the "duplicate" case). */ + unsigned __REPB_PREFIX(can_be_null) : 1; + + /* If REGS_UNALLOCATED, allocate space in the 'regs' structure + for 'max (RE_NREGS, re_nsub + 1)' groups. + If REGS_REALLOCATE, reallocate space if necessary. + If REGS_FIXED, use what's there. */ +#ifdef __USE_GNU +# define REGS_UNALLOCATED 0 +# define REGS_REALLOCATE 1 +# define REGS_FIXED 2 +#endif + unsigned __REPB_PREFIX(regs_allocated) : 2; + + /* Set to zero when 're_compile_pattern' compiles a pattern; set to + one by 're_compile_fastmap' if it updates the fastmap. */ + unsigned __REPB_PREFIX(fastmap_accurate) : 1; + + /* If set, 're_match_2' does not return information about + subexpressions. */ + unsigned __REPB_PREFIX(no_sub) : 1; + + /* If set, a beginning-of-line anchor doesn't match at the beginning + of the string. */ + unsigned __REPB_PREFIX(not_bol) : 1; + + /* Similarly for an end-of-line anchor. */ + unsigned __REPB_PREFIX(not_eol) : 1; + + /* If true, an anchor at a newline matches. */ + unsigned __REPB_PREFIX(newline_anchor) : 1; +}; + +typedef struct re_pattern_buffer regex_t; + +/* Type for byte offsets within the string. POSIX mandates this. */ +#ifdef _REGEX_LARGE_OFFSETS +/* POSIX 1003.1-2008 requires that regoff_t be at least as wide as + ptrdiff_t and ssize_t. We don't know of any hosts where ptrdiff_t + is wider than ssize_t, so ssize_t is safe. ptrdiff_t is not + visible here, so use ssize_t. */ +typedef ssize_t regoff_t; +#else +/* The traditional GNU regex implementation mishandles strings longer + than INT_MAX. */ +typedef int regoff_t; +#endif + + +#ifdef __USE_GNU +/* This is the structure we store register match data in. See + regex.texinfo for a full description of what registers match. */ +struct re_registers +{ + __re_size_t num_regs; + regoff_t *start; + regoff_t *end; +}; + + +/* If 'regs_allocated' is REGS_UNALLOCATED in the pattern buffer, + 're_match_2' returns information about at least this many registers + the first time a 'regs' structure is passed. */ +# ifndef RE_NREGS +# define RE_NREGS 30 +# endif +#endif + + +/* POSIX specification for registers. Aside from the different names than + 're_registers', POSIX uses an array of structures, instead of a + structure of arrays. */ +typedef struct +{ + regoff_t rm_so; /* Byte offset from string's start to substring's start. */ + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */ +} regmatch_t; + +/* Declarations for routines. */ + +#ifndef _REGEX_NELTS +# if (defined __STDC_VERSION__ && 199901L <= __STDC_VERSION__ \ + && !defined __STDC_NO_VLA__) +# define _REGEX_NELTS(n) n +# else +# define _REGEX_NELTS(n) +# endif +#endif + +#if defined __GNUC__ && 4 < __GNUC__ + (6 <= __GNUC_MINOR__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wvla" +#endif + +#ifndef _Attr_access_ +# ifdef __attr_access +# define _Attr_access_(arg) __attr_access (arg) +# elif defined __GNUC__ && 10 <= __GNUC__ +# define _Attr_access_(x) __attribute__ ((__access__ x)) +# else +# define _Attr_access_(x) +# endif +#endif + +#ifdef __USE_GNU +/* Sets the current default syntax to SYNTAX, and return the old syntax. + You can also simply assign to the 're_syntax_options' variable. */ +extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax); + +/* Compile the regular expression PATTERN, with length LENGTH + and syntax given by the global 're_syntax_options', into the buffer + BUFFER. Return NULL if successful, and an error string if not. + + To free the allocated storage, you must call 'regfree' on BUFFER. + Note that the translate table must either have been initialized by + 'regcomp', with a malloc'ed value, or set to NULL before calling + 'regfree'. */ +extern const char *re_compile_pattern (const char *__pattern, size_t __length, + struct re_pattern_buffer *__buffer) + _Attr_access_ ((__read_only__, 1, 2)); + + +/* Compile a fastmap for the compiled pattern in BUFFER; used to + accelerate searches. Return 0 if successful and -2 if was an + internal error. */ +extern int re_compile_fastmap (struct re_pattern_buffer *__buffer); + + +/* Search in the string STRING (with length LENGTH) for the pattern + compiled into BUFFER. Start searching at position START, for RANGE + characters. Return the starting position of the match, -1 for no + match, or -2 for an internal error. Also return register + information in REGS (if REGS and BUFFER->no_sub are nonzero). */ +extern regoff_t re_search (struct re_pattern_buffer *__buffer, + const char *__String, regoff_t __length, + regoff_t __start, regoff_t __range, + struct re_registers *__regs) + _Attr_access_ ((__read_only__, 2, 3)); + + +/* Like 're_search', but search in the concatenation of STRING1 and + STRING2. Also, stop searching at index START + STOP. */ +extern regoff_t re_search_2 (struct re_pattern_buffer *__buffer, + const char *__string1, regoff_t __length1, + const char *__string2, regoff_t __length2, + regoff_t __start, regoff_t __range, + struct re_registers *__regs, + regoff_t __stop) + _Attr_access_ ((__read_only__, 2, 3)) + _Attr_access_ ((__read_only__, 4, 5)); + + +/* Like 're_search', but return how many characters in STRING the regexp + in BUFFER matched, starting at position START. */ +extern regoff_t re_match (struct re_pattern_buffer *__buffer, + const char *__String, regoff_t __length, + regoff_t __start, struct re_registers *__regs) + _Attr_access_ ((__read_only__, 2, 3)); + + +/* Relates to 're_match' as 're_search_2' relates to 're_search'. */ +extern regoff_t re_match_2 (struct re_pattern_buffer *__buffer, + const char *__string1, regoff_t __length1, + const char *__string2, regoff_t __length2, + regoff_t __start, struct re_registers *__regs, + regoff_t __stop) + _Attr_access_ ((__read_only__, 2, 3)) + _Attr_access_ ((__read_only__, 4, 5)); + + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using BUFFER and REGS will use this memory + for recording register information. STARTS and ENDS must be + allocated with malloc, and must each be at least 'NUM_REGS * sizeof + (regoff_t)' bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + BUFFER will allocate its own register data, without + freeing the old data. */ +extern void re_set_registers (struct re_pattern_buffer *__buffer, + struct re_registers *__regs, + __re_size_t __num_regs, + regoff_t *__starts, regoff_t *__ends); +#endif /* Use GNU */ + +#if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_MISC) +/* 4.2 bsd compatibility. */ +extern char *re_comp (const char *); +extern int re_exec (const char *); +#endif + +/* For plain 'restrict', use glibc's __restrict if defined. + Otherwise, GCC 2.95 and later have "__restrict"; C99 compilers have + "restrict", and "configure" may have defined "restrict". + Other compilers use __restrict, __restrict__, and _Restrict, and + 'configure' might #define 'restrict' to those words, so pick a + different name. */ +#ifndef _Restrict_ +# if defined __restrict \ + || 2 < __GNUC__ + (95 <= __GNUC_MINOR__) \ + || __clang_major__ >= 3 +# define _Restrict_ __restrict +# elif 199901L <= __STDC_VERSION__ || defined restrict +# define _Restrict_ restrict +# else +# define _Restrict_ +# endif +#endif +/* For the ISO C99 syntax + array_name[restrict] + use glibc's __restrict_arr if available. + Otherwise, GCC 3.1 and clang support this syntax (but not in C++ mode). + Other ISO C99 compilers support it as well. */ +#ifndef _Restrict_arr_ +# ifdef __restrict_arr +# define _Restrict_arr_ __restrict_arr +# elif ((199901L <= __STDC_VERSION__ \ + || 3 < __GNUC__ + (1 <= __GNUC_MINOR__) \ + || __clang_major__ >= 3) \ + && !defined __cplusplus) +# define _Restrict_arr_ _Restrict_ +# else +# define _Restrict_arr_ +# endif +#endif + +/* POSIX compatibility. */ +extern int regcomp (regex_t *_Restrict_ __preg, + const char *_Restrict_ __pattern, + int __cflags); + +extern int regexec (const regex_t *_Restrict_ __preg, + const char *_Restrict_ __String, size_t __nmatch, + regmatch_t __pmatch[], + int __eflags); + +extern size_t regerror (int __errcode, const regex_t *_Restrict_ __preg, + char *_Restrict_ __errbuf, size_t __errbuf_size) + _Attr_access_ ((__write_only__, 3, 4)); + +extern void regfree (regex_t *__preg); + +#if defined __GNUC__ && 4 < __GNUC__ + (6 <= __GNUC_MINOR__) +# pragma GCC diagnostic pop +#endif + +#ifdef __cplusplus +} +#endif /* C++ */ + +#endif /* regex.h */ diff --git a/scripts/stdincludes/stdarg.h b/scripts/stdincludes/stdarg.h new file mode 100644 index 0000000..73563b4 --- /dev/null +++ b/scripts/stdincludes/stdarg.h @@ -0,0 +1,46 @@ +#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; +typedef va_list __builtin_va_list; +typedef va_list __gnuc_va_list; + +extern void __builtin_va_start(void *ap, void *last); +#define va_start(ap, last) do { void *_ap = ≈ __builtin_va_start(_ap, last); } while (0) +#define va_end(ap) +static void *__va_arg_mem(__va_elem *ap, int sz, int align) { + unsigned long 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 (void*)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))); \ + }) + +#endif diff --git a/scripts/stdincludes/stddef.h b/scripts/stdincludes/stddef.h new file mode 100644 index 0000000..4d71d7b --- /dev/null +++ b/scripts/stdincludes/stddef.h @@ -0,0 +1,6 @@ +#define offsetof(type, member) ((unsigned long)&(((type *)0)->member)) +#define __builtin_offsetof(type, member) ((unsigned long)&(((type *)0)->member)) +#define alignof _Alignof +#define __alignof__ _Alignof + +#include_next <stddef.h> diff --git a/strings.c b/strings.c new file mode 100644 index 0000000..9e67428 --- /dev/null +++ b/strings.c @@ -0,0 +1,31 @@ +#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; + size_t buflen; + FILE *out = open_memstream(&buf, &buflen); + + va_list ap; + va_start(ap, fmt); + vfprintf(out, fmt, ap); + va_end(ap); + fclose(out); + return buf; +} diff --git a/tests/array_of_structs.c b/tests/array_of_structs.c new file mode 100644 index 0000000..0ab53b9 --- /dev/null +++ b/tests/array_of_structs.c @@ -0,0 +1,23 @@ +struct option { + char *name; + int has_arg; + int *flag; + int val; +}; + +static struct option const options[] = { + {"whatisup", 1, 0, 0}, + {"helloworld", 2, 0, 0}, + {"version", 3, 0, 0}, + {"help", 4, 0, 0}, + {"etc", 5, 0, 0}, + {0, 0, 0, 0}, +}; + +void puts(char *x); + +int main() { + for (struct option *opt = options; opt->name; opt++) { + puts(opt->name); + } +} diff --git a/tests/coreutils.c b/tests/coreutils.c new file mode 100644 index 0000000..60b9176 --- /dev/null +++ b/tests/coreutils.c @@ -0,0 +1,16 @@ + /* Check that off_t can represent 2**63 - 1 correctly. + We can't simply define LARGE_OFF_T to be 9223372036854775807, + since some C++ compilers masquerading as C compilers + incorrectly reject 9223372036854775807. */ +#define LARGE_OFF_T (((off_t) 1 << 31 << 31) - 1 + ((off_t) 1 << 31 << 31)) + +int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 + && LARGE_OFF_T % 2147483647 == 1) + ? 1 : -1]; +int +main (void) +{ + + ; + return 0; +} diff --git a/tests/debug.c b/tests/debug.c new file mode 100644 index 0000000..95ad579 --- /dev/null +++ b/tests/debug.c @@ -0,0 +1,7 @@ +void __open_too_many_args(void); +void __open_missing_mode(void); + +int open () { + __open_too_many_args (); + return 0; +} diff --git a/tests/extern_array.c b/tests/extern_array.c new file mode 100644 index 0000000..c24500f --- /dev/null +++ b/tests/extern_array.c @@ -0,0 +1,6 @@ +extern const int foo[]; +extern void baz(void *); + +void bar() { + baz((void*)foo); +} diff --git a/tests/fgets.c b/tests/fgets.c new file mode 100644 index 0000000..914eecc --- /dev/null +++ b/tests/fgets.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +void foo(FILE *s) { + char *x; + fgets(x, 0, s); +} diff --git a/tests/ldbl.c b/tests/ldbl.c new file mode 100644 index 0000000..f3d8cae --- /dev/null +++ b/tests/ldbl.c @@ -0,0 +1,9 @@ +// #include <float.h> +int main (void) { + typedef int check[sizeof (long double) == sizeof (double) + // && LDBL_MANT_DIG == DBL_MANT_DIG + // && LDBL_MAX_EXP == DBL_MAX_EXP + // && LDBL_MIN_EXP == DBL_MIN_EXP + ? 1 : -1]; + return 0; +} diff --git a/tests/local_array.c b/tests/local_array.c new file mode 100644 index 0000000..3d07d04 --- /dev/null +++ b/tests/local_array.c @@ -0,0 +1,4 @@ +int lookup_arr(int i) { + int arr[3]; + return arr[i]; +} diff --git a/tests/local_const_array.c b/tests/local_const_array.c new file mode 100644 index 0000000..38de887 --- /dev/null +++ b/tests/local_const_array.c @@ -0,0 +1,4 @@ +int lookup_arr(int i) { + int arr[1] = {1}; + return arr[i]; +} diff --git a/tests/no_includes.c b/tests/no_includes.c new file mode 100644 index 0000000..89796d0 --- /dev/null +++ b/tests/no_includes.c @@ -0,0 +1,15 @@ +typedef struct ___dietc_va_list { char ___dietc_va_list[24]; } va_list; + +void foo(int x, int y) { + x = y; + va_list z; + va_list a; + z = a; +} + +int main() { + int x = 5; + int y = 7; + x = y; + return x; +} diff --git a/tests/ptr_arith.c b/tests/ptr_arith.c new file mode 100644 index 0000000..1f0d0aa --- /dev/null +++ b/tests/ptr_arith.c @@ -0,0 +1,4 @@ +int main(void) { + int *x; + x[5] = 1; +} diff --git a/tests/struct_return.c b/tests/struct_return.c new file mode 100644 index 0000000..3d98574 --- /dev/null +++ b/tests/struct_return.c @@ -0,0 +1,16 @@ +struct foo { + int xyz1; + int xyz2; + int xyz3; + int xyz4; + int xyz5; + int xyz6; + int xyz7; + int xyz8; +}; + +extern struct foo baz(int bar); + +struct foo baz(int bar) { + return (struct foo){1}; +} diff --git a/tests/super_simple.c b/tests/super_simple.c new file mode 100644 index 0000000..8f1a336 --- /dev/null +++ b/tests/super_simple.c @@ -0,0 +1,5 @@ +int main(void) { + int x = 1; + int y = 2; + return y; +} diff --git a/tests/test.c b/tests/test.c new file mode 100644 index 0000000..2e23b8b --- /dev/null +++ b/tests/test.c @@ -0,0 +1,21 @@ +#include <stdio.h> + +struct foo { + long bar; + char baz; +}; + +struct foo bar = {1,2}; + +struct foo *barptr[2] = {&bar, &bar}; +struct foo **barptrptr[2] = {&barptr[0], &barptr[1]}; + +int main(void) { + int x = 5; + if (x) + x++; + long y = 6; + y++; + printf("Hello, World!\n"); + return 0; +} diff --git a/tests/union_init.c b/tests/union_init.c new file mode 100644 index 0000000..80cee1d --- /dev/null +++ b/tests/union_init.c @@ -0,0 +1,10 @@ +union foo { + int x; + long y; +}; + +int foo() { + static union foo initval; + union foo v = initval; + return 0; +} diff --git a/tests/unnamed_fields.c b/tests/unnamed_fields.c new file mode 100644 index 0000000..d259d44 --- /dev/null +++ b/tests/unnamed_fields.c @@ -0,0 +1,13 @@ +struct xyz { + int z; + struct { + int x; + int y; + }; +}; + +int main() { + struct xyz abc; + abc.x = 5; + return abc.x; +} diff --git a/tests/unsized_extern_array.c b/tests/unsized_extern_array.c new file mode 100644 index 0000000..112a590 --- /dev/null +++ b/tests/unsized_extern_array.c @@ -0,0 +1,4 @@ +extern const unsigned int arr[]; +int lookup_arr(int i) { + return arr[i]; +} diff --git a/tests/va_start.c b/tests/va_start.c new file mode 100644 index 0000000..8644950 --- /dev/null +++ b/tests/va_start.c @@ -0,0 +1,21 @@ +// https://en.cppreference.com/w/cpp/utility/variadic/va_start + +#include <stdio.h> +#include <stdarg.h> + +int add_nums(int count, ...) +{ + int result = 0; + va_list args; + va_start(args, count); + for (int i = 0; i < count; ++i) { + result += va_arg(args, int); + } + va_end(args); + return result; +} + +int main() +{ + printf("%d\n", add_nums(4, 25, 25, 50, 50)); +} diff --git a/tokenize.c b/tokenize.c new file mode 100644 index 0000000..5c49c02 --- /dev/null +++ b/tokenize.c @@ -0,0 +1,805 @@ +#include "chibicc.h" + +// Input file +static File *current_file; + +// A list of all input files. +static File **input_files; + +// True if the current position is at the beginning of a line +static bool at_bol; + +// True if the current position follows a space character +static bool has_space; + +// Reports an error and exit. +void error(char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + exit(1); +} + +// Reports an error message in the following format. +// +// foo.c:10: x = y + 1; +// ^ <error message here> +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 (input < line && line[-1] != '\n') + line--; + + char *end = loc; + while (*end && *end != '\n') + end++; + + // Print out the line. + int indent = fprintf(stderr, "%s:%d: ", filename, line_no); + fprintf(stderr, "%.*s\n", (int)(end - line), line); + + // Show the error message. + int pos = display_width(line, loc - line) + indent; + + fprintf(stderr, "%*s", pos, ""); // print pos spaces. + fprintf(stderr, "^ "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +} + +void error_at(char *loc, char *fmt, ...) { + int line_no = 1; + for (char *p = current_file->contents; p < loc; p++) + if (*p == '\n') + line_no++; + + va_list ap; + va_start(ap, fmt); + 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->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`. +bool equal(Token *tok, char *op) { + return memcmp(tok->loc, op, tok->len) == 0 && op[tok->len] == '\0'; +} + +// Ensure that the current token is `op`. +Token *skip(Token *tok, char *op) { + if (!equal(tok, op)) + error_tok(tok, "expected '%s'", op); + return tok->next; +} + +bool consume(Token **rest, Token *tok, char *str) { + if (equal(tok, str)) { + *rest = tok->next; + return true; + } + *rest = tok; + return false; +} + +// Create a new token. +static Token *new_token(TokenKind kind, char *start, char *end) { + Token *tok = calloc(1, sizeof(Token)); + 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; +} + +static bool startswith(char *p, char *q) { + return strncmp(p, q, strlen(q)) == 0; +} + +// 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; + + for (;;) { + char *q; + c = decode_utf8(&q, p); + if (!is_ident2(c)) + return p - start; + p = q; + } +} + +static int from_hex(char c) { + if ('0' <= c && c <= '9') + return c - '0'; + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + return c - 'A' + 10; +} + +// Read a punctuator token from p and returns its length. +static int read_punct(char *p) { + static char *kw[] = { + "<<=", ">>=", "...", "==", "!=", "<=", ">=", "->", "+=", + "-=", "*=", "/=", "++", "--", "%=", "&=", "|=", "^=", "&&", + "||", "<<", ">>", "##", + }; + + for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) + if (startswith(p, kw[i])) + return strlen(kw[i]); + + return ispunct(*p) ? 1 : 0; +} + +static bool is_keyword(Token *tok) { + 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); + } + + return hashmap_get2(&map, tok->loc, tok->len); +} + +static int read_escaped_char(char **new_pos, char *p) { + if ('0' <= *p && *p <= '7') { + // Read an octal number. + int c = *p++ - '0'; + if ('0' <= *p && *p <= '7') { + c = (c << 3) + (*p++ - '0'); + if ('0' <= *p && *p <= '7') + c = (c << 3) + (*p++ - '0'); + } + *new_pos = p; + return c; + } + + if (*p == 'x') { + // Read a hexadecimal number. + p++; + if (!isxdigit(*p)) + error_at(p, "invalid hex escape sequence"); + + int c = 0; + for (; isxdigit(*p); p++) + c = (c << 4) + from_hex(*p); + *new_pos = p; + return c; + } + + *new_pos = p + 1; + + // Escape sequences are defined using themselves here. E.g. + // '\n' is implemented using '\n'. This tautological definition + // works because the compiler that compiles our compiler knows + // what '\n' actually is. In other words, we "inherit" the ASCII + // code of '\n' from the compiler that compiles our compiler, + // so we don't have to teach the actual code here. + // + // This fact has huge implications not only for the correctness + // of the compiler but also for the security of the generated code. + // For more info, read "Reflections on Trusting Trust" by Ken Thompson. + // https://github.com/rui314/chibicc/wiki/thompson1984.pdf + switch (*p) { + case 'a': return '\a'; + case 'b': return '\b'; + case 't': return '\t'; + case 'n': return '\n'; + case 'v': return '\v'; + case 'f': return '\f'; + case 'r': return '\r'; + // [GNU] \e for the ASCII escape character is a GNU C extension. + case 'e': return 27; + default: return *p; + } +} + +// Find a closing double-quote. +static char *string_literal_end(char *p) { + char *start = p; + for (; *p != '"'; p++) { + if (*p == '\n' || *p == '\0') + error_at(start, "unclosed string literal"); + if (*p == '\\') + p++; + } + return p; +} + +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 = quote + 1; p < end;) { + if (*p == '\\') + buf[len++] = read_escaped_char(&p, p + 1); + else + buf[len++] = *p++; + } + + Token *tok = new_token(TK_STR, start, end + 1); + tok->ty = array_of(ty_char, len + 1); + tok->str = buf; + return tok; +} + +// 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_file->contents; + int n = 1; + + do { + if (p == tok->loc) { + tok->line_no = n; + tok = tok->next; + } + if (*p == '\n') + n++; + } 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. +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; + } + + // Skip block comments. + if (startswith(p, "/*")) { + char *q = strstr(p + 2, "*/"); + 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) || (*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, 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 + int ident_len = read_ident(p); + if (ident_len) { + cur = cur->next = new_token(TK_IDENT, p, p + ident_len); + p += cur->len; + continue; + } + + // Punctuators + int punct_len = read_punct(p); + if (punct_len) { + cur = cur->next = new_token(TK_PUNCT, p, p + punct_len); + p += cur->len; + continue; + } + + error_at(p, "invalid token"); + } + + cur = cur->next = new_token(TK_EOF, p, p); + add_line_numbers(head.next); + return head.next; +} + +// Returns the contents of a given file. +static char *read_file(char *path) { + FILE *fp; + + if (strcmp(path, "-") == 0) { + // By convention, read from stdin if a given filename is "-". + fp = stdin; + } else { + fp = fopen(path, "r"); + if (!fp) + return NULL; + } + + char *buf; + size_t buflen; + FILE *out = open_memstream(&buf, &buflen); + + // Read the entire file. + for (;;) { + char buf2[4096]; + int n = fread(buf2, 1, sizeof(buf2), fp); + if (n == 0) + break; + fwrite(buf2, 1, n, out); + } + + if (fp != stdin) + fclose(fp); + + // Make sure that the last line is properly terminated with '\n'. + fflush(out); + if (buflen == 0 || buf[buflen - 1] != '\n') + fputc('\n', out); + fputc('\0', out); + fclose(out); + 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) { + 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); +} @@ -0,0 +1,321 @@ +#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; + ty->size = size; + ty->align = align; + return ty; +} + +bool is_integer(Type *ty) { + TypeKind k = ty->kind; + 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) { + // The C spec disallows sizeof(<function type>), 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; +} + +Type *array_of(Type *base, int len) { + Type *ty = new_type(TY_ARRAY, base->size * len, base->align); + ty->base = base; + ty->array_len = 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); + if (ty == (*lhs)->ty && ty == (*rhs)->ty) + return; + if (ty->base && is_numeric((*rhs)->ty)) { + if ((*rhs)->ty != ty_ulong) + *rhs = new_cast(*rhs, ty_ulong); + return; + } + *lhs = new_cast(*lhs, ty); + *rhs = new_cast(*rhs, ty); +} + +void add_type(Node *node) { + if (node && node->kind == ND_VAR && node->ty && node->ty->base && node->ty->kind == TY_ARRAY) + node->ty = pointer_to(node->ty->base); + + if (!node || node->ty) + return; + + add_type(node->lhs); + add_type(node->rhs); + add_type(node->cond); + add_type(node->then); + add_type(node->els); + add_type(node->init); + add_type(node->inc); + + for (Node *n = node->body; n; n = n->next) + add_type(n); + for (Node *n = node->args; n; n = n->next) + 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_MOD: + case ND_BITAND: + case ND_BITOR: + case ND_BITXOR: + usual_arith_conv(&node->lhs, &node->rhs); + if (node->lhs->ty->kind != TY_ARRAY) + node->ty = node->lhs->ty; + else + node->ty = pointer_to(node->lhs->ty->base); + 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: + usual_arith_conv(&node->lhs, &node->rhs); + node->ty = ty_int; + return; + case ND_FUNCALL: + 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: { + Type *ty = node->lhs->ty; + if (ty->kind == TY_ARRAY) + node->ty = pointer_to(ty->base); + else + 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: + if (node->body) { + Node *stmt = node->body; + while (stmt->next) + stmt = stmt->next; + if (stmt->kind == ND_EXPR_STMT) { + node->ty = stmt->lhs->ty; + return; + } + } + /* MASOT error_tok(node->tok, "statement expression returning void is not supported"); */ + node->ty = ty_void; + 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/type_helpers.c b/type_helpers.c new file mode 100644 index 0000000..0f6b022 --- /dev/null +++ b/type_helpers.c @@ -0,0 +1,78 @@ +#include <stddef.h> +#include "chibicc.h" + +unsigned long hash_type(Type *type) { + unsigned long hash = 6997; + if (!type) return hash; + if (type->hashing) return hash; + type->hashing = 1; +#define EXTEND_HASH(v) hash = (hash * 33) ^ (size_t)(v) + EXTEND_HASH(type->kind); + EXTEND_HASH(type->size); + EXTEND_HASH(type->align); + EXTEND_HASH(type->is_unsigned); + EXTEND_HASH(type->is_atomic); + // EXTEND_HASH(hash_type(type->origin)); + + EXTEND_HASH(hash_type(type->base)); + EXTEND_HASH(type->array_len); + EXTEND_HASH(type->vla_len); + EXTEND_HASH(type->vla_size); + EXTEND_HASH(type->members); + + // TODO: assume the members pointers are actually same? + // for (Member *m = type->members; m; m = m->next) { + // EXTEND_HASH(hash_type(m->ty)); + // EXTEND_HASH(m->name); // token + // EXTEND_HASH(m->idx); + // EXTEND_HASH(m->align); + // EXTEND_HASH(m->offset); + // EXTEND_HASH(m->is_bitfield); + // EXTEND_HASH(m->bit_offset); + // EXTEND_HASH(m->bit_width); + // } + EXTEND_HASH(type->is_flexible); + EXTEND_HASH(type->is_packed); + + EXTEND_HASH(hash_type(type->return_ty)); + EXTEND_HASH(hash_type(type->params)); + EXTEND_HASH(type->is_variadic); + // EXTEND_HASH(type->next); + + type->hashing = 0; + return hash; +} + +int definitely_same_type(Type *type1, Type *type2) { + if (!type1 || !type2) return type1 == type2; + if (type1->hashing != type2->hashing) return 0; + if (type1->hashing) return type1 == type2; + type1->hashing = 1; + type2->hashing = 1; +#define FIELD_CHECK(field) if (type1->field != type2->field) goto disequal +#define RECURSE_CHECK(field) if (!definitely_same_type(type1->field, type2->field)) goto disequal + FIELD_CHECK(kind); + FIELD_CHECK(size); + FIELD_CHECK(align); + FIELD_CHECK(is_unsigned); + FIELD_CHECK(is_atomic); + // RECURSE_CHECK(origin); + RECURSE_CHECK(base); + FIELD_CHECK(array_len); + FIELD_CHECK(vla_len); + FIELD_CHECK(vla_size); + FIELD_CHECK(members); + FIELD_CHECK(is_flexible); + FIELD_CHECK(is_packed); + RECURSE_CHECK(return_ty); + FIELD_CHECK(params); + FIELD_CHECK(is_variadic); + // RECURSE_CHECK(next); + type1->hashing = 0; + type2->hashing = 0; + return 1; +disequal: + type1->hashing = 0; + type2->hashing = 0; + return 0; +} diff --git a/typegen.c b/typegen.c new file mode 100644 index 0000000..75b3cff --- /dev/null +++ b/typegen.c @@ -0,0 +1,304 @@ +#include "chibicc.h" + +static Type **HASH_MAP = 0; +static int CAP_HASH_MAP = 0; +static int N_HASH_MAP = 0; +Type *hash_lookup(Type *type) { + if (!CAP_HASH_MAP) return 0; + + size_t idx = hash_type(type) % CAP_HASH_MAP; + while (HASH_MAP[idx]) { + if (definitely_same_type(HASH_MAP[idx], type)) { + return HASH_MAP[idx]; + } + if (++idx == CAP_HASH_MAP) idx = 0; + } + return 0; +} + +void hash_insert(Type *type) { + if ((N_HASH_MAP + 1) >= (CAP_HASH_MAP / 2)) { + // RESIZE AND REHASH + int old_cap = CAP_HASH_MAP; + Type **old_hash_map = HASH_MAP; + + CAP_HASH_MAP = (CAP_HASH_MAP + 1) * 4; + N_HASH_MAP = 0; + HASH_MAP = calloc(CAP_HASH_MAP, sizeof(HASH_MAP[0])); + for (int i = 0; i < old_cap; i++) { + if (old_hash_map[i]) + hash_insert(old_hash_map[i]); + } + if (old_hash_map) free(old_hash_map); + } + + N_HASH_MAP++; + size_t idx = hash_type(type) % CAP_HASH_MAP; + while (HASH_MAP[idx]) { + if (++idx == CAP_HASH_MAP) idx = 0; + } + HASH_MAP[idx] = type; +} + +static int count(void) { + static int i = 1; + return i++; +} + +static void printnoln(char *fmt, ...); +static void println(char *fmt, ...); + +static void print_tok(Token *tok) { + if (tok->str) printnoln("%s", tok->str); + else { + assert(tok->loc); + for (int i = 0; i < tok->len; i++) + printnoln("%c", tok->loc[i]); + } +} + +const void addrdecl(Type *type); +const void typedecl(Type *type) { + if (type->id) return; + if (type->kind == TY_ARRAY) + addrdecl(type->base); + Type *hashed = hash_lookup(type); + if (hashed && !(hashed->typedecling)) { + assert(hashed->id); + type->id = hashed->id; + if (hashed->pointer_type) + type->pointer_type = hashed->pointer_type; + if (hashed->return_ty) + type->return_ty = hashed->return_ty; + if (hashed->params) + type->params = hashed->params; + return; + } + type->typedecling = 1; + type->id = count(); + hash_insert(type); + switch (type->kind) { + case TY_FLOAT: + println("typedef float Type_%d ;", type->id); + break; + case TY_DOUBLE: + println("typedef double Type_%d ;", type->id); + break; + case TY_LDOUBLE: + println("typedef long double Type_%d ;", type->id); + break; + case TY_INT: + if (type->is_unsigned) + println("typedef unsigned int Type_%d ;", type->id); + else + println("typedef int Type_%d ;", type->id); + break; + case TY_LONG: + if (type->is_unsigned) + println("typedef unsigned long Type_%d ;", type->id); + else + println("typedef long Type_%d ;", type->id); + break; + case TY_SHORT: + if (type->is_unsigned) + println("typedef unsigned short Type_%d ;", type->id); + else + println("typedef short Type_%d ;", type->id); + break; + case TY_VOID: + println("typedef void Type_%d ;", type->id); + break; + case TY_BOOL: + println("typedef _Bool Type_%d ;", type->id); + break; + case TY_CHAR: + println("typedef char Type_%d ;", type->id); + break; + case TY_PTR: + typedecl(type->base); + println("typedef Type_%d * Type_%d ;", type->base->id, type->id); + break; + case TY_FUNC: { + typedecl(type->return_ty); + for (Type *p = type->params; p; p = p->next) + typedecl(p); + printnoln("typedef Type_%d Type_%d ( ", type->return_ty->id, type->id); + + int i = 0; + for (Type *p = type->params; p; p = p->next, i++) + if (i) printnoln(", Type_%d ", p->id); + else printnoln("Type_%d ", p->id); + + if (type->is_variadic) { + if (i) printnoln(", ... "); + // else printnoln("... "); + } + println(") ;"); + addrdecl(type); + break; + } + case TY_ARRAY: + typedecl(type->base); + if (type->array_len == -1) + println("typedef Type_%d Type_%d [ ] ;", type->base->id, type->id); + else + println("typedef Type_%d Type_%d [ %d ] ;", type->base->id, type->id, + type->array_len); + break; + case TY_STRUCT: + case TY_UNION: + if (type->kind == TY_STRUCT) + println("typedef struct Struct_%d Type_%d ;", type->id, type->id); + else + println("typedef union Union_%d Type_%d ;", type->id, type->id); + + for (Member *m = type->members; m; m = m->next) + typedecl(m->ty); + + if (type->kind == TY_STRUCT) printnoln("struct Struct_%d { ", type->id); + else printnoln("union Union_%d { ", type->id); + for (Member *m = type->members; m; m = m->next) { + printnoln("Type_%d ", m->ty->id); + if (m->name) + print_tok(m->name); + else if (m->ty->kind == TY_STRUCT || m->ty->kind == TY_UNION) + printnoln("___dietc_f%d", m->idx); + else + printnoln("__field_%d", count()); + printnoln(" ; "); + } + println("} ;"); + break; + case TY_ENUM: + println("typedef enum Enum_%d { EN_%d } Type_%d ;", type->id, type->id, type->id); + break; + case TY_VLA: + assert(!"unimplemented vla?"); + break; + default: + assert(!"unimplemented?"); + break; + } + type->typedecling = 0; +} + +static FILE *output_file; +static Obj *current_fn; + +__attribute__((format(printf, 1, 2))) +static void println(char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(output_file, fmt, ap); + va_end(ap); + fprintf(output_file, "\n"); +} + +__attribute__((format(printf, 1, 2))) +static void printnoln(char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(output_file, fmt, ap); + va_end(ap); +} + +const void addrdecl(Type *type) { + if (type->pointer_type) return; + type->pointer_type = pointer_to(type); + typedecl(type->pointer_type); +} + +static void gen_addr_decls(Node *node) { + switch (node->kind) { + case ND_VAR: + addrdecl(node->ty); + return; + case ND_COMMA: + gen_addr_decls(node->rhs); + return; + case ND_MEMBER: + gen_addr_decls(node->lhs); + addrdecl(node->ty); + return; + } +} + +// Generate code for a given node. +static void gen_typedecls(Node *node) { + if (!node) return; + if (node->ty) typedecl(node->ty); + gen_typedecls(node->lhs); + gen_typedecls(node->rhs); + gen_typedecls(node->cond); + gen_typedecls(node->then); + gen_typedecls(node->els); + gen_typedecls(node->init); + gen_typedecls(node->inc); + for (Node *n = node->body; n; n = n->next) + gen_typedecls(n); + for (Node *arg = node->args; arg; arg = arg->next) + gen_typedecls(arg); + if (node->kind == ND_SWITCH) + for (Node *n = node->case_next; n; n = n->case_next) + gen_typedecls(n); + + switch (node->kind) { + case ND_VAR: + if (node->ty->kind == TY_ARRAY) + addrdecl(node->ty->base); + break; + case ND_CAST: + if (node->lhs->ty->kind == TY_UNION) + gen_addr_decls(node->lhs); + addrdecl(node->ty); + break; + case ND_MEMBER: + gen_addr_decls(node); + if (node->ty->kind == TY_ARRAY) + addrdecl(node->ty->base); + break; + case ND_ADDR: + case ND_ASSIGN: + gen_addr_decls(node->lhs); + break; + } +} + +static void typedecl_data(Obj *prog) { + for (Obj *var = prog; var; var = var->next) { + // if (var->is_function || !var->is_definition) + // continue; + typedecl(var->ty); + } +} + +static void typedecl_text(Obj *prog) { + for (Obj *fn = prog; fn; fn = fn->next) { + 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; + + current_fn = fn; + + typedecl(fn->ty); + gen_typedecls(fn->body); + + // ensure function params & locals are typegened + for (Obj *var = fn->locals; var; var = var->next) { + typedecl(var->ty); + } + } +} + +void typegen(Obj *prog, FILE *out) { + output_file = out; + + printnoln("#include \"%s/scripts/dietc_helpers.h\"\n", DIETC_ROOT); + typedecl(ty_int); + typedecl_data(prog); + typedecl_text(prog); +} diff --git a/unicode.c b/unicode.c new file mode 100644 index 0000000..2eeeeed --- /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; +} |