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 | ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe); | 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 | static Sym sym_dot; | 36 | static Sym sym_dot; |
| 37 | 37 | ||
| 38 | /* Return a symbol we can use inside the assembler, having name NAME. | 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 | use_section1(s1, prev); | 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 | int n, offset, v, size, tok1; | 467 | int n, offset, v, size, tok1; |
| 468 | Section *sec; | 468 | Section *sec; |
| @@ -644,7 +644,8 @@ static void asm_parse_directive(TCCState *s1) · 175 unmodified lines | |||
| 644 | save_parse_state(&saved_parse_state); | 644 | save_parse_state(&saved_parse_state); |
| 645 | begin_macro(init_str, 1); | 645 | begin_macro(init_str, 1); |
| 646 | while (repeat-- > 0) { | 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 | macro_ptr = init_str->str; | 649 | macro_ptr = init_str->str; |
| 649 | } | 650 | } |
| 650 | end_macro(); | 651 | end_macro(); |
| @@ -913,14 +914,10 @@ static void asm_parse_directive(TCCState *s1) · 262 unmodified lines | |||
| 913 | 914 | ||
| 914 | 915 | ||
| 915 | /* assemble a file */ | 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 | int opcode; | 919 | int opcode; |
| 920 | 920 | ||
| 921 | saved_nocode_wanted = nocode_wanted; | ||
| 922 | nocode_wanted = 0; | ||
| 923 | |||
| 924 | /* XXX: undefine C labels */ | 921 | /* XXX: undefine C labels */ |
| 925 | 922 | ||
| 926 | ch = file->buf_ptr[0]; | 923 | ch = file->buf_ptr[0]; |
| @@ -940,7 +937,7 @@ static int tcc_assemble_internal(TCCState *s1, int do_preprocess) · 13 unmodified lines | |||
| 940 | while (tok != TOK_LINEFEED) | 937 | while (tok != TOK_LINEFEED) |
| 941 | next(); | 938 | next(); |
| 942 | } else if (tok >= TOK_ASMDIR_FIRST && tok <= TOK_ASMDIR_LAST) { | 939 | } else if (tok >= TOK_ASMDIR_FIRST && tok <= TOK_ASMDIR_LAST) { |
| 943 | asm_parse_directive(s1); | 940 | asm_parse_directive(s1, global); |
| 944 | } else if (tok == TOK_PPNUM) { | 941 | } else if (tok == TOK_PPNUM) { |
| 945 | Sym *sym; | 942 | Sym *sym; |
| 946 | const char *p; | 943 | const char *p; |
| @@ -964,7 +961,7 @@ static int tcc_assemble_internal(TCCState *s1, int do_preprocess) · 17 unmodified lines | |||
| 964 | /* handle "extern void vide(void); __asm__("vide: ret");" as | 961 | /* handle "extern void vide(void); __asm__("vide: ret");" as |
| 965 | "__asm__("globl vide\nvide: ret");" */ | 962 | "__asm__("globl vide\nvide: ret");" */ |
| 966 | Sym *sym = sym_find(opcode); | 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 | sym = label_find(opcode); | 965 | sym = label_find(opcode); |
| 969 | if (!sym) { | 966 | if (!sym) { |
| 970 | sym = label_push(&s1->asm_labels, opcode, 0); | 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 | asm_free_labels(s1); | 991 | asm_free_labels(s1); |
| 995 | 992 | ||
| 996 | nocode_wanted = saved_nocode_wanted; | ||
| 997 | return 0; | 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 | ELFW(ST_INFO)(STB_LOCAL, STT_FILE), 0, | 1013 | ELFW(ST_INFO)(STB_LOCAL, STT_FILE), 0, |
| 1018 | SHN_ABS, file->filename); | 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 | cur_text_section->data_offset = ind; | 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 | /* assemble the string 'str' in the current C compilation unit without | 1028 | /* assemble the string 'str' in the current C compilation unit without |
| 1033 | C preprocessing. NOTE: str is modified by modifying the '\0' at the | 1029 | C preprocessing. NOTE: str is modified by modifying the '\0' at the |
| 1034 | end */ | 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 | int saved_parse_flags; | 1033 | int saved_parse_flags; |
| 1038 | const int *saved_macro_ptr; | 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 | memcpy(file->buffer, str, len); | 1040 | memcpy(file->buffer, str, len); |
| 1045 | 1041 | ||
| 1046 | macro_ptr = NULL; | 1042 | macro_ptr = NULL; |
| 1047 | tcc_assemble_internal(s1, 0); | 1043 | tcc_assemble_internal(s1, 0, global); |
| 1048 | tcc_close(); | 1044 | tcc_close(); |
| 1049 | 1045 | ||
| 1050 | parse_flags = saved_parse_flags; | 1046 | parse_flags = saved_parse_flags; |
| @@ -1277,7 +1273,7 @@ ST_FUNC void asm_instr(void) · 226 unmodified lines | |||
| 1277 | clobber_regs, out_reg); | 1273 | clobber_regs, out_reg); |
| 1278 | 1274 | ||
| 1279 | /* assemble the string with tcc internal assembler */ | 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 | /* restore the current C token */ | 1278 | /* restore the current C token */ |
| 1283 | next(); | 1279 | next(); |
| @@ -1299,7 +1295,10 @@ ST_FUNC void asm_instr(void) · 15 unmodified lines | |||
| 1299 | ST_FUNC void asm_global_instr(void) | 1295 | ST_FUNC void asm_global_instr(void) |
| 1300 | { | 1296 | { |
| 1301 | CString astr; | 1297 | CString astr; |
| 1298 | int saved_nocode_wanted = nocode_wanted; | ||
| 1302 | 1299 | ||
| 1300 | /* Global asm blocks are always emitted. */ | ||
| 1301 | nocode_wanted = 0; | ||
| 1303 | next(); | 1302 | next(); |
| 1304 | parse_asm_str(&astr); | 1303 | parse_asm_str(&astr); |
| 1305 | skip(')'); | 1304 | skip(')'); |
| @@ -1315,7 +1314,7 @@ ST_FUNC void asm_global_instr(void) · 9 unmodified lines | |||
| 1315 | ind = cur_text_section->data_offset; | 1314 | ind = cur_text_section->data_offset; |
| 1316 | 1315 | ||
| 1317 | /* assemble the string with tcc internal assembler */ | 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 | cur_text_section->data_offset = ind; | 1319 | cur_text_section->data_offset = ind; |
| 1321 | 1320 | ||
| @@ -1323,5 +1322,6 @@ ST_FUNC void asm_global_instr(void) · 1 unmodified line | |||
| 1323 | next(); | 1322 | next(); |
| 1324 | 1323 | ||
| 1325 | cstr_free(&astr); | 1324 | cstr_free(&astr); |
| 1325 | nocode_wanted = saved_nocode_wanted; | ||
| 1326 | } | 1326 | } |
| 1327 | #endif /* CONFIG_TCC_ASM */ | 1327 | #endif /* CONFIG_TCC_ASM */ |
| @@ -575,8 +575,16 @@ static void vsetc(CType *type, int r, CValue *vc) · 574 unmodified lines | |||
| 575 | tcc_error("memory full (vstack)"); | 575 | tcc_error("memory full (vstack)"); |
| 576 | /* cannot let cpu flags if other instruction are generated. Also | 576 | /* cannot let cpu flags if other instruction are generated. Also |
| 577 | avoid leaving VT_JMP anywhere except on the top of the stack | 577 | avoid leaving VT_JMP anywhere except on the top of the stack |
| 578 | because it would complicate the code generator. */ | 578 | because it would complicate the code generator. |
| 579 | if (vtop >= vstack) { | 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 | v = vtop->r & VT_VALMASK; | 588 | v = vtop->r & VT_VALMASK; |
| 581 | if (v == VT_CMP || (v & ~1) == VT_JMP) | 589 | if (v == VT_CMP || (v & ~1) == VT_JMP) |
| 582 | gv(RC_INT); | 590 | gv(RC_INT); |
| @@ -4367,13 +4375,18 @@ ST_FUNC void unary(void) · 3784 unmodified lines | |||
| 4367 | gen_cast(&type); | 4375 | gen_cast(&type); |
| 4368 | } | 4376 | } |
| 4369 | } else if (tok == '{') { | 4377 | } else if (tok == '{') { |
| 4378 | int saved_nocode_wanted = nocode_wanted; | ||
| 4370 | if (const_wanted) | 4379 | if (const_wanted) |
| 4371 | tcc_error("expected constant"); | 4380 | tcc_error("expected constant"); |
| 4372 | /* save all registers */ | 4381 | /* save all registers */ |
| 4373 | save_regs(0); | 4382 | save_regs(0); |
| 4374 | /* statement expression : we do not accept break/continue | 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 | block(NULL, NULL, 1); | 4388 | block(NULL, NULL, 1); |
| 4389 | nocode_wanted = saved_nocode_wanted; | ||
| 4377 | skip(')'); | 4390 | skip(')'); |
| 4378 | } else { | 4391 | } else { |
| 4379 | gexpr(); | 4392 | gexpr(); |
| @@ -1404,6 +1404,15 @@ void optimize_out(void) · 1403 unmodified lines | |||
| 1404 | if (defined_function() && 0) | 1404 | if (defined_function() && 0) |
| 1405 | refer_to_undefined(); | 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 | if (1) | 1416 | if (1) |
| 1408 | return; | 1417 | return; |
| 1409 | printf ("oor:%d\n", undefined_function()); | 1418 | printf ("oor:%d\n", undefined_function()); |
| @@ -3251,6 +3260,28 @@ void trace_console(long len, long len2) · 1841 unmodified lines | |||
| 3251 | } | 3260 | } |
| 3252 | #endif | 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 | void asm_test(void) | 3285 | void asm_test(void) |
| 3255 | { | 3286 | { |
| 3256 | char buf[128]; | 3287 | char buf[128]; |
| @@ -3334,6 +3365,7 @@ void asm_test(void) · 77 unmodified lines | |||
| 3334 | printf ("regvar=%x\n", regvar); | 3365 | printf ("regvar=%x\n", regvar); |
| 3335 | test_high_clobbers(); | 3366 | test_high_clobbers(); |
| 3336 | trace_console(8, 8); | 3367 | trace_console(8, 8); |
| 3368 | test_asm_dead_code(); | ||
| 3337 | return; | 3369 | return; |
| 3338 | label1: | 3370 | label1: |
| 3339 | goto label2; | 3371 | goto label2; |
| @@ -1,7 +1,9 @@ | |||
| 1 | extern int printf (const char *, ...); | ||
| 1 | extern void vide(void); | 2 | extern void vide(void); |
| 2 | __asm__("vide: ret"); | 3 | __asm__("vide: ret"); |
| 3 | 4 | ||
| 4 | int main() { | 5 | int main() { |
| 5 | vide(); | 6 | vide(); |
| 7 | printf ("okay\n"); | ||
| 6 | return 0; | 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 | $(TCC) $< -o ./$*.exe $(FILTER) 2>&1 && \ | 59 | $(TCC) $< -o ./$*.exe $(FILTER) 2>&1 && \ |
| 60 | ./$*.exe $(ARGS) >$*.output 2>&1 || true; \ | 60 | ./$*.exe $(ARGS) >$*.output 2>&1 || true; \ |
| 61 | fi | 61 | fi |
| 62 | @diff -Nbu $(SRC)/$*.expect $*.output && rm -f $*.output | 62 | @diff -Nbu $(SRC)/$*.expect $*.output && rm -f $*.output $*.exe |
| 63 | 63 | ||
| 64 | # automatically generate .expect files with gcc: | 64 | # automatically generate .expect files with gcc: |
| 65 | %.expect : %.c | 65 | %.expect : %.c |