1 | /*****************************************************************************/ |
2 | /* */ |
3 | /* asmstmt.c */ |
4 | /* */ |
5 | /* Inline assembler statements for the cc65 C compiler */ |
6 | /* */ |
7 | /* */ |
8 | /* */ |
9 | /* (C) 2001-2008 Ullrich von Bassewitz */ |
10 | /* Roemerstrasse 52 */ |
11 | /* D-70794 Filderstadt */ |
12 | /* EMail: uz@musoftware.de */ |
13 | /* */ |
14 | /* */ |
15 | /* This software is provided 'as-is', without any expressed or implied */ |
16 | /* warranty. In no event will the authors be held liable for any damages */ |
17 | /* arising from the use of this software. */ |
18 | /* */ |
19 | /* Permission is granted to anyone to use this software for any purpose, */ |
20 | /* including commercial applications, and to alter it and redistribute it */ |
21 | /* freely, subject to the following restrictions: */ |
22 | /* */ |
23 | /* 1. The origin of this software must not be misrepresented; you must not */ |
24 | /* claim that you wrote the original software. If you use this software */ |
25 | /* in a product, an acknowledgment in the product documentation would be */ |
26 | /* appreciated but is not required. */ |
27 | /* 2. Altered source versions must be plainly marked as such, and must not */ |
28 | /* be misrepresented as being the original software. */ |
29 | /* 3. This notice may not be removed or altered from any source */ |
30 | /* distribution. */ |
31 | /* */ |
32 | /*****************************************************************************/ |
33 | |
34 | |
35 | |
36 | #include <string.h> |
37 | |
38 | /* common */ |
39 | #include "xsprintf.h" |
40 | |
41 | /* cc65 */ |
42 | #include "asmlabel.h" |
43 | #include "codegen.h" |
44 | #include "codeseg.h" |
45 | #include "datatype.h" |
46 | #include "error.h" |
47 | #include "expr.h" |
48 | #include "function.h" |
49 | #include "litpool.h" |
50 | #include "scanner.h" |
51 | #include "segments.h" |
52 | #include "stackptr.h" |
53 | #include "symtab.h" |
54 | #include "asmstmt.h" |
55 | |
56 | |
57 | |
58 | /*****************************************************************************/ |
59 | /* Code */ |
60 | /*****************************************************************************/ |
61 | |
62 | |
63 | |
64 | static void AsmRangeError (unsigned Arg) |
65 | /* Print a diagnostic about a range error in the argument with the given number */ |
66 | { |
67 | Error ("Range error in argument %u" , Arg); |
68 | } |
69 | |
70 | |
71 | |
72 | static void AsmErrorSkip (void) |
73 | /* Called in case of an error, skips tokens until the closing paren or a |
74 | ** semicolon is reached. |
75 | */ |
76 | { |
77 | static const token_t TokenList[] = { TOK_RPAREN, TOK_SEMI }; |
78 | SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0])); |
79 | } |
80 | |
81 | |
82 | |
83 | static SymEntry* AsmGetSym (unsigned Arg, unsigned Type) |
84 | /* Find the symbol with the name currently in NextTok. The symbol must be of |
85 | ** the given type. On errors, NULL is returned. |
86 | */ |
87 | { |
88 | SymEntry* Sym; |
89 | |
90 | /* We expect an argument separated by a comma */ |
91 | ConsumeComma (); |
92 | |
93 | /* Argument must be an identifier */ |
94 | if (CurTok.Tok != TOK_IDENT) { |
95 | Error ("Identifier expected for argument %u" , Arg); |
96 | AsmErrorSkip (); |
97 | return 0; |
98 | } |
99 | |
100 | /* Get a pointer to the symbol table entry */ |
101 | Sym = FindSym (CurTok.Ident); |
102 | |
103 | /* Did we find a symbol with this name? */ |
104 | if (Sym == 0) { |
105 | Error ("Undefined symbol '%s' for argument %u" , CurTok.Ident, Arg); |
106 | AsmErrorSkip (); |
107 | return 0; |
108 | } |
109 | |
110 | /* We found the symbol - skip the name token */ |
111 | NextToken (); |
112 | |
113 | /* Check if we have a global symbol */ |
114 | if ((Sym->Flags & Type) != Type) { |
115 | Error ("Type of argument %u differs from format specifier" , Arg); |
116 | AsmErrorSkip (); |
117 | return 0; |
118 | } |
119 | |
120 | /* Mark the symbol as referenced */ |
121 | Sym->Flags |= SC_REF; |
122 | |
123 | /* Return it */ |
124 | return Sym; |
125 | } |
126 | |
127 | |
128 | |
129 | static void ParseByteArg (StrBuf* T, unsigned Arg) |
130 | /* Parse the %b format specifier */ |
131 | { |
132 | ExprDesc Expr; |
133 | char Buf [16]; |
134 | |
135 | /* We expect an argument separated by a comma */ |
136 | ConsumeComma (); |
137 | |
138 | /* Evaluate the expression */ |
139 | ConstAbsIntExpr (hie1, &Expr); |
140 | |
141 | /* Check the range but allow negative values if the type is signed */ |
142 | if (IsSignUnsigned (Expr.Type)) { |
143 | if (Expr.IVal < 0 || Expr.IVal > 0xFF) { |
144 | AsmRangeError (Arg); |
145 | Expr.IVal = 0; |
146 | } |
147 | } else { |
148 | if (Expr.IVal < -128 || Expr.IVal > 127) { |
149 | AsmRangeError (Arg); |
150 | Expr.IVal = 0; |
151 | } |
152 | } |
153 | |
154 | /* Convert into a hex number */ |
155 | xsprintf (Buf, sizeof (Buf), "$%02lX" , Expr.IVal & 0xFF); |
156 | |
157 | /* Add the number to the target buffer */ |
158 | SB_AppendStr (T, Buf); |
159 | } |
160 | |
161 | |
162 | |
163 | static void ParseWordArg (StrBuf* T, unsigned Arg) |
164 | /* Parse the %w format specifier */ |
165 | { |
166 | ExprDesc Expr; |
167 | char Buf [16]; |
168 | |
169 | /* We expect an argument separated by a comma */ |
170 | ConsumeComma (); |
171 | |
172 | /* Evaluate the expression */ |
173 | ConstAbsIntExpr (hie1, &Expr); |
174 | |
175 | /* Check the range but allow negative values if the type is signed */ |
176 | if (IsSignUnsigned (Expr.Type)) { |
177 | if (Expr.IVal < 0 || Expr.IVal > 0xFFFF) { |
178 | AsmRangeError (Arg); |
179 | Expr.IVal = 0; |
180 | } |
181 | } else { |
182 | if (Expr.IVal < -32768 || Expr.IVal > 32767) { |
183 | AsmRangeError (Arg); |
184 | Expr.IVal = 0; |
185 | } |
186 | } |
187 | |
188 | /* Convert into a hex number */ |
189 | xsprintf (Buf, sizeof (Buf), "$%04lX" , Expr.IVal & 0xFFFF); |
190 | |
191 | /* Add the number to the target buffer */ |
192 | SB_AppendStr (T, Buf); |
193 | } |
194 | |
195 | |
196 | |
197 | static void ParseLongArg (StrBuf* T, unsigned Arg attribute ((unused))) |
198 | /* Parse the %l format specifier */ |
199 | { |
200 | ExprDesc Expr; |
201 | char Buf [16]; |
202 | |
203 | /* We expect an argument separated by a comma */ |
204 | ConsumeComma (); |
205 | |
206 | /* Evaluate the expression */ |
207 | ConstAbsIntExpr (hie1, &Expr); |
208 | |
209 | /* Convert into a hex number */ |
210 | xsprintf (Buf, sizeof (Buf), "$%08lX" , Expr.IVal & 0xFFFFFFFF); |
211 | |
212 | /* Add the number to the target buffer */ |
213 | SB_AppendStr (T, Buf); |
214 | } |
215 | |
216 | |
217 | |
218 | static void ParseGVarArg (StrBuf* T, unsigned Arg) |
219 | /* Parse the %v format specifier */ |
220 | { |
221 | /* Parse the symbol name parameter and check the type */ |
222 | SymEntry* Sym = AsmGetSym (Arg, SC_STATIC); |
223 | if (Sym == 0) { |
224 | /* Some sort of error */ |
225 | return; |
226 | } |
227 | |
228 | /* Check for external linkage */ |
229 | if (Sym->Flags & (SC_EXTERN | SC_STORAGE | SC_FUNC)) { |
230 | /* External linkage or a function */ |
231 | /* ### FIXME: Asm name should be generated by codegen */ |
232 | SB_AppendChar (T, '_'); |
233 | SB_AppendStr (T, Sym->Name); |
234 | } else if (Sym->Flags & SC_REGISTER) { |
235 | char Buf[32]; |
236 | xsprintf (Buf, sizeof (Buf), "regbank+%d" , Sym->V.R.RegOffs); |
237 | SB_AppendStr (T, Buf); |
238 | } else { |
239 | /* Static variable */ |
240 | char Buf [16]; |
241 | xsprintf (Buf, sizeof (Buf), "L%04X" , Sym->V.L.Label); |
242 | SB_AppendStr (T, Buf); |
243 | } |
244 | } |
245 | |
246 | |
247 | |
248 | static void ParseLVarArg (StrBuf* T, unsigned Arg) |
249 | /* Parse the %o format specifier */ |
250 | { |
251 | unsigned Offs; |
252 | char Buf [16]; |
253 | |
254 | /* Parse the symbol name parameter and check the type */ |
255 | SymEntry* Sym = AsmGetSym (Arg, SC_AUTO); |
256 | if (Sym == 0) { |
257 | /* Some sort of error */ |
258 | return; |
259 | } |
260 | |
261 | /* The symbol may be a parameter to a variadic function. In this case, we |
262 | ** don't have a fixed stack offset, so check it and bail out with an error |
263 | ** if this is the case. |
264 | */ |
265 | if ((Sym->Flags & SC_PARAM) == SC_PARAM && F_IsVariadic (CurrentFunc)) { |
266 | Error ("Argument %u has no fixed stack offset" , Arg); |
267 | AsmErrorSkip (); |
268 | return; |
269 | } |
270 | |
271 | /* Calculate the current offset from SP */ |
272 | Offs = Sym->V.Offs - StackPtr; |
273 | |
274 | /* Output the offset */ |
275 | xsprintf (Buf, sizeof (Buf), (Offs > 0xFF)? "$%04X" : "$%02X" , Offs); |
276 | SB_AppendStr (T, Buf); |
277 | } |
278 | |
279 | |
280 | |
281 | static void ParseLabelArg (StrBuf* T, unsigned Arg attribute ((unused))) |
282 | /* Parse the %g format specifier */ |
283 | { |
284 | /* We expect an identifier separated by a comma */ |
285 | ConsumeComma (); |
286 | if (CurTok.Tok != TOK_IDENT) { |
287 | |
288 | Error ("Label name expected" ); |
289 | |
290 | } else { |
291 | |
292 | /* Add a new label symbol if we don't have one until now */ |
293 | SymEntry* Entry = AddLabelSym (CurTok.Ident, SC_REF); |
294 | |
295 | /* Append the label name to the buffer */ |
296 | SB_AppendStr (T, LocalLabelName (Entry->V.L.Label)); |
297 | |
298 | /* Eat the label name */ |
299 | NextToken (); |
300 | |
301 | } |
302 | } |
303 | |
304 | |
305 | |
306 | static void ParseStrArg (StrBuf* T, unsigned Arg attribute ((unused))) |
307 | /* Parse the %s format specifier */ |
308 | { |
309 | ExprDesc Expr; |
310 | char Buf [64]; |
311 | |
312 | /* We expect an argument separated by a comma */ |
313 | ConsumeComma (); |
314 | |
315 | /* Check what comes */ |
316 | switch (CurTok.Tok) { |
317 | |
318 | case TOK_IDENT: |
319 | /* Identifier */ |
320 | SB_AppendStr (T, CurTok.Ident); |
321 | NextToken (); |
322 | break; |
323 | |
324 | case TOK_SCONST: |
325 | /* String constant */ |
326 | SB_Append (T, GetLiteralStrBuf (CurTok.SVal)); |
327 | NextToken (); |
328 | break; |
329 | |
330 | default: |
331 | ConstAbsIntExpr (hie1, &Expr); |
332 | xsprintf (Buf, sizeof (Buf), "%ld" , Expr.IVal); |
333 | SB_AppendStr (T, Buf); |
334 | break; |
335 | } |
336 | } |
337 | |
338 | |
339 | |
340 | static void ParseAsm (void) |
341 | /* Parse the contents of the ASM statement */ |
342 | { |
343 | unsigned Arg; |
344 | char C; |
345 | |
346 | /* Create a target string buffer */ |
347 | StrBuf T = AUTO_STRBUF_INITIALIZER; |
348 | |
349 | /* Create a string buffer from the string literal */ |
350 | StrBuf S = AUTO_STRBUF_INITIALIZER; |
351 | SB_Append (&S, GetLiteralStrBuf (CurTok.SVal)); |
352 | |
353 | /* Skip the string token */ |
354 | NextToken (); |
355 | |
356 | /* Parse the statement. It may contain several lines and one or more |
357 | ** of the following place holders: |
358 | ** %b - Numerical 8 bit value |
359 | ** %w - Numerical 16 bit value |
360 | ** %l - Numerical 32 bit value |
361 | ** %v - Assembler name of a (global) variable |
362 | ** %o - Stack offset of a (local) variable |
363 | ** %g - Assembler name of a C label |
364 | ** %s - Any argument converted to a string (almost) |
365 | ** %% - The % sign |
366 | */ |
367 | Arg = 0; |
368 | while ((C = SB_Get (&S)) != '\0') { |
369 | |
370 | /* If it is a newline, the current line is ready to go */ |
371 | if (C == '\n') { |
372 | |
373 | /* Pass it to the backend and start over */ |
374 | g_asmcode (&T); |
375 | SB_Clear (&T); |
376 | |
377 | } else if (C == '%') { |
378 | |
379 | /* Format specifier */ |
380 | ++Arg; |
381 | C = SB_Get (&S); |
382 | switch (C) { |
383 | case '%': SB_AppendChar (&T, '%'); break; |
384 | case 'b': ParseByteArg (&T, Arg); break; |
385 | case 'g': ParseLabelArg (&T, Arg); break; |
386 | case 'l': ParseLongArg (&T, Arg); break; |
387 | case 'o': ParseLVarArg (&T, Arg); break; |
388 | case 's': ParseStrArg (&T, Arg); break; |
389 | case 'v': ParseGVarArg (&T, Arg); break; |
390 | case 'w': ParseWordArg (&T, Arg); break; |
391 | default: |
392 | Error ("Error in __asm__ format specifier %u" , Arg); |
393 | AsmErrorSkip (); |
394 | goto Done; |
395 | } |
396 | |
397 | } else { |
398 | |
399 | /* A normal character, just copy it */ |
400 | SB_AppendChar (&T, C); |
401 | |
402 | } |
403 | } |
404 | |
405 | /* If the target buffer is not empty, we have a last line in there */ |
406 | if (!SB_IsEmpty (&T)) { |
407 | g_asmcode (&T); |
408 | } |
409 | |
410 | Done: |
411 | /* Call the string buf destructors */ |
412 | SB_Done (&S); |
413 | SB_Done (&T); |
414 | } |
415 | |
416 | |
417 | |
418 | void AsmStatement (void) |
419 | /* This function parses ASM statements. The syntax of the ASM directive |
420 | ** looks like the one defined for C++ (C has no ASM directive), that is, |
421 | ** a string literal in parenthesis. |
422 | */ |
423 | { |
424 | /* Skip the ASM */ |
425 | NextToken (); |
426 | |
427 | /* An optional volatile qualifier disables optimization for |
428 | ** the entire function [same as #pragma optimize(push, off)]. |
429 | */ |
430 | if (CurTok.Tok == TOK_VOLATILE) { |
431 | /* Don't optimize the Current code Segment */ |
432 | CS->Code->Optimize = 0; |
433 | NextToken (); |
434 | } |
435 | |
436 | /* Need left parenthesis */ |
437 | if (!ConsumeLParen ()) { |
438 | return; |
439 | } |
440 | |
441 | /* String literal */ |
442 | if (CurTok.Tok != TOK_SCONST) { |
443 | |
444 | /* Print a diagnostic */ |
445 | Error ("String literal expected" ); |
446 | |
447 | /* Try some smart error recovery: Skip tokens until we reach the |
448 | ** enclosing paren, or a semicolon. |
449 | */ |
450 | AsmErrorSkip (); |
451 | |
452 | } else { |
453 | |
454 | /* Parse the ASM statement */ |
455 | ParseAsm (); |
456 | } |
457 | |
458 | /* Closing paren needed */ |
459 | ConsumeRParen (); |
460 | } |
461 | |