1 /**
2  * Inline assembler for the GCC D compiler.
3  *
4  *              Copyright (C) 2018-2020 by The D Language Foundation, All Rights Reserved
5  * Authors:     Iain Buclaw
6  * License:     $(LINK2 http://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@@@
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.leftParentheses)
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.leftParentheses);
99                     // Parse the assign expression
100                     arg = p.parseAssignExp();
101                     // Look for the closing `)`
102                     p.check(TOK.rightParentheses);
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  *      |     Expression
244  *      |
245  *      | ExtAsmInstruction:
246  *      |     Expression : Operands(opt) : Operands(opt) : Clobbers(opt)
247  *      |
248  *      | GotoAsmInstruction:
249  *      |     Expression : : 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.parseExpression();
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 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     p.reportDiagnostics();
326     if (errors != global.errors)
327         return null;
328     s.stc = sc.stc;
329 
330     // Fold the instruction template string.
331     s.insn = semanticString(sc, s.insn, "asm instruction template");
332 
333     if (s.labels && s.outputargs)
334         s.error("extended asm statements with labels cannot have output constraints");
335 
336     // Analyse all input and output operands.
337     if (s.args)
338     {
339         foreach (i; 0 .. s.args.dim)
340         {
341             Expression e = (*s.args)[i];
342             e = e.expressionSemantic(sc);
343             // Check argument is a valid lvalue/rvalue.
344             if (i < s.outputargs)
345                 e = e.modifiableLvalue(sc, null);
346             else if (e.checkValue())
347                 e = ErrorExp.get();
348             (*s.args)[i] = e;
349 
350             e = (*s.constraints)[i];
351             e = e.expressionSemantic(sc);
352             assert(e.op == TOK.string_ && (cast(StringExp) e).sz == 1);
353             (*s.constraints)[i] = e;
354         }
355     }
356 
357     // Analyse all clobbers.
358     if (s.clobbers)
359     {
360         foreach (i; 0 .. s.clobbers.dim)
361         {
362             Expression e = (*s.clobbers)[i];
363             e = e.expressionSemantic(sc);
364             assert(e.op == TOK.string_ && (cast(StringExp) e).sz == 1);
365             (*s.clobbers)[i] = e;
366         }
367     }
368 
369     // Analyse all goto labels.
370     if (s.labels)
371     {
372         foreach (i; 0 .. s.labels.dim)
373         {
374             Identifier ident = (*s.labels)[i];
375             GotoStatement gs = new GotoStatement(s.loc, ident);
376             if (!s.gotos)
377                 s.gotos = new GotoStatements();
378             s.gotos.push(gs);
379             gs.statementSemantic(sc);
380         }
381     }
382 
383     return s;
384 }
385 
386 unittest
387 {
388     import dmd.mtype : TypeBasic;
389 
390     uint errors = global.startGagging();
391     scope(exit) global.endGagging(errors);
392 
393     // If this check fails, then Type._init() was called before reaching here,
394     // and the entire chunk of code that follows can be removed.
395     assert(ASTCodegen.Type.tint32 is null);
396     // Minimally initialize the cached types in ASTCodegen.Type, as they are
397     // dependencies for some fail asm tests to succeed.
398     ASTCodegen.Type.stringtable._init();
399     scope(exit)
400     {
401         ASTCodegen.Type.deinitialize();
402         ASTCodegen.Type.tint32 = null;
403     }
404     scope tint32 = new TypeBasic(ASTCodegen.Tint32);
405     ASTCodegen.Type.tint32 = tint32;
406 
407     // Imitates asmSemantic if version = IN_GCC.
408     static int semanticAsm(Token* tokens)
409     {
410         const errors = global.errors;
411         scope gas = new GccAsmStatement(Loc.initial, tokens);
412         scope p = new Parser!ASTCodegen(null, ";", false);
413         p.token = *tokens;
414         p.parseGccAsm(gas);
415         p.reportDiagnostics();
416         return global.errors - errors;
417     }
418 
419     // Imitates parseStatement for asm statements.
420     static void parseAsm(string input, bool expectError)
421     {
422         // Generate tokens from input test.
423         scope p = new Parser!ASTCodegen(null, input, false);
424         p.nextToken();
425 
426         Token* toklist = null;
427         Token** ptoklist = &toklist;
428         p.check(TOK.asm_);
429         p.check(TOK.leftCurly);
430         while (1)
431         {
432             if (p.token.value == TOK.rightCurly || p.token.value == TOK.endOfFile)
433                 break;
434             *ptoklist = p.allocateToken();
435             memcpy(*ptoklist, &p.token, Token.sizeof);
436             ptoklist = &(*ptoklist).next;
437             *ptoklist = null;
438             p.nextToken();
439         }
440         p.check(TOK.rightCurly);
441 
442         auto res = semanticAsm(toklist);
443         // Checks for both unexpected passes and failures.
444         assert((res == 0) != expectError);
445     }
446 
447     /// Assembly Tests, all should pass.
448     /// Note: Frontend is not initialized, use only strings and identifiers.
449     immutable string[] passAsmTests = [
450         // Basic asm statement
451         q{ asm { "nop";
452         } },
453 
454         // Extended asm statement
455         q{ asm { "cpuid"
456                : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
457                : "a" (input);
458         } },
459 
460         // Assembly with symbolic names
461         q{ asm { "bts %[base], %[offset]"
462                : [base] "+rm" (*ptr),
463                : [offset] "Ir" (bitnum);
464         } },
465 
466         // Assembly with clobbers
467         q{ asm { "cpuid"
468                : "=a" (a)
469                : "a" (input)
470                : "ebx", "ecx", "edx";
471         } },
472 
473         // Goto asm statement
474         q{ asm { "jmp %l0"
475                :
476                :
477                :
478                : Ljmplabel;
479         } },
480 
481         // Any CTFE-able string allowed as instruction template.
482         q{ asm { generateAsm();
483         } },
484 
485         // Likewise mixins, permissible so long as the result is a string.
486         q{ asm { mixin(`"repne"`, `~ "scasb"`);
487         } },
488     ];
489 
490     immutable string[] failAsmTests = [
491         // Found 'h' when expecting ';'
492         q{ asm { ""h;
493         } },
494 
495         // https://issues.dlang.org/show_bug.cgi?id=20592
496         q{ asm { "nop" : [name] string (expr); } },
497 
498         // Expression expected, not ';'
499         q{ asm { ""[;
500         } },
501 
502         // Expression expected, not ':'
503         q{ asm { ""
504                :
505                : "g" (a ? b : : c);
506         } },
507     ];
508 
509     foreach (test; passAsmTests)
510         parseAsm(test, false);
511 
512     foreach (test; failAsmTests)
513         parseAsm(test, true);
514 }