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 0
    int numargs = 0;
49

50 0
    while (1)
51
    {
52 0
        Expression arg;
53 0
        Identifier name;
54 0
        Expression constraint;
55

56 0
        switch (p.token.value)
57
        {
58 0
            case TOK.semicolon:
59 0
            case TOK.colon:
60 0
            case TOK.endOfFile:
61 0
                return numargs;
62

63 0
            case TOK.leftBracket:
64 0
                if (p.peekNext() == TOK.identifier)
65
                {
66
                    // Skip over opening `[`
67 0
                    p.nextToken();
68
                    // Store the symbolic name
69 0
                    name = p.token.ident;
70 0
                    p.nextToken();
71
                }
72
                else
73
                {
74 0
                    p.error(s.loc, "expected identifier after `[`");
75 0
                    goto Lerror;
76
                }
77
                // Look for closing `]`
78 0
                p.check(TOK.rightBracket);
79
                // Look for the string literal and fall through
80 0
                if (p.token.value == TOK.string_)
81 0
                    goto case;
82
                else
83 0
                    goto default;
84

85 0
            case TOK.string_:
86 0
                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 0
                if (p.token.value != TOK.leftParentheses)
91
                {
92 0
                    arg = p.parseAssignExp();
93 0
                    deprecation(arg.loc, "`%s` must be surrounded by parentheses", arg.toChars());
94
                }
95
                else
96
                {
97
                    // Look for the opening `(`
98 0
                    p.check(TOK.leftParentheses);
99
                    // Parse the assign expression
100 0
                    arg = p.parseAssignExp();
101
                    // Look for the closing `)`
102 0
                    p.check(TOK.rightParentheses);
103
                }
104

105 0
                if (!s.args)
106
                {
107 0
                    s.names = new Identifiers();
108 0
                    s.constraints = new Expressions();
109 0
                    s.args = new Expressions();
110
                }
111 0
                s.names.push(name);
112 0
                s.args.push(arg);
113 0
                s.constraints.push(constraint);
114 0
                numargs++;
115

116 0
                if (p.token.value == TOK.comma)
117 0
                    p.nextToken();
118 0
                break;
119

120 0
            default:
121 0
                p.error("expected constant string constraint for operand, not `%s`",
122
                        p.token.toChars());
123 0
                goto Lerror;
124
        }
125
    }
126
Lerror:
127 0
    while (p.token.value != TOK.rightCurly &&
128 0
           p.token.value != TOK.semicolon &&
129 0
           p.token.value != TOK.endOfFile)
130 0
        p.nextToken();
131

132 0
    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 0
    Expressions *clobbers;
149

150 0
    while (1)
151
    {
152 0
        Expression clobber;
153

154 0
        switch (p.token.value)
155
        {
156 0
            case TOK.semicolon:
157 0
            case TOK.colon:
158 0
            case TOK.endOfFile:
159 0
                return clobbers;
160

161 0
            case TOK.string_:
162 0
                clobber = p.parsePrimaryExp();
163 0
                if (!clobbers)
164 0
                    clobbers = new Expressions();
165 0
                clobbers.push(clobber);
166

167 0
                if (p.token.value == TOK.comma)
168 0
                    p.nextToken();
169 0
                break;
170

171 0
            default:
172 0
                p.error("expected constant string constraint for clobber name, not `%s`",
173
                        p.token.toChars());
174 0
                goto Lerror;
175
        }
176
    }
177
Lerror:
178 0
    while (p.token.value != TOK.rightCurly &&
179 0
           p.token.value != TOK.semicolon &&
180 0
           p.token.value != TOK.endOfFile)
181 0
        p.nextToken();
182

183 0
    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 0
    Identifiers *labels;
200

201 0
    while (1)
202
    {
203 0
        switch (p.token.value)
204
        {
205 0
            case TOK.semicolon:
206 0
            case TOK.endOfFile:
207 0
                return labels;
208

209 0
            case TOK.identifier:
210 0
                if (!labels)
211 0
                    labels = new Identifiers();
212 0
                labels.push(p.token.ident);
213

214 0
                if (p.nextToken() == TOK.comma)
215 0
                    p.nextToken();
216 0
                break;
217

218 0
            default:
219 0
                p.error("expected identifier for goto label name, not `%s`",
220
                        p.token.toChars());
221 0
                goto Lerror;
222
        }
223
    }
224
Lerror:
225 0
    while (p.token.value != TOK.rightCurly &&
226 0
           p.token.value != TOK.semicolon &&
227 0
           p.token.value != TOK.endOfFile)
228 0
        p.nextToken();
229

230 0
    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 0
    s.insn = p.parseExpression();
259 0
    if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile)
260 0
        goto Ldone;
261

262
    // No semicolon followed after instruction template, treat as extended asm.
263 0
    foreach (section; 0 .. 4)
264
    {
265 0
        p.check(TOK.colon);
266

267 0
        final switch (section)
268
        {
269 0
            case 0:
270 0
                s.outputargs = p.parseExtAsmOperands(s);
271 0
                break;
272

273 0
            case 1:
274 0
                p.parseExtAsmOperands(s);
275 0
                break;
276

277 0
            case 2:
278 0
                s.clobbers = p.parseExtAsmClobbers();
279 0
                break;
280

281 0
            case 3:
282 0
                s.labels = p.parseExtAsmGotoLabels();
283 0
                break;
284
        }
285

286 0
        if (p.token.value == TOK.semicolon || p.token.value == TOK.endOfFile)
287 0
            goto Ldone;
288
    }
