diff --git a/.gitignore b/.gitignore index 6870b8d308..918ca6244a 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,8 @@ tests/vla_test tests/hello tests/tests2/fred.txt libtcc.dylib + +em_include +*.data +*.wasm +*.mjs diff --git a/Makefile b/Makefile index d21f1cada5..fbddded8e8 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,9 @@ TCCDOCS = tcc.1 tcc-doc.html tcc-doc.info all: $(PROGS) $(TCCLIBS) $(TCCDOCS) +syntax_check.wasm: syntax_check.c libtcc.c tccpp.c tccgen.c tcc-stubs.c + bash embuild.sh + # cross compiler targets to build TCC_X = i386 x86_64 i386-win32 x86_64-win32 x86_64-osx arm arm64 arm-wince c67 TCC_X += riscv64 arm64-osx diff --git a/build_syntax_checker.sh b/build_syntax_checker.sh new file mode 100644 index 0000000000..afbc2caf84 --- /dev/null +++ b/build_syntax_checker.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +CC="${CC:-musl-gcc}" +# on O0 missing dead code elimination triggers linker errors +CFLAGS="-DTCC_SYNTAX_ONLY -ggdb -O2 -DONE_SOURCE=1" +LDFLAGS="-static" +OUTPUT="syntax_check_file" +LIBRARY="libtcc-syntax.a" + +$CC $CFLAGS $LDFLAGS -o $OUTPUT syntax_check_file.c tcc-stubs.c libtcc.c tccpp.c tccgen.c -I. diff --git a/embuild.sh b/embuild.sh new file mode 100644 index 0000000000..b223dfb3a9 --- /dev/null +++ b/embuild.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +CC="${CC:-emcc}" +CFLAGS="-DTCC_SYNTAX_ONLY -DTCC_TARGET_X86_64 -DPTR_SIZE=8 -DLONG_SIZE=8 -O3 -DONE_SOURCE=1" +LDFLAGS="-sINITIAL_MEMORY=33554432 -sEXPORTED_RUNTIME_METHODS=ccall,UTF8ToString,stackAlloc -sEXPORTED_FUNCTIONS=_check_syntax,_get_type_info_buffer,_get_type_info_length,_get_debug_calls_buffer,_get_debug_calls_length,_get_error_count,_get_error,_get_error_filename,_get_error_line_num,_get_error_is_warning,_get_error_msg -sENVIRONMENT=worker -sEXIT_RUNTIME=0 -sSTRICT=1" +OUTPUT="syntax_check.mjs" +WARNS="-Wno-string-plus-int -Wno-undefined-internal" + +rm -f $OUTPUT syntax_check.wasm + +mv em_include/task.h.pch . +$CC -sEXPORT_NAME=TCC -sMODULARIZE=1 -sEVAL_CTORS -sWASM_BIGINT -flto $WARNS $CFLAGS $LDFLAGS -o $OUTPUT syntax_check.c libtcc.c tccpp.c tccgen.c tcc-stubs.c -I. --preload-file em_include@/ +mv task.h.pch em_include/ diff --git a/libtcc.c b/libtcc.c index 171e36226d..620ec3a387 100644 --- a/libtcc.c +++ b/libtcc.c @@ -235,6 +235,40 @@ static void tcc_concat_str(char **pp, const char *str, int sep) #undef free #undef realloc +static struct { + char *base; + size_t offset; + size_t size; +} tcc_arena; + +PUB_FUNC void tcc_arena_init(unsigned int size_bytes) +{ + if (size_bytes == 0) + return; /* no arena requested, use standard allocator */ + if (tcc_arena.base) { + if (size_bytes <= tcc_arena.size) + return; + free(tcc_arena.base); + } + tcc_arena.base = malloc(size_bytes); + if (!tcc_arena.base) { + fprintf(stderr, "fatal: could not allocate arena\n"); + exit(1); + } + tcc_arena.offset = 0; + tcc_arena.size = size_bytes; +} + +PUB_FUNC void tcc_arena_free(void) +{ + tcc_arena.offset = 0; +} + +PUB_FUNC size_t tcc_arena_watermark(void) +{ + return tcc_arena.offset; +} + static void *default_reallocator(void *ptr, unsigned long size) { void *ptr1; @@ -278,16 +312,61 @@ LIBTCCAPI void tcc_set_realloc(TCCReallocFunc *realloc) PUB_FUNC void tcc_free(void *ptr) { + if (tcc_arena.base) + return; /* arena mode: no individual frees */ reallocator(ptr, 0); } PUB_FUNC void *tcc_malloc(unsigned long size) { + if (tcc_arena.base) { + void *ptr; + size_t aligned_size, total_size; + size_t *size_ptr; + + total_size = sizeof(size_t) + size; + aligned_size = (total_size + 15) & ~15; + + if (tcc_arena.offset + aligned_size > tcc_arena.size) { + fprintf(stderr, "fatal: arena exhausted (requested %lu bytes, %zu bytes available)\n", + size, tcc_arena.size - tcc_arena.offset); + exit(1); + } + + size_ptr = (size_t *)(tcc_arena.base + tcc_arena.offset); + *size_ptr = size; + ptr = (void *)(size_ptr + 1); + tcc_arena.offset += aligned_size; + + return ptr; + } return reallocator(0, size); } PUB_FUNC void *tcc_realloc(void *ptr, unsigned long size) { + if (tcc_arena.base) { + void *new_ptr; + size_t old_size, copy_size; + size_t *size_ptr; + + if (!ptr) + return tcc_malloc(size); + + if (size == 0) { + tcc_free(ptr); + return NULL; + } + + size_ptr = ((size_t *)ptr) - 1; + old_size = *size_ptr; + + new_ptr = tcc_malloc(size); + copy_size = old_size < size ? old_size : size; + memcpy(new_ptr, ptr, copy_size); + + return new_ptr; + } return reallocator(ptr, size); } @@ -623,6 +702,7 @@ static void error1(int mode, const char *fmt, va_list ap) BufferedFile **pf, *f; TCCState *s1 = tcc_state; CString cs; + TCCErrorInfo error_info; int line = 0; tcc_exit_state(s1); @@ -654,32 +734,53 @@ static void error1(int mode, const char *fmt, va_list ap) for (f = file; f && f->filename[0] == ':'; f = f->prev) ; } + + /* Build structured error info */ 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); + error_info.filename = f->filename; + error_info.line_num = line; } else if (s1->current_filename) { - cstr_printf(&cs, "%s: ", s1->current_filename); + error_info.filename = s1->current_filename; + error_info.line_num = 0; } else { - cstr_printf(&cs, "tcc: "); + error_info.filename = NULL; + error_info.line_num = 0; } - cstr_printf(&cs, mode == ERROR_WARN ? "warning: " : "error: "); + error_info.is_warning = (mode == ERROR_WARN); + if (pp_expr > 1) pp_error(&cs); /* special handler for preprocessor expression errors */ else cstr_vprintf(&cs, fmt, ap); + + error_info.msg = (char*)cs.data; + 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); + + for(pf = s1->include_stack; pf < s1->include_stack_ptr; pf++) + fprintf(stderr, "In file included from %s:%d:\n", + (*pf)->filename, (*pf)->line_num - 1); + + if (error_info.filename) { + if (error_info.line_num) + fprintf(stderr, "%s:%d: ", error_info.filename, error_info.line_num); + else + fprintf(stderr, "%s: ", error_info.filename); + } else { + fprintf(stderr, "tcc: "); + } + + fprintf(stderr, "%s: %s\n", mode == ERROR_WARN ? "warning" : "error", + (char*)cs.data); fflush(stderr); /* print error/warning now (win32) */ } else { - s1->error_func(s1->error_opaque, (char*)cs.data); + s1->error_func(s1->error_opaque, &error_info); } cstr_free(&cs); if (mode != ERROR_WARN) @@ -790,6 +891,15 @@ ST_FUNC int tcc_open(TCCState *s1, const char *filename) return 0; } +/* Use the public TCCBufWriter type internally */ +typedef TCCBufWriter BufWriter; + +static void buf_puts(BufWriter *w, const char *s); +static void buf_putc(BufWriter *w, char c); +static void buf_printf(BufWriter *w, const char *fmt, ...); +static void json_write_struct(BufWriter *w, TCCState *s1, Sym *s, const char *name, int *first); +static void json_write_debug_calls(BufWriter *w, TCCState *s1); + /* compile the file opened in 'file'. Return non zero if errors. */ static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd, const char *filename) { @@ -846,7 +956,59 @@ static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd, cons LIBTCCAPI int tcc_compile_string(TCCState *s, const char *str) { - return tcc_compile(s, s->filetype, str, -1, NULL); + return tcc_compile_string_ex(s, str, NULL); +} + +LIBTCCAPI int tcc_compile_string_ex(TCCState *s, const char *str, BufWriter *w) +{ + int ret; + + ret = tcc_compile(s, s->filetype, str, -1, NULL); + + /* generate JSON export of type definitions if requested */ + if (w && w->buf && w->size > 0) { + buf_puts(w, "[\n"); + { + int first = 1; + int i; + for (i = TOK_IDENT; i < tok_ident; i++) { + TokenSym *ts = table_ident[i - TOK_IDENT]; + if (ts && ts->sym_struct) { + json_write_struct(w, s, ts->sym_struct, ts->str, &first); + } + if (w->full) break; + } + } + if (w->full) { + w->buf[0] = '\0'; + } else { + buf_puts(w, "\n]\n"); + /* Null-terminate the buffer */ + if (w->pos < w->size) { + w->buf[w->pos] = '\0'; + } + } + } + + return ret; +} + +LIBTCCAPI int tcc_get_debug_calls(TCCState *s, TCCBufWriter *w) +{ + if (!s || !w) return -1; + + w->pos = 0; + w->full = 0; + + if (w->buf && w->size > 0) { + json_write_debug_calls((BufWriter *)w, s); + + if (!w->full && w->pos < w->size) { + w->buf[w->pos] = '\0'; + } + } + + return w->full ? -1 : 0; } LIBTCCAPI int tcc_compile_string_file(TCCState *s, const char *str, const char *filename) @@ -872,10 +1034,11 @@ LIBTCCAPI void tcc_undefine_symbol(TCCState *s1, const char *sym) } -LIBTCCAPI TCCState *tcc_new(void) +LIBTCCAPI TCCState *tcc_new(unsigned int arena_size_bytes) { TCCState *s; + tcc_arena_init(arena_size_bytes); s = tcc_mallocz(sizeof(TCCState)); #ifdef MEM_DEBUG tcc_memcheck(1); @@ -892,6 +1055,10 @@ LIBTCCAPI TCCState *tcc_new(void) s->ms_extensions = 1; s->unwind_tables = 1; + s->debug_calls = NULL; + s->nb_debug_calls = 0; + s->debug_calls_capacity = 0; + #ifdef CHAR_IS_UNSIGNED s->char_is_unsigned = 1; #endif @@ -959,10 +1126,30 @@ LIBTCCAPI void tcc_delete(TCCState *s1) #endif /* free loaded dlls array */ dynarray_reset(&s1->loaded_dlls, &s1->nb_loaded_dlls); + + /* Free debug call records */ + if (s1->debug_calls) { + int i; + for (i = 0; i < s1->nb_debug_calls; i++) { + DebugCallRecord *rec = &s1->debug_calls[i]; + + switch (rec->func_type) { + case DEBUG_FUNC_STRUCT: + if (rec->args.debug_struct.label) + tcc_free((void *)rec->args.debug_struct.label); + if (rec->args.debug_struct.struct_name) + tcc_free((void *)rec->args.debug_struct.struct_name); + break; + } + } + tcc_free(s1->debug_calls); + } + tcc_free(s1); #ifdef MEM_DEBUG tcc_memcheck(-1); #endif + tcc_arena_free(); } LIBTCCAPI int tcc_set_output_type(TCCState *s, int output_type) @@ -1088,11 +1275,7 @@ static int tcc_add_binary(TCCState *s1, int flags, const char *filename, int fd) case AFF_BINTYPE_DYN: if (s1->output_type == TCC_OUTPUT_MEMORY) { #ifdef TCC_IS_NATIVE - void* dl = dlopen(filename, RTLD_GLOBAL | RTLD_LAZY); - if (dl) - tcc_add_dllref(s1, filename, 0)->handle = dl; - else - ret = FILE_NOT_RECOGNIZED; + fprintf(stderr, "tried to dlopen %s\n", filename); #endif } else ret = tcc_load_dll(s1, fd, filename, (flags & AFF_REFERENCED_DLL) != 0); @@ -1720,6 +1903,7 @@ static const FlagDef options_f[] = { { offsetof(TCCState, leading_underscore), 0, "leading-underscore" }, { offsetof(TCCState, ms_extensions), 0, "ms-extensions" }, { offsetof(TCCState, dollars_in_identifiers), 0, "dollars-in-identifiers" }, + { offsetof(TCCState, syntax_only), 0, "syntax-only" }, { offsetof(TCCState, test_coverage), 0, "test-coverage" }, { offsetof(TCCState, reverse_funcargs), 0, "reverse-funcargs" }, { offsetof(TCCState, gnu89_inline), 0, "gnu89-inline" }, @@ -2240,6 +2424,388 @@ LIBTCCAPI int tcc_set_options(TCCState *s, const char *r) return ret; } +/********************************************************/ +/* JSON Export - Direct Buffer Writing */ + +static void buf_puts(BufWriter *w, const char *s) +{ + if (w->full) return; /* Already full, skip */ + int len = strlen(s); + if (w->pos + len < w->size) { + memcpy(w->buf + w->pos, s, len); + w->pos += len; + } else { + w->full = 1; + } +} + +static void buf_putc(BufWriter *w, char c) +{ + if (w->full) return; /* Already full, skip */ + if (w->pos < w->size) { + w->buf[w->pos++] = c; + } else { + w->full = 1; + } +} + +static void buf_printf(BufWriter *w, const char *fmt, ...) +{ + if (w->full) return; /* Already full, skip */ + if (w->pos >= w->size) { + w->full = 1; + return; + } + va_list args; + va_start(args, fmt); + int written = vsnprintf(w->buf + w->pos, w->size - w->pos, fmt, args); + va_end(args); + if (written > 0 && w->pos + written < w->size) { + w->pos += written; + } else { + w->full = 1; + } +} + +static void json_write_base_type_name(BufWriter *w, TCCState *s1, CType *type, int omit_struct_union_keyword, int omit_unsigned) +{ + int bt = type->t & VT_BTYPE; + int is_unsigned = type->t & VT_UNSIGNED; + int is_long = type->t & VT_LONG; + + /* Check if this is a typedef - if ref has a valid identifier name that's not a struct */ + if (type->ref && type->ref->v >= TOK_IDENT && !(type->ref->v & SYM_STRUCT) && bt != VT_STRUCT) { + const char *name = get_tok_str(type->ref->v, NULL); + /* Only use it if it doesn't look like a generated label (starts with 'L.') */ + if (name && !(name[0] == 'L' && name[1] == '.')) { + buf_puts(w, name); + return; + } + } + + if (bt == VT_STRUCT) { + if (!omit_struct_union_keyword) { + if (type->t & (1 << VT_STRUCT_SHIFT)) + buf_puts(w, "union "); + else + buf_puts(w, "struct "); + } + if (type->ref && type->ref->v >= TOK_IDENT) { + const char *name = get_tok_str(type->ref->v & ~SYM_STRUCT, NULL); + /* Check if this is a generated label (anonymous type) */ + if (name && name[0] == 'L' && name[1] == '.') { + buf_puts(w, ""); + } else { + buf_puts(w, name); + } + } else { + buf_puts(w, ""); + } + return; + } + + if (bt == VT_FUNC) { + buf_puts(w, "function"); + return; + } + + if (!omit_unsigned && is_unsigned && bt != VT_BYTE) + buf_puts(w, "unsigned "); + + switch (bt) { + case VT_VOID: buf_puts(w, "void"); break; + case VT_BYTE: + if (!omit_unsigned) + buf_puts(w, is_unsigned ? "unsigned char" : "char"); + else + buf_puts(w, "char"); + break; + case VT_SHORT: buf_puts(w, "short"); break; + case VT_INT: buf_puts(w, is_long ? "long" : "int"); break; + case VT_LLONG: buf_puts(w, "long long"); break; + case VT_FLOAT: buf_puts(w, "float"); break; + case VT_DOUBLE: buf_puts(w, is_long ? "long double" : "double"); break; + case VT_BOOL: buf_puts(w, "_Bool"); break; + default: buf_puts(w, "unknown"); break; + } +} + +static void json_write_struct_members(BufWriter *w, TCCState *s1, Sym *s, int indent) +{ + Sym *m = s->next; + int first = 1; + + while (m) { + if (w->full) return; /* Stop processing if buffer is full */ + + if (!(m->v & SYM_FIELD)) { + m = m->next; + continue; + } + + if (!first) + buf_puts(w, ",\n"); + first = 0; + + { + int i; + for (i = 0; i < indent; i++) buf_puts(w, " "); + } + buf_puts(w, "{"); + + buf_puts(w, "\"name\": \""); + if (m->v >= TOK_IDENT) { + const char *name = get_tok_str(m->v & ~SYM_FIELD, NULL); + buf_puts(w, name); + } else { + buf_puts(w, ""); + } + buf_putc(w, '"'); + + { + CType type_copy = m->type; + int is_array = type_copy.t & VT_ARRAY; + int array_size = -1; + int is_pointer = 0; + int final_bt, is_struct_or_union, is_scalar, is_anonymous; + + /* Handle arrays first (arrays have VT_PTR in BTYPE too) */ + if (is_array) { + if (type_copy.ref && type_copy.ref->c >= 0) + array_size = type_copy.ref->c; + type_copy.t &= ~VT_ARRAY; + /* Get element type from ref */ + if (type_copy.ref && type_copy.ref->type.t) { + type_copy = type_copy.ref->type; + } + } + + /* Now check for pointers (only if not already handled as array) */ + { + int bt = type_copy.t & VT_BTYPE; + if (!is_array && bt == VT_PTR) { + is_pointer = 1; + if (type_copy.ref) { + type_copy = type_copy.ref->type; + } else { + type_copy.t = VT_VOID; + } + } + } + + final_bt = type_copy.t & VT_BTYPE; + is_struct_or_union = (final_bt == VT_STRUCT); + is_scalar = !is_pointer && !is_array && !is_struct_or_union; + + /* Check if this is an anonymous struct/union */ + is_anonymous = 0; + if (is_struct_or_union && type_copy.ref) { + const char *struct_name = NULL; + if (type_copy.ref->v >= TOK_IDENT) { + struct_name = get_tok_str(type_copy.ref->v & ~SYM_STRUCT, NULL); + } + if (!struct_name || (struct_name[0] == 'L' && struct_name[1] == '.')) { + is_anonymous = 1; + } + } + + /* Only output type field for non-anonymous types */ + if (!is_anonymous) { + buf_puts(w, ", \"type\": \""); + json_write_base_type_name(w, s1, &type_copy, 1, is_scalar); + buf_puts(w, "\""); + } + + buf_puts(w, ", \"kind\": \""); + if (is_pointer) { + buf_puts(w, "pointer"); + } else if (is_array) { + buf_puts(w, "array"); + } else if (is_struct_or_union) { + if (is_anonymous) { + /* Anonymous struct/union gets special kind */ + if (type_copy.t & (1 << VT_STRUCT_SHIFT)) + buf_puts(w, "anon_union"); + else + buf_puts(w, "anon_struct"); + } else { + /* Named struct/union */ + if (type_copy.t & (1 << VT_STRUCT_SHIFT)) + buf_puts(w, "union"); + else + buf_puts(w, "struct"); + } + } else { + buf_puts(w, "scalar"); + } + buf_puts(w, "\""); + + if (is_scalar && (type_copy.t & VT_UNSIGNED)) { + buf_puts(w, ", \"unsigned\": true"); + } + + if (is_array && array_size >= 0) { + buf_puts(w, ", \"element_count\": "); + buf_printf(w, "%d", array_size); + } + + buf_puts(w, ", \"offset\": "); + buf_printf(w, "%d", m->c); + + { + int align = 0; + int size = type_size(&m->type, &align); + buf_puts(w, ", \"size\": "); + buf_printf(w, "%d", size); + } + + if (m->type.t & VT_BITFIELD) { + int bit_pos = BIT_POS(m->type.t); + int bit_size = BIT_SIZE(m->type.t); + buf_puts(w, ", \"bitfield\": {\"pos\": "); + buf_printf(w, "%d", bit_pos); + buf_puts(w, ", \"size\": "); + buf_printf(w, "%d", bit_size); + buf_puts(w, "}"); + } + + /* For anonymous structs/unions, inline their members */ + if (is_anonymous) { + buf_puts(w, ", \"members\": [\n"); + json_write_struct_members(w, s1, type_copy.ref, indent + 1); + buf_puts(w, "\n"); + { + int i; + for (i = 0; i < indent; i++) buf_puts(w, " "); + } + buf_puts(w, "]"); + } + } + + buf_puts(w, "}"); + + m = m->next; + } +} + +static void json_write_struct(BufWriter *w, TCCState *s1, Sym *s, const char *name, int *first) +{ + if (w->full) return; /* Stop if buffer is already full */ + + if (!s || (s->type.t & VT_BTYPE) != VT_STRUCT) + return; + + if (!*first) + buf_puts(w, ",\n"); + *first = 0; + + buf_puts(w, " {"); + + buf_puts(w, "\"name\": \""); + if (name) { + buf_puts(w, name); + } else { + buf_puts(w, ""); + } + buf_putc(w, '"'); + + buf_puts(w, ", \"kind\": \""); + /* VT_UNION is (1 << VT_STRUCT_SHIFT | VT_STRUCT), check bit 20 */ + if (s->type.t & (1 << VT_STRUCT_SHIFT)) + buf_puts(w, "union"); + else + buf_puts(w, "struct"); + buf_puts(w, "\""); + + { + int align = 0; + int size = 0; + if (s->type.ref && s->type.ref->r != 0) { + size = type_size(&s->type, &align); + } else { + size = s->c; + align = s->r; + } + buf_puts(w, ", \"size\": "); + buf_printf(w, "%d", size); + + buf_puts(w, ", \"align\": "); + buf_printf(w, "%d", align); + } + + buf_puts(w, ", \"members\": [\n"); + json_write_struct_members(w, s1, s, 2); + buf_puts(w, "\n ]"); + + buf_puts(w, "}"); +} + +static void json_write_debug_calls(BufWriter *w, TCCState *s1) +{ + if (w->full) return; + if (!s1->debug_calls || s1->nb_debug_calls == 0) { + buf_puts(w, "[]"); + return; + } + + buf_puts(w, "[\n"); + + { + int i; + for (i = 0; i < s1->nb_debug_calls; i++) { + DebugCallRecord *rec; + if (w->full) break; + + rec = &s1->debug_calls[i]; + + if (i > 0) buf_puts(w, ",\n"); + buf_puts(w, " {\n"); + + switch (rec->func_type) { + case DEBUG_FUNC_STRUCT: + buf_puts(w, " \"type_constant\": "); + buf_printf(w, "%d,\n", DEBUG_FUNC_STRUCT); + buf_puts(w, " \"label\": \""); + buf_puts(w, rec->args.debug_struct.label ? rec->args.debug_struct.label : ""); + buf_puts(w, "\",\n"); + buf_printf(w, " \"counter\": %d,\n", rec->args.debug_struct.counter); + buf_puts(w, " \"type_name\": \""); + buf_puts(w, rec->args.debug_struct.struct_name ? rec->args.debug_struct.struct_name : ""); + buf_puts(w, "\",\n"); + buf_printf(w, " \"is_union\": %s\n", + rec->args.debug_struct.is_union ? "true" : "false"); + break; + case DEBUG_FUNC_STR: + buf_puts(w, " \"type\": "); + buf_printf(w, "%d,\n", 3); /* type=3 for strings */ + buf_puts(w, " \"label\": \""); + buf_puts(w, rec->args.debug_str.label ? rec->args.debug_str.label : ""); + buf_puts(w, "\",\n"); + buf_printf(w, " \"counter\": %d\n", rec->args.debug_str.counter); + break; + case DEBUG_FUNC_NUM: + buf_puts(w, " \"type\": "); + buf_printf(w, "%d,\n", 2); /* type=2 for numbers */ + buf_puts(w, " \"label\": \""); + buf_puts(w, rec->args.debug_num.label ? rec->args.debug_num.label : ""); + buf_puts(w, "\",\n"); + buf_printf(w, " \"counter\": %d,\n", rec->args.debug_num.counter); + buf_printf(w, " \"is_signed\": %s\n", + rec->args.debug_num.is_signed ? "true" : "false"); + break; + default: + buf_printf(w, " \"type_constant\": %d\n", rec->func_type); + break; + } + + buf_puts(w, " }"); + } + } + + buf_puts(w, "\n]"); +} + + PUB_FUNC void tcc_print_stats(TCCState *s1, unsigned total_time) { if (!total_time) diff --git a/libtcc.h b/libtcc.h index 11cd96cdea..d2e2eae5f1 100644 --- a/libtcc.h +++ b/libtcc.h @@ -18,7 +18,7 @@ LIBTCCAPI void tcc_set_realloc(TCCReallocFunc *my_realloc); typedef struct TCCState TCCState; /* create a new TCC compilation context */ -LIBTCCAPI TCCState *tcc_new(void); +LIBTCCAPI TCCState *tcc_new(unsigned int arena_size_bytes); /* free a TCC compilation context */ LIBTCCAPI void tcc_delete(TCCState *s); @@ -26,8 +26,16 @@ LIBTCCAPI void tcc_delete(TCCState *s); /* set CONFIG_TCCDIR at runtime */ LIBTCCAPI void tcc_set_lib_path(TCCState *s, const char *path); +/* Error information structure passed to error callback */ +typedef struct TCCErrorInfo { + const char *filename; /* file where error occurred, or NULL if no file context */ + int line_num; /* line number where error occurred, or 0 if no line context */ + int is_warning; /* 1 if this is a warning, 0 if it's an error */ + const char *msg; /* the formatted error/warning message */ +} TCCErrorInfo; + /* set error/warning callback (optional) */ -typedef void TCCErrorFunc(void *opaque, const char *msg); +typedef void TCCErrorFunc(void *opaque, const TCCErrorInfo *info); LIBTCCAPI void tcc_set_error_func(TCCState *s, void *error_opaque, TCCErrorFunc *error_func); /* set options as from command line (multiple supported) */ @@ -54,9 +62,27 @@ LIBTCCAPI void tcc_undefine_symbol(TCCState *s, const char *sym); /* add a file (C file, dll, object, library, ld script). Return -1 if error. */ LIBTCCAPI int tcc_add_file(TCCState *s, const char *filename); +/* Buffer writer for type export */ +typedef struct TCCBufWriter { + char *buf; /* output buffer */ + int pos; /* current position in buffer */ + int size; /* total buffer size */ + int full; /* set to 1 if buffer became full during writing */ +} TCCBufWriter; + /* compile a string containing a C source. Return -1 if error. */ LIBTCCAPI int tcc_compile_string(TCCState *s, const char *buf); +/* compile and optionally write type info to buffer. + If w is not NULL, writes JSON array of struct/union definitions. + Check w->full after to see if buffer was too small. + Return -1 if error. */ +LIBTCCAPI int tcc_compile_string_ex(TCCState *s, const char *buf, TCCBufWriter *w); + +/* Get debug function calls as JSON array. + Returns -1 if error or buffer too small (check w->full). */ +LIBTCCAPI int tcc_get_debug_calls(TCCState *s, TCCBufWriter *w); + /* Tip: to have more specific errors/warnings from tcc_compile_string(), you can prefix the string with "#line \"\"\n" */ diff --git a/syntax_check.c b/syntax_check.c new file mode 100644 index 0000000000..8956f65401 --- /dev/null +++ b/syntax_check.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include "libtcc.h" + +TCCBufWriter buf_writer = { NULL, 0, 0, 0 }; +TCCBufWriter debug_buf_writer = { NULL, 0, 0, 0 }; + +typedef struct { + char *filename; + int line_num; + int is_warning; + char *msg; +} ErrorEntry; + +static ErrorEntry error_list[16]; +static int error_count = 0; + +void reset_errors() { + for (int i = 0; i < error_count; i++) { + free(error_list[i].filename); + free(error_list[i].msg); + } + error_count = 0; +} + +void error_callback(void *opaque, const TCCErrorInfo *info) { + if (error_count >= 16) return; + + ErrorEntry *entry = &error_list[error_count]; + entry->filename = info->filename ? strdup(info->filename) : NULL; + entry->line_num = info->line_num; + entry->is_warning = info->is_warning; + entry->msg = strdup(info->msg); + error_count++; +} + +TCCState* init_tcc() { + reset_errors(); + TCCState *s = tcc_new(16*1024*1024); + tcc_set_lib_path(s, "."); + tcc_add_sysinclude_path(s, "."); + tcc_set_error_func(s, NULL, error_callback); + tcc_set_output_type(s, TCC_OUTPUT_MEMORY); + tcc_set_options(s, "-Wall"); + tcc_set_options(s, "-Werror"); + return s; +} +int check_syntax(const char *source, int with_type_info) { + + TCCState *s = init_tcc(); + if (!s) { + fprintf(stderr, "Could not create tcc state\n"); + return -1; + } + + int result; + + if (with_type_info) { + if (buf_writer.buf == NULL) { + buf_writer.buf = malloc(1 * 1024 * 1024); + buf_writer.size = 1 * 1024 * 1024; + } + do { + buf_writer.full = 0; + buf_writer.pos = 0; + result = tcc_compile_string_ex(s, source, &buf_writer); + if (buf_writer.full) { + buf_writer.size *= 2; + buf_writer.buf = realloc(buf_writer.buf, buf_writer.size); + tcc_delete(s); + s = init_tcc(); + } + } while (buf_writer.full); + + /* Get debug calls */ + if (debug_buf_writer.buf == NULL) { + debug_buf_writer.buf = malloc(1 * 1024 * 1024); + debug_buf_writer.size = 1 * 1024 * 1024; + } + debug_buf_writer.full = 0; + debug_buf_writer.pos = 0; + tcc_get_debug_calls(s, &debug_buf_writer); + } else { + result = tcc_compile_string_ex(s, source, NULL); + } + + tcc_delete(s); + return result; +} + +const char* get_type_info_buffer() { + return buf_writer.buf; +} + +int get_type_info_length() { + return buf_writer.pos; +} + +const char* get_debug_calls_buffer() { + return debug_buf_writer.buf; +} + +int get_debug_calls_length() { + return debug_buf_writer.pos; +} + +int get_error_count() { + return error_count; +} + +const ErrorEntry* get_error(int index) { + if (index >= 0 && index < error_count) { + return &error_list[index]; + } + return NULL; +} + +const char* get_error_filename(int index) { + if (index >= 0 && index < error_count) { + return error_list[index].filename; + } + return NULL; +} + +int get_error_line_num(int index) { + if (index >= 0 && index < error_count) { + return error_list[index].line_num; + } + return 0; +} + +int get_error_is_warning(int index) { + if (index >= 0 && index < error_count) { + return error_list[index].is_warning; + } + return 0; +} + +const char* get_error_msg(int index) { + if (index >= 0 && index < error_count) { + return error_list[index].msg; + } + return NULL; +} diff --git a/syntax_check_file.c b/syntax_check_file.c new file mode 100644 index 0000000000..8b2f5fff5d --- /dev/null +++ b/syntax_check_file.c @@ -0,0 +1,107 @@ +#include +#include +#include "libtcc.h" + +void error_callback(void *opaque, const TCCErrorInfo *info) +{ + fprintf(stderr, "file %s\n", info->filename); + fprintf(stderr, "line %d\n", info->line_num); + fprintf(stderr, "msg %s\n", info->msg); +} + +char *read_file(const char *filename) +{ + FILE *f = fopen(filename, "rb"); + if (!f) { + fprintf(stderr, "Could not open file: %s\n", filename); + return NULL; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + + char *content = malloc(size + 1); + if (!content) { + fprintf(stderr, "Could not allocate memory for file\n"); + fclose(f); + return NULL; + } + + size_t read_size = fread(content, 1, size, f); + content[read_size] = '\0'; + + fclose(f); + return content; +} + + +int check_syntax(const char *content) +{ + TCCState *s = tcc_new(16 * 1024 * 1024); + if (!s) { + fprintf(stderr, "Could not create tcc state\n"); + return -1; + } + + tcc_set_lib_path(s, "."); + tcc_set_error_func(s, NULL, error_callback); + tcc_set_output_type(s, TCC_OUTPUT_MEMORY); + + char *json = malloc(2 * 1024 * 1024); /* 2MB buffer for JSON */ + if (!json) { + fprintf(stderr, "Could not allocate JSON buffer\n"); + tcc_delete(s); + return -1; + } + + TCCBufWriter w = { json, 0, 2 * 1024 * 1024, 0 }; + int result = tcc_compile_string_ex(s, content, &w); + if (result == 0) { + if (w.full) { + fprintf(stderr, "Warning: JSON buffer full, output may be truncated\n"); + } + //printf("Type Info:\n%s\n\n", json); + } + + /* Get debug calls */ + char *debug_json = malloc(2 * 1024 * 1024); /* 2MB buffer for debug calls JSON */ + if (debug_json) { + TCCBufWriter debug_w = { debug_json, 0, 2 * 1024 * 1024, 0 }; + int debug_result = tcc_get_debug_calls(s, &debug_w); + if (debug_result == 0 && !debug_w.full) { + printf("Debug Calls:\n%s\n", debug_json); + } + free(debug_json); + } + + free(json); + tcc_delete(s); + + return result; +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + char* file_contents = read_file(argv[1]); + if (!file_contents) { + return 1; + } + + for(int i = 0; i<1; i++) { + int result = check_syntax(file_contents); + + if (result == 0) { + printf("Syntax OK: %s\n", argv[1]); + } else { + printf("Syntax errors in: %s\n", argv[1]); + } + } + + free(file_contents); +} diff --git a/tcc-stubs.c b/tcc-stubs.c new file mode 100644 index 0000000000..1991af117e --- /dev/null +++ b/tcc-stubs.c @@ -0,0 +1,87 @@ +/* Minimal stubs for syntax-only mode */ + +#define ONE_SOURCE 0 +#include "tcc.h" +#define ONE_SOURCE 1 + +/* Code generation stubs (never called when nocode_wanted is set) */ +void gen_opi(int op) {} +void gen_opf(int op) {} +void gen_opl(int op) {} +void gen_cvt_ftoi(int t) {} +void gen_cvt_ftof(int t) {} +void gen_cvt_itof(int t) {} +void gen_vla_sp_save(int addr) {} +void gen_vla_sp_restore(int addr) {} +void gen_vla_alloc(CType *type, int align) {} +void ggoto(void) {} +int gjmp(int t) { return 0; } +void gjmp_addr(int a) {} +int gtst(int inv, int t) { return 0; } +void gtst_addr(int inv, int a) {} +void gsym(int t) {} +void gsym_addr(int t, int a) {} +void load(int r, SValue *sv) {} +void store(int r, SValue *v) {} +void gfunc_call(int nb_args) {} +void gfunc_prolog(CType *func_type) {} +void gfunc_epilog(void) {} +int gfunc_sret(CType *vt, int variadic, CType *ret, int *align, int *regsize) { return 0; } +void o(unsigned int c) {} +void gen_le16(int c) {} +void gen_le32(int c) {} +int oad(int c, int s) { return 0; } +void g(int c) {} + +/* x86_64 specific stubs */ +int classify_x86_64_va_arg(CType *ty) { return 0; } + +/* Assembly stubs */ +void asm_instr(void) { tcc_error("inline assembly not supported in syntax-only mode"); } +void asm_global_instr(void) { tcc_error("inline assembly not supported in syntax-only mode"); } +int asm_parse_regvar(int t) { return -1; } +int tcc_assemble(TCCState *s1, int do_preprocess) { tcc_error("assembly not supported"); return -1; } + +/* ELF/section management stubs */ +void tccelf_begin_file(TCCState *s1) {} +void tccelf_end_file(TCCState *s1) {} +void tccelf_new(TCCState *s) {} +void tccelf_delete(TCCState *s1) {} +void tccelf_stab_new(TCCState *s) {} +int tcc_object_type(int fd, ElfW(Ehdr) *h) { return 0; } +int tcc_load_object_file(TCCState *s1, int fd, unsigned long file_offset) { return -1; } +int tcc_load_archive(TCCState *s1, int fd) { return -1; } +int tcc_load_dll(TCCState *s1, int fd, const char *filename, int level) { return -1; } +int tcc_load_ldscript(TCCState *s1) { return -1; } +int set_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name) { return 0; } +int put_elf_sym(Section *s, addr_t value, unsigned long size, int info, int other, int shndx, const char *name) { return 0; } +void put_elf_reloca(Section *symtab, Section *s, unsigned long offset, int type, int symbol, addr_t addend) {} +void put_stabs(const char *str, int type, int other, int desc, unsigned long value) {} +void put_stabs_r(const char *str, int type, int other, int desc, unsigned long value, Section *sec, int sym_index) {} +void put_stabn(int type, int other, int desc, int value) {} +void put_stabd(int type, int other, int desc) {} +void squeeze_multi_relocs(Section *sec, size_t oldrelocoffset) {} +void section_realloc(Section *sec, unsigned long new_size) {} +size_t section_add(Section *sec, addr_t size, int align) { return 0; } +void section_reserve(Section *sec, unsigned long size) {} + +/* Runtime stubs */ +void tcc_run_free(TCCState *s1) {} +void tcc_set_num_callers(int n) {} + +/* Section variables (dummy sections for syntax checking) */ +static Section dummy_section = {0}; +Section *text_section = &dummy_section; +Section *data_section = &dummy_section; +Section *bss_section = &dummy_section; +Section *common_section = &dummy_section; +Section *cur_text_section = &dummy_section; +Section *symtab_section = &dummy_section; + +/* Backend data */ +const int reg_classes[NB_REGS] = {0}; + +/* Additional backend functions */ +Section *find_section(TCCState *s1, const char *name) { return &dummy_section; } +void *section_ptr_add(Section *sec, addr_t size) { return NULL; } + diff --git a/tcc.c b/tcc.c index e1819239d6..4efb663de1 100644 --- a/tcc.c +++ b/tcc.c @@ -297,7 +297,7 @@ int main(int argc, char **argv) redo: argc = argc0, argv = argv0; - s = s1 = tcc_new(); + s = s1 = tcc_new(0); opt = tcc_parse_args(s, &argc, &argv); if (n == 0) { @@ -392,6 +392,8 @@ int main(int argc, char **argv) t = 0; } else if (s->output_type == TCC_OUTPUT_PREPROCESS) { ; + } else if (s->syntax_only) { + ; } else if (0 == ret) { if (s->output_type == TCC_OUTPUT_MEMORY) { #ifdef TCC_IS_NATIVE diff --git a/tcc.h b/tcc.h index e7a2f1e26e..84df43cc9d 100644 --- a/tcc.h +++ b/tcc.h @@ -735,6 +735,44 @@ struct sym_attr { #endif }; +/* Debug function call types */ +#define DEBUG_FUNC_STRUCT 1 +#define DEBUG_FUNC_U32 2 +#define DEBUG_FUNC_U64 3 +#define DEBUG_FUNC_STR 4 +#define DEBUG_FUNC_NUM 5 + +/* Record of a debug function call */ +typedef struct DebugCallRecord { + int func_type; /* DEBUG_FUNC_STRUCT, etc. */ + + union { + struct { + const char *label; /* Arg 0: label string */ + int counter; /* Arg 1: __COUNTER__ value */ + const char *struct_name; /* Arg 2: struct name from pointer */ + int is_union; /* Union vs struct flag */ + } debug_struct; + + struct { + const char *label; + int counter; + uint32_t value; + } debug_u32; + + struct { + const char *label; + int counter; + } debug_str; + + struct { + const char *label; + int counter; + int is_signed; + } debug_num; + } args; +} DebugCallRecord; + struct TCCState { unsigned char verbose; /* if true, display some information during compilation */ unsigned char nostdinc; /* if true, no standard headers are added */ @@ -756,6 +794,7 @@ struct TCCState { unsigned char leading_underscore; unsigned char ms_extensions; /* allow nested named struct w/o identifier behave like unnamed */ unsigned char dollars_in_identifiers; /* allows '$' char in identifiers */ + unsigned char syntax_only; /* if true, only check syntax without code generation */ unsigned char ms_bitfields; /* if true, emulate MS algorithm for aligning bitfields */ unsigned char reverse_funcargs; /* if true, evaluate last function arg first */ unsigned char gnu89_inline; /* treat 'extern inline' like 'static inline' */ @@ -853,7 +892,7 @@ struct TCCState { /* error handling */ void *error_opaque; - void (*error_func)(void *opaque, const char *msg); + void (*error_func)(void *opaque, const TCCErrorInfo *info); int error_set_jmp_enabled; jmp_buf error_jmp_buf; int nb_errors; @@ -933,6 +972,12 @@ struct TCCState { /* extra attributes (eg. GOT/PLT value) for symtab symbols */ struct sym_attr *sym_attrs; int nb_sym_attrs; + + /* Debug function call tracking */ + DebugCallRecord *debug_calls; + int nb_debug_calls; + int debug_calls_capacity; + /* ptr to next reloc entry reused */ ElfW_Rel *qrel; #define qrel s1->qrel @@ -1219,6 +1264,10 @@ ST_FUNC char *pstrncpy(char *out, size_t buf_size, const char *s, size_t num); PUB_FUNC char *tcc_basename(const char *name); PUB_FUNC char *tcc_fileextension (const char *name); +PUB_FUNC void tcc_arena_init(unsigned int arena_size); +PUB_FUNC void tcc_arena_free(void); +PUB_FUNC size_t tcc_arena_watermark(void); + /* all allocations - even MEM_DEBUG - use these */ PUB_FUNC void tcc_free(void *ptr); PUB_FUNC void *tcc_malloc(unsigned long size); diff --git a/tccelf.c b/tccelf.c index b71c6f2b76..e1c12868fe 100644 --- a/tccelf.c +++ b/tccelf.c @@ -2944,8 +2944,6 @@ static int elf_output_file(TCCState *s1, const char *filename) char *ptr; /* allow override the dynamic loader */ const char *elfint = s1->elfint; - if (elfint == NULL) - elfint = getenv("LD_SO"); if (elfint == NULL) elfint = DEFAULT_ELFINTERP(s1); /* add interpreter section only if executable */ diff --git a/tccgen.c b/tccgen.c index 50802edf16..63e8e70791 100644 --- a/tccgen.c +++ b/tccgen.c @@ -158,6 +158,196 @@ static int get_temp_local_var(int size,int align,int *r2); static void cast_error(CType *st, CType *dt); static void end_switch(void); static void do_Static_assert(void); +static const char* get_real_struct_name(Sym *struct_sym); + +/* Debug function tracking table */ +typedef struct DebugFuncHandler { + const char *func_name; + int func_type; + int expected_args; + void (*handler)(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args); +} DebugFuncHandler; + +static void handle_debug_struct_call(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args); +static void handle_debug_str_call(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args); +static void handle_debug_num_call(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args); + +static const DebugFuncHandler debug_func_table[] = { + { "__debug_struct", DEBUG_FUNC_STRUCT, 5, handle_debug_struct_call }, + { "__debug_str", DEBUG_FUNC_STR, 6, handle_debug_str_call }, + { "__debug_num", DEBUG_FUNC_NUM, 5, handle_debug_num_call }, + { NULL, 0, 0, NULL } /* Sentinel */ +}; + +static void handle_debug_struct_call(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args) +{ + DebugCallRecord *rec; + SValue *arg0, *arg1; + CType *arg2_type; + + if (nb_args != 5) { + tcc_warning("__debug_struct expects 5 arguments, got %d", nb_args); + return; + } + + /* Expand array if needed */ + if (s1->nb_debug_calls >= s1->debug_calls_capacity) { + int new_cap = s1->debug_calls_capacity == 0 ? 16 : s1->debug_calls_capacity * 2; + DebugCallRecord *new_array = tcc_realloc(s1->debug_calls, + new_cap * sizeof(DebugCallRecord)); + if (!new_array) { + tcc_error("out of memory tracking debug calls"); + return; + } + s1->debug_calls = new_array; + s1->debug_calls_capacity = new_cap; + } + + rec = &s1->debug_calls[s1->nb_debug_calls]; + memset(rec, 0, sizeof(DebugCallRecord)); + + rec->func_type = DEBUG_FUNC_STRUCT; + + /* Extract arg 0: label string - take ownership */ + arg0 = &arg_values[0]; + if ((arg0->r & VT_VALMASK) == VT_CONST && arg0->c.str.data) { + rec->args.debug_struct.label = (char *)arg0->c.str.data; + arg0->c.str.data = NULL; /* Mark as transferred to prevent double-free */ + } else { + rec->args.debug_struct.label = tcc_strdup(""); + } + + /* Extract arg 1: counter (integer constant) */ + arg1 = &arg_values[1]; + if ((arg1->r & VT_VALMASK) == VT_CONST) { + rec->args.debug_struct.counter = (int)arg1->c.i; + } else { + rec->args.debug_struct.counter = -1; + } + + /* Extract arg 2: struct name from pointer type */ + arg2_type = &arg_types[2]; + if ((arg2_type->t & VT_BTYPE) == VT_PTR && arg2_type->ref) { + CType *pointed = &arg2_type->ref->type; + if ((pointed->t & VT_BTYPE) == VT_STRUCT && pointed->ref) { + const char *name = get_real_struct_name(pointed->ref); + rec->args.debug_struct.struct_name = tcc_strdup(name ? name : ""); + rec->args.debug_struct.is_union = (pointed->t & (1 << VT_STRUCT_SHIFT)) ? 1 : 0; + } else { + rec->args.debug_struct.struct_name = tcc_strdup(""); + } + } else { + rec->args.debug_struct.struct_name = tcc_strdup(""); + } + + s1->nb_debug_calls++; +} + +static void handle_debug_str_call(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args) +{ + DebugCallRecord *rec; + SValue *arg0, *arg1; + + if (nb_args != 6) { + tcc_warning("__debug_str expects 6 arguments, got %d", nb_args); + return; + } + + /* Expand array if needed */ + if (s1->nb_debug_calls >= s1->debug_calls_capacity) { + int new_cap = s1->debug_calls_capacity == 0 ? 16 : s1->debug_calls_capacity * 2; + DebugCallRecord *new_array = tcc_realloc(s1->debug_calls, + new_cap * sizeof(DebugCallRecord)); + if (!new_array) { + tcc_error("out of memory tracking debug calls"); + return; + } + s1->debug_calls = new_array; + s1->debug_calls_capacity = new_cap; + } + + rec = &s1->debug_calls[s1->nb_debug_calls]; + memset(rec, 0, sizeof(DebugCallRecord)); + + rec->func_type = DEBUG_FUNC_STR; + + /* Extract arg 0: label string - take ownership */ + arg0 = &arg_values[0]; + if ((arg0->r & VT_VALMASK) == VT_CONST && arg0->c.str.data) { + rec->args.debug_str.label = (char *)arg0->c.str.data; + arg0->c.str.data = NULL; /* Mark as transferred to prevent double-free */ + } else { + rec->args.debug_str.label = tcc_strdup(""); + } + + /* Extract arg 1: counter (integer constant) */ + arg1 = &arg_values[1]; + if ((arg1->r & VT_VALMASK) == VT_CONST) { + rec->args.debug_str.counter = (int)arg1->c.i; + } else { + rec->args.debug_str.counter = -1; + } + + s1->nb_debug_calls++; +} + +static void handle_debug_num_call(TCCState *s1, CType *arg_types, SValue *arg_values, int nb_args) +{ + DebugCallRecord *rec; + SValue *arg0, *arg1; + CType *arg2_type; + + if (nb_args != 5) { + tcc_warning("__debug_num expects 5 arguments, got %d", nb_args); + return; + } + + /* Expand array if needed */ + if (s1->nb_debug_calls >= s1->debug_calls_capacity) { + int new_cap = s1->debug_calls_capacity == 0 ? 16 : s1->debug_calls_capacity * 2; + DebugCallRecord *new_array = tcc_realloc(s1->debug_calls, + new_cap * sizeof(DebugCallRecord)); + if (!new_array) { + tcc_error("out of memory tracking debug calls"); + return; + } + s1->debug_calls = new_array; + s1->debug_calls_capacity = new_cap; + } + + rec = &s1->debug_calls[s1->nb_debug_calls]; + memset(rec, 0, sizeof(DebugCallRecord)); + + rec->func_type = DEBUG_FUNC_NUM; + + /* Extract arg 0: label string - take ownership */ + arg0 = &arg_values[0]; + if ((arg0->r & VT_VALMASK) == VT_CONST && arg0->c.str.data) { + rec->args.debug_num.label = (char *)arg0->c.str.data; + arg0->c.str.data = NULL; /* Mark as transferred to prevent double-free */ + } else { + rec->args.debug_num.label = tcc_strdup(""); + } + + /* Extract arg 1: counter (integer constant) */ + arg1 = &arg_values[1]; + if ((arg1->r & VT_VALMASK) == VT_CONST) { + rec->args.debug_num.counter = (int)arg1->c.i; + } else { + rec->args.debug_num.counter = -1; + } + + /* Extract arg 2: check signedness from pointed-to type */ + arg2_type = &arg_types[2]; + if ((arg2_type->t & VT_BTYPE) == VT_PTR && arg2_type->ref) { + CType *pointed = &arg2_type->ref->type; + rec->args.debug_num.is_signed = !(pointed->t & VT_UNSIGNED); + } else { + rec->args.debug_num.is_signed = 0; + } + + s1->nb_debug_calls++; +} /* ------------------------------------------------------------------------- */ /* Automagical code suppression */ @@ -716,6 +906,21 @@ ST_INLN Sym *sym_find(int v) return table_ident[v]->sym_identifier; } +/* Helper to get real struct name - returns NULL for anonymous/generated names */ +static const char* get_real_struct_name(Sym *struct_sym) +{ + const char *name; + if (!struct_sym || struct_sym->v < TOK_IDENT) + return NULL; + + /* Use get_tok_str() and filter generated labels (L.*) */ + name = get_tok_str(struct_sym->v & ~SYM_STRUCT, NULL); + if (name && !(name[0] == 'L' && name[1] == '.')) { + return name; + } + return NULL; +} + /* make sym in-/visible to the parser */ static inline void sym_link(Sym *s, int yes) { @@ -4792,6 +4997,12 @@ static int parse_btype(CType *type, AttributeDef *ad, int ignore_label) } next(); break; + case TOK_INT128: + case TOK_UINT128: + /* __int128 and __uint128_t appear in some Linux header files. + Use VT_QLONG (128-bit integer type) for parse-level tolerance. */ + u = VT_QLONG; + goto basic_type; case TOK_BOOL: u = VT_BOOL; goto basic_type; @@ -5797,6 +6008,58 @@ ST_FUNC void unary(void) parse_builtin_params(0, "ee"); vpop(); break; + case TOK_builtin_classify_type: + parse_builtin_params(0, "e"); + { + int t = vtop->type.t; + int type_class; + + if (t & VT_ARRAY) { + type_class = 14; + } else if (IS_ENUM(t)) { + type_class = 3; + } else { + switch (t & VT_BTYPE) { + case VT_VOID: + type_class = 0; + break; + case VT_BOOL: + type_class = 4; + break; + case VT_PTR: + type_class = 5; + break; + case VT_BYTE: + case VT_SHORT: + case VT_INT: + case VT_LLONG: + case VT_QLONG: + type_class = 1; + break; + case VT_FLOAT: + case VT_DOUBLE: + case VT_LDOUBLE: + case VT_QFLOAT: + type_class = 8; + break; + case VT_FUNC: + type_class = 10; + break; + case VT_STRUCT: + if (IS_UNION(t)) + type_class = 13; + else + type_class = 12; + break; + default: + type_class = 1; + break; + } + } + vtop--; + vpushi(type_class); + } + break; case TOK_builtin_types_compatible_p: parse_builtin_params(0, "tt"); vtop[-1].type.t &= ~(VT_CONSTANT | VT_VOLATILE); @@ -6180,6 +6443,13 @@ ST_FUNC void unary(void) Sym *sa; int nb_args, ret_nregs, ret_align, regsize, variadic; TokenString *p, *p2; + const DebugFuncHandler *debug_handler = NULL; + CType debug_arg_types[8]; + SValue debug_arg_values[8]; + char *debug_saved_strings[8]; + int debug_saved_nb_args = 0; + + memset(debug_saved_strings, 0, sizeof(debug_saved_strings)); /* function call */ if ((vtop->type.t & VT_BTYPE) != VT_FUNC) { @@ -6195,6 +6465,22 @@ ST_FUNC void unary(void) } else { vtop->r &= ~VT_LVAL; /* no lvalue */ } + + /* Check if this is a tracked debug function */ + if (vtop->r & VT_SYM) { + Sym *func_sym = vtop->sym; + if (func_sym && func_sym->v >= TOK_IDENT) { + const char *func_name = get_tok_str(func_sym->v, NULL); + const DebugFuncHandler *h; + for (h = debug_func_table; h->func_name; h++) { + if (strcmp(func_name, h->func_name) == 0) { + debug_handler = h; + break; + } + } + } + } + /* get return type */ s = vtop->type.ref; next(); @@ -6249,14 +6535,47 @@ ST_FUNC void unary(void) if (tok != ')') { r = tcc_state->reverse_funcargs; for(;;) { + /* Save string literal before expr_eq() processes it */ + CString saved_tokc_str = {0, NULL, 0}; + int was_string_tok = (tok == TOK_STR); + if (was_string_tok && debug_handler) { + /* Make a copy of the string data from tokc */ + if (tokc.str.size > 0 && tokc.str.data) { + saved_tokc_str.size = tokc.str.size; + saved_tokc_str.data = tcc_malloc(tokc.str.size + 1); + if (saved_tokc_str.data) { + memcpy(saved_tokc_str.data, tokc.str.data, tokc.str.size); + ((char *)saved_tokc_str.data)[tokc.str.size] = '\0'; + } + } + } + if (r) { skip_or_save_block(&p2); p2->prev = p, p = p2; } else { expr_eq(); + /* Save argument type and value for debug function tracking */ + if (debug_handler && debug_saved_nb_args < 8) { + debug_arg_types[debug_saved_nb_args] = vtop->type; + debug_arg_values[debug_saved_nb_args] = *vtop; + + /* Use the saved string literal if we had one */ + if (was_string_tok && saved_tokc_str.data) { + debug_saved_strings[debug_saved_nb_args] = (char *)saved_tokc_str.data; + debug_arg_values[debug_saved_nb_args].c.str.data = saved_tokc_str.data; + debug_arg_values[debug_saved_nb_args].c.str.size = saved_tokc_str.size; + saved_tokc_str.data = NULL; /* ownership transferred */ + } + } + if (saved_tokc_str.data) { + /* Free if we're not using it */ + tcc_free(saved_tokc_str.data); + } gfunc_param_typed(s, sa); } nb_args++; + debug_saved_nb_args++; if (sa) sa = sa->next; if (tok == ')') @@ -6264,8 +6583,33 @@ ST_FUNC void unary(void) skip(','); } } - if (sa) - tcc_error("too few arguments to function"); + if (sa) { + char buf[1024]; + char type_buf[256]; + Sym *_p = sa; + int count = 0; + + strcpy(buf, "missing arguments: "); + + while (_p) { + int name_tok = _p->v & ~SYM_FIELD; + const char *param_name = NULL; + + if (count > 0) + pstrcat(buf, sizeof(buf), ", "); + + if (name_tok >= TOK_IDENT && name_tok < SYM_FIRST_ANOM) + param_name = get_tok_str(name_tok, NULL); + + type_to_str(type_buf, sizeof(type_buf), &_p->type, param_name); + pstrcat(buf, sizeof(buf), type_buf); + + _p = _p->next; + count++; + } + + tcc_error("%s", buf); + } if (p) { /* with reverse_funcargs */ for (n = 0; p; p = p2, ++n) { @@ -6283,6 +6627,21 @@ ST_FUNC void unary(void) } next(); + + /* Invoke debug function handler if matched */ + if (debug_handler) { + int di; + debug_handler->handler(tcc_state, debug_arg_types, debug_arg_values, debug_saved_nb_args); + + /* Free saved string copies that weren't transferred */ + for (di = 0; di < debug_saved_nb_args && di < 8; di++) { + if (debug_saved_strings[di] && debug_arg_values[di].c.str.data == debug_saved_strings[di]) { + /* Handler didn't take ownership (string still in arg_values) */ + tcc_free(debug_saved_strings[di]); + } + } + } + vcheck_cmp(); /* the generators don't like VT_CMP on vtop */ gfunc_call(nb_args); @@ -8507,7 +8866,9 @@ static void gen_function(Sym *sym) struct scope f = { 0 }; cur_scope = root_scope = &f; +#ifndef TCC_SYNTAX_ONLY nocode_wanted = 0; +#endif ind = cur_text_section->data_offset; if (sym->a.aligned) { @@ -8547,7 +8908,9 @@ static void gen_function(Sym *sym) func_vla_arg(sym); block(0); gsym(rsym); +#ifndef TCC_SYNTAX_ONLY nocode_wanted = 0; +#endif tcc_debug_end_scope(NULL, !func_var); tcc_debug_prolog_epilog(tcc_state, 1); gfunc_epilog(); diff --git a/tccpp.c b/tccpp.c index e19e85046a..470651a0ef 100644 --- a/tccpp.c +++ b/tccpp.c @@ -3367,8 +3367,31 @@ static int macro_subst_tok( var arg argument if it is omitted */ if (sa->type.t && gnu_ext) goto empty_arg; - tcc_error("macro '%s' used with too few args", - get_tok_str(v, 0)); + { + char buf[1024]; + Sym *p = sa; + int count = 0; + + snprintf(buf, sizeof(buf), "macro '%s' missing arguments: ", + get_tok_str(v, 0)); + + while (p) { + int name_tok = p->v & ~SYM_FIELD; + + if (count > 0) + pstrcat(buf, sizeof(buf), ", "); + + if (name_tok >= TOK_IDENT && name_tok < SYM_FIRST_ANOM) + pstrcat(buf, sizeof(buf), get_tok_str(name_tok, NULL)); + else + pstrcat(buf, sizeof(buf), ""); + + p = p->next; + count++; + } + + tcc_error("%s", buf); + } } i = 1; } diff --git a/tcctok.h b/tcctok.h index b7cc9d409f..36a3667725 100644 --- a/tcctok.h +++ b/tcctok.h @@ -49,6 +49,8 @@ DEF(TOK_FLOAT, "float") DEF(TOK_DOUBLE, "double") DEF(TOK_BOOL, "_Bool") + DEF(TOK_INT128, "__int128") + DEF(TOK_UINT128, "__uint128_t") DEF(TOK_COMPLEX, "_Complex") DEF(TOK_SHORT, "short") DEF(TOK_LONG, "long") @@ -170,6 +172,7 @@ DEF(TOK_builtin_frame_address, "__builtin_frame_address") DEF(TOK_builtin_return_address, "__builtin_return_address") DEF(TOK_builtin_expect, "__builtin_expect") + DEF(TOK_builtin_classify_type, "__builtin_classify_type") DEF(TOK_builtin_unreachable, "__builtin_unreachable") /*DEF(TOK_builtin_va_list, "__builtin_va_list")*/ #if defined TCC_TARGET_PE && defined TCC_TARGET_X86_64 diff --git a/tests/abitest.c b/tests/abitest.c index 5fc868a998..0d9e39ba74 100644 --- a/tests/abitest.c +++ b/tests/abitest.c @@ -42,7 +42,7 @@ static int run_callback(const char *src, callback_type callback) { int result; void *ptr; - s = tcc_new(); + s = tcc_new(0); if (!s) return -1; diff --git a/tests/libtcc_debug.c b/tests/libtcc_debug.c index eff6d2fe6b..7b786ec6e7 100644 --- a/tests/libtcc_debug.c +++ b/tests/libtcc_debug.c @@ -21,16 +21,22 @@ static const char program[] = " return 0;\n" "}\n"; -void handle_error(void *opaque, const char *msg) +void handle_error(void *opaque, const TCCErrorInfo *info) { - fprintf(opaque, "%s\n", msg); + if (info->filename) { + if (info->line_num) + fprintf(opaque, "%s:%d: ", info->filename, info->line_num); + else + fprintf(opaque, "%s: ", info->filename); + } + fprintf(opaque, "%s: %s\n", info->is_warning ? "warning" : "error", info->msg); } int main(void) { int (*func)(void); - TCCState *s = tcc_new(); + TCCState *s = tcc_new(0); if (!s) { fprintf(stderr, __FILE__ ": could not create tcc state\n"); diff --git a/tests/libtcc_test.c b/tests/libtcc_test.c index 1428b1eabf..7be7945ec0 100644 --- a/tests/libtcc_test.c +++ b/tests/libtcc_test.c @@ -8,9 +8,15 @@ #include #include "libtcc.h" -void handle_error(void *opaque, const char *msg) +void handle_error(void *opaque, const TCCErrorInfo *info) { - fprintf(opaque, "%s\n", msg); + if (info->filename) { + if (info->line_num) + fprintf(opaque, "%s:%d: ", info->filename, info->line_num); + else + fprintf(opaque, "%s: ", info->filename); + } + fprintf(opaque, "%s: %s\n", info->is_warning ? "warning" : "error", info->msg); } /* this function is called by the generated code */ @@ -51,7 +57,7 @@ int main(int argc, char **argv) int i; int (*func)(int); - s = tcc_new(); + s = tcc_new(0); if (!s) { fprintf(stderr, "Could not create tcc state\n"); exit(1); diff --git a/tests/libtcc_test_mt.c b/tests/libtcc_test_mt.c index f9c90bd6d5..9a3a5b61b3 100644 --- a/tests/libtcc_test_mt.c +++ b/tests/libtcc_test_mt.c @@ -55,9 +55,15 @@ void sleep_ms(unsigned n) } #endif -void handle_error(void *opaque, const char *msg) +void handle_error(void *opaque, const TCCErrorInfo *info) { - fprintf(opaque, "%s\n", msg); + if (info->filename) { + if (info->line_num) + fprintf(opaque, "%s:%d: ", info->filename, info->line_num); + else + fprintf(opaque, "%s: ", info->filename); + } + fprintf(opaque, "%s: %s\n", info->is_warning ? "warning" : "error", info->msg); } /* this function is called by the generated code */ @@ -140,7 +146,7 @@ int backtrace_func( TCCState *new_state(int w) { - TCCState *s = tcc_new(); + TCCState *s = tcc_new(0); if (!s) { fprintf(stderr, __FILE__ ": could not create tcc state\n"); exit(1); diff --git a/tests/tests2/100_static_assert.c b/tests/tests2/100_static_assert.c new file mode 100644 index 0000000000..97ee649c73 --- /dev/null +++ b/tests/tests2/100_static_assert.c @@ -0,0 +1,39 @@ +#include + +/* Test 1: Global scope - should pass */ +_Static_assert(1, "basic test"); +_Static_assert(sizeof(int) >= 2, "int size"); +_Static_assert(sizeof(char) == 1, "char size"); + +/* Test 2: Complex expressions */ +_Static_assert(sizeof(char) < sizeof(int), "size comparison"); +_Static_assert((1 << 3) == 8, "bit operations"); + +/* Test 3: String concatenation */ +_Static_assert(1, "multi" "ple " "strings"); + +/* Test 4: In struct */ +struct test { + int x; + _Static_assert(sizeof(int) == 4, "struct scope"); +}; + +/* Test 5: Macro with _Static_assert */ +#define CHECK_SIZE(type, max_size) \ + _Static_assert(sizeof(type) <= max_size, "type too large") + +CHECK_SIZE(int, 8); + +int main() { + /* Test 6: Function scope */ + _Static_assert(sizeof(void*) >= sizeof(int), "pointer size"); + + /* Test 7: In statement context (like in a macro) */ + do { + _Static_assert(sizeof(char) == 1, "char is 1 byte"); + printf("Test 7: Statement context assertion passed\n"); + } while (0); + + printf("All static assertions passed!\n"); + return 0; +} diff --git a/tests/tests2/100_static_assert.expect b/tests/tests2/100_static_assert.expect new file mode 100644 index 0000000000..08389da729 --- /dev/null +++ b/tests/tests2/100_static_assert.expect @@ -0,0 +1,2 @@ +Test 7: Statement context assertion passed +All static assertions passed!