Home | History | Annotate | Line # | Download | only in dmd
iasmgcc.d revision 1.1.1.1
      1 /**
      2  * Inline assembler for the GCC D compiler.
      3  *
      4  *              Copyright (C) 2018-2022 by The D Language Foundation, All Rights Reserved
      5  * Authors:     Iain Buclaw
      6  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
      7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/iasmgcc.d, _iasmgcc.d)
      8  * Documentation:  https://dlang.org/phobos/dmd_iasmgcc.html
      9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/iasmgcc.d
     10  */
     11 
     12 module dmd.iasmgcc;
     13 
     14 import core.stdc.string;
     15 
     16 import dmd.arraytypes;
     17 import dmd.astcodegen;
     18 import dmd.dscope;
     19 import dmd.errors;
     20 import dmd.expression;
     21 import dmd.expressionsem;
     22 import dmd.identifier;
     23 import dmd.globals;
     24 import dmd.parse;
     25 import dmd.tokens;
     26 import dmd.statement;
     27 import dmd.statementsem;
     28 
     29 private:
     30 
     31 /***********************************
     32  * Parse list of extended asm input or output operands.
     33  * Grammar:
     34  *      | Operands:
     35  *      |     SymbolicName(opt) StringLiteral ( AssignExpression )
     36  *      |     SymbolicName(opt) StringLiteral ( AssignExpression ), Operands
     37  *      |
     38  *      | SymbolicName:
     39  *      |     [ Identifier ]
     40  * Params:
     41  *      p = parser state
     42  *      s = asm statement to parse
     43  * Returns:
     44  *      number of operands added to the gcc asm statement
     45  */
     46 int parseExtAsmOperands(Parser)(Parser p, GccAsmStatement s)
     47 {
     48     int numargs = 0;
     49 
     50     while (1)
     51     {
     52         Expression arg;
     53         Identifier name;
     54         Expression constraint;
     55 
     56         switch (p.token.value)
     57         {
     58             case TOK.semicolon:
     59             case TOK.colon:
     60             case TOK.endOfFile:
     61                 return numargs;
     62 
     63             case TOK.leftBracket:
     64                 if (p.peekNext() == TOK.identifier)
     65                 {
     66                     // Skip over opening `[`
     67                     p.nextToken();
     68                     // Store the symbolic name
     69                     name = p.token.ident;
     70                     p.nextToken();
     71                 }
     72                 else
     73                 {
     74                     p.error(s.loc, "expected identifier after `[`");
     75                     goto Lerror;
     76                 }
     77                 // Look for closing `]`
     78                 p.check(TOK.rightBracket);
     79                 // Look for the string literal and fall through
     80                 if (p.token.value == TOK.string_)
     81                     goto case;
     82                 else
     83                     goto default;
     84 
     85             case TOK.string_:
     86                 constraint = p.parsePrimaryExp();
     87                 // @@@DEPRECATED_2.101@@@
     88                 // Old parser allowed omitting parentheses around the expression.
     89                 // Deprecated in 2.091. Can be made permanent error after 2.100
     90                 if (p.token.value != TOK.leftParenthesis)
     91                 {
     92                     arg = p.parseAssignExp();
     93                     deprecation(arg.loc, "`%s` must be surrounded by parentheses", arg.toChars());
     94                 }
     95                 else
     96                 {
     97                     // Look for the opening `(`
     98                     p.check(TOK.leftParenthesis);
     99                     // Parse the assign expression
    100                     arg = p.parseAssignExp();
    101                     // Look for the closing `)`
    102                     p.check(TOK.rightParenthesis);
    103                 }
    104 
    105                 if (!s.args)
    106                 {
    107                     s.names = new Identifiers();
    108                     s.constraints = new Expressions();
    109                     s.args = new Expressions();
    110                 }
    111                 s.names.push(name);
    112                 s.args.push(arg);
    113                 s.constraints.push(constraint);
    114                 numargs++;
    115 
    116                 if (p.token.value == TOK.comma)
    117                     p.nextToken();
    118                 break;
    119 
    120             default:
    121                 p.error("expected constant string constraint for operand, not `%s`",
    122                         p.token.toChars());
    123                 goto Lerror;
    124         }
    125     }
    126 Lerror:
    127     while (p.token.value != TOK.rightCurly &&
    128            p.token.value != TOK.semicolon &&
    129            p.token.value != TOK.endOfFile)
    130         p.nextToken();
    131 
    132     return numargs;
    133 }
    134 
    135 /***********************************
    136  * Parse list of extended asm clobbers.
    137  * Grammar:
    138  *      | Clobbers:
    139  *      |     StringLiteral
    140  *      |     StringLiteral , Clobbers
    141  * Params:
    142  *      p = parser state
    143  * Returns:
    144  *      array of parsed clobber expressions
    145  */
    146 Expressions *parseExtAsmClobbers(Parser)(Parser p)
    147 {
    148     Expressions *clobbers;
    149 
    150     while (1)
    151     {
    152         Expression clobber;
    153 
    154         switch (p.token.value)
    155         {
    156             case TOK.semicolon:
    157             case TOK.colon:
    158             case TOK.endOfFile:
    159                 return clobbers;
    160 
    161             case TOK.string_:
    162                 clobber = p.parsePrimaryExp();
    163                 if (!clobbers)
    164                     clobbers = new Expressions();
    165                 clobbers.push(clobber);
    166 
    167                 if (p.token.value == TOK.comma)
    168                     p.nextToken();
    169                 break;
    170 
    171             default:
    172                 p.error("expected constant string constraint for clobber name, not `%s`",
    173                         p.token.toChars());
    174                 goto Lerror;
    175         }
    176     }
    177 Lerror:
    178     while (p.token.value != TOK.rightCurly &&
    179            p.token.value != TOK.semicolon &&
    180            p.token.value != TOK.endOfFile)
    181         p.nextToken();
    182 
    183     return clobbers;
    184 }
    185 
    186 /***********************************
    187  * Parse list of extended asm goto labels.
    188  * Grammar:
    189  *      | GotoLabels:
    190  *      |     Identifier
    191  *      |     Identifier , GotoLabels
    192  * Params:
    193  *      p = parser state
    194  * Returns:
    195  *      array of parsed goto labels
    196  */
    197 Identifiers *parseExtAsmGotoLabels(Parser)(Parser p)
    198 {
    199     Identifiers *labels;
    200 
    201     while (1)
    202     {
    203         switch (p.token.value)
    204         {
    205             case TOK.semicolon:
    206             case TOK.endOfFile:
    207                 return labels;
    208 
    209             case TOK.identifier:
    210                 if (!labels)
    211                     labels = new Identifiers();
    212                 labels.push(p.token.ident);
    213 
    214                 if (p.nextToken() == TOK.comma)
    215                     p.nextToken();
    216                 break;
    217 
    218             default:
    219                 p.error("expected identifier for goto label name, not `%s`",
    220                         p.token.toChars());
    221                 goto Lerror;
    222         }
    223     }
    224 Lerror:
    225     while (p.token.value != TOK.rightCurly &&
    226            p.token.value != TOK.semicolon &&
    227            p.token.value != TOK.endOfFile)
    228         p.nextToken();
    229 
    230     return labels;
    231 }
    232 
    233 /***********************************
    234  * Parse a gcc asm statement.
    235  * There are three forms of inline asm statements, basic, extended, and goto.
    236  * Grammar:
    237  *      | AsmInstruction:
    238  *      |     BasicAsmInstruction
    239  *      |     ExtAsmInstruction
    240  *      |     GotoAsmInstruction
    241  *      |
    242  *      | BasicAsmInstruction:
    243  *      |     AssignExpression
    244  *      |
    245  *      | ExtAsmInstruction:
    246  *      |     AssignExpression : Operands(opt) : Operands(opt) : Clobbers(opt)
    247  *      |
    248  *      | GotoAsmInstruction:
    249  *      |     AssignExpression : : Operands(opt) : Clobbers(opt) : GotoLabels(opt)
    250  * Params:
    251  *      p = parser state
    252  *      s = asm statement to parse
    253  * Returns:
    254  *      the parsed gcc asm statement
    255  */
    256 GccAsmStatement parseGccAsm(Parser)(Parser p, GccAsmStatement s)
    257 {
    258     s.insn = p.parseAssignExp();
    259     if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile)
    260         goto Ldone;
    261 
    262     // No semicolon followed after instruction template, treat as extended asm.
    263     foreach (section; 0 .. 4)
    264     {
    265         p.check(TOK.colon);
    266 
    267         final switch (section)
    268         {
    269             case 0:
    270                 s.outputargs = p.parseExtAsmOperands(s);
    271                 break;
    272 
    273             case 1:
    274                 p.parseExtAsmOperands(s);
    275                 break;
    276 
    277             case 2:
    278                 s.clobbers = p.parseExtAsmClobbers();
    279                 break;
    280 
    281             case 3:
    282                 s.labels = p.parseExtAsmGotoLabels();
    283                 break;
    284         }
    285 
    286         if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile)
    287             goto Ldone;
    288     }
    289 Ldone:
    290     p.check(TOK.semicolon);
    291 
    292     return s;
    293 }
    294 
    295 /***********************************
    296  * Parse and run semantic analysis on a GccAsmStatement.
    297  * Params:
    298  *      s  = gcc asm statement being parsed
    299  *      sc = the scope where the asm statement is located
    300  * Returns:
    301  *      the completed gcc asm statement, or null if errors occurred
    302  */
    303 extern (C++) public Statement gccAsmSemantic(GccAsmStatement s, Scope *sc)
    304 {
    305     //printf("GccAsmStatement.semantic()\n");
    306     scope p = new Parser!ASTCodegen(sc._module, ";", false);
    307 
    308     // Make a safe copy of the token list before parsing.
    309     Token *toklist = null;
    310     Token **ptoklist = &toklist;
    311 
    312     for (Token *token = s.tokens; token; token = token.next)
    313     {
    314         *ptoklist = p.allocateToken();
    315         memcpy(*ptoklist, token, Token.sizeof);
    316         ptoklist = &(*ptoklist).next;
    317         *ptoklist = null;
    318     }
    319     p.token = *toklist;
    320     p.scanloc = s.loc;
    321 
    322     // Parse the gcc asm statement.
    323     const errors = global.errors;
    324     s = p.parseGccAsm(s);
    325     if (errors != global.errors)
    326         return null;
    327     s.stc = sc.stc;
    328 
    329     // Fold the instruction template string.
    330     s.insn = semanticString(sc, s.insn, "asm instruction template");
    331 
    332     if (s.labels && s.outputargs)
    333         s.error("extended asm statements with labels cannot have output constraints");
    334 
    335     // Analyse all input and output operands.
    336     if (s.args)
    337     {
    338         foreach (i; 0 .. s.args.dim)
    339         {
    340             Expression e = (*s.args)[i];
    341             e = e.expressionSemantic(sc);
    342             // Check argument is a valid lvalue/rvalue.
    343             if (i < s.outputargs)
    344                 e = e.modifiableLvalue(sc, null);
    345             else if (e.checkValue())
    346                 e = ErrorExp.get();
    347             (*s.args)[i] = e;
    348 
    349             e = (*s.constraints)[i];
    350             e = e.expressionSemantic(sc);
    351             assert(e.op == EXP.string_ && (cast(StringExp) e).sz == 1);
    352             (*s.constraints)[i] = e;
    353         }
    354     }
    355 
    356     // Analyse all clobbers.
    357     if (s.clobbers)
    358     {
    359         foreach (i; 0 .. s.clobbers.dim)
    360         {
    361             Expression e = (*s.clobbers)[i];
    362             e = e.expressionSemantic(sc);
    363             assert(e.op == EXP.string_ && (cast(StringExp) e).sz == 1);
    364             (*s.clobbers)[i] = e;
    365         }
    366     }
    367 
    368     // Analyse all goto labels.
    369     if (s.labels)
    370     {
    371         foreach (i; 0 .. s.labels.dim)
    372         {
    373             Identifier ident = (*s.labels)[i];
    374             GotoStatement gs = new GotoStatement(s.loc, ident);
    375             if (!s.gotos)
    376                 s.gotos = new GotoStatements();
    377             s.gotos.push(gs);
    378             gs.statementSemantic(sc);
    379         }
    380     }
    381 
    382     return s;
    383 }
    384 
    385 unittest
    386 {
    387     import dmd.mtype : TypeBasic;
    388 
    389     uint errors = global.startGagging();
    390     scope(exit) global.endGagging(errors);
    391 
    392     // If this check fails, then Type._init() was called before reaching here,
    393     // and the entire chunk of code that follows can be removed.
    394     assert(ASTCodegen.Type.tint32 is null);
    395     // Minimally initialize the cached types in ASTCodegen.Type, as they are
    396     // dependencies for some fail asm tests to succeed.
    397     ASTCodegen.Type.stringtable._init();
    398     scope(exit)
    399     {
    400         ASTCodegen.Type.deinitialize();
    401         ASTCodegen.Type.tint32 = null;
    402     }
    403     scope tint32 = new TypeBasic(ASTCodegen.Tint32);
    404     ASTCodegen.Type.tint32 = tint32;
    405 
    406     // Imitates asmSemantic if version = IN_GCC.
    407     static int semanticAsm(Token* tokens)
    408     {
    409         const errors = global.errors;
    410         scope gas = new GccAsmStatement(Loc.initial, tokens);
    411         scope p = new Parser!ASTCodegen(null, ";", false);
    412         p.token = *tokens;
    413         p.parseGccAsm(gas);
    414         return global.errors - errors;
    415     }
    416 
    417     // Imitates parseStatement for asm statements.
    418     static void parseAsm(string input, bool expectError)
    419     {
    420         // Generate tokens from input test.
    421         scope p = new Parser!ASTCodegen(null, input, false);
    422         p.nextToken();
    423 
    424         Token* toklist = null;
    425         Token** ptoklist = &toklist;
    426         p.check(TOK.asm_);
    427         p.check(TOK.leftCurly);
    428         while (1)
    429         {
    430             if (p.token.value == TOK.rightCurly || p.token.value == TOK.endOfFile)
    431                 break;
    432             if (p.token.value == TOK.colonColon)
    433             {
    434                 *ptoklist = p.allocateToken();
    435                 memcpy(*ptoklist, &p.token, Token.sizeof);
    436                 (*ptoklist).value = TOK.colon;
    437                 ptoklist = &(*ptoklist).next;
    438 
    439                 *ptoklist = p.allocateToken();
    440                 memcpy(*ptoklist, &p.token, Token.sizeof);
    441                 (*ptoklist).value = TOK.colon;
    442                 ptoklist = &(*ptoklist).next;
    443             }
    444             else
    445             {
    446                 *ptoklist = p.allocateToken();
    447                 memcpy(*ptoklist, &p.token, Token.sizeof);
    448                 ptoklist = &(*ptoklist).next;
    449             }
    450             *ptoklist = null;
    451             p.nextToken();
    452         }
    453         p.check(TOK.rightCurly);
    454 
    455         auto res = semanticAsm(toklist);
    456         // Checks for both unexpected passes and failures.
    457         assert((res == 0) != expectError);
    458     }
    459 
    460     /// Assembly Tests, all should pass.
    461     /// Note: Frontend is not initialized, use only strings and identifiers.
    462     immutable string[] passAsmTests = [
    463         // Basic asm statement
    464         q{ asm { "nop";
    465         } },
    466 
    467         // Extended asm statement
    468         q{ asm { "cpuid"
    469                : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
    470                : "a" (input);
    471         } },
    472 
    473         // Assembly with symbolic names
    474         q{ asm { "bts %[base], %[offset]"
    475                : [base] "+rm" (*ptr),
    476                : [offset] "Ir" (bitnum);
    477         } },
    478 
    479         // Assembly with clobbers
    480         q{ asm { "cpuid"
    481                : "=a" (a)
    482                : "a" (input)
    483                : "ebx", "ecx", "edx";
    484         } },
    485 
    486         // Goto asm statement
    487         q{ asm { "jmp %l0"
    488                :
    489                :
    490                :
    491                : Ljmplabel;
    492         } },
    493 
    494         // Any CTFE-able string allowed as instruction template.
    495         q{ asm { generateAsm();
    496         } },
    497 
    498         // Likewise mixins, permissible so long as the result is a string.
    499         q{ asm { mixin(`"repne"`, `~ "scasb"`);
    500         } },
    501 
    502         // :: token tests
    503         q{ asm { "" : : : "memory"; } },
    504         q{ asm { "" :: : "memory"; } },
    505         q{ asm { "" : :: "memory"; } },
    506         q{ asm { "" ::: "memory"; } },
    507     ];
    508 
    509     immutable string[] failAsmTests = [
    510         // Found 'h' when expecting ';'
    511         q{ asm { ""h;
    512         } },
    513 
    514         // https://issues.dlang.org/show_bug.cgi?id=20592
    515         q{ asm { "nop" : [name] string (expr); } },
    516 
    517         // Expression expected, not ';'
    518         q{ asm { ""[;
    519         } },
    520 
    521         // Expression expected, not ':'
    522         q{ asm { ""
    523                :
    524                : "g" (a ? b : : c);
    525         } },
    526 
    527         // Found ',' when expecting ':'
    528         q{ asm { "", "";
    529         } },
    530     ];
    531 
    532     foreach (test; passAsmTests)
    533         parseAsm(test, false);
    534 
    535     foreach (test; failAsmTests)
    536         parseAsm(test, true);
    537 }
    538