289
Ldone:
290 0
    p.check(TOK.semicolon);
291

292 0
    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 0
    scope p = new Parser!ASTCodegen(sc._module, ";", false);
307

308
    // Make a safe copy of the token list before parsing.
309 0
    Token *toklist = null;
310 0
    Token **ptoklist = &toklist;
311

312 0
    for (Token *token = s.tokens; token; token = token.next)
313
    {
314 0
        *ptoklist = p.allocateToken();
315 0
        memcpy(*ptoklist, token, Token.sizeof);
316 0
        ptoklist = &(*ptoklist).next;
317 0
        *ptoklist = null;
318
    }
319 0
    p.token = *toklist;
320 0
    p.scanloc = s.loc;
321

322
    // Parse the gcc asm statement.
323 0
    const errors = global.errors;
324 0
    s = p.parseGccAsm(s);
325 0
    if (errors != global.errors)
326 0
        return null;
327 0
    s.stc = sc.stc;
328

329
    // Fold the instruction template string.
330 0
    s.insn = semanticString(sc, s.insn, "asm instruction template");
331

332 0
    if (s.labels && s.outputargs)
333 0
        s.error("extended asm statements with labels cannot have output constraints");
334

335
    // Analyse all input and output operands.
336 0
    if (s.args)
337
    {
338 0
        foreach (i; 0 .. s.args.dim)
339
        {
340 0
            Expression e = (*s.args)[i];
341 0
            e = e.expressionSemantic(sc);
342
            // Check argument is a valid lvalue/rvalue.
343 0
            if (i < s.outputargs)
344 0
                e = e.modifiableLvalue(sc, null);
345 0
            else if (e.checkValue())
346 0
                e = ErrorExp.get();
347 0
            (*s.args)[i] = e;
348

349 0
            e = (*s.constraints)[i];
350 0
            e = e.expressionSemantic(sc);
351 0
            assert(e.op == TOK.string_ && (cast(StringExp) e).sz == 1);
352 0
            (*s.constraints)[i] = e;
353
        }
354
    }
355

356
    // Analyse all clobbers.
357 0
    if (s.clobbers)
358
    {
359 0
        foreach (i; 0 .. s.clobbers.dim)
360
        {
361 0
            Expression e = (*s.clobbers)[i];
362 0
            e = e.expressionSemantic(sc);
363 0
            assert(e.op == TOK.string_ && (cast(StringExp) e).sz == 1);
364 0
            (*s.clobbers)[i] = e;
365
        }
366
    }
367

368
    // Analyse all goto labels.
369 0
    if (s.labels)
370
    {
371 0
        foreach (i; 0 .. s.labels.dim)
372
        {
373 0
            Identifier ident = (*s.labels)[i];
374 0
            GotoStatement gs = new GotoStatement(s.loc, ident);
375 0
            if (!s.gotos)
376 0
                s.gotos = new GotoStatements();
377 0
            s.gotos.push(gs);
378 0
            gs.statementSemantic(sc);
379
        }
380
    }
381

382 0
    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
            *ptoklist = p.allocateToken();
433
            memcpy(*ptoklist, &p.token, Token.sizeof);
434
            ptoklist = &(*ptoklist).next;
435
            *ptoklist = null;
436
            p.nextToken();
437
        }
438
        p.check(TOK.rightCurly);
439

440
        auto res = semanticAsm(toklist);
441
        // Checks for both unexpected passes and failures.
442
        assert((res == 0) != expectError);
443
    }
444

445
    /// Assembly Tests, all should pass.
446
    /// Note: Frontend is not initialized, use only strings and identifiers.
447
    immutable string[] passAsmTests = [
448
        // Basic asm statement
449
        q{ asm { "nop";
450
        } },
451

452
        // Extended asm statement
453
        q{ asm { "cpuid"
454
               : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
455
               : "a" (input);
456
        } },
457

458
        // Assembly with symbolic names
459
        q{ asm { "bts %[base], %[offset]"
460
               : [base] "+rm" (*ptr),
461
               : [offset] "Ir" (bitnum);
462
        } },
463

464
        // Assembly with clobbers
465
        q{ asm { "cpuid"
466
               : "=a" (a)
467
               : "a" (input)
468
               : "ebx", "ecx", "edx";
469
        } },
470

471
        // Goto asm statement
472
        q{ asm { "jmp %l0"
473
               :
474
               :
475
               :
476
               : Ljmplabel;
477
        } },
478

479
        // Any CTFE-able string allowed as instruction template.
480
        q{ asm { generateAsm();
481
        } },
482

483
        // Likewise mixins, permissible so long as the result is a string.
484
        q{ asm { mixin(`"repne"`, `~ "scasb"`);
485
        } },
486
    ];
487

488
    immutable string[] failAsmTests = [
489
        // Found 'h' when expecting ';'
490
        q{ asm { ""h;
491
        } },
492

493
        // https://issues.dlang.org/show_bug.cgi?id=20592
494
        q{ asm { "nop" : [name] string (expr); } },
495

496
        // Expression expected, not ';'
497
        q{ asm { ""[;
498
        } },
499

500
        // Expression expected, not ':'
501
        q{ asm { ""
502
               :
503
               : "g" (a ? b : : c);
504
        } },
505
    ];
506

507
    foreach (test; passAsmTests)
508
        parseAsm(test, false);
509

510
    foreach (test; failAsmTests)
511
        parseAsm(test, true);
512
}

Read our documentation on viewing source code .

Loading