1 /**
2  * Inline assembler for the GCC D compiler.
3  *
4  *              Copyright (C) 2018-2023 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.errorsink;
21 import dmd.expression;
22 import dmd.expressionsem;
23 import dmd.identifier;
24 import dmd.globals;
25 import dmd.location;
26 import dmd.parse;
27 import dmd.tokens;
28 import dmd.statement;
29 import dmd.statementsem;
30 
31 private:
32 
33 /***********************************
34  * Parse list of extended asm input or output operands.
35  * Grammar:
36  *      | Operands:
37  *      |     SymbolicName(opt) StringLiteral ( AssignExpression )
38  *      |     SymbolicName(opt) StringLiteral ( AssignExpression ), Operands
39  *      |
40  *      | SymbolicName:
41  *      |     [ Identifier ]
42  * Params:
43  *      p = parser state
44  *      s = asm statement to parse
45  * Returns:
46  *      number of operands added to the gcc asm statement
47  */
48 int parseExtAsmOperands(Parser)(Parser p, GccAsmStatement s)
49 {
50     int numargs = 0;
51 
52     while (1)
53     {
54         Expression arg;
55         Identifier name;
56         Expression constraint;
57 
58         switch (p.token.value)
59         {
60             case TOK.semicolon:
61             case TOK.colon:
62             case TOK.endOfFile:
63                 return numargs;
64 
65             case TOK.leftBracket:
66                 if (p.peekNext() == TOK.identifier)
67                 {
68                     // Skip over opening `[`
69                     p.nextToken();
70                     // Store the symbolic name
71                     name = p.token.ident;
72                     p.nextToken();
73                 }
74                 else
75                 {
76                     p.eSink.error(s.loc, "expected identifier after `[`");
77                     goto Lerror;
78                 }
79                 // Look for closing `]`
80                 p.check(TOK.rightBracket);
81                 // Look for the string literal and fall through
82                 if (p.token.value == TOK.string_)
83                     goto case;
84                 else
85                     goto default;
86 
87             case TOK.string_:
88                 constraint = p.parsePrimaryExp();
89                 if (p.token.value != TOK.leftParenthesis)
90                 {
91                     arg = p.parseAssignExp();
92                     error(arg.loc, "`%s` must be surrounded by parentheses", arg.toChars());
93                 }
94                 else
95                 {
96                     // Look for the opening `(`
97                     p.check(TOK.leftParenthesis);
98                     // Parse the assign expression
99                     arg = p.parseAssignExp();
100                     // Look for the closing `)`
101                     p.check(TOK.rightParenthesis);
102                 }
103 
104                 if (!s.args)
105                 {
106                     s.names = new Identifiers();
107                     s.constraints = new Expressions();
108                     s.args = new Expressions();
109                 }
110                 s.names.push(name);
111                 s.args.push(arg);
112                 s.constraints.push(constraint);
113                 numargs++;
114 
115                 if (p.token.value == TOK.comma)
116                     p.nextToken();
117                 break;
118 
119             default:
120                 p.eSink.error(p.token.loc, "expected constant string constraint for operand, not `%s`",
121                         p.token.toChars());
122                 goto Lerror;
123         }
124     }
125 Lerror:
126     while (p.token.value != TOK.rightCurly &&
127            p.token.value != TOK.semicolon &&
128            p.token.value != TOK.endOfFile)
129         p.nextToken();
130 
131     return numargs;
132 }
133 
134 /***********************************
135  * Parse list of extended asm clobbers.
136  * Grammar:
137  *      | Clobbers:
138  *      |     StringLiteral
139  *      |     StringLiteral , Clobbers
140  * Params:
141  *      p = parser state
142  * Returns:
143  *      array of parsed clobber expressions
144  */
145 Expressions *parseExtAsmClobbers(Parser)(Parser p)
146 {
147     Expressions *clobbers;
148 
149     while (1)
150     {
151         Expression clobber;
152 
153         switch (p.token.value)
154         {
155             case TOK.semicolon:
156             case TOK.colon:
157             case TOK.endOfFile:
158                 return clobbers;
159 
160             case TOK.string_:
161                 clobber = p.parsePrimaryExp();
162                 if (!clobbers)
163                     clobbers = new Expressions();
164                 clobbers.push(clobber);
165 
166                 if (p.token.value == TOK.comma)
167                     p.nextToken();
168                 break;
169 
170             default:
171                 p.eSink.error(p.token.loc, "expected constant string constraint for clobber name, not `%s`",
172                         p.token.toChars());
173                 goto Lerror;
174         }
175     }
176 Lerror:
177     while (p.token.value != TOK.rightCurly &&
178            p.token.value != TOK.semicolon &&
179            p.token.value != TOK.endOfFile)
180         p.nextToken();
181 
182     return clobbers;
183 }
184 
185 /***********************************
186  * Parse list of extended asm goto labels.
187  * Grammar:
188  *      | GotoLabels:
189  *      |     Identifier
190  *      |     Identifier , GotoLabels
191  * Params:
192  *      p = parser state
193  * Returns:
194  *      array of parsed goto labels
195  */
196 Identifiers *parseExtAsmGotoLabels(Parser)(Parser p)
197 {
198     Identifiers *labels;
199 
200     while (1)
201     {
202         switch (p.token.value)
203         {
204             case TOK.semicolon:
205             case TOK.endOfFile:
206                 return labels;
207 
208             case TOK.identifier:
209                 if (!labels)
210                     labels = new Identifiers();
211                 labels.push(p.token.ident);
212 
213                 if (p.nextToken() == TOK.comma)
214                     p.nextToken();
215                 break;
216 
217             default:
218                 p.eSink.error(p.token.loc, "expected identifier for goto label name, not `%s`",
219                         p.token.toChars());
220                 goto Lerror;
221         }
222     }
223 Lerror:
224     while (p.token.value != TOK.rightCurly &&
225            p.token.value != TOK.semicolon &&
226            p.token.value != TOK.endOfFile)
227         p.nextToken();
228 
229     return labels;
230 }
231 
232 /***********************************
233  * Parse a gcc asm statement.
234  * There are three forms of inline asm statements, basic, extended, and goto.
235  * Grammar:
236  *      | AsmInstruction:
237  *      |     BasicAsmInstruction
238  *      |     ExtAsmInstruction
239  *      |     GotoAsmInstruction
240  *      |
241  *      | BasicAsmInstruction:
242  *      |     AssignExpression
243  *      |
244  *      | ExtAsmInstruction:
245  *      |     AssignExpression : Operands(opt) : Operands(opt) : Clobbers(opt)
246  *      |
247  *      | GotoAsmInstruction:
248  *      |     AssignExpression : : Operands(opt) : Clobbers(opt) : GotoLabels(opt)
249  * Params:
250  *      p = parser state
251  *      s = asm statement to parse
252  * Returns:
253  *      the parsed gcc asm statement
254  */
255 GccAsmStatement parseGccAsm(Parser)(Parser p, GccAsmStatement s)
256 {
257     s.insn = p.parseAssignExp();
258     if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile)
259         goto Ldone;
260 
261     // No semicolon followed after instruction template, treat as extended asm.
262     foreach (section; 0 .. 4)
263     {
264         p.check(TOK.colon);
265 
266         final switch (section)
267         {
268             case 0:
269                 s.outputargs = p.parseExtAsmOperands(s);
270                 break;
271 
272             case 1:
273                 p.parseExtAsmOperands(s);
274                 break;
275 
276             case 2:
277                 s.clobbers = p.parseExtAsmClobbers();
278                 break;
279 
280             case 3:
281                 s.labels = p.parseExtAsmGotoLabels();
282                 break;
283         }
284 
285         if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile)
286             goto Ldone;
287     }
288 Ldone:
289     p.check(TOK.semicolon);
290 
291     return s;
292 }
293 
294 /***********************************
295  * Parse and run semantic analysis on a GccAsmStatement.
296  * Params:
297  *      s  = gcc asm statement being parsed
298  *      sc = the scope where the asm statement is located
299  * Returns:
300  *      the completed gcc asm statement, or null if errors occurred
301  */
302 extern (C++) public Statement gccAsmSemantic(GccAsmStatement s, Scope *sc)
303 {
304     //printf("GccAsmStatement.semantic()\n");
305     const bool doUnittests = global.params.useUnitTests || global.params.ddoc.doOutput || global.params.dihdr.doOutput;
306     scope p = new Parser!ASTCodegen(sc._module, ";", false, global.errorSink, &global.compileEnv, doUnittests);
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         error(s.loc, "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.length)
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.length)
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.length)
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     if (!global.errorSink)
390         global.errorSink = new ErrorSinkCompiler;
391 
392     uint errors = global.startGagging();
393     scope(exit) global.endGagging(errors);
394 
395     // If this check fails, then Type._init() was called before reaching here,
396     // and the entire chunk of code that follows can be removed.
397     assert(ASTCodegen.Type.tint32 is null);
398     // Minimally initialize the cached types in ASTCodegen.Type, as they are
399     // dependencies for some fail asm tests to succeed.
400     ASTCodegen.Type.stringtable._init();
401     scope(exit)
402     {
403         ASTCodegen.Type.deinitialize();
404         ASTCodegen.Type.tint32 = null;
405     }
406     scope tint32 = new TypeBasic(ASTCodegen.Tint32);
407     ASTCodegen.Type.tint32 = tint32;
408 
409     // Imitates asmSemantic if version = IN_GCC.
410     static int semanticAsm(Token* tokens)
411     {
412         const errors = global.errors;
413         scope gas = new GccAsmStatement(Loc.initial, tokens);
414         const bool doUnittests = false;
415         scope p = new Parser!ASTCodegen(null, ";", false, global.errorSink, &global.compileEnv, doUnittests);
416         p.token = *tokens;
417         p.parseGccAsm(gas);
418         return global.errors - errors;
419     }
420 
421     // Imitates parseStatement for asm statements.
422     static void parseAsm(string input, bool expectError)
423     {
424         // Generate tokens from input test.
425         const bool doUnittests = false;
426         scope p = new Parser!ASTCodegen(null, input, false, global.errorSink, &global.compileEnv, doUnittests);
427         p.nextToken();
428 
429         Token* toklist = null;
430         Token** ptoklist = &toklist;
431         p.check(TOK.asm_);
432         p.check(TOK.leftCurly);
433         while (1)
434         {
435             if (p.token.value == TOK.rightCurly || p.token.value == TOK.endOfFile)
436                 break;
437             if (p.token.value == TOK.colonColon)
438             {
439                 *ptoklist = p.allocateToken();
440                 memcpy(*ptoklist, &p.token, Token.sizeof);
441                 (*ptoklist).value = TOK.colon;
442                 ptoklist = &(*ptoklist).next;
443 
444                 *ptoklist = p.allocateToken();
445                 memcpy(*ptoklist, &p.token, Token.sizeof);
446                 (*ptoklist).value = TOK.colon;
447                 ptoklist = &(*ptoklist).next;
448             }
449             else
450             {
451                 *ptoklist = p.allocateToken();
452                 memcpy(*ptoklist, &p.token, Token.sizeof);
453                 ptoklist = &(*ptoklist).next;
454             }
455             *ptoklist = null;
456             p.nextToken();
457         }
458         p.check(TOK.rightCurly);
459 
460         auto res = semanticAsm(toklist);
461         // Checks for both unexpected passes and failures.
462         assert((res == 0) != expectError);
463     }
464 
465     /// Assembly Tests, all should pass.
466     /// Note: Frontend is not initialized, use only strings and identifiers.
467     immutable string[] passAsmTests = [
468         // Basic asm statement
469         q{ asm { "nop";
470         } },
471 
472         // Extended asm statement
473         q{ asm { "cpuid"
474                : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
475                : "a" (input);
476         } },
477 
478         // Assembly with symbolic names
479         q{ asm { "bts %[base], %[offset]"
480                : [base] "+rm" (*ptr),
481                : [offset] "Ir" (bitnum);
482         } },
483 
484         // Assembly with clobbers
485         q{ asm { "cpuid"
486                : "=a" (a)
487                : "a" (input)
488                : "ebx", "ecx", "edx";
489         } },
490 
491         // Goto asm statement
492         q{ asm { "jmp %l0"
493                :
494                :
495                :
496                : Ljmplabel;
497         } },
498 
499         // Any CTFE-able string allowed as instruction template.
500         q{ asm { generateAsm();
501         } },
502 
503         // Likewise mixins, permissible so long as the result is a string.
504         q{ asm { mixin(`"repne"`, `~ "scasb"`);
505         } },
506 
507         // :: token tests
508         q{ asm { "" : : : "memory"; } },
509         q{ asm { "" :: : "memory"; } },
510         q{ asm { "" : :: "memory"; } },
511         q{ asm { "" ::: "memory"; } },
512     ];
513 
514     immutable string[] failAsmTests = [
515         // Found 'h' when expecting ';'
516         q{ asm { ""h;
517         } },
518 
519         // https://issues.dlang.org/show_bug.cgi?id=20592
520         q{ asm { "nop" : [name] string (expr); } },
521 
522         // Expression expected, not ';'
523         q{ asm { ""[;
524         } },
525 
526         // Expression expected, not ':'
527         q{ asm { ""
528                :
529                : "g" (a ? b : : c);
530         } },
531 
532         // Found ',' when expecting ':'
533         q{ asm { "", "";
534         } },
535 
536         // https://issues.dlang.org/show_bug.cgi?id=20593
537         q{ asm { "instruction" : : "operand" 123; } },
538     ];
539 
540     foreach (test; passAsmTests)
541         parseAsm(test, false);
542 
543     foreach (test; failAsmTests)
544         parseAsm(test, true);
545 }