Some more subtle issues with code suppression: - outputting asms but not their operand setup is broken - but global asms must always be output - statement expressions are transparent to code suppression - vtop can't be transformed from VT_CMP/VT_JMP when nocode_wanted Also remove .exe files from tests2 if they don't fail.
Unified · Split
| @@ -32,7 +32,7 @@ ST_FUNC int asm_get_local_label_name(TCCState *s1, unsigned int n) · 31 unmodified lines | ||
| 32 | 32 | } |
| 33 | 33 | |
| 34 | 34 | ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe); |
| 35 | −static int tcc_assemble_internal(TCCState *s1, int do_preprocess); | |
| 35 | +static int tcc_assemble_internal(TCCState *s1, int do_preprocess, int global); | |
| 36 | 36 | static Sym sym_dot; |
| 37 | 37 | |
| 38 | 38 | /* Return a symbol we can use inside the assembler, having name NAME. |
| @@ -462,7 +462,7 @@ static void pop_section(TCCState *s1) · 423 unmodified lines | ||
| 462 | 462 | use_section1(s1, prev); |
| 463 | 463 | } |
| 464 | 464 | |
| 465 | −static void asm_parse_directive(TCCState *s1) | |
| 465 | +static void asm_parse_directive(TCCState *s1, int global) | |
| 466 | 466 | { |
| 467 | 467 | int n, offset, v, size, tok1; |
| 468 | 468 | Section *sec; |
| @@ -644,7 +644,8 @@ static void asm_parse_directive(TCCState *s1) · 175 unmodified lines | ||
| 644 | 644 | save_parse_state(&saved_parse_state); |
| 645 | 645 | begin_macro(init_str, 1); |
| 646 | 646 | while (repeat-- > 0) { |
| 647 | − tcc_assemble_internal(s1, (parse_flags & PARSE_FLAG_PREPROCESS)); | |
| 647 | + tcc_assemble_internal(s1, (parse_flags & PARSE_FLAG_PREPROCESS), | |
| 648 | + global); | |
| 648 | 649 | macro_ptr = init_str->str; |
| 649 | 650 | } |
| 650 | 651 | end_macro(); |
| @@ -913,14 +914,10 @@ static void asm_parse_directive(TCCState *s1) · 262 unmodified lines | ||
| 913 | 914 | |
| 914 | 915 | |
| 915 | 916 | /* assemble a file */ |
| 916 | −static int tcc_assemble_internal(TCCState *s1, int do_preprocess) | |
| 917 | +static int tcc_assemble_internal(TCCState *s1, int do_preprocess, int global) | |
| 917 | 918 | { |
| 918 | − int saved_nocode_wanted; | |
| 919 | 919 | int opcode; |
| 920 | 920 | |
| 921 | − saved_nocode_wanted = nocode_wanted; | |
| 922 | − nocode_wanted = 0; | |
| 923 | − | |
| 924 | 921 | /* XXX: undefine C labels */ |
| 925 | 922 | |
| 926 | 923 | ch = file->buf_ptr[0]; |
| @@ -940,7 +937,7 @@ static int tcc_assemble_internal(TCCState *s1, int do_preprocess) · 13 unmodified lines | ||
| 940 | 937 | while (tok != TOK_LINEFEED) |
| 941 | 938 | next(); |
| 942 | 939 | } else if (tok >= TOK_ASMDIR_FIRST && tok <= TOK_ASMDIR_LAST) { |
| 943 | − asm_parse_directive(s1); | |
| 940 | + asm_parse_directive(s1, global); | |
| 944 | 941 | } else if (tok == TOK_PPNUM) { |
| 945 | 942 | Sym *sym; |
| 946 | 943 | const char *p; |
| @@ -964,7 +961,7 @@ static int tcc_assemble_internal(TCCState *s1, int do_preprocess) · 17 unmodified lines | ||
| 964 | 961 | /* handle "extern void vide(void); __asm__("vide: ret");" as |
| 965 | 962 | "__asm__("globl vide\nvide: ret");" */ |
| 966 | 963 | Sym *sym = sym_find(opcode); |
| 967 | − if (sym && (sym->type.t & VT_EXTERN) && saved_nocode_wanted) { | |
| 964 | + if (sym && (sym->type.t & VT_EXTERN) && global) { | |
| 968 | 965 | sym = label_find(opcode); |
| 969 | 966 | if (!sym) { |
| 970 | 967 | sym = label_push(&s1->asm_labels, opcode, 0); |
| @@ -993,7 +990,6 @@ static int tcc_assemble_internal(TCCState *s1, int do_preprocess) · 22 unmodified lines | ||
| 993 | 990 | |
| 994 | 991 | asm_free_labels(s1); |
| 995 | 992 | |
| 996 | − nocode_wanted = saved_nocode_wanted; | |
| 997 | 993 | return 0; |
| 998 | 994 | } |
| 999 | 995 | |
| @@ -1017,7 +1013,7 @@ ST_FUNC int tcc_assemble(TCCState *s1, int do_preprocess) · 17 unmodified lines | ||
| 1017 | 1013 | ELFW(ST_INFO)(STB_LOCAL, STT_FILE), 0, |
| 1018 | 1014 | SHN_ABS, file->filename); |
| 1019 | 1015 | |
| 1020 | − ret = tcc_assemble_internal(s1, do_preprocess); | |
| 1016 | + ret = tcc_assemble_internal(s1, do_preprocess, 1); | |
| 1021 | 1017 | |
| 1022 | 1018 | cur_text_section->data_offset = ind; |
| 1023 | 1019 | |
| @@ -1032,7 +1028,7 @@ ST_FUNC int tcc_assemble(TCCState *s1, int do_preprocess) · 8 unmodified lines | ||
| 1032 | 1028 | /* assemble the string 'str' in the current C compilation unit without |
| 1033 | 1029 | C preprocessing. NOTE: str is modified by modifying the '\0' at the |
| 1034 | 1030 | end */ |
| 1035 | −static void tcc_assemble_inline(TCCState *s1, char *str, int len) | |
| 1031 | +static void tcc_assemble_inline(TCCState *s1, char *str, int len, int global) | |
| 1036 | 1032 | { |
| 1037 | 1033 | int saved_parse_flags; |
| 1038 | 1034 | const int *saved_macro_ptr; |
| @@ -1044,7 +1040,7 @@ static void tcc_assemble_inline(TCCState *s1, char *str, int len) · 5 unmodified lines | ||
| 1044 | 1040 | memcpy(file->buffer, str, len); |
| 1045 | 1041 | |
| 1046 | 1042 | macro_ptr = NULL; |
| 1047 | − tcc_assemble_internal(s1, 0); | |
| 1043 | + tcc_assemble_internal(s1, 0, global); | |
| 1048 | 1044 | tcc_close(); |
| 1049 | 1045 | |
| 1050 | 1046 | parse_flags = saved_parse_flags; |
| @@ -1277,7 +1273,7 @@ ST_FUNC void asm_instr(void) · 226 unmodified lines | ||
| 1277 | 1273 | clobber_regs, out_reg); |
| 1278 | 1274 | |
| 1279 | 1275 | /* assemble the string with tcc internal assembler */ |
| 1280 | − tcc_assemble_inline(tcc_state, astr1.data, astr1.size - 1); | |
| 1276 | + tcc_assemble_inline(tcc_state, astr1.data, astr1.size - 1, 0); | |
| 1281 | 1277 | |
| 1282 | 1278 | /* restore the current C token */ |
| 1283 | 1279 | next(); |
| @@ -1299,7 +1295,10 @@ ST_FUNC void asm_instr(void) · 15 unmodified lines | ||
| 1299 | 1295 | ST_FUNC void asm_global_instr(void) |
| 1300 | 1296 | { |
| 1301 | 1297 | CString astr; |
| 1298 | + int saved_nocode_wanted = nocode_wanted; | |
| 1302 | 1299 | |
| 1300 | + /* Global asm blocks are always emitted. */ | |
| 1301 | + nocode_wanted = 0; | |
| 1303 | 1302 | next(); |
| 1304 | 1303 | parse_asm_str(&astr); |
| 1305 | 1304 | skip(')'); |
| @@ -1315,7 +1314,7 @@ ST_FUNC void asm_global_instr(void) · 9 unmodified lines | ||
| 1315 | 1314 | ind = cur_text_section->data_offset; |
| 1316 | 1315 | |
| 1317 | 1316 | /* assemble the string with tcc internal assembler */ |
| 1318 | − tcc_assemble_inline(tcc_state, astr.data, astr.size - 1); | |
| 1317 | + tcc_assemble_inline(tcc_state, astr.data, astr.size - 1, 1); | |
| 1319 | 1318 | |
| 1320 | 1319 | cur_text_section->data_offset = ind; |
| 1321 | 1320 | |
| @@ -1323,5 +1322,6 @@ ST_FUNC void asm_global_instr(void) · 1 unmodified line | ||
| 1323 | 1322 | next(); |
| 1324 | 1323 | |
| 1325 | 1324 | cstr_free(&astr); |
| 1325 | + nocode_wanted = saved_nocode_wanted; | |
| 1326 | 1326 | } |
| 1327 | 1327 | #endif /* CONFIG_TCC_ASM */ |
| @@ -575,8 +575,16 @@ static void vsetc(CType *type, int r, CValue *vc) · 574 unmodified lines | ||
| 575 | 575 | tcc_error("memory full (vstack)"); |
| 576 | 576 | /* cannot let cpu flags if other instruction are generated. Also |
| 577 | 577 | avoid leaving VT_JMP anywhere except on the top of the stack |
| 578 | − because it would complicate the code generator. */ | |
| 579 | − if (vtop >= vstack) { | |
| 578 | + because it would complicate the code generator. | |
| 579 | + | |
| 580 | + Don't do this when nocode_wanted. vtop might come from | |
| 581 | + !nocode_wanted regions (see 88_codeopt.c) and transforming | |
| 582 | + it to a register without actually generating code is wrong | |
| 583 | + as their value might still be used for real. All values | |
| 584 | + we push under nocode_wanted will eventually be popped | |
| 585 | + again, so that the VT_CMP/VT_JMP value will be in vtop | |
| 586 | + when code is unsuppressed again. */ | |
| 587 | + if (vtop >= vstack && !nocode_wanted) { | |
| 580 | 588 | v = vtop->r & VT_VALMASK; |
| 581 | 589 | if (v == VT_CMP || (v & ~1) == VT_JMP) |
| 582 | 590 | gv(RC_INT); |
| @@ -4367,13 +4375,18 @@ ST_FUNC void unary(void) · 3784 unmodified lines | ||
| 4367 | 4375 | gen_cast(&type); |
| 4368 | 4376 | } |
| 4369 | 4377 | } else if (tok == '{') { |
| 4378 | + int saved_nocode_wanted = nocode_wanted; | |
| 4370 | 4379 | if (const_wanted) |
| 4371 | 4380 | tcc_error("expected constant"); |
| 4372 | 4381 | /* save all registers */ |
| 4373 | 4382 | save_regs(0); |
| 4374 | 4383 | /* statement expression : we do not accept break/continue |
| 4375 | − inside as GCC does */ | |
| 4384 | + inside as GCC does. We do retain the nocode_wanted state, | |
| 4385 | + as statement expressions can't ever be entered from the | |
| 4386 | + outside, so any reactivation of code emission (from labels | |
| 4387 | + or loop heads) can be disabled again after the end of it. */ | |
| 4376 | 4388 | block(NULL, NULL, 1); |
| 4389 | + nocode_wanted = saved_nocode_wanted; | |
| 4377 | 4390 | skip(')'); |
| 4378 | 4391 | } else { |
| 4379 | 4392 | gexpr(); |
| @@ -1404,6 +1404,15 @@ void optimize_out(void) · 1403 unmodified lines | ||
| 1404 | 1404 | if (defined_function() && 0) |
| 1405 | 1405 | refer_to_undefined(); |
| 1406 | 1406 | |
| 1407 | + if (0) { | |
| 1408 | + (void)sizeof( ({ | |
| 1409 | + do { } while (0); | |
| 1410 | + 0; | |
| 1411 | + }) ); | |
| 1412 | + undefined_function(); | |
| 1413 | + } | |
| 1414 | + | |
| 1415 | + /* Leave the "if(1)return; printf()" in this order and last in the function */ | |
| 1407 | 1416 | if (1) |
| 1408 | 1417 | return; |
| 1409 | 1418 | printf ("oor:%d\n", undefined_function()); |
| @@ -3251,6 +3260,28 @@ void trace_console(long len, long len2) · 1841 unmodified lines | ||
| 3251 | 3260 | } |
| 3252 | 3261 | #endif |
| 3253 | 3262 | } |
| 3263 | + | |
| 3264 | +void test_asm_dead_code(void) | |
| 3265 | +{ | |
| 3266 | + long rdi; | |
| 3267 | + /* Try to make sure that xdi contains a zero, and hence will | |
| 3268 | + lead to a segfault if the next asm is evaluated without | |
| 3269 | + arguments being set up. */ | |
| 3270 | + asm volatile ("" : "=D" (rdi) : "0" (0)); | |
| 3271 | + (void)sizeof (({ | |
| 3272 | + int var; | |
| 3273 | + /* This shouldn't trigger a segfault, either the argument | |
| 3274 | + registers need to be set up and the asm emitted despite | |
| 3275 | + this being in an unevaluated context, or both the argument | |
| 3276 | + setup _and_ the asm emission need to be suppressed. The latter | |
| 3277 | + is better. Disabling asm code gen when suppression is on | |
| 3278 | + also fixes the above trace_console bug, but that came earlier | |
| 3279 | + than asm suppression. */ | |
| 3280 | + asm volatile ("movl $0,(%0)" : : "D" (&var) : "memory"); | |
| 3281 | + var; | |
| 3282 | + })); | |
| 3283 | +} | |
| 3284 | + | |
| 3254 | 3285 | void asm_test(void) |
| 3255 | 3286 | { |
| 3256 | 3287 | char buf[128]; |
| @@ -3334,6 +3365,7 @@ void asm_test(void) · 77 unmodified lines | ||
| 3334 | 3365 | printf ("regvar=%x\n", regvar); |
| 3335 | 3366 | test_high_clobbers(); |
| 3336 | 3367 | trace_console(8, 8); |
| 3368 | + test_asm_dead_code(); | |
| 3337 | 3369 | return; |
| 3338 | 3370 | label1: |
| 3339 | 3371 | goto label2; |
| @@ -1,7 +1,9 @@ | ||
| 1 | +extern int printf (const char *, ...); | |
| 1 | 2 | extern void vide(void); |
| 2 | 3 | __asm__("vide: ret"); |
| 3 | 4 | |
| 4 | 5 | int main() { |
| 5 | 6 | vide(); |
| 7 | + printf ("okay\n"); | |
| 6 | 8 | return 0; |
| 7 | 9 | } |
| @@ -0,0 +1 @@ | ||
| 1 | +okay | |
| @@ -0,0 +1,68 @@ | ||
| 1 | +/* Check some way in where code suppression caused various | |
| 2 | + miscompilations. */ | |
| 3 | +extern int printf (const char *, ...); | |
| 4 | +typedef unsigned long size_t; | |
| 5 | + | |
| 6 | +size_t _brk_start, _brk_end; | |
| 7 | +void * extend_brk(size_t size, size_t align) | |
| 8 | +{ | |
| 9 | + size_t mask = align - 1; | |
| 10 | + void *ret = 0; | |
| 11 | + | |
| 12 | + do { | |
| 13 | + if (__builtin_expect(!!(_brk_start == 0), 0)) | |
| 14 | + do { | |
| 15 | + printf("wrong1\n"); | |
| 16 | + } while (0); | |
| 17 | + } while (0); | |
| 18 | + _brk_end = (_brk_end + mask) & ~mask; | |
| 19 | + ret = (void *)_brk_end; | |
| 20 | + _brk_end += size; | |
| 21 | + | |
| 22 | + return ret; | |
| 23 | +} | |
| 24 | + | |
| 25 | +static void get_args (int a, int b) | |
| 26 | +{ | |
| 27 | + if (a != 1) | |
| 28 | + printf("wrong2\n"); | |
| 29 | + else | |
| 30 | + printf("okay\n"); | |
| 31 | +} | |
| 32 | + | |
| 33 | +void bla(void) | |
| 34 | +{ | |
| 35 | + int __ret = 42; | |
| 36 | + ({ | |
| 37 | + if (__builtin_expect(!!(0), 0)) { | |
| 38 | + if (__builtin_expect(!!__ret, 0)) | |
| 39 | + printf("wrong3\n"); | |
| 40 | + int x = !!(__ret); | |
| 41 | + } | |
| 42 | + __ret; | |
| 43 | + }); | |
| 44 | + get_args(!!__ret, sizeof(__ret)); | |
| 45 | +} | |
| 46 | + | |
| 47 | +_Bool chk(unsigned long addr, unsigned long limit, unsigned long size) | |
| 48 | +{ | |
| 49 | + _Bool ret; | |
| 50 | + /* This just needs to compile, no runtime test. (And it doesn't compile | |
| 51 | + only with certain internal checking added that's not committed). */ | |
| 52 | + if (0) | |
| 53 | + ret = 0 != (!!(addr > limit - size)); | |
| 54 | +} | |
| 55 | + | |
| 56 | +int main() | |
| 57 | +{ | |
| 58 | + void *r; | |
| 59 | + _brk_start = 1024; | |
| 60 | + _brk_end = 1024; | |
| 61 | + r = extend_brk (4096, 16); | |
| 62 | + if (!r) | |
| 63 | + printf("wrong4\n"); | |
| 64 | + else | |
| 65 | + printf("okay\n"); | |
| 66 | + bla(); | |
| 67 | + return 0; | |
| 68 | +} | |
| @@ -0,0 +1,2 @@ | ||
| 1 | +okay | |
| 2 | +okay | |
| @@ -59,7 +59,7 @@ all test: $(filter-out $(SKIP),$(TESTS)) · 58 unmodified lines | ||
| 59 | 59 | $(TCC) $< -o ./$*.exe $(FILTER) 2>&1 && \ |
| 60 | 60 | ./$*.exe $(ARGS) >$*.output 2>&1 || true; \ |
| 61 | 61 | fi |
| 62 | − @diff -Nbu $(SRC)/$*.expect $*.output && rm -f $*.output | |
| 62 | + @diff -Nbu $(SRC)/$*.expect $*.output && rm -f $*.output $*.exe | |
| 63 | 63 | |
| 64 | 64 | # automatically generate .expect files with gcc: |
| 65 | 65 | %.expect : %.c |