1 /**
2  * Describes a back-end compiler and implements compiler-specific actions.
3  *
4  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
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/compiler.d, _compiler.d)
8  * Documentation:  https://dlang.org/phobos/dmd_compiler.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/compiler.d
10  */
11 
12 module dmd.compiler;
13 
14 import core.stdc.string;
15 
16 import dmd.astenums;
17 import dmd.arraytypes;
18 import dmd.dmodule;
19 import dmd.errors;
20 import dmd.expression;
21 import dmd.globals;
22 import dmd.id;
23 import dmd.identifier;
24 import dmd.mtype;
25 import dmd.root.array;
26 import dmd.root.ctfloat;
27 
28 version (DMDLIB)
29 {
30     version = CallbackAPI;
31 }
32 
33 extern (C++) __gshared
34 {
35     bool includeImports = false;
36     // array of module patterns used to include/exclude imported modules
37     Array!(const(char)*) includeModulePatterns;
38     Modules compiledImports;
39 }
40 
41 
42 /**
43  * A data structure that describes a back-end compiler and implements
44  * compiler-specific actions.
45  */
46 extern (C++) struct Compiler
47 {
48     /******************************
49      * Encode the given expression, which is assumed to be an rvalue literal
50      * as another type for use in CTFE.
51      * This corresponds roughly to the idiom *(Type *)&e.
52      */
53     extern (C++) static Expression paintAsType(UnionExp* pue, Expression e, Type type)
54     {
55         union U
56         {
57             int int32value;
58             long int64value;
59             float float32value;
60             double float64value;
61         }
62         U u = void;
63 
64         assert(e.type.size() == type.size());
65 
66         switch (e.type.ty)
67         {
68         case Tint32:
69         case Tuns32:
70             u.int32value = cast(int) e.toInteger();
71             break;
72         case Tint64:
73         case Tuns64:
74             u.int64value = cast(long) e.toInteger();
75             break;
76         case Tfloat32:
77             u.float32value = cast(float) e.toReal();
78             break;
79         case Tfloat64:
80             u.float64value = cast(double) e.toReal();
81             break;
82         case Tfloat80:
83             assert(e.type.size() == 8); // 64-bit target `real`
84             goto case Tfloat64;
85         default:
86             assert(0, "Unsupported source type");
87         }
88 
89         real_t r = void;
90         switch (type.ty)
91         {
92         case Tint32:
93         case Tuns32:
94             emplaceExp!(IntegerExp)(pue, e.loc, u.int32value, type);
95             break;
96 
97         case Tint64:
98         case Tuns64:
99             emplaceExp!(IntegerExp)(pue, e.loc, u.int64value, type);
100             break;
101 
102         case Tfloat32:
103             r = u.float32value;
104             emplaceExp!(RealExp)(pue, e.loc, r, type);
105             break;
106 
107         case Tfloat64:
108             r = u.float64value;
109             emplaceExp!(RealExp)(pue, e.loc, r, type);
110             break;
111 
112         case Tfloat80:
113             assert(type.size() == 8); // 64-bit target `real`
114             goto case Tfloat64;
115 
116         default:
117             assert(0, "Unsupported target type");
118         }
119         return pue.exp();
120     }
121 
122     /******************************
123      * For the given module, perform any post parsing analysis.
124      * Certain compiler backends (ie: GDC) have special placeholder
125      * modules whose source are empty, but code gets injected
126      * immediately after loading.
127      */
128     extern (C++) static void onParseModule(Module m)
129     {
130     }
131 
132     /**
133      * A callback function that is called once an imported module is
134      * parsed. If the callback returns true, then it tells the
135      * frontend that the driver intends on compiling the import.
136      */
137     extern(C++) static bool onImport(Module m)
138     {
139         if (includeImports && m.filetype == FileType.d)
140         {
141             if (includeImportedModuleCheck(ModuleComponentRange(
142                 m.md ? m.md.packages : [], m.ident, m.isPackageFile)))
143             {
144                 if (global.params.v.verbose)
145                     message("compileimport (%s)", m.srcfile.toChars);
146                 compiledImports.push(m);
147                 return true; // this import will be compiled
148             }
149         }
150         return false; // this import will not be compiled
151     }
152 
153     version (CallbackAPI)
154     {
155         import dmd.statement;
156         import dmd.dscope;
157         alias OnStatementSemanticStart = void function(Statement, Scope*);
158         alias OnStatementSemanticDone = void function(Statement, Scope*);
159 
160         /**
161          * Used to insert functionality before the start of the
162          * semantic analysis of a statement when importing DMD as a library
163          */
164         __gshared OnStatementSemanticStart onStatementSemanticStart
165                     = function void(Statement s, Scope *sc) {};
166 
167         /**
168          * Used to insert functionality after the end of the
169          * semantic analysis of a statement when importing DMD as a library
170          */
171         __gshared OnStatementSemanticDone onStatementSemanticDone
172                     = function void(Statement s, Scope *sc) {};
173     }
174 }
175 
176 /******************************
177  * Private helpers for Compiler::onImport.
178  */
179 // A range of component identifiers for a module
180 private struct ModuleComponentRange
181 {
182     Identifier[] packages;
183     Identifier name;
184     bool isPackageFile;
185     size_t index;
186     @property auto totalLength() const { return packages.length + 1 + (isPackageFile ? 1 : 0); }
187 
188     @property auto empty() { return index >= totalLength(); }
189     @property auto front() const
190     {
191         if (index < packages.length)
192             return packages[index];
193         if (index == packages.length)
194             return name;
195         else
196             return Identifier.idPool("package");
197     }
198     void popFront() @safe { index++; }
199 }
200 
201 /*
202  * Determines if the given module should be included in the compilation.
203  * Returns:
204  *  True if the given module should be included in the compilation.
205  */
206 private bool includeImportedModuleCheck(ModuleComponentRange components)
207     in { assert(includeImports); }
208 do
209 {
210     createMatchNodes();
211     size_t nodeIndex = 0;
212     while (nodeIndex < matchNodes.length)
213     {
214         //printf("matcher ");printMatcher(nodeIndex);printf("\n");
215         auto info = matchNodes[nodeIndex++];
216         if (info.depth <= components.totalLength())
217         {
218             size_t nodeOffset = 0;
219             for (auto range = components;;range.popFront())
220             {
221                 if (range.empty || nodeOffset >= info.depth)
222                 {
223                     // MATCH
224                     return !info.isExclude;
225                 }
226                 if (!(range.front is matchNodes[nodeIndex + nodeOffset].id))
227                 {
228                     break;
229                 }
230                 nodeOffset++;
231             }
232         }
233         nodeIndex += info.depth;
234     }
235     assert(nodeIndex == matchNodes.length, "code bug");
236     return includeByDefault;
237 }
238 
239 // Matching module names is done with an array of matcher nodes.
240 // The nodes are sorted by "component depth" from largest to smallest
241 // so that the first match is always the longest (best) match.
242 private struct MatcherNode
243 {
244     union
245     {
246         struct
247         {
248             ushort depth;
249             bool isExclude;
250         }
251         Identifier id;
252     }
253     this(Identifier id) { this.id = id; }
254     this(bool isExclude, ushort depth)
255     {
256         this.depth = depth;
257         this.isExclude = isExclude;
258     }
259 }
260 
261 /*
262  * $(D includeByDefault) determines whether to include/exclude modules when they don't
263  * match any pattern. This setting changes depending on if the user provided any "inclusive" module
264  * patterns. When a single "inclusive" module pattern is given, it likely means the user only
265  * intends to include modules they've "included", however, if no module patterns are given or they
266  * are all "exclusive", then it is likely they intend to include everything except modules
267  * that have been excluded. i.e.
268  * ---
269  * -i=-foo // include everything except modules that match "foo*"
270  * -i=foo  // only include modules that match "foo*" (exclude everything else)
271  * ---
272  * Note that this default behavior can be overridden using the '.' module pattern. i.e.
273  * ---
274  * -i=-foo,-.  // this excludes everything
275  * -i=foo,.    // this includes everything except the default exclusions (-std,-core,-etc.-object)
276  * ---
277 */
278 private __gshared bool includeByDefault = true;
279 private __gshared Array!MatcherNode matchNodes;
280 
281 /*
282  * Creates the global list of match nodes used to match module names
283  * given strings provided by the -i commmand line option.
284  */
285 private void createMatchNodes()
286 {
287     static size_t findSortedIndexToAddForDepth(size_t depth)
288     {
289         size_t index = 0;
290         while (index < matchNodes.length)
291         {
292             auto info = matchNodes[index];
293             if (depth > info.depth)
294                 break;
295             index += 1 + info.depth;
296         }
297         return index;
298     }
299 
300     if (matchNodes.length == 0)
301     {
302         foreach (modulePattern; includeModulePatterns)
303         {
304             auto depth = parseModulePatternDepth(modulePattern[0 .. strlen(modulePattern)]);
305             auto entryIndex = findSortedIndexToAddForDepth(depth);
306             matchNodes.split(entryIndex, depth + 1);
307             parseModulePattern(modulePattern, &matchNodes[entryIndex], depth);
308             // if at least 1 "include pattern" is given, then it is assumed the
309             // user only wants to include modules that were explicitly given, which
310             // changes the default behavior from inclusion to exclusion.
311             if (includeByDefault && !matchNodes[entryIndex].isExclude)
312             {
313                 //printf("Matcher: found 'include pattern', switching default behavior to exclusion\n");
314                 includeByDefault = false;
315             }
316         }
317 
318         // Add the default 1 depth matchers
319         MatcherNode[8] defaultDepth1MatchNodes = [
320             MatcherNode(true, 1), MatcherNode(Id.std),
321             MatcherNode(true, 1), MatcherNode(Id.core),
322             MatcherNode(true, 1), MatcherNode(Id.etc),
323             MatcherNode(true, 1), MatcherNode(Id.object),
324         ];
325         {
326             auto index = findSortedIndexToAddForDepth(1);
327             matchNodes.split(index, defaultDepth1MatchNodes.length);
328             auto slice = matchNodes[];
329             slice[index .. index + defaultDepth1MatchNodes.length] = defaultDepth1MatchNodes[];
330         }
331     }
332 }
333 
334 /*
335  * Determines the depth of the given module pattern.
336  * Params:
337  *  modulePattern = The module pattern to determine the depth of.
338  * Returns:
339  *  The component depth of the given module pattern.
340  */
341 pure @safe
342 private ushort parseModulePatternDepth(const char[] modulePattern)
343 {
344     const length = modulePattern.length;
345     size_t i = (length && modulePattern[0] == '-'); // skip past leading '-'
346 
347     // handle special case
348     if (i + 1 == length && modulePattern[i] == '.')
349         return 0;
350 
351     int depth = 1;
352     foreach (c; modulePattern[i .. length])
353         depth += c == '.';
354     return cast(ushort)depth;
355 }
356 unittest
357 {
358     assert(".".parseModulePatternDepth == 0);
359     assert("-.".parseModulePatternDepth == 0);
360     assert("abc".parseModulePatternDepth == 1);
361     assert("-abc".parseModulePatternDepth == 1);
362     assert("abc.foo".parseModulePatternDepth == 2);
363     assert("-abc.foo".parseModulePatternDepth == 2);
364 }
365 
366 /*
367  * Parses a 'module pattern', which is the "include import" components
368  * given on the command line, i.e. "-i=<module_pattern>,<module_pattern>,...".
369  * Params:
370  *  modulePattern = The module pattern to parse.
371  *  dst = the data structure to save the parsed module pattern to.
372  *  depth = the depth of the module pattern previously retrieved from $(D parseModulePatternDepth).
373  */
374 private void parseModulePattern(const(char)* modulePattern, MatcherNode* dst, ushort depth)
375 {
376     bool isExclude = false;
377     if (modulePattern[0] == '-')
378     {
379         isExclude = true;
380         modulePattern++;
381     }
382 
383     *dst = MatcherNode(isExclude, depth);
384     dst++;
385 
386     // Create and add identifiers for each component in the modulePattern
387     if (depth > 0)
388     {
389         auto idStart = modulePattern;
390         auto lastNode = dst + depth - 1;
391         for (; dst < lastNode; dst++)
392         {
393             for (;; modulePattern++)
394             {
395                 if (*modulePattern == '.')
396                 {
397                     assert(modulePattern > idStart, "empty module pattern");
398                     *dst = MatcherNode(Identifier.idPool(idStart[0 .. modulePattern - idStart]));
399                     modulePattern++;
400                     idStart = modulePattern;
401                     break;
402                 }
403             }
404         }
405         for (;; modulePattern++)
406         {
407             if (*modulePattern == '\0')
408             {
409                 assert(modulePattern > idStart, "empty module pattern");
410                 *lastNode = MatcherNode(Identifier.idPool(idStart[0 .. modulePattern - idStart]));
411                 break;
412             }
413         }
414     }
415 }