/*****************************************************************************/ /* */ /* asmstmt.c */ /* */ /* Inline assembler statements for the cc65 C compiler */ /* */ /* */ /* */ /* (C) 2001-2008 Ullrich von Bassewitz */ /* Roemerstrasse 52 */ /* D-70794 Filderstadt */ /* EMail: uz@musoftware.de */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ /* warranty. In no event will the authors be held liable for any damages */ /* arising from the use of this software. */ /* */ /* Permission is granted to anyone to use this software for any purpose, */ /* including commercial applications, and to alter it and redistribute it */ /* freely, subject to the following restrictions: */ /* */ /* 1. The origin of this software must not be misrepresented; you must not */ /* claim that you wrote the original software. If you use this software */ /* in a product, an acknowledgment in the product documentation would be */ /* appreciated but is not required. */ /* 2. Altered source versions must be plainly marked as such, and must not */ /* be misrepresented as being the original software. */ /* 3. This notice may not be removed or altered from any source */ /* distribution. */ /* */ /*****************************************************************************/ #include /* common */ #include "xsprintf.h" /* cc65 */ #include "asmlabel.h" #include "codegen.h" #include "codeseg.h" #include "datatype.h" #include "error.h" #include "expr.h" #include "function.h" #include "litpool.h" #include "scanner.h" #include "segments.h" #include "stackptr.h" #include "symtab.h" #include "asmstmt.h" /*****************************************************************************/ /* Code */ /*****************************************************************************/ static void AsmRangeError (unsigned Arg) /* Print a diagnostic about a range error in the argument with the given number */ { Error ("Range error in argument %u", Arg); } static void AsmErrorSkip (void) /* Called in case of an error, skips tokens until the closing paren or a ** semicolon is reached. */ { static const token_t TokenList[] = { TOK_RPAREN, TOK_SEMI }; SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0])); } static SymEntry* AsmGetSym (unsigned Arg, int OnStack) /* Find the symbol with the name currently in NextTok. The symbol must be on ** the stack if OnStack is true. On errors, NULL is returned. */ { SymEntry* Sym; /* We expect an argument separated by a comma */ ConsumeComma (); /* Argument must be an identifier */ if (CurTok.Tok != TOK_IDENT) { Error ("Identifier expected for argument %u", Arg); AsmErrorSkip (); return 0; } /* Get a pointer to the symbol table entry */ Sym = FindSym (CurTok.Ident); /* Did we find a symbol with this name? */ if (Sym == 0) { Error ("Undeclared symbol '%s' for argument %u", CurTok.Ident, Arg); AsmErrorSkip (); return 0; } /* We found the symbol - skip the name token */ NextToken (); /* Check if the symbol is on the stack */ if ((Sym->Flags & SC_STORAGEMASK) != SC_AUTO ? OnStack : !OnStack) { Error ("Type of argument %u differs from format specifier", Arg); AsmErrorSkip (); return 0; } /* Mark the symbol as referenced */ Sym->Flags |= SC_REF; /* Return it */ return Sym; } static void ParseByteArg (StrBuf* T, unsigned Arg) /* Parse the %b format specifier */ { char Buf [16]; /* We expect an argument separated by a comma */ ConsumeComma (); /* Evaluate the expression */ ExprDesc Expr = NoCodeConstAbsIntExpr (hie1); /* Check the range but allow negative values if the type is signed */ if (IsSignUnsigned (Expr.Type)) { if (Expr.IVal < 0 || Expr.IVal > 0xFF) { AsmRangeError (Arg); Expr.IVal = 0; } } else { if (Expr.IVal < -128 || Expr.IVal > 127) { AsmRangeError (Arg); Expr.IVal = 0; } } /* Convert into a hex number */ xsprintf (Buf, sizeof (Buf), "$%02lX", Expr.IVal & 0xFF); /* Add the number to the target buffer */ SB_AppendStr (T, Buf); } static void ParseWordArg (StrBuf* T, unsigned Arg) /* Parse the %w format specifier */ { char Buf [16]; /* We expect an argument separated by a comma */ ConsumeComma (); /* Evaluate the expression */ ExprDesc Expr = NoCodeConstAbsIntExpr (hie1); /* Check the range but allow negative values if the type is signed */ if (IsSignUnsigned (Expr.Type)) { if (Expr.IVal < 0 || Expr.IVal > 0xFFFF) { AsmRangeError (Arg); Expr.IVal = 0; } } else { if (Expr.IVal < -32768 || Expr.IVal > 32767) { AsmRangeError (Arg); Expr.IVal = 0; } } /* Convert into a hex number */ xsprintf (Buf, sizeof (Buf), "$%04lX", Expr.IVal & 0xFFFF); /* Add the number to the target buffer */ SB_AppendStr (T, Buf); } static void ParseLongArg (StrBuf* T, unsigned Arg attribute ((unused))) /* Parse the %l format specifier */ { char Buf [16]; /* We expect an argument separated by a comma */ ConsumeComma (); /* Evaluate the expression */ ExprDesc Expr = NoCodeConstAbsIntExpr (hie1); /* Convert into a hex number */ xsprintf (Buf, sizeof (Buf), "$%08lX", Expr.IVal & 0xFFFFFFFF); /* Add the number to the target buffer */ SB_AppendStr (T, Buf); } static void ParseGVarArg (StrBuf* T, unsigned Arg) /* Parse the %v format specifier. ** ### FIXME: Asm names should be generated in the same place. */ { /* Parse the symbol name parameter and check the type */ SymEntry* Sym = AsmGetSym (Arg, 0); if (Sym == 0) { /* Some sort of error */ return; } /* Get the correct asm name */ if ((Sym->Flags & SC_TYPEMASK) == SC_FUNC || SymIsGlobal (Sym)) { /* External or internal linkage or a function */ SB_AppendChar (T, '_'); SB_AppendStr (T, Sym->Name); } else if ((Sym->Flags & SC_STORAGEMASK) == SC_REGISTER) { /* Register variable */ char Buf[32]; xsprintf (Buf, sizeof (Buf), "regbank+%d", Sym->V.R.RegOffs); SB_AppendStr (T, Buf); } else { /* Local static variable */ SB_AppendStr (T, LocalDataLabelName (Sym->V.L.Label)); } } static void ParseLVarArg (StrBuf* T, unsigned Arg) /* Parse the %o format specifier */ { unsigned Offs; char Buf [16]; /* Parse the symbol name parameter and check the type */ SymEntry* Sym = AsmGetSym (Arg, 1); if (Sym == 0) { /* Some sort of error */ return; } /* The symbol may be a parameter to a variadic function. In this case, we ** don't have a fixed stack offset, so check it and bail out with an error ** if this is the case. */ if ((Sym->Flags & SC_PARAM) == SC_PARAM && F_IsVariadic (CurrentFunc)) { Error ("Argument %u has no fixed stack offset", Arg); AsmErrorSkip (); return; } /* Calculate the current offset from SP */ Offs = Sym->V.Offs - StackPtr; /* Output the offset */ xsprintf (Buf, sizeof (Buf), (Offs > 0xFF)? "$%04X" : "$%02X", Offs); SB_AppendStr (T, Buf); } static void ParseLabelArg (StrBuf* T, unsigned Arg attribute ((unused))) /* Parse the %g format specifier */ { /* We expect an identifier separated by a comma */ ConsumeComma (); if (CurTok.Tok != TOK_IDENT) { Error ("Label name expected"); } else { /* Add a new C label symbol if we don't have one until now */ SymEntry* Entry = AddLabelSym (CurTok.Ident, SC_REF); /* Append the label name to the buffer */ SB_AppendStr (T, LocalLabelName (Entry->V.L.Label)); /* Eat the label name */ NextToken (); } } static void ParseStrArg (StrBuf* T, unsigned Arg attribute ((unused))) /* Parse the %s format specifier */ { ExprDesc Expr; char Buf [64]; /* We expect an argument separated by a comma */ ConsumeComma (); /* Check what comes */ switch (CurTok.Tok) { case TOK_IDENT: /* Identifier */ SB_AppendStr (T, CurTok.Ident); NextToken (); break; case TOK_SCONST: /* String constant */ SB_Append (T, GetLiteralStrBuf (CurTok.SVal)); NextToken (); break; default: Expr = NoCodeConstAbsIntExpr (hie1); xsprintf (Buf, sizeof (Buf), "%ld", Expr.IVal); SB_AppendStr (T, Buf); break; } } static void ParseAsm (void) /* Parse the contents of the ASM statement */ { unsigned Arg; char C; /* Create a target string buffer */ StrBuf T = AUTO_STRBUF_INITIALIZER; /* Create a string buffer from the string literal */ StrBuf S = AUTO_STRBUF_INITIALIZER; SB_Append (&S, GetLiteralStrBuf (CurTok.SVal)); /* Skip the string token */ NextToken (); /* Parse the statement. It may contain several lines and one or more ** of the following place holders: ** %b - Numerical 8 bit value ** %w - Numerical 16 bit value ** %l - Numerical 32 bit value ** %v - Assembler name of a (global) variable ** %o - Stack offset of a (local) variable ** %g - Assembler name of a C label ** %s - Any argument converted to a string (almost) ** %% - The % sign */ Arg = 0; while ((C = SB_Get (&S)) != '\0') { /* If it is a newline, the current line is ready to go */ if (C == '\n') { /* Pass it to the backend and start over */ g_asmcode (&T); SB_Clear (&T); } else if (C == '%') { /* Format specifier */ ++Arg; C = SB_Get (&S); switch (C) { case '%': SB_AppendChar (&T, '%'); break; case 'b': ParseByteArg (&T, Arg); break; case 'g': ParseLabelArg (&T, Arg); break; case 'l': ParseLongArg (&T, Arg); break; case 'o': ParseLVarArg (&T, Arg); break; case 's': ParseStrArg (&T, Arg); break; case 'v': ParseGVarArg (&T, Arg); break; case 'w': ParseWordArg (&T, Arg); break; default: Error ("Error in __asm__ format specifier %u", Arg); AsmErrorSkip (); goto Done; } } else { /* A normal character, just copy it */ SB_AppendChar (&T, C); } } /* If the target buffer is not empty, we have a last line in there */ if (!SB_IsEmpty (&T)) { g_asmcode (&T); } Done: /* Call the string buf destructors */ SB_Done (&S); SB_Done (&T); } void AsmStatement (void) /* This function parses ASM statements. The syntax of the ASM directive ** looks like the one defined for C++ (C has no ASM directive), that is, ** a string literal in parenthesis. */ { /* Prevent from translating the inline code string literal in asm */ NoCharMap = 1; /* Skip the ASM */ NextToken (); /* An optional volatile qualifier disables optimization for ** the entire function [same as #pragma optimize(push, off)]. */ if (CurTok.Tok == TOK_VOLATILE) { /* Don't optimize the Current code Segment */ CS->Code->Optimize = 0; NextToken (); } /* Need left parenthesis */ if (!ConsumeLParen ()) { NoCharMap = 0; return; } /* We have got the inline code string untranslated, now reenable string ** literal translation for string arguments (if any). */ NoCharMap = 0; /* String literal */ if (CurTok.Tok != TOK_SCONST) { /* Print a diagnostic */ Error ("String literal expected"); /* Try some smart error recovery: Skip tokens until we reach the ** enclosing paren, or a semicolon. */ AsmErrorSkip (); } else { /* Parse the ASM statement */ ParseAsm (); } /* Closing paren needed */ ConsumeRParen (); }