1 | /* |
2 | * GAS like assembler for TCC |
3 | * |
4 | * Copyright (c) 2001-2004 Fabrice Bellard |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, write to the Free Software |
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
19 | */ |
20 | |
21 | #define USING_GLOBALS |
22 | #include "tcc.h" |
23 | #ifdef CONFIG_TCC_ASM |
24 | |
25 | static Section *last_text_section; /* to handle .previous asm directive */ |
26 | |
27 | ST_FUNC int asm_get_local_label_name(TCCState *s1, unsigned int n) |
28 | { |
29 | char buf[64]; |
30 | TokenSym *ts; |
31 | |
32 | snprintf(buf, sizeof(buf), "L..%u" , n); |
33 | ts = tok_alloc(buf, strlen(buf)); |
34 | return ts->tok; |
35 | } |
36 | |
37 | static int tcc_assemble_internal(TCCState *s1, int do_preprocess, int global); |
38 | static Sym* asm_new_label(TCCState *s1, int label, int is_local); |
39 | static Sym* asm_new_label1(TCCState *s1, int label, int is_local, int sh_num, int value); |
40 | |
41 | /* If a C name has an _ prepended then only asm labels that start |
42 | with _ are representable in C, by removing the first _. ASM names |
43 | without _ at the beginning don't correspond to C names, but we use |
44 | the global C symbol table to track ASM names as well, so we need to |
45 | transform those into ones that don't conflict with a C name, |
46 | so prepend a '.' for them, but force the ELF asm name to be set. */ |
47 | static int asm2cname(int v, int *addeddot) |
48 | { |
49 | const char *name; |
50 | *addeddot = 0; |
51 | if (!tcc_state->leading_underscore) |
52 | return v; |
53 | name = get_tok_str(v, NULL); |
54 | if (!name) |
55 | return v; |
56 | if (name[0] == '_') { |
57 | v = tok_alloc(name + 1, strlen(name) - 1)->tok; |
58 | } else if (!strchr(name, '.')) { |
59 | int n = strlen(name) + 2; |
60 | char newname[256]; |
61 | snprintf(newname, sizeof newname, ".%s" , name); |
62 | v = tok_alloc(newname, n - 1)->tok; |
63 | *addeddot = 1; |
64 | } |
65 | return v; |
66 | } |
67 | |
68 | static Sym *asm_label_find(int v) |
69 | { |
70 | Sym *sym; |
71 | int addeddot; |
72 | v = asm2cname(v, &addeddot); |
73 | sym = sym_find(v); |
74 | while (sym && sym->sym_scope && !(sym->type.t & VT_STATIC)) |
75 | sym = sym->prev_tok; |
76 | return sym; |
77 | } |
78 | |
79 | static Sym *asm_label_push(int v) |
80 | { |
81 | int addeddot, v2 = asm2cname(v, &addeddot); |
82 | /* We always add VT_EXTERN, for sym definition that's tentative |
83 | (for .set, removed for real defs), for mere references it's correct |
84 | as is. */ |
85 | Sym *sym = global_identifier_push(v2, VT_ASM | VT_EXTERN | VT_STATIC, 0); |
86 | if (addeddot) |
87 | sym->asm_label = v; |
88 | return sym; |
89 | } |
90 | |
91 | /* Return a symbol we can use inside the assembler, having name NAME. |
92 | Symbols from asm and C source share a namespace. If we generate |
93 | an asm symbol it's also a (file-global) C symbol, but it's |
94 | either not accessible by name (like "L.123"), or its type information |
95 | is such that it's not usable without a proper C declaration. |
96 | |
97 | Sometimes we need symbols accessible by name from asm, which |
98 | are anonymous in C, in this case CSYM can be used to transfer |
99 | all information from that symbol to the (possibly newly created) |
100 | asm symbol. */ |
101 | ST_FUNC Sym* get_asm_sym(int name, Sym *csym) |
102 | { |
103 | Sym *sym = asm_label_find(name); |
104 | if (!sym) { |
105 | sym = asm_label_push(name); |
106 | if (csym) |
107 | sym->c = csym->c; |
108 | } |
109 | return sym; |
110 | } |
111 | |
112 | static Sym* asm_section_sym(TCCState *s1, Section *sec) |
113 | { |
114 | char buf[100]; |
115 | int label = tok_alloc(buf, |
116 | snprintf(buf, sizeof buf, "L.%s" , sec->name) |
117 | )->tok; |
118 | Sym *sym = asm_label_find(label); |
119 | return sym ? sym : asm_new_label1(s1, label, 1, sec->sh_num, 0); |
120 | } |
121 | |
122 | /* We do not use the C expression parser to handle symbols. Maybe the |
123 | C expression parser could be tweaked to do so. */ |
124 | |
125 | static void asm_expr_unary(TCCState *s1, ExprValue *pe) |
126 | { |
127 | Sym *sym; |
128 | int op, label; |
129 | uint64_t n; |
130 | const char *p; |
131 | |
132 | switch(tok) { |
133 | case TOK_PPNUM: |
134 | p = tokc.str.data; |
135 | n = strtoull(p, (char **)&p, 0); |
136 | if (*p == 'b' || *p == 'f') { |
137 | /* backward or forward label */ |
138 | label = asm_get_local_label_name(s1, n); |
139 | sym = asm_label_find(label); |
140 | if (*p == 'b') { |
141 | /* backward : find the last corresponding defined label */ |
142 | if (sym && (!sym->c || elfsym(sym)->st_shndx == SHN_UNDEF)) |
143 | sym = sym->prev_tok; |
144 | if (!sym) |
145 | tcc_error("local label '%d' not found backward" , (int)n); |
146 | } else { |
147 | /* forward */ |
148 | if (!sym || (sym->c && elfsym(sym)->st_shndx != SHN_UNDEF)) { |
149 | /* if the last label is defined, then define a new one */ |
150 | sym = asm_label_push(label); |
151 | } |
152 | } |
153 | pe->v = 0; |
154 | pe->sym = sym; |
155 | pe->pcrel = 0; |
156 | } else if (*p == '\0') { |
157 | pe->v = n; |
158 | pe->sym = NULL; |
159 | pe->pcrel = 0; |
160 | } else { |
161 | tcc_error("invalid number syntax" ); |
162 | } |
163 | next(); |
164 | break; |
165 | case '+': |
166 | next(); |
167 | asm_expr_unary(s1, pe); |
168 | break; |
169 | case '-': |
170 | case '~': |
171 | op = tok; |
172 | next(); |
173 | asm_expr_unary(s1, pe); |
174 | if (pe->sym) |
175 | tcc_error("invalid operation with label" ); |
176 | if (op == '-') |
177 | pe->v = -pe->v; |
178 | else |
179 | pe->v = ~pe->v; |
180 | break; |
181 | case TOK_CCHAR: |
182 | case TOK_LCHAR: |
183 | pe->v = tokc.i; |
184 | pe->sym = NULL; |
185 | pe->pcrel = 0; |
186 | next(); |
187 | break; |
188 | case '(': |
189 | next(); |
190 | asm_expr(s1, pe); |
191 | skip(')'); |
192 | break; |
193 | case '.': |
194 | pe->v = ind; |
195 | pe->sym = asm_section_sym(s1, cur_text_section); |
196 | pe->pcrel = 0; |
197 | next(); |
198 | break; |
199 | default: |
200 | if (tok >= TOK_IDENT) { |
201 | ElfSym *esym; |
202 | /* label case : if the label was not found, add one */ |
203 | sym = get_asm_sym(tok, NULL); |
204 | esym = elfsym(sym); |
205 | if (esym && esym->st_shndx == SHN_ABS) { |
206 | /* if absolute symbol, no need to put a symbol value */ |
207 | pe->v = esym->st_value; |
208 | pe->sym = NULL; |
209 | pe->pcrel = 0; |
210 | } else { |
211 | pe->v = 0; |
212 | pe->sym = sym; |
213 | pe->pcrel = 0; |
214 | } |
215 | next(); |
216 | } else { |
217 | tcc_error("bad expression syntax [%s]" , get_tok_str(tok, &tokc)); |
218 | } |
219 | break; |
220 | } |
221 | } |
222 | |
223 | static void asm_expr_prod(TCCState *s1, ExprValue *pe) |
224 | { |
225 | int op; |
226 | ExprValue e2; |
227 | |
228 | asm_expr_unary(s1, pe); |
229 | for(;;) { |
230 | op = tok; |
231 | if (op != '*' && op != '/' && op != '%' && |
232 | op != TOK_SHL && op != TOK_SAR) |
233 | break; |
234 | next(); |
235 | asm_expr_unary(s1, &e2); |
236 | if (pe->sym || e2.sym) |
237 | tcc_error("invalid operation with label" ); |
238 | switch(op) { |
239 | case '*': |
240 | pe->v *= e2.v; |
241 | break; |
242 | case '/': |
243 | if (e2.v == 0) { |
244 | div_error: |
245 | tcc_error("division by zero" ); |
246 | } |
247 | pe->v /= e2.v; |
248 | break; |
249 | case '%': |
250 | if (e2.v == 0) |
251 | goto div_error; |
252 | pe->v %= e2.v; |
253 | break; |
254 | case TOK_SHL: |
255 | pe->v <<= e2.v; |
256 | break; |
257 | default: |
258 | case TOK_SAR: |
259 | pe->v >>= e2.v; |
260 | break; |
261 | } |
262 | } |
263 | } |
264 | |
265 | static void asm_expr_logic(TCCState *s1, ExprValue *pe) |
266 | { |
267 | int op; |
268 | ExprValue e2; |
269 | |
270 | asm_expr_prod(s1, pe); |
271 | for(;;) { |
272 | op = tok; |
273 | if (op != '&' && op != '|' && op != '^') |
274 | break; |
275 | next(); |
276 | asm_expr_prod(s1, &e2); |
277 | if (pe->sym || e2.sym) |
278 | tcc_error("invalid operation with label" ); |
279 | switch(op) { |
280 | case '&': |
281 | pe->v &= e2.v; |
282 | break; |
283 | case '|': |
284 | pe->v |= e2.v; |
285 | break; |
286 | default: |
287 | case '^': |
288 | pe->v ^= e2.v; |
289 | break; |
290 | } |
291 | } |
292 | } |
293 | |
294 | static inline void asm_expr_sum(TCCState *s1, ExprValue *pe) |
295 | { |
296 | int op; |
297 | ExprValue e2; |
298 | |
299 | asm_expr_logic(s1, pe); |
300 | for(;;) { |
301 | op = tok; |
302 | if (op != '+' && op != '-') |
303 | break; |
304 | next(); |
305 | asm_expr_logic(s1, &e2); |
306 | if (op == '+') { |
307 | if (pe->sym != NULL && e2.sym != NULL) |
308 | goto cannot_relocate; |
309 | pe->v += e2.v; |
310 | if (pe->sym == NULL && e2.sym != NULL) |
311 | pe->sym = e2.sym; |
312 | } else { |
313 | pe->v -= e2.v; |
314 | /* NOTE: we are less powerful than gas in that case |
315 | because we store only one symbol in the expression */ |
316 | if (!e2.sym) { |
317 | /* OK */ |
318 | } else if (pe->sym == e2.sym) { |
319 | /* OK */ |
320 | pe->sym = NULL; /* same symbols can be subtracted to NULL */ |
321 | } else { |
322 | ElfSym *esym1, *esym2; |
323 | esym1 = elfsym(pe->sym); |
324 | esym2 = elfsym(e2.sym); |
325 | if (esym1 && esym1->st_shndx == esym2->st_shndx |
326 | && esym1->st_shndx != SHN_UNDEF) { |
327 | /* we also accept defined symbols in the same section */ |
328 | pe->v += esym1->st_value - esym2->st_value; |
329 | pe->sym = NULL; |
330 | } else if (esym2->st_shndx == cur_text_section->sh_num) { |
331 | /* When subtracting a defined symbol in current section |
332 | this actually makes the value PC-relative. */ |
333 | pe->v -= esym2->st_value - ind - 4; |
334 | pe->pcrel = 1; |
335 | e2.sym = NULL; |
336 | } else { |
337 | cannot_relocate: |
338 | tcc_error("invalid operation with label" ); |
339 | } |
340 | } |
341 | } |
342 | } |
343 | } |
344 | |
345 | static inline void asm_expr_cmp(TCCState *s1, ExprValue *pe) |
346 | { |
347 | int op; |
348 | ExprValue e2; |
349 | |
350 | asm_expr_sum(s1, pe); |
351 | for(;;) { |
352 | op = tok; |
353 | if (op != TOK_EQ && op != TOK_NE |
354 | && (op > TOK_GT || op < TOK_ULE)) |
355 | break; |
356 | next(); |
357 | asm_expr_sum(s1, &e2); |
358 | if (pe->sym || e2.sym) |
359 | tcc_error("invalid operation with label" ); |
360 | switch(op) { |
361 | case TOK_EQ: |
362 | pe->v = pe->v == e2.v; |
363 | break; |
364 | case TOK_NE: |
365 | pe->v = pe->v != e2.v; |
366 | break; |
367 | case TOK_LT: |
368 | pe->v = (int64_t)pe->v < (int64_t)e2.v; |
369 | break; |
370 | case TOK_GE: |
371 | pe->v = (int64_t)pe->v >= (int64_t)e2.v; |
372 | break; |
373 | case TOK_LE: |
374 | pe->v = (int64_t)pe->v <= (int64_t)e2.v; |
375 | break; |
376 | case TOK_GT: |
377 | pe->v = (int64_t)pe->v > (int64_t)e2.v; |
378 | break; |
379 | default: |
380 | break; |
381 | } |
382 | /* GAS compare results are -1/0 not 1/0. */ |
383 | pe->v = -(int64_t)pe->v; |
384 | } |
385 | } |
386 | |
387 | ST_FUNC void asm_expr(TCCState *s1, ExprValue *pe) |
388 | { |
389 | asm_expr_cmp(s1, pe); |
390 | } |
391 | |
392 | ST_FUNC int asm_int_expr(TCCState *s1) |
393 | { |
394 | ExprValue e; |
395 | asm_expr(s1, &e); |
396 | if (e.sym) |
397 | expect("constant" ); |
398 | return e.v; |
399 | } |
400 | |
401 | static Sym* asm_new_label1(TCCState *s1, int label, int is_local, |
402 | int sh_num, int value) |
403 | { |
404 | Sym *sym; |
405 | ElfSym *esym; |
406 | |
407 | sym = asm_label_find(label); |
408 | if (sym) { |
409 | esym = elfsym(sym); |
410 | /* A VT_EXTERN symbol, even if it has a section is considered |
411 | overridable. This is how we "define" .set targets. Real |
412 | definitions won't have VT_EXTERN set. */ |
413 | if (esym && esym->st_shndx != SHN_UNDEF) { |
414 | /* the label is already defined */ |
415 | if (IS_ASM_SYM(sym) |
416 | && (is_local == 1 || (sym->type.t & VT_EXTERN))) |
417 | goto new_label; |
418 | if (!(sym->type.t & VT_EXTERN)) |
419 | tcc_error("assembler label '%s' already defined" , |
420 | get_tok_str(label, NULL)); |
421 | } |
422 | } else { |
423 | new_label: |
424 | sym = asm_label_push(label); |
425 | } |
426 | if (!sym->c) |
427 | put_extern_sym2(sym, SHN_UNDEF, 0, 0, 1); |
428 | esym = elfsym(sym); |
429 | esym->st_shndx = sh_num; |
430 | esym->st_value = value; |
431 | if (is_local != 2) |
432 | sym->type.t &= ~VT_EXTERN; |
433 | return sym; |
434 | } |
435 | |
436 | static Sym* asm_new_label(TCCState *s1, int label, int is_local) |
437 | { |
438 | return asm_new_label1(s1, label, is_local, cur_text_section->sh_num, ind); |
439 | } |
440 | |
441 | /* Set the value of LABEL to that of some expression (possibly |
442 | involving other symbols). LABEL can be overwritten later still. */ |
443 | static Sym* set_symbol(TCCState *s1, int label) |
444 | { |
445 | long n; |
446 | ExprValue e; |
447 | Sym *sym; |
448 | ElfSym *esym; |
449 | next(); |
450 | asm_expr(s1, &e); |
451 | n = e.v; |
452 | esym = elfsym(e.sym); |
453 | if (esym) |
454 | n += esym->st_value; |
455 | sym = asm_new_label1(s1, label, 2, esym ? esym->st_shndx : SHN_ABS, n); |
456 | elfsym(sym)->st_other |= ST_ASM_SET; |
457 | return sym; |
458 | } |
459 | |
460 | static void use_section1(TCCState *s1, Section *sec) |
461 | { |
462 | cur_text_section->data_offset = ind; |
463 | cur_text_section = sec; |
464 | ind = cur_text_section->data_offset; |
465 | } |
466 | |
467 | static void use_section(TCCState *s1, const char *name) |
468 | { |
469 | Section *sec; |
470 | sec = find_section(s1, name); |
471 | use_section1(s1, sec); |
472 | } |
473 | |
474 | static void push_section(TCCState *s1, const char *name) |
475 | { |
476 | Section *sec = find_section(s1, name); |
477 | sec->prev = cur_text_section; |
478 | use_section1(s1, sec); |
479 | } |
480 | |
481 | static void pop_section(TCCState *s1) |
482 | { |
483 | Section *prev = cur_text_section->prev; |
484 | if (!prev) |
485 | tcc_error(".popsection without .pushsection" ); |
486 | cur_text_section->prev = NULL; |
487 | use_section1(s1, prev); |
488 | } |
489 | |
490 | static void asm_parse_directive(TCCState *s1, int global) |
491 | { |
492 | int n, offset, v, size, tok1; |
493 | Section *sec; |
494 | uint8_t *ptr; |
495 | |
496 | /* assembler directive */ |
497 | sec = cur_text_section; |
498 | switch(tok) { |
499 | case TOK_ASMDIR_align: |
500 | case TOK_ASMDIR_balign: |
501 | case TOK_ASMDIR_p2align: |
502 | case TOK_ASMDIR_skip: |
503 | case TOK_ASMDIR_space: |
504 | tok1 = tok; |
505 | next(); |
506 | n = asm_int_expr(s1); |
507 | if (tok1 == TOK_ASMDIR_p2align) |
508 | { |
509 | if (n < 0 || n > 30) |
510 | tcc_error("invalid p2align, must be between 0 and 30" ); |
511 | n = 1 << n; |
512 | tok1 = TOK_ASMDIR_align; |
513 | } |
514 | if (tok1 == TOK_ASMDIR_align || tok1 == TOK_ASMDIR_balign) { |
515 | if (n < 0 || (n & (n-1)) != 0) |
516 | tcc_error("alignment must be a positive power of two" ); |
517 | offset = (ind + n - 1) & -n; |
518 | size = offset - ind; |
519 | /* the section must have a compatible alignment */ |
520 | if (sec->sh_addralign < n) |
521 | sec->sh_addralign = n; |
522 | } else { |
523 | if (n < 0) |
524 | n = 0; |
525 | size = n; |
526 | } |
527 | v = 0; |
528 | if (tok == ',') { |
529 | next(); |
530 | v = asm_int_expr(s1); |
531 | } |
532 | zero_pad: |
533 | if (sec->sh_type != SHT_NOBITS) { |
534 | sec->data_offset = ind; |
535 | ptr = section_ptr_add(sec, size); |
536 | memset(ptr, v, size); |
537 | } |
538 | ind += size; |
539 | break; |
540 | case TOK_ASMDIR_quad: |
541 | #ifdef TCC_TARGET_X86_64 |
542 | size = 8; |
543 | goto asm_data; |
544 | #else |
545 | next(); |
546 | for(;;) { |
547 | uint64_t vl; |
548 | const char *p; |
549 | |
550 | p = tokc.str.data; |
551 | if (tok != TOK_PPNUM) { |
552 | error_constant: |
553 | tcc_error("64 bit constant" ); |
554 | } |
555 | vl = strtoll(p, (char **)&p, 0); |
556 | if (*p != '\0') |
557 | goto error_constant; |
558 | next(); |
559 | if (sec->sh_type != SHT_NOBITS) { |
560 | /* XXX: endianness */ |
561 | gen_le32(vl); |
562 | gen_le32(vl >> 32); |
563 | } else { |
564 | ind += 8; |
565 | } |
566 | if (tok != ',') |
567 | break; |
568 | next(); |
569 | } |
570 | break; |
571 | #endif |
572 | case TOK_ASMDIR_byte: |
573 | size = 1; |
574 | goto asm_data; |
575 | case TOK_ASMDIR_word: |
576 | case TOK_ASMDIR_short: |
577 | size = 2; |
578 | goto asm_data; |
579 | case TOK_ASMDIR_long: |
580 | case TOK_ASMDIR_int: |
581 | size = 4; |
582 | asm_data: |
583 | next(); |
584 | for(;;) { |
585 | ExprValue e; |
586 | asm_expr(s1, &e); |
587 | if (sec->sh_type != SHT_NOBITS) { |
588 | if (size == 4) { |
589 | gen_expr32(&e); |
590 | #ifdef TCC_TARGET_X86_64 |
591 | } else if (size == 8) { |
592 | gen_expr64(&e); |
593 | #endif |
594 | } else { |
595 | if (e.sym) |
596 | expect("constant" ); |
597 | if (size == 1) |
598 | g(e.v); |
599 | else |
600 | gen_le16(e.v); |
601 | } |
602 | } else { |
603 | ind += size; |
604 | } |
605 | if (tok != ',') |
606 | break; |
607 | next(); |
608 | } |
609 | break; |
610 | case TOK_ASMDIR_fill: |
611 | { |
612 | int repeat, size, val, i, j; |
613 | uint8_t repeat_buf[8]; |
614 | next(); |
615 | repeat = asm_int_expr(s1); |
616 | if (repeat < 0) { |
617 | tcc_error("repeat < 0; .fill ignored" ); |
618 | break; |
619 | } |
620 | size = 1; |
621 | val = 0; |
622 | if (tok == ',') { |
623 | next(); |
624 | size = asm_int_expr(s1); |
625 | if (size < 0) { |
626 | tcc_error("size < 0; .fill ignored" ); |
627 | break; |
628 | } |
629 | if (size > 8) |
630 | size = 8; |
631 | if (tok == ',') { |
632 | next(); |
633 | val = asm_int_expr(s1); |
634 | } |
635 | } |
636 | /* XXX: endianness */ |
637 | repeat_buf[0] = val; |
638 | repeat_buf[1] = val >> 8; |
639 | repeat_buf[2] = val >> 16; |
640 | repeat_buf[3] = val >> 24; |
641 | repeat_buf[4] = 0; |
642 | repeat_buf[5] = 0; |
643 | repeat_buf[6] = 0; |
644 | repeat_buf[7] = 0; |
645 | for(i = 0; i < repeat; i++) { |
646 | for(j = 0; j < size; j++) { |
647 | g(repeat_buf[j]); |
648 | } |
649 | } |
650 | } |
651 | break; |
652 | case TOK_ASMDIR_rept: |
653 | { |
654 | int repeat; |
655 | TokenString *init_str; |
656 | next(); |
657 | repeat = asm_int_expr(s1); |
658 | init_str = tok_str_alloc(); |
659 | while (next(), tok != TOK_ASMDIR_endr) { |
660 | if (tok == CH_EOF) |
661 | tcc_error("we at end of file, .endr not found" ); |
662 | tok_str_add_tok(init_str); |
663 | } |
664 | tok_str_add(init_str, -1); |
665 | tok_str_add(init_str, 0); |
666 | begin_macro(init_str, 1); |
667 | while (repeat-- > 0) { |
668 | tcc_assemble_internal(s1, (parse_flags & PARSE_FLAG_PREPROCESS), |
669 | global); |
670 | macro_ptr = init_str->str; |
671 | } |
672 | end_macro(); |
673 | next(); |
674 | break; |
675 | } |
676 | case TOK_ASMDIR_org: |
677 | { |
678 | unsigned long n; |
679 | ExprValue e; |
680 | ElfSym *esym; |
681 | next(); |
682 | asm_expr(s1, &e); |
683 | n = e.v; |
684 | esym = elfsym(e.sym); |
685 | if (esym) { |
686 | if (esym->st_shndx != cur_text_section->sh_num) |
687 | expect("constant or same-section symbol" ); |
688 | n += esym->st_value; |
689 | } |
690 | if (n < ind) |
691 | tcc_error("attempt to .org backwards" ); |
692 | v = 0; |
693 | size = n - ind; |
694 | goto zero_pad; |
695 | } |
696 | break; |
697 | case TOK_ASMDIR_set: |
698 | next(); |
699 | tok1 = tok; |
700 | next(); |
701 | /* Also accept '.set stuff', but don't do anything with this. |
702 | It's used in GAS to set various features like '.set mips16'. */ |
703 | if (tok == ',') |
704 | set_symbol(s1, tok1); |
705 | break; |
706 | case TOK_ASMDIR_globl: |
707 | case TOK_ASMDIR_global: |
708 | case TOK_ASMDIR_weak: |
709 | case TOK_ASMDIR_hidden: |
710 | tok1 = tok; |
711 | do { |
712 | Sym *sym; |
713 | next(); |
714 | sym = get_asm_sym(tok, NULL); |
715 | if (tok1 != TOK_ASMDIR_hidden) |
716 | sym->type.t &= ~VT_STATIC; |
717 | if (tok1 == TOK_ASMDIR_weak) |
718 | sym->a.weak = 1; |
719 | else if (tok1 == TOK_ASMDIR_hidden) |
720 | sym->a.visibility = STV_HIDDEN; |
721 | update_storage(sym); |
722 | next(); |
723 | } while (tok == ','); |
724 | break; |
725 | case TOK_ASMDIR_string: |
726 | case TOK_ASMDIR_ascii: |
727 | case TOK_ASMDIR_asciz: |
728 | { |
729 | const uint8_t *p; |
730 | int i, size, t; |
731 | |
732 | t = tok; |
733 | next(); |
734 | for(;;) { |
735 | if (tok != TOK_STR) |
736 | expect("string constant" ); |
737 | p = tokc.str.data; |
738 | size = tokc.str.size; |
739 | if (t == TOK_ASMDIR_ascii && size > 0) |
740 | size--; |
741 | for(i = 0; i < size; i++) |
742 | g(p[i]); |
743 | next(); |
744 | if (tok == ',') { |
745 | next(); |
746 | } else if (tok != TOK_STR) { |
747 | break; |
748 | } |
749 | } |
750 | } |
751 | break; |
752 | case TOK_ASMDIR_text: |
753 | case TOK_ASMDIR_data: |
754 | case TOK_ASMDIR_bss: |
755 | { |
756 | char sname[64]; |
757 | tok1 = tok; |
758 | n = 0; |
759 | next(); |
760 | if (tok != ';' && tok != TOK_LINEFEED) { |
761 | n = asm_int_expr(s1); |
762 | next(); |
763 | } |
764 | if (n) |
765 | sprintf(sname, "%s%d" , get_tok_str(tok1, NULL), n); |
766 | else |
767 | sprintf(sname, "%s" , get_tok_str(tok1, NULL)); |
768 | use_section(s1, sname); |
769 | } |
770 | break; |
771 | case TOK_ASMDIR_file: |
772 | { |
773 | char filename[512]; |
774 | |
775 | filename[0] = '\0'; |
776 | next(); |
777 | |
778 | if (tok == TOK_STR) |
779 | pstrcat(filename, sizeof(filename), tokc.str.data); |
780 | else |
781 | pstrcat(filename, sizeof(filename), get_tok_str(tok, NULL)); |
782 | |
783 | if (s1->warn_unsupported) |
784 | tcc_warning("ignoring .file %s" , filename); |
785 | |
786 | next(); |
787 | } |
788 | break; |
789 | case TOK_ASMDIR_ident: |
790 | { |
791 | char ident[256]; |
792 | |
793 | ident[0] = '\0'; |
794 | next(); |
795 | |
796 | if (tok == TOK_STR) |
797 | pstrcat(ident, sizeof(ident), tokc.str.data); |
798 | else |
799 | pstrcat(ident, sizeof(ident), get_tok_str(tok, NULL)); |
800 | |
801 | if (s1->warn_unsupported) |
802 | tcc_warning("ignoring .ident %s" , ident); |
803 | |
804 | next(); |
805 | } |
806 | break; |
807 | case TOK_ASMDIR_size: |
808 | { |
809 | Sym *sym; |
810 | |
811 | next(); |
812 | sym = asm_label_find(tok); |
813 | if (!sym) { |
814 | tcc_error("label not found: %s" , get_tok_str(tok, NULL)); |
815 | } |
816 | |
817 | /* XXX .size name,label2-label1 */ |
818 | if (s1->warn_unsupported) |
819 | tcc_warning("ignoring .size %s,*" , get_tok_str(tok, NULL)); |
820 | |
821 | next(); |
822 | skip(','); |
823 | while (tok != TOK_LINEFEED && tok != ';' && tok != CH_EOF) { |
824 | next(); |
825 | } |
826 | } |
827 | break; |
828 | case TOK_ASMDIR_type: |
829 | { |
830 | Sym *sym; |
831 | const char *newtype; |
832 | |
833 | next(); |
834 | sym = get_asm_sym(tok, NULL); |
835 | next(); |
836 | skip(','); |
837 | if (tok == TOK_STR) { |
838 | newtype = tokc.str.data; |
839 | } else { |
840 | if (tok == '@' || tok == '%') |
841 | next(); |
842 | newtype = get_tok_str(tok, NULL); |
843 | } |
844 | |
845 | if (!strcmp(newtype, "function" ) || !strcmp(newtype, "STT_FUNC" )) { |
846 | sym->type.t = (sym->type.t & ~VT_BTYPE) | VT_FUNC; |
847 | } |
848 | else if (s1->warn_unsupported) |
849 | tcc_warning("change type of '%s' from 0x%x to '%s' ignored" , |
850 | get_tok_str(sym->v, NULL), sym->type.t, newtype); |
851 | |
852 | next(); |
853 | } |
854 | break; |
855 | case TOK_ASMDIR_pushsection: |
856 | case TOK_ASMDIR_section: |
857 | { |
858 | char sname[256]; |
859 | int old_nb_section = s1->nb_sections; |
860 | |
861 | tok1 = tok; |
862 | /* XXX: support more options */ |
863 | next(); |
864 | sname[0] = '\0'; |
865 | while (tok != ';' && tok != TOK_LINEFEED && tok != ',') { |
866 | if (tok == TOK_STR) |
867 | pstrcat(sname, sizeof(sname), tokc.str.data); |
868 | else |
869 | pstrcat(sname, sizeof(sname), get_tok_str(tok, NULL)); |
870 | next(); |
871 | } |
872 | if (tok == ',') { |
873 | /* skip section options */ |
874 | next(); |
875 | if (tok != TOK_STR) |
876 | expect("string constant" ); |
877 | next(); |
878 | if (tok == ',') { |
879 | next(); |
880 | if (tok == '@' || tok == '%') |
881 | next(); |
882 | next(); |
883 | } |
884 | } |
885 | last_text_section = cur_text_section; |
886 | if (tok1 == TOK_ASMDIR_section) |
887 | use_section(s1, sname); |
888 | else |
889 | push_section(s1, sname); |
890 | /* If we just allocated a new section reset its alignment to |
891 | 1. new_section normally acts for GCC compatibility and |
892 | sets alignment to PTR_SIZE. The assembler behaves different. */ |
893 | if (old_nb_section != s1->nb_sections) |
894 | cur_text_section->sh_addralign = 1; |
895 | } |
896 | break; |
897 | case TOK_ASMDIR_previous: |
898 | { |
899 | Section *sec; |
900 | next(); |
901 | if (!last_text_section) |
902 | tcc_error("no previous section referenced" ); |
903 | sec = cur_text_section; |
904 | use_section1(s1, last_text_section); |
905 | last_text_section = sec; |
906 | } |
907 | break; |
908 | case TOK_ASMDIR_popsection: |
909 | next(); |
910 | pop_section(s1); |
911 | break; |
912 | #ifdef TCC_TARGET_I386 |
913 | case TOK_ASMDIR_code16: |
914 | { |
915 | next(); |
916 | s1->seg_size = 16; |
917 | } |
918 | break; |
919 | case TOK_ASMDIR_code32: |
920 | { |
921 | next(); |
922 | s1->seg_size = 32; |
923 | } |
924 | break; |
925 | #endif |
926 | #ifdef TCC_TARGET_X86_64 |
927 | /* added for compatibility with GAS */ |
928 | case TOK_ASMDIR_code64: |
929 | next(); |
930 | break; |
931 | #endif |
932 | default: |
933 | tcc_error("unknown assembler directive '.%s'" , get_tok_str(tok, NULL)); |
934 | break; |
935 | } |
936 | } |
937 | |
938 | |
939 | /* assemble a file */ |
940 | static int tcc_assemble_internal(TCCState *s1, int do_preprocess, int global) |
941 | { |
942 | int opcode; |
943 | int saved_parse_flags = parse_flags; |
944 | |
945 | parse_flags = PARSE_FLAG_ASM_FILE | PARSE_FLAG_TOK_STR; |
946 | if (do_preprocess) |
947 | parse_flags |= PARSE_FLAG_PREPROCESS; |
948 | for(;;) { |
949 | next(); |
950 | if (tok == TOK_EOF) |
951 | break; |
952 | parse_flags |= PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */ |
953 | redo: |
954 | if (tok == '#') { |
955 | /* horrible gas comment */ |
956 | while (tok != TOK_LINEFEED) |
957 | next(); |
958 | } else if (tok >= TOK_ASMDIR_FIRST && tok <= TOK_ASMDIR_LAST) { |
959 | asm_parse_directive(s1, global); |
960 | } else if (tok == TOK_PPNUM) { |
961 | const char *p; |
962 | int n; |
963 | p = tokc.str.data; |
964 | n = strtoul(p, (char **)&p, 10); |
965 | if (*p != '\0') |
966 | expect("':'" ); |
967 | /* new local label */ |
968 | asm_new_label(s1, asm_get_local_label_name(s1, n), 1); |
969 | next(); |
970 | skip(':'); |
971 | goto redo; |
972 | } else if (tok >= TOK_IDENT) { |
973 | /* instruction or label */ |
974 | opcode = tok; |
975 | next(); |
976 | if (tok == ':') { |
977 | /* new label */ |
978 | asm_new_label(s1, opcode, 0); |
979 | next(); |
980 | goto redo; |
981 | } else if (tok == '=') { |
982 | set_symbol(s1, opcode); |
983 | goto redo; |
984 | } else { |
985 | asm_opcode(s1, opcode); |
986 | } |
987 | } |
988 | /* end of line */ |
989 | if (tok != ';' && tok != TOK_LINEFEED) |
990 | expect("end of line" ); |
991 | parse_flags &= ~PARSE_FLAG_LINEFEED; /* XXX: suppress that hack */ |
992 | } |
993 | |
994 | parse_flags = saved_parse_flags; |
995 | return 0; |
996 | } |
997 | |
998 | /* Assemble the current file */ |
999 | ST_FUNC int tcc_assemble(TCCState *s1, int do_preprocess) |
1000 | { |
1001 | int ret; |
1002 | tcc_debug_start(s1); |
1003 | /* default section is text */ |
1004 | cur_text_section = text_section; |
1005 | ind = cur_text_section->data_offset; |
1006 | nocode_wanted = 0; |
1007 | ret = tcc_assemble_internal(s1, do_preprocess, 1); |
1008 | cur_text_section->data_offset = ind; |
1009 | tcc_debug_end(s1); |
1010 | return ret; |
1011 | } |
1012 | |
1013 | /********************************************************************/ |
1014 | /* GCC inline asm support */ |
1015 | |
1016 | /* assemble the string 'str' in the current C compilation unit without |
1017 | C preprocessing. NOTE: str is modified by modifying the '\0' at the |
1018 | end */ |
1019 | static void tcc_assemble_inline(TCCState *s1, char *str, int len, int global) |
1020 | { |
1021 | const int *saved_macro_ptr = macro_ptr; |
1022 | int dotid = set_idnum('.', IS_ID); |
1023 | int dolid = set_idnum('$', 0); |
1024 | |
1025 | tcc_open_bf(s1, ":asm:" , len); |
1026 | memcpy(file->buffer, str, len); |
1027 | macro_ptr = NULL; |
1028 | tcc_assemble_internal(s1, 0, global); |
1029 | tcc_close(); |
1030 | |
1031 | set_idnum('$', dolid); |
1032 | set_idnum('.', dotid); |
1033 | macro_ptr = saved_macro_ptr; |
1034 | } |
1035 | |
1036 | /* find a constraint by its number or id (gcc 3 extended |
1037 | syntax). return -1 if not found. Return in *pp in char after the |
1038 | constraint */ |
1039 | ST_FUNC int find_constraint(ASMOperand *operands, int nb_operands, |
1040 | const char *name, const char **pp) |
1041 | { |
1042 | int index; |
1043 | TokenSym *ts; |
1044 | const char *p; |
1045 | |
1046 | if (isnum(*name)) { |
1047 | index = 0; |
1048 | while (isnum(*name)) { |
1049 | index = (index * 10) + (*name) - '0'; |
1050 | name++; |
1051 | } |
1052 | if ((unsigned)index >= nb_operands) |
1053 | index = -1; |
1054 | } else if (*name == '[') { |
1055 | name++; |
1056 | p = strchr(name, ']'); |
1057 | if (p) { |
1058 | ts = tok_alloc(name, p - name); |
1059 | for(index = 0; index < nb_operands; index++) { |
1060 | if (operands[index].id == ts->tok) |
1061 | goto found; |
1062 | } |
1063 | index = -1; |
1064 | found: |
1065 | name = p + 1; |
1066 | } else { |
1067 | index = -1; |
1068 | } |
1069 | } else { |
1070 | index = -1; |
1071 | } |
1072 | if (pp) |
1073 | *pp = name; |
1074 | return index; |
1075 | } |
1076 | |
1077 | static void subst_asm_operands(ASMOperand *operands, int nb_operands, |
1078 | CString *out_str, CString *in_str) |
1079 | { |
1080 | int c, index, modifier; |
1081 | const char *str; |
1082 | ASMOperand *op; |
1083 | SValue sv; |
1084 | |
1085 | cstr_new(out_str); |
1086 | str = in_str->data; |
1087 | for(;;) { |
1088 | c = *str++; |
1089 | if (c == '%') { |
1090 | if (*str == '%') { |
1091 | str++; |
1092 | goto add_char; |
1093 | } |
1094 | modifier = 0; |
1095 | if (*str == 'c' || *str == 'n' || |
1096 | *str == 'b' || *str == 'w' || *str == 'h' || *str == 'k' || |
1097 | *str == 'q' || |
1098 | /* P in GCC would add "@PLT" to symbol refs in PIC mode, |
1099 | and make literal operands not be decorated with '$'. */ |
1100 | *str == 'P') |
1101 | modifier = *str++; |
1102 | index = find_constraint(operands, nb_operands, str, &str); |
1103 | if (index < 0) |
1104 | tcc_error("invalid operand reference after %%" ); |
1105 | op = &operands[index]; |
1106 | sv = *op->vt; |
1107 | if (op->reg >= 0) { |
1108 | sv.r = op->reg; |
1109 | if ((op->vt->r & VT_VALMASK) == VT_LLOCAL && op->is_memory) |
1110 | sv.r |= VT_LVAL; |
1111 | } |
1112 | subst_asm_operand(out_str, &sv, modifier); |
1113 | } else { |
1114 | add_char: |
1115 | cstr_ccat(out_str, c); |
1116 | if (c == '\0') |
1117 | break; |
1118 | } |
1119 | } |
1120 | } |
1121 | |
1122 | |
1123 | static void parse_asm_operands(ASMOperand *operands, int *nb_operands_ptr, |
1124 | int is_output) |
1125 | { |
1126 | ASMOperand *op; |
1127 | int nb_operands; |
1128 | |
1129 | if (tok != ':') { |
1130 | nb_operands = *nb_operands_ptr; |
1131 | for(;;) { |
1132 | CString astr; |
1133 | if (nb_operands >= MAX_ASM_OPERANDS) |
1134 | tcc_error("too many asm operands" ); |
1135 | op = &operands[nb_operands++]; |
1136 | op->id = 0; |
1137 | if (tok == '[') { |
1138 | next(); |
1139 | if (tok < TOK_IDENT) |
1140 | expect("identifier" ); |
1141 | op->id = tok; |
1142 | next(); |
1143 | skip(']'); |
1144 | } |
1145 | parse_mult_str(&astr, "string constant" ); |
1146 | op->constraint = tcc_malloc(astr.size); |
1147 | strcpy(op->constraint, astr.data); |
1148 | cstr_free(&astr); |
1149 | skip('('); |
1150 | gexpr(); |
1151 | if (is_output) { |
1152 | if (!(vtop->type.t & VT_ARRAY)) |
1153 | test_lvalue(); |
1154 | } else { |
1155 | /* we want to avoid LLOCAL case, except when the 'm' |
1156 | constraint is used. Note that it may come from |
1157 | register storage, so we need to convert (reg) |
1158 | case */ |
1159 | if ((vtop->r & VT_LVAL) && |
1160 | ((vtop->r & VT_VALMASK) == VT_LLOCAL || |
1161 | (vtop->r & VT_VALMASK) < VT_CONST) && |
1162 | !strchr(op->constraint, 'm')) { |
1163 | gv(RC_INT); |
1164 | } |
1165 | } |
1166 | op->vt = vtop; |
1167 | skip(')'); |
1168 | if (tok == ',') { |
1169 | next(); |
1170 | } else { |
1171 | break; |
1172 | } |
1173 | } |
1174 | *nb_operands_ptr = nb_operands; |
1175 | } |
1176 | } |
1177 | |
1178 | /* parse the GCC asm() instruction */ |
1179 | ST_FUNC void asm_instr(void) |
1180 | { |
1181 | CString astr, astr1; |
1182 | ASMOperand operands[MAX_ASM_OPERANDS]; |
1183 | int nb_outputs, nb_operands, i, must_subst, out_reg; |
1184 | uint8_t clobber_regs[NB_ASM_REGS]; |
1185 | Section *sec; |
1186 | |
1187 | /* since we always generate the asm() instruction, we can ignore |
1188 | volatile */ |
1189 | if (tok == TOK_VOLATILE1 || tok == TOK_VOLATILE2 || tok == TOK_VOLATILE3) { |
1190 | next(); |
1191 | } |
1192 | parse_asm_str(&astr); |
1193 | nb_operands = 0; |
1194 | nb_outputs = 0; |
1195 | must_subst = 0; |
1196 | memset(clobber_regs, 0, sizeof(clobber_regs)); |
1197 | if (tok == ':') { |
1198 | next(); |
1199 | must_subst = 1; |
1200 | /* output args */ |
1201 | parse_asm_operands(operands, &nb_operands, 1); |
1202 | nb_outputs = nb_operands; |
1203 | if (tok == ':') { |
1204 | next(); |
1205 | if (tok != ')') { |
1206 | /* input args */ |
1207 | parse_asm_operands(operands, &nb_operands, 0); |
1208 | if (tok == ':') { |
1209 | /* clobber list */ |
1210 | /* XXX: handle registers */ |
1211 | next(); |
1212 | for(;;) { |
1213 | if (tok != TOK_STR) |
1214 | expect("string constant" ); |
1215 | asm_clobber(clobber_regs, tokc.str.data); |
1216 | next(); |
1217 | if (tok == ',') { |
1218 | next(); |
1219 | } else { |
1220 | break; |
1221 | } |
1222 | } |
1223 | } |
1224 | } |
1225 | } |
1226 | } |
1227 | skip(')'); |
1228 | /* NOTE: we do not eat the ';' so that we can restore the current |
1229 | token after the assembler parsing */ |
1230 | if (tok != ';') |
1231 | expect("';'" ); |
1232 | |
1233 | /* save all values in the memory */ |
1234 | save_regs(0); |
1235 | |
1236 | /* compute constraints */ |
1237 | asm_compute_constraints(operands, nb_operands, nb_outputs, |
1238 | clobber_regs, &out_reg); |
1239 | |
1240 | /* substitute the operands in the asm string. No substitution is |
1241 | done if no operands (GCC behaviour) */ |
1242 | #ifdef ASM_DEBUG |
1243 | printf("asm: \"%s\"\n" , (char *)astr.data); |
1244 | #endif |
1245 | if (must_subst) { |
1246 | subst_asm_operands(operands, nb_operands, &astr1, &astr); |
1247 | cstr_free(&astr); |
1248 | } else { |
1249 | astr1 = astr; |
1250 | } |
1251 | #ifdef ASM_DEBUG |
1252 | printf("subst_asm: \"%s\"\n" , (char *)astr1.data); |
1253 | #endif |
1254 | |
1255 | /* generate loads */ |
1256 | asm_gen_code(operands, nb_operands, nb_outputs, 0, |
1257 | clobber_regs, out_reg); |
1258 | |
1259 | /* We don't allow switching section within inline asm to |
1260 | bleed out to surrounding code. */ |
1261 | sec = cur_text_section; |
1262 | /* assemble the string with tcc internal assembler */ |
1263 | tcc_assemble_inline(tcc_state, astr1.data, astr1.size - 1, 0); |
1264 | if (sec != cur_text_section) { |
1265 | tcc_warning("inline asm tries to change current section" ); |
1266 | use_section1(tcc_state, sec); |
1267 | } |
1268 | |
1269 | /* restore the current C token */ |
1270 | next(); |
1271 | |
1272 | /* store the output values if needed */ |
1273 | asm_gen_code(operands, nb_operands, nb_outputs, 1, |
1274 | clobber_regs, out_reg); |
1275 | |
1276 | /* free everything */ |
1277 | for(i=0;i<nb_operands;i++) { |
1278 | ASMOperand *op; |
1279 | op = &operands[i]; |
1280 | tcc_free(op->constraint); |
1281 | vpop(); |
1282 | } |
1283 | cstr_free(&astr1); |
1284 | } |
1285 | |
1286 | ST_FUNC void asm_global_instr(void) |
1287 | { |
1288 | CString astr; |
1289 | int saved_nocode_wanted = nocode_wanted; |
1290 | |
1291 | /* Global asm blocks are always emitted. */ |
1292 | nocode_wanted = 0; |
1293 | next(); |
1294 | parse_asm_str(&astr); |
1295 | skip(')'); |
1296 | /* NOTE: we do not eat the ';' so that we can restore the current |
1297 | token after the assembler parsing */ |
1298 | if (tok != ';') |
1299 | expect("';'" ); |
1300 | |
1301 | #ifdef ASM_DEBUG |
1302 | printf("asm_global: \"%s\"\n" , (char *)astr.data); |
1303 | #endif |
1304 | cur_text_section = text_section; |
1305 | ind = cur_text_section->data_offset; |
1306 | |
1307 | /* assemble the string with tcc internal assembler */ |
1308 | tcc_assemble_inline(tcc_state, astr.data, astr.size - 1, 1); |
1309 | |
1310 | cur_text_section->data_offset = ind; |
1311 | |
1312 | /* restore the current C token */ |
1313 | next(); |
1314 | |
1315 | cstr_free(&astr); |
1316 | nocode_wanted = saved_nocode_wanted; |
1317 | } |
1318 | #endif /* CONFIG_TCC_ASM */ |
1319 | |