/* * TCC - Tiny C Compiler * * Copyright (c) 2001-2004 Fabrice Bellard * * This 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 of the License, or (at your option) any later version. * * This 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 this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include "cc/cc.h" /********************************************************/ /* global variables */ /* XXX: get rid of this ASAP (or maybe not) */ struct TCCState *tcc_state; /* an array of pointers to memory to be free'd after errors */ void** stk_data; int nb_stk_data; /********************************************************/ PUB_FUNC void tcc_enter_state(TCCState *s1) { if (s1->error_set_jmp_enabled) return; tcc_state = s1; } PUB_FUNC void tcc_exit_state(TCCState *s1) { if (s1->error_set_jmp_enabled) return; tcc_state = NULL; } char * tcc_load_text(int fd) { int len; char *buf; len = lseek(fd, 0, SEEK_END); buf = load_data(fd, 0, len + 1); /** XXX: rename func */ if (buf) { buf[len] = 0; } return (buf); } #ifdef _WIN32 # define realpath(file, buf) _fullpath(buf, file, 260) #endif /* for #pragma once */ int normalized_PATHCMP(const char *f1, const char *f2) { char *p1, *p2; int ret = 1; if (!!(p1 = realpath(f1, NULL))) { if (!!(p2 = realpath(f2, NULL))) { ret = PATHCMP(p1, p2); free(p2); /* realpath() requirement */ } free(p1); } return ret; } /********************************************************/ /* dynarrays */ void dynarray_add(void *ptab, int *nb_ptr, void *data) { int nb, nb_alloc; void **pp; nb = *nb_ptr; pp = *(void ***)ptab; /* every power of two we double array size */ if ((nb & (nb - 1)) == 0) { if (!nb) nb_alloc = 1; else nb_alloc = nb * 2; pp = tcc_realloc(pp, nb_alloc * sizeof(void *)); *(void***)ptab = pp; } pp[nb++] = data; *nb_ptr = nb; } void dynarray_reset(void *pp, int *n) { void **p; for (p = *(void***)pp; *n; ++p, --*n) if (*p) tcc_free(*p); tcc_free(*(void**)pp); *(void**)pp = NULL; } static void tcc_split_path(TCCState *s, void *p_ary, int *p_nb_ary, const char *in) { const char *p; do { int c; CString str; cstr_new(&str); for (p = in; c = *p, c != '\0' && c != PATHSEP[0]; ++p) { if (c == '{' && p[1] && p[2] == '}') { c = p[1], p += 2; if (c == 'B') cstr_cat(&str, s->tcc_lib_path, -1); if (c == 'R') cstr_cat(&str, CONFIG_SYSROOT, -1); if (c == 'f' && file) { /* substitute current file's dir */ const char *f = file->true_filename; const char *b = tcc_basename(f); if (b > f) cstr_cat(&str, f, b - f - 1); else cstr_cat(&str, ".", 1); } } else { cstr_ccat(&str, c); } } if (str.size) { cstr_ccat(&str, '\0'); dynarray_add(p_ary, p_nb_ary, tcc_strdup(str.data)); } cstr_free(&str); in = p+1; } while (*p); } /********************************************************/ /* warning / error */ /* warn_... option bits */ #define WARN_ON 1 /* warning is on (-Woption) */ #define WARN_ERR 2 /* warning is an error (-Werror=option) */ #define WARN_NOE 4 /* warning is not an error (-Wno-error=option) */ /* error1() modes */ enum { ERROR_WARN, ERROR_NOABORT, ERROR_ERROR }; static void error1(int mode, const char *fmt, va_list ap) { BufferedFile **pf, *f; TCCState *s1 = tcc_state; CString cs; int line = 0; tcc_exit_state(s1); if (mode == ERROR_WARN) { if (s1->warn_error) mode = ERROR_ERROR; if (s1->warn_num) { /* handle tcc_warning_c(warn_option)(fmt, ...) */ int wopt = *(&s1->warn_none + s1->warn_num); s1->warn_num = 0; if (0 == (wopt & WARN_ON)) return; if (wopt & WARN_ERR) mode = ERROR_ERROR; if (wopt & WARN_NOE) mode = ERROR_WARN; } if (s1->warn_none) return; } cstr_new(&cs); if (fmt[0] == '%' && fmt[1] == 'i' && fmt[2] == ':') line = va_arg(ap, int), fmt += 3; f = NULL; if (s1->error_set_jmp_enabled) { /* we're called while parsing a file */ /* use upper file if inline ":asm:" or token ":paste:" */ for (f = file; f && f->filename[0] == ':'; f = f->prev) ; } if (f) { for(pf = s1->include_stack; pf < s1->include_stack_ptr; pf++) cstr_printf(&cs, "In file included from %s:%d:\n", (*pf)->filename, (*pf)->line_num - 1); if (0 == line) line = f->line_num - ((tok_flags & TOK_FLAG_BOL) && !macro_ptr); cstr_printf(&cs, "%s:%d: ", f->filename, line); } else if (s1->current_filename) { cstr_printf(&cs, "%s: ", s1->current_filename); } else { cstr_printf(&cs, "tcc: "); } cstr_printf(&cs, mode == ERROR_WARN ? "warning: " : "error: "); if (pp_expr > 1) pp_error(&cs); /* special handler for preprocessor expression errors */ else cstr_vprintf(&cs, fmt, ap); if (!s1->error_func) { /* default case: stderr */ if (s1 && s1->output_type == TCC_OUTPUT_PREPROCESS && s1->ppfp == stdout) printf("\n"); /* print a newline during tcc -E */ fflush(stdout); /* flush -v output */ fprintf(stderr, "%s\n", (char*)cs.data); fflush(stderr); /* print error/warning now (win32) */ } else { s1->error_func(s1->error_opaque, (char*)cs.data); } cstr_free(&cs); if (mode != ERROR_WARN) s1->nb_errors++; if (mode == ERROR_ERROR && s1->error_set_jmp_enabled) { while (nb_stk_data) tcc_free(*(void**)stk_data[--nb_stk_data]); longjmp(s1->error_jmp_buf, 1); } } void tcc_set_error_func(TCCState *s, void *error_opaque, TCCErrorFunc *error_func) { s->error_opaque = error_opaque; s->error_func = error_func; } /* error without aborting current compilation */ PUB_FUNC int _tcc_error_noabort(const char *fmt, ...) { va_list ap; va_start(ap, fmt); error1(ERROR_NOABORT, fmt, ap); va_end(ap); return -1; } #undef _tcc_error PUB_FUNC void _tcc_error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); error1(ERROR_ERROR, fmt, ap); exit(1); } #define _tcc_error use_tcc_error_noabort PUB_FUNC void _tcc_warning(const char *fmt, ...) { va_list ap; va_start(ap, fmt); error1(ERROR_WARN, fmt, ap); va_end(ap); } /********************************************************/ /* I/O layer */ static int _tcc_open(TCCState *s1, const char *filename) { int fd; if (strcmp(filename, "-") == 0) fd = 0, filename = ""; else fd = open(filename, O_RDONLY | O_BINARY); if ((s1->verbose == 2 && fd >= 0) || s1->verbose == 3) printf("%s %*s%s\n", fd < 0 ? "nf":"->", (int)(s1->include_stack_ptr - s1->include_stack), "", filename); return fd; } int tcc_open(TCCState *s1, const char *filename) { int fd = _tcc_open(s1, filename); if (fd < 0) return -1; tcc_open_bf(s1, filename, 0); file->fd = fd; return 0; } /* compile the file opened in 'file'. Return non zero if errors. */ static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd) { /* Here we enter the code section where we use the global variables for parsing and code generation (tccpp.c, tccgen.c, -gen.c). Other threads need to wait until we're done. Alternatively we could use thread local storage for those global variables, which may or may not have advantages */ int len; tcc_enter_state(s1); s1->error_set_jmp_enabled = 1; if (setjmp(s1->error_jmp_buf) == 0) { s1->nb_errors = 0; if (fd == -1) { len = strlen(str); tcc_open_bf(s1, "", len); memcpy(file->buffer, str, len); } else { tcc_open_bf(s1, str, 0); file->fd = fd; } preprocess_start(s1, filetype); tccgen_init(s1); if (s1->output_type == TCC_OUTPUT_PREPROCESS) { tcc_preprocess(s1); } else { coff_begin_file(s1); if (filetype & (AFF_TYPE_ASM | AFF_TYPE_ASMPP)) { tcc_assemble(s1, !!(filetype & AFF_TYPE_ASMPP)); } else { tccgen_compile(s1); } coff_end_file(s1); } } tccgen_finish(s1); preprocess_end(s1); s1->error_set_jmp_enabled = 0; tcc_exit_state(s1); return (s1->nb_errors != 0 ? -1 : 0); } /* define a preprocessor symbol. value can be NULL, sym can be "sym=val" */ void tcc_define_symbol(TCCState *s1, const char *sym, const char *value) { const char *eq; if (NULL == (eq = strchr(sym, '='))) eq = strchr(sym, 0); if (NULL == value) value = *eq ? eq + 1 : "1"; cstr_printf(&s1->cmdline_defs, "#define %.*s %s\n", (int)(eq-sym), sym, value); } /* undefine a preprocessor symbol */ void tcc_undefine_symbol(TCCState *s1, const char *sym) { cstr_printf(&s1->cmdline_defs, "#undef %s\n", sym); } TCCState *tcc_new(void) { TCCState *s; s = tcc_mallocz(sizeof(TCCState)); #ifdef TCC_MEMORY_DEBUG tcc_memcheck(1); #endif #undef gnu_ext s->gnu_ext = 1; s->tcc_ext = 1; s->dollars_in_identifiers = 1; /*on by default like in gcc/clang*/ s->cversion = 199901; /* default unless -std=c11 is supplied */ s->warn_implicit_function_declaration = 1; s->warn_discarded_qualifiers = 1; s->ms_extensions = 1; #ifdef __CHAR_UNSIGNED__ s->char_is_unsigned = 1; #endif s->ppfp = stdout; /* might be used in error() before preprocess_start() */ s->include_stack_ptr = s->include_stack; tcc_set_lib_path(s, CONFIG_TCCDIR); return s; } void tcc_delete(TCCState *s1) { /* free sections */ coff_delete(s1); /* free library paths */ dynarray_reset(&s1->library_paths, &s1->nb_library_paths); dynarray_reset(&s1->crt_paths, &s1->nb_crt_paths); /* free include paths */ dynarray_reset(&s1->include_paths, &s1->nb_include_paths); dynarray_reset(&s1->sysinclude_paths, &s1->nb_sysinclude_paths); tcc_free(s1->tcc_lib_path);; tcc_free(s1->entryname); tcc_free(s1->outfile); tcc_free(s1->deps_outfile); dynarray_reset(&s1->files, &s1->nb_files); dynarray_reset(&s1->target_deps, &s1->nb_target_deps); dynarray_reset(&s1->pragma_libs, &s1->nb_pragma_libs); dynarray_reset(&s1->argv, &s1->argc); cstr_free(&s1->cmdline_defs); cstr_free(&s1->cmdline_incl); cstr_free(&s1->linker_arg); tcc_free(s1); #ifdef TCC_MEMORY_DEBUG tcc_memcheck(-1); #endif } int tcc_set_output_type(TCCState *s, int output_type) { s->output_type = output_type; if (!s->nostdinc) { /* default include paths */ /* -isystem paths have already been handled */ tcc_add_sysinclude_path(s, CONFIG_TCC_SYSINCLUDEPATHS); } if (output_type == TCC_OUTPUT_PREPROCESS) { return 0; } /* add sections */ coff_new(s); if (output_type == TCC_OUTPUT_OBJ) { /* always elf for objects */ s->output_format = TCC_OUTPUT_FORMAT_COFF; return 0; } tcc_add_library_path(s, CONFIG_TCC_LIBPATHS); /* paths for crt objects */ tcc_split_path(s, &s->crt_paths, &s->nb_crt_paths, CONFIG_TCC_CRTPREFIX); if (!s->nostdlib) coff_add_crtbegin(s); return 0; } int tcc_add_include_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->include_paths, &s->nb_include_paths, pathname); return 0; } int tcc_add_sysinclude_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->sysinclude_paths, &s->nb_sysinclude_paths, pathname); return 0; } static int guess_filetype(const char *filename); int tcc_add_file_internal(TCCState *s1, const char *filename, int flags) { COFFFileHeader fhdr; int obj_type; int fd, ret = -1; if (0 == (flags & AFF_TYPE_MASK)) { flags |= guess_filetype(filename); } /* ignore binary files with -E */ if (s1->output_type == TCC_OUTPUT_PREPROCESS && (flags & AFF_TYPE_BIN)) { return (0); } /* open the file */ fd = _tcc_open(s1, filename); if (fd < 0) { if (flags & AFF_PRINT_ERROR) { tcc_error_noabort("file '%s' not found", filename); } return (FILE_NOT_FOUND); } s1->current_filename = filename; if (flags & AFF_TYPE_BIN) { obj_type = tcc_object_type(fd, &fhdr); lseek(fd, 0, SEEK_SET); switch (obj_type) { case TCC_BINTYPE_COFF: ret = coff_load_file(s1, fd, 0); break; case TCC_BINTYPE_ARCHIVE: ret = archive_load(s1, fd, !(flags & AFF_WHOLE_ARCHIVE)); break; default: tcc_error_noabort("%s: unrecognized file type", filename); break; } close(fd); } else { /* update target deps */ dynarray_add(&s1->target_deps, &s1->nb_target_deps, tcc_strdup(filename)); ret = tcc_compile(s1, flags, filename, fd); } s1->current_filename = NULL; return (ret); } static int guess_filetype(const char *filename) { int filetype = 0; if (1) { /* use a file extension to detect a filetype */ const char *ext = tcc_fileextension(filename); if (ext[0]) { ext++; if (!strcmp(ext, "S")) filetype = AFF_TYPE_ASMPP; else if (!strcmp(ext, "s")) filetype = AFF_TYPE_ASM; else if (!PATHCMP(ext, "c") || !PATHCMP(ext, "h") || !PATHCMP(ext, "i")) filetype = AFF_TYPE_C; else filetype |= AFF_TYPE_BIN; } else { filetype = AFF_TYPE_C; } } return filetype; } int tcc_add_file(TCCState *s, const char *filename) { return tcc_add_file_internal(s, filename, s->filetype | AFF_PRINT_ERROR); } int tcc_add_library_path(TCCState *s, const char *pathname) { tcc_split_path(s, &s->library_paths, &s->nb_library_paths, pathname); return 0; } static int tcc_add_library_internal(TCCState *s1, const char *fmt, const char *filename, int flags, char **paths, int nb_paths) { char buf[1024]; int i, ret; for(i = 0; i < nb_paths; i++) { snprintf(buf, sizeof(buf), fmt, paths[i], filename); ret = tcc_add_file_internal(s1, buf, flags & ~AFF_PRINT_ERROR); if (ret != FILE_NOT_FOUND) return ret; } if (flags & AFF_PRINT_ERROR) tcc_error_noabort("library '%s' not found", filename); return FILE_NOT_FOUND; } /* find and load a dll. Return non zero if not found */ int tcc_add_dll(TCCState *s, const char *filename, int flags) { return tcc_add_library_internal(s, "%s/%s", filename, flags, s->library_paths, s->nb_library_paths); } /* find [cross-]libtcc1.a and tcc helper objects in library path */ int tcc_add_support(TCCState *s1, const char *filename) { char buf[100]; if (CONFIG_TCC_CROSSPREFIX[0]) filename = strcat(strcpy(buf, CONFIG_TCC_CROSSPREFIX), filename); return tcc_add_dll(s1, filename, AFF_PRINT_ERROR); } int tcc_add_crt(TCCState *s1, const char *filename) { return tcc_add_library_internal(s1, "%s/%s", filename, AFF_PRINT_ERROR, s1->crt_paths, s1->nb_crt_paths); } /* the library name is the same as the argument of the '-l' option */ int tcc_add_library(TCCState *s, const char *libraryname) { static const char * const libs[] = { "%s/lib%s.a", NULL }; const char * const *pp = libs; int flags = s->filetype & AFF_WHOLE_ARCHIVE; while (*pp) { int ret = tcc_add_library_internal(s, *pp, libraryname, flags, s->library_paths, s->nb_library_paths); if (ret != FILE_NOT_FOUND) return ret; ++pp; } return tcc_add_dll(s, libraryname, AFF_PRINT_ERROR); } /* handle #pragma comment(lib,) */ void tcc_add_pragma_libs(TCCState *s1) { int i; for (i = 0; i < s1->nb_pragma_libs; i++) tcc_add_library(s1, s1->pragma_libs[i]); } void tcc_set_lib_path(TCCState *s, const char *path) { tcc_free(s->tcc_lib_path); s->tcc_lib_path = tcc_strdup(path); } /********************************************************/ /* options parser */ #ifdef TCC_MEMORY_DEBUG extern size_t mem_max_size; #endif /* TCC_MEMORY_DEBUG */ PUB_FUNC void tcc_print_stats(TCCState *s1, unsigned total_time) { if (!total_time) total_time = 1; fprintf(stderr, "# %d idents, %d lines, %u bytes\n" "# %0.3f s, %u lines/s, %0.1f MB/s\n", total_idents, total_lines, total_bytes, (double)total_time/1000, (unsigned)total_lines*1000/total_time, (double)total_bytes/1000/total_time); fprintf(stderr, "# text %u, data.rw %u, data.ro %u, bss %u bytes\n", s1->total_output[0], s1->total_output[1], s1->total_output[2], s1->total_output[3] ); #ifdef TCC_MEMORY_DEBUG fprintf(stderr, "# memory usage"); fprintf(stderr, " %d max (bytes)\n", mem_max_size); #endif }