reproduce bug:
$ ./configure --cc=gcc
$ make
$ make install
(OK)
run a test:
extern int printf(const char *str, ...);
int main()
{
int t2 = (int)(-1.847759065f * 4096);
printf("%d\n", t2);
}
$ tcc test.c
$ ./a.out
$ -7568
(OK)
(self compiled now)
$ ./configure --cc=tcc
$ make
$ make install
(OK)
$ tcc test.c
$ ./a.out
$ 7568
(WRONG!!!)
why:
gcc does not have intristics for
uint to long double conversion
therefore it does cast implicitly, so
the sign bit is preserved, but this does
not happen when __fixunsxfdi is called
because tcc was bootstrapped.
solution:
force cast to int64 and preserve the
sign bit.
side effects:
not found.
This allows for example this scenario:
- A dll to be linked with is specified in file.c, where file.c
and the dll exist in the same directory:
#pragma comment(lib, "txml")
#pragma comment(option, "-L{f}")
- tcc is called to run file.c from other, varying directories:
$ tcc -run some/dir/file.c <args...>
Note that tcc replaces {f} by the currently compiled file's
directory ('some/dir' in this example).
Also:
- tccgen.c: fix last commit for gen_cast.
Please respect some conventions:
- tests2 filenames don't end with '..._test'
- tests2 tests are meant to produce some output
- the output should be somehow informative, not just
"error" or "dummy". Because other people would want to
know where it fails if it does.
- tests2 tests should work with both GCC and TCC, except
if there are specifc reasons (like testing tcc-only
feature such as bounds checking)
- tests2 tests should never crash or abort. Because that
would cause gui dialogs to pop up on windows, and because
other people would not know where it fails if it does.
- tests2 tests should be somehow specific, in general.
(rather than just collections of random stuff)
- in general, do not use 'long' if you mean 'larger than int'
Because it isn't on many platforms.
- use four (4) spaces for block indention. Do not insert
tab characters in files if possible.
Also:
- tccgen.c:gen_cast() simplify last fix.
Providing both run-time and compile-time control for bounds
checking as an user interface appears unnecessary and confusing.
Also:
- replace 'bound_...' by 'bounds_...' for consistency
- tcc-doc: put related info into one place and cleanup
The __bounds_checking(x) function is still missing explanation.
(I.e. what happens if the accumulated value drops below zero.)
The switch/case operation was entirely performed on int64_t, resulting
in a warning and bad code to be emitted on 64 bit machines when used on
an unsigned long with a case range whose signed representation starts
positive and ends negative like in the example below:
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int nbdg(unsigned long n)
{
switch (n) {
case 1UL ... 9UL: return 1;
case 10UL ... 99UL: return 2;
case 100UL ... 999UL: return 3;
case 1000UL ... 9999UL: return 4;
case 10000UL ... 99999UL: return 5;
case 100000UL ... 999999UL: return 6;
case 1000000UL ... 9999999UL: return 7;
case 10000000UL ... 99999999UL: return 8;
case 100000000UL ... 999999999UL: return 9;
case 1000000000UL ... 9999999999UL: return 10;
case 10000000000UL ... 99999999999UL: return 11;
case 100000000000UL ... 999999999999UL: return 12;
case 1000000000000UL ... 9999999999999UL: return 13;
case 10000000000000UL ... 99999999999999UL: return 14;
case 100000000000000UL ... 999999999999999UL: return 15;
case 1000000000000000UL ... 9999999999999999UL: return 16;
case 10000000000000000UL ... 99999999999999999UL: return 17;
case 100000000000000000UL ... 999999999999999999UL: return 18;
case 1000000000000000000UL ... 9999999999999999999UL: return 19; // this one
case 10000000000000000000UL ... ULONG_MAX: return 20;
}
return 0;
}
int main(int argc, char **argv)
{
unsigned long v = strtoul(argc > 1 ? argv[1] : "1111", NULL, 0);
printf("%lu : %d\n", v, nbdg(v));
return 0;
}
$ tcc dg.c
dg.c:26: warning: empty case range
$ x="";for i in 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0; do x=$x$i; ./a.out $x;done
1 : 1
12 : 2
123 : 3
1234 : 4
12345 : 5
123456 : 6
1234567 : 7
12345678 : 8
123456789 : 9
1234567890 : 10
12345678901 : 11
123456789012 : 12
1234567890123 : 13
12345678901234 : 14
123456789012345 : 15
1234567890123456 : 16
12345678901234567 : 17
123456789012345678 : 18
1234567890123456789 : 0
12345678901234567890 : 20
What this patch does is to use a separate set of signed and unsigned
case_cmp functions depending on whether the expression is signed or
unsigned, and also does this to decide when to emit the warning.
The bad code on output was caused by the removal of the unsigned bit
resulting from the signed sort, which causes only signed comparisons
to be emitted in the asm code. As such some sets could not match.
Note that there is no way to rely on the values only to sort properly
nor to emit the warning because we're effectively dealing with 65-bit
arithmetic here and any two values will have a different behavior
depending on the signed or unsigned expectation.
For unsigned expressions now the warning only happens when bounds are
switched, For signed expressions (e.g. if the input is signed long
above), the warning remains and the abnormal output as well. In both
cases this remains consistent with what gcc produces.
tcctok.h:
- Add CONFIG_TCC_BCHECK arround TOK_NO_BOUND_CHECK1/TOK_NO_BOUND_CHECK2
tccgen.c:
- Add CONFIG_TCC_BCHECK arround TOK_NO_BOUND_CHECK1/TOK_NO_BOUND_CHECK2
- Undo alias definition in tccpp.c when function bound checking if off
tests/tests2/114_bound_signal.c:
- Test alias undo
- fix sleep problem
tccgen.c:
- cleanup __builtin_... stuff
- merge __attribute((alias("sym"))) with __asm__("sym")
Now one cannot have both, however for alias underscores are
added if enabled. For __asm__ they aren't.
tccpp.c:
- extend tcc_predefs accordingly. Was generated with
'cd tests/misc && tcc -run c2str.c tcc_predef.h tcc_predefs'
xxx-gen.c:
- move bcheck setjmp test to tccgen.c:gbound_args()
i386-gen.c:
- create win32 compatible stack space for big structures
tcctest.c:
- some cleanup + nicer output
- configure/Makefile : cleanup, really use CC_NAME
- tccasm.c : remove C99 construct that MSVC doesn't compile
- arm-gen.c, x86_64-gen.c, riscv64-gen.c, tccmacho.c : ditto
- arm64-gen.c: commit 383acf8eff wrote:
"Instead of a cast, it would be better to pass the exact type."
It is true that there are better solutions but it is not
passing the exact type (I think).
- tcctest.c: revert "fix cast test for clang" 03646ad46f
this obviously wants to test non-portable conversions
- 114_bound_signal.test: clock_nanosleep is too new for older
linuxes, just use sleep() instead
some linux programs (kernel) really want to have 'extern inline'
functions be visible from other units, i.e. not be static, but also
mark them as always_inline. That's -fgnu89-inline semantics, so it's
fine, but we don't (yet) implement this, so we can't make them static
just so. But we do need this hack on MacOS due to some uses in
system headers (see commit f18f8651).
So, for now conditionalize the hack on Mach-O.
this is needed for multi-file testcases using stdio.h, as
the __sputc function is implemented as a extern inline
function (with gnu_inline attribute, but we don't support that for now).
Without this change that leads to multiply defined symbols when using
multiple units including stdio.h.
It also has an always_inline attribute, which we can use to guide our
behaviour, as in ISO-C an always_inline can't be defined with ISO
'extern inline' semantics. This is the minimal change and not a full
implementation of GNU inline semantics, which would require thorough
testcases.
If __clang__ would be defined the header would make use of C99 semantics,
which would work for us. It would also do that if _GNUC_ wouldn't be
defined. But we can't do the latter (as the whole MacOSX SDK refuses
to be compiled with anything not defining that). I haven't tested
defining __clang__, but suspect that's going to be problematic.
this does generate a working executable for a very simple
example input, e.g. this:
% cat simple.c
int main(void)
{
return 0;
}
% ./tcc -B. -c simple.c
% ./tcc -nostdlib -B. simple.o -lc
% ./a.out && echo okay
okay
(the -lc is actually not necessary right now, see below). This
has many limitations:
* no symbol table, hence no calls to external functions from
e.g. libc, aka libSystemB
* no proper entry point (should be main, but is hardcoded to first
real .text address)
* libSystemB is hardcoded, no other libs are supported (but again
no external calls anyway)
* generated Mach-O executable is in old format: neither LC_DYLD_INFO
no export tries for symbols are created (no symbol table at all!)
* the __LINKEDIT segment is faked and empty, as dyld doesn't like
it empty even if no symbols point into it
* same with __DATA, dyld wants a non-empty writable segment which
we enforce with useless data
* no relocations, hence no function call stubs (lazy or not) are
generated
* hardcodes some other constants as well
- call TOK_GET() as a function only for tokens with values
- get rid of 'next_nomacro_spc()'
- be sligtly more efficient in next()
This made about 4-5%+ speed in my tests.
Also: tcc.h: reorder tokens
Add code for VT_FUNC.
Use octal number for unsigned int/unsigned long for 32 bits targets.
Add VT_BYTE | VT_UNSIGNED for targets with default unsigned signed char.
Remove extra ',' in default_debug struct.
Checked on:
- i386/x86_64 (linux/windows)
- arm/arm64 (rapberry pi)
- riscv64 (simulator)
Not tested for arm softfloat because raspberry pi does not support it.
Modifications:
Makefile:
add arm-asm.c to arm64_FILES
add riscv64-asm.c (new file) to riscv64_FILES
lib/Makefile:
add fetch_and_add_arm.o(new file) to ARM_O
add fetch_and_add_arm64.o(new file) to ARM64_O
add fetch_and_add_riscv64.o(new file) to RISCV64_O
add $(BCHECK_O) to OBJ-arm/OBJ-arm64/OBJ-riscv64
tcc.h:
Enable CONFIG_TCC_BCHECK for arm32/arm64/riscv64
Add arm-asm.c, riscv64-asm.c
tcctok.h:
for arm use memmove4 instead of memcpy4
for arm use memmove8 instead of memcpy8
tccgen.c:
put_extern_sym2: for arm check memcpy/memmove/memset/memmove4/memmove8
only use alloca for i386/x86_64
for arm use memmove4 instead of memcpy4
for arm use memmove8 instead of memcpy8
fix builtin_frame_address/builtin_return_address for arm/riscv64
tccrun.c:
Add riscv64 support
fix rt_getcontext/rt_get_caller_pc for arm
tccelf.c:
tcc_load_dll: Print filename for bad architecture
libtcc.c:
add arm-asm.c/riscv64-asm.c
tcc-doc.texi:
Add arm, arm64, riscv64 support for bound checking
lib/bcheck.c:
add __bound___aeabi_memcpy/__bound___aeabi_memmove
__bound___aeabi_memmove4/__bound___aeabi_memmove8
__bound___aeabi_memset for arm
call fetch_and_add_arm/fetch_and_add_arm64/fetch_and_add_riscv64
__bound_init: Fix type for start/end/ad
__bound_malloc/__bound_memalign/__bound_realloc/__bound_calloc: Use size + 1
arm-gen.c:
add bound checking code like i386/x86_64
assign_regs: only malloc if nb_args != 0
gen_opi/gen_opf: Fix reload problems
arm-link.c:
relocate_plt: Fix address calculating
arm64-gen.c:
add bound checking code like i386/x86_64
load/store: remove VT_BOUNDED from sv->r
arm64_hfa_aux/arm64_hfa_aux: Fix array code
gfunc_prolog: only malloc if n != 0
arm64-link.c:
code_reloc/gotplt_entry_type/relocate: add R_AARCH64_LDST64_ABS_LO12_NC
relocate: Use addXXle instead of writeXXle
riscv64-gen.c:
add bound checking code like i386/x86_64
add NB_ASM_REGS/CONFIG_TCC_ASM
riscv64-link.c:
relocate: Use addXXle instead of writeXXle
i386-gen.c/x86_64-gen.c
gen_bounds_epilog: Fix code (unrelated)
tests/Makefile:
add $(BTESTS) for arm/arm64/riscv64
tests/tests2/Makefile:
Use 85 only on i386/x86_64 because of asm code
Use 113 only on i386/x86_64 because of DLL code
Add 112/114/115/116 for arm/arm64/riscv64
Fix FILTER (failed on riscv64)
tests/boundtest.c:
Only use alloca for i386/x86_64
an enum must be compatible with one or more integer type,
so adjust the test accordingly. That means the following
redeclarations should work:
enum e6 { E1 = -1, E0 };
void f3(enum e6);
void f3(int); // should work as int and e6 are compatible
while the following should not:
void f4(enum e6 e);
void f4(unsigned e); // should error as unsigned and e6 are incompatible
i386-gen.c:
- load/gen_opf: set v1.sym to NULL
lib/Makefile:
- Add -gstabs -fno-omit-frame-pointer -Wno-unused-function -Wno-unused-variable
lib/bt-log.c:
- tcc_backtrace: Prevent __builtin_frame_address warning
tccgen.c:
- struct_layout: Set t.t to VT_BYTE
- default_debug: Use octal instead of -1 to make size_t work
tccpp.c:
- tal_realloc_impl: Only memcpy when p set
x86_64-gen.c:
- gen_bounds_epilog: Do not save/restore rcx (not caller/callee saved)
This also made stack not aligned to 16 bytes.
tcctok.h:
- Add __bound_setjmp/setjmp/_setjmp/longjmp
tccgen.c:
- redirect setjmp/longjmp to bcheck.c code
i386-gen.c/x86_64-gen.c
- Change func_bound_alloca_used into func_bound_add_epilog
- Set func_bound_add_epilog also when setjmp is called
bcheck.c:
- Add __bound_setjmp/__bound_longjmp
- __bound_local_delete: remove setjmp if used in function
- __bound_exit: clear setjmp list and print statistic
- make malloc_redir more readable (unrelated)
New testcases:
- 115_bound_setjmp
- 116_bound_setjmp2
Merge function attributes with those given given for the
prototype, also handle post-decl appearance such as
void func() __attribute__((noreturn))
{
}
Also, some test fixes (unrelated).
from 3e731e3a78
tccgen.c:
- make 'struct default_debug' const
- pass TCCState* as parameter to tcc_debug_xxx functions
- always check tcc_state->do_debug before calling functions
- factor out tcc_debug_extern_sym()
- remove formats "%lld"/"%llu" (not reliable on windows)
xxx-gen files:
- set func_vt/var from caller
This allows debugging with variables/structs/unions/enums/bitfields.
Add new functions:
- tcc_debug_stabs: store stabs debug info
- tcc_debug_stabn: store stabn debug info
- tcc_get_debug_info: generate stabs debug info
- tcc_debug_finish: store debug info in debug section
- tcc_add_debug_info: Add function debug info
Update functions:
- tcc_debug_end: free debug hash
- tcc_debug_funcstart: Generate correct function debug info
- tcc_debug_funcend: Finish debug info
- put_extern_sym2: Generate debug info for symbols
- pop_local_syms: Add debug info
- prev_scope: Add local symbols
This makes available the __builtin_va_list type and __builtin variants
of va_start, va_arg, va_copy and va_end. We do this via a header file
that's prepended to all compilations always (except if merely
preprocessing): tcc_predefs.h. That header could also be used
for predefining other builtins in the future.
We don't need the define hacks for musl anymore with this.
Also fix x86_64 gfunc_prologue to reserve enoug space for the
full va_list structure, not just 16 bytes.
as there's overlap between handling types for binary and ternay
operations. Factor this into a single routine (combine_types).
This uses the structure that gen_op was following, and expr_cond
was using as well in the past, which I find easier to reconvene
with the standard language. But it reuses the new functions for
diagnostics to improve (a little) on what GCC or clang produce :)
On windows. there is no long double really IOW it is the
same as double. However setting the VT_LONG flag in
combination with VT_DOUBLE allows to keep track of the
original type for the purpose of '_Generic() or more
accurate type warnings.
Fix static assert to support literal string instead of just printing
the sring of the current token as it use to be
so we can now use _Static_assert(0, "0" "1") which will print
__FILE__ __LINE__ error: 01
- revert const-folding in gvtst() and put it back into
expr_landor(). Although it did make sense, one reason
not to do it is __builtin_constant_p() which may return
true when it shouldn't because of nocode_wanted, see test.
- tccgen_init() can do init_prec(), also for tcc -E.
- for nostalgic reasons, keep the original expression parser
functions in the source.
- Makefile: remove stale stuff
so that it also is called from the precedence parser. This
is complicated by the fact that something needs to be done before
the second operand is parsed in a single pass compiler, so it
doesn't quite fit into expr_infix itself. It turns out the smallest
code changes result when expr_landor remains separate. But it can
be tidied a bit.
This is smaller and uses less stack depth per expression (eight function
calls from expr_or to get down to a unary). It's a tiny bit faster
depending on how good the branch predictor is, on my machine a wash.