|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +#include <slang/ast/ASTVisitor.h> |
| 3 | +#include <unordered_map> |
| 4 | +#include "IncrementalRewriter.hpp" |
| 5 | + |
| 6 | +static bool hasExternQualifier(const TokenList& qualifiers) { |
| 7 | + for (const auto token : qualifiers) { |
| 8 | + if (token.kind == parsing::TokenKind::ExternKeyword) { |
| 9 | + return true; |
| 10 | + } |
| 11 | + } |
| 12 | + return false; |
| 13 | +} |
| 14 | + |
| 15 | +static std::vector<parsing::Trivia> mergeExternLeadingTrivia( |
| 16 | + const std::vector<parsing::Trivia>& movedTrivia, |
| 17 | + std::span<const parsing::Trivia> originalTrivia) { |
| 18 | + std::vector<parsing::Trivia> mergedTrivia; |
| 19 | + mergedTrivia.reserve(movedTrivia.size() + originalTrivia.size()); |
| 20 | + mergedTrivia.insert(mergedTrivia.end(), movedTrivia.begin(), movedTrivia.end()); |
| 21 | + |
| 22 | + size_t firstTrivia = 0; |
| 23 | + if (!movedTrivia.empty() && !originalTrivia.empty() && |
| 24 | + originalTrivia.front().kind == parsing::TriviaKind::Whitespace) { |
| 25 | + // Remove the single separator space that used to be between `extern` and |
| 26 | + // the next token (for example, `extern virtual` -> `virtual`). |
| 27 | + firstTrivia = 1; |
| 28 | + } |
| 29 | + |
| 30 | + mergedTrivia.insert(mergedTrivia.end(), originalTrivia.begin() + firstTrivia, |
| 31 | + originalTrivia.end()); |
| 32 | + return mergedTrivia; |
| 33 | +} |
| 34 | + |
| 35 | +struct ExternInlineCandidate { |
| 36 | + // Clone the declaration body, but remove the whole source node that owns it. |
| 37 | + not_null<const FunctionDeclarationSyntax*> implementationDecl; |
| 38 | + not_null<const SyntaxNode*> implementationRemovalNode; |
| 39 | + std::string methodName; |
| 40 | +}; |
| 41 | + |
| 42 | +using ExternInlineMap = |
| 43 | + std::unordered_map<const ClassMethodPrototypeSyntax*, ExternInlineCandidate>; |
| 44 | + |
| 45 | +class ExternInlineMapper final : public ASTVisitor<ExternInlineMapper, true, true, true> { |
| 46 | + public: |
| 47 | + ExternInlineMap candidates; |
| 48 | + |
| 49 | + void handle(const GenericClassDefSymbol& node) { |
| 50 | + ASTVisitor<ExternInlineMapper, true, true, true>::visitDefault(node); |
| 51 | + if (node.numSpecializations() == 0) { |
| 52 | + // In order to visit members of not specialized class we create an artificial |
| 53 | + // specialization. |
| 54 | + node.getInvalidSpecialization().visit(*this); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + void handle(const MethodPrototypeSymbol& proto) { |
| 59 | + const auto* protoSyntax = |
| 60 | + proto.getSyntax() ? proto.getSyntax()->as_if<ClassMethodPrototypeSyntax>() : nullptr; |
| 61 | + const auto* impl = proto.getSubroutine(); |
| 62 | + const auto* implSyntax = impl ? impl->getSyntax() : nullptr; |
| 63 | + const auto* implDecl = |
| 64 | + implSyntax ? implSyntax->as_if<FunctionDeclarationSyntax>() : nullptr; |
| 65 | + const SyntaxNode* implNode = implSyntax; |
| 66 | + |
| 67 | + if (const auto* classMethodDecl = |
| 68 | + implSyntax ? implSyntax->as_if<ClassMethodDeclarationSyntax>() : nullptr) { |
| 69 | + implDecl = classMethodDecl->declaration; |
| 70 | + implNode = classMethodDecl; |
| 71 | + } |
| 72 | + |
| 73 | + if (protoSyntax && hasExternQualifier(protoSyntax->qualifiers) && implDecl && implNode && |
| 74 | + implNode != protoSyntax && |
| 75 | + (implDecl->kind == SyntaxKind::FunctionDeclaration || |
| 76 | + implDecl->kind == SyntaxKind::TaskDeclaration)) { |
| 77 | + candidates.emplace(protoSyntax, |
| 78 | + ExternInlineCandidate{implDecl, implNode, std::string(proto.name)}); |
| 79 | + } |
| 80 | + |
| 81 | + visitDefault(proto); |
| 82 | + } |
| 83 | +}; |
| 84 | + |
| 85 | +static ExternInlineMap makeExternInlineMap(const std::shared_ptr<SyntaxTree>& tree) { |
| 86 | + Compilation compilation; |
| 87 | + compilation.addSyntaxTree(tree); |
| 88 | + compilation.getAllDiagnostics(); |
| 89 | + |
| 90 | + ExternInlineMapper mapper; |
| 91 | + compilation.getRoot().visit(mapper); |
| 92 | + return std::move(mapper.candidates); |
| 93 | +} |
| 94 | + |
| 95 | +class ExternInliner : public IncrementalRewriter<ExternInliner> { |
| 96 | + public: |
| 97 | + std::shared_ptr<SyntaxTree> transform(const std::shared_ptr<SyntaxTree> tree, |
| 98 | + AttemptStats& stats, |
| 99 | + int n = 1) { |
| 100 | + // transform() copies the syntax tree, so pointers found during earlier attempts are stale. |
| 101 | + // Rebuild the map on every attempt to point at nodes from the current tree. |
| 102 | + candidateByPrototype = makeExternInlineMap(tree); |
| 103 | + currentClassNode = nullptr; |
| 104 | + return IncrementalRewriter<ExternInliner>::transform(tree, stats, n); |
| 105 | + } |
| 106 | + |
| 107 | + ShouldVisitChildren handle(const ClassDeclarationSyntax& node, bool isNodeRemovable) { |
| 108 | + currentClassNode = &node; |
| 109 | + visitDefault(node); |
| 110 | + currentClassNode = nullptr; |
| 111 | + return DONT_VISIT_CHILDREN; |
| 112 | + } |
| 113 | + |
| 114 | + ShouldVisitChildren handle(const ClassMethodPrototypeSyntax& node, bool isNodeRemovable) { |
| 115 | + if (!isNodeRemovable) { |
| 116 | + return VISIT_CHILDREN; |
| 117 | + } |
| 118 | + |
| 119 | + if (!currentClassNode) { |
| 120 | + return VISIT_CHILDREN; |
| 121 | + } |
| 122 | + |
| 123 | + auto it = candidateByPrototype.find(&node); |
| 124 | + if (it == candidateByPrototype.end()) { |
| 125 | + return VISIT_CHILDREN; |
| 126 | + } |
| 127 | + |
| 128 | + bool removedExtern = false; |
| 129 | + auto& replacement = |
| 130 | + createInlinedMember(node, *it->second.implementationDecl, removedExtern); |
| 131 | + if (!removedExtern) { |
| 132 | + return VISIT_CHILDREN; |
| 133 | + } |
| 134 | + |
| 135 | + if (!shouldReplace(node)) { |
| 136 | + return VISIT_CHILDREN; |
| 137 | + } |
| 138 | + |
| 139 | + logType<ClassMethodPrototypeSyntax>(); |
| 140 | + std::cerr << prefixLines(node.toString(), "-") << "\n"; |
| 141 | + std::cerr << prefixLines(it->second.implementationRemovalNode->toString(), "-") << "\n"; |
| 142 | + std::cerr << prefixLines(replacement.toString(), "+") << "\n"; |
| 143 | + |
| 144 | + if (!it->second.methodName.empty()) { |
| 145 | + rewrittenTypeInfo = it->second.methodName; |
| 146 | + } |
| 147 | + |
| 148 | + // Remove the extern declaration and place the inlined method at the end of the class, |
| 149 | + // so member accesses remain valid even if fields are declared below the prototype. |
| 150 | + insertAtBack(currentClassNode->items, replacement); |
| 151 | + remove(node); |
| 152 | + remove(*it->second.implementationRemovalNode); |
| 153 | + |
| 154 | + checkPoints.push_back({node.sourceRange()}); |
| 155 | + state = REGISTER_CHILD; |
| 156 | + return DONT_VISIT_CHILDREN; |
| 157 | + } |
| 158 | + |
| 159 | + private: |
| 160 | + ExternInlineMap candidateByPrototype; |
| 161 | + const ClassDeclarationSyntax* currentClassNode = nullptr; |
| 162 | + |
| 163 | + TokenList& cloneQualifiersWithoutExtern(const TokenList& qualifiers, |
| 164 | + bool& removedExtern, |
| 165 | + std::vector<parsing::Trivia>& leadingExternTrivia) { |
| 166 | + std::vector<Token> out; |
| 167 | + out.reserve(qualifiers.size()); |
| 168 | + for (const auto token : qualifiers) { |
| 169 | + if (token.kind == parsing::TokenKind::ExternKeyword) { |
| 170 | + removedExtern = true; |
| 171 | + if (out.empty()) { |
| 172 | + for (const auto trivia : token.trivia()) { |
| 173 | + leadingExternTrivia.push_back(trivia); |
| 174 | + } |
| 175 | + } |
| 176 | + continue; |
| 177 | + } |
| 178 | + |
| 179 | + if (out.empty() && !leadingExternTrivia.empty()) { |
| 180 | + auto mergedTrivia = mergeExternLeadingTrivia(leadingExternTrivia, token.trivia()); |
| 181 | + auto copiedTrivia = alloc.copyFrom(std::span<const parsing::Trivia>(mergedTrivia)); |
| 182 | + out.push_back(token.withTrivia(alloc, copiedTrivia)); |
| 183 | + leadingExternTrivia.clear(); |
| 184 | + continue; |
| 185 | + } |
| 186 | + |
| 187 | + out.push_back(token.deepClone(alloc)); |
| 188 | + } |
| 189 | + |
| 190 | + auto copied = alloc.copyFrom(std::span<const Token>(out)); |
| 191 | + return *alloc.emplace<TokenList>(copied); |
| 192 | + } |
| 193 | + |
| 194 | + ClassMethodDeclarationSyntax& createInlinedMember( |
| 195 | + const ClassMethodPrototypeSyntax& prototype, |
| 196 | + const FunctionDeclarationSyntax& implementation, |
| 197 | + bool& removedExtern) { |
| 198 | + std::vector<parsing::Trivia> leadingExternTrivia; |
| 199 | + auto& qualifiers = |
| 200 | + cloneQualifiersWithoutExtern(prototype.qualifiers, removedExtern, leadingExternTrivia); |
| 201 | + |
| 202 | + auto& newFunctionDecl = factory.functionDeclaration( |
| 203 | + implementation.kind, *deepClone(implementation.attributes, alloc), |
| 204 | + *deepClone(*prototype.prototype, alloc), prototype.semi.deepClone(alloc), |
| 205 | + *deepClone(implementation.items, alloc), implementation.end.deepClone(alloc), |
| 206 | + implementation.endBlockName ? deepClone(*implementation.endBlockName, alloc) : nullptr); |
| 207 | + |
| 208 | + auto& classMethod = factory.classMethodDeclaration(*deepClone(prototype.attributes, alloc), |
| 209 | + qualifiers, newFunctionDecl); |
| 210 | + |
| 211 | + if (!leadingExternTrivia.empty()) { |
| 212 | + auto* firstTok = classMethod.getFirstTokenPtr(); |
| 213 | + ASSERT(firstTok, "inlined class method must have a first token"); |
| 214 | + |
| 215 | + auto mergedTrivia = mergeExternLeadingTrivia(leadingExternTrivia, firstTok->trivia()); |
| 216 | + auto copiedTrivia = alloc.copyFrom(std::span<const parsing::Trivia>(mergedTrivia)); |
| 217 | + *firstTok = firstTok->withTrivia(alloc, copiedTrivia); |
| 218 | + } |
| 219 | + |
| 220 | + return classMethod; |
| 221 | + } |
| 222 | +}; |
| 223 | + |
| 224 | +template bool rewriteLoop<ExternInliner>(std::shared_ptr<SyntaxTree>& tree, |
| 225 | + std::string stageName, |
| 226 | + std::string passIdx, |
| 227 | + SvBugpoint* svBugpoint); |
0 commit comments