LILAC
Language to language Interop LAyer Compiler
Loading...
Searching...
No Matches
pluginaction.cxx
Go to the documentation of this file.
1/*
2 * Copyright (C) 2024 Yeong-won Seo
3 *
4 * This file is part of LILAC.
5 *
6 * LILAC is free software: you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free
8 * Software Foundation, either version 3, or (at your option) any later
9 * version.
10 *
11 * LILAC is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "pluginaction.h"
21
22#include "exportattr.h"
23#include "typemap.h"
24
25namespace lilac::cxx
26{
27 frxml::dom& GetDOMRoot()
28 {
29 static frxml::dom dom = frxml::dom::element("assembly");
30 return dom;
31 }
32
33 std::unique_ptr<clang::ASTConsumer> LilacAction::CreateASTConsumer(
34 clang::CompilerInstance& ci,
35 llvm::StringRef)
36 {
37 return std::make_unique<LilacASTConsumer>(GetDOMRoot());
38 }
39
40 LilacASTConsumer::LilacASTConsumer(frxml::dom& dom) : m_Root(dom)
41 {
42 }
43
44 void LilacASTConsumer::InitializeSema(clang::Sema& sema)
45 {
46 m_Sema = &sema;
47 }
48
49 void LilacASTConsumer::HandleTranslationUnit(clang::ASTContext& context)
50 {
51 LilacASTVisitor visitor(*m_Sema, m_Root);
52 visitor.TraverseDecl(context.getTranslationUnitDecl());
53 }
54
55 frxml::dom* LilacASTVisitor::GetNamespaceDOM(clang::NamedDecl* decl) const
56 {
57 const auto ctx = decl->getDeclContext();
58
59 if (const auto parent = ctx;
60 parent &&
61 parent->getDeclKind() != clang::Decl::Namespace &&
62 parent->getDeclKind() != clang::Decl::TranslationUnit)
63 {
64 static auto err = m_Diag.getCustomDiagID(
65 Level::Error,
66 "Couldn't handle a nested declaration");
67
68 m_Sema.Diag(decl->getLocation(), err);
69
70 return nullptr;
71 }
72
73 std::stack<clang::NamespaceDecl*> stk;
74 for (
75 auto cur = ctx;
76 cur != nullptr && cur->getDeclKind() == clang::Decl::Namespace;
77 cur = cur->getParent())
78 {
79 stk.emplace(clang::cast<clang::NamespaceDecl>(cur));
80 }
81
82 frxml::dom* cur = &m_Root;
83 for (; !stk.empty(); stk.pop())
84 {
85 frxml::dom* node = nullptr;
86
87 auto ns = stk.top()->getNameAsString();
88 for (auto& child: cur->children())
89 {
90 if (child.tag().view() != "namespace")
91 continue;
92 if (child.attr()["name"].view() == ns)
93 node = &child;
94 }
95 if (!node)
96 {
97 node = &cur->children().emplace_back(frxml::dom::element(
98 "namespace",
99 { { "name", ns } }));
100 }
101
102 cur = node;
103 }
104
105 return cur;
106 }
107
108 LilacASTVisitor::LilacASTVisitor(clang::Sema& sema, frxml::dom& dom)
109 : m_Sema(sema),
110 m_Diag(m_Sema.getDiagnostics()),
111 m_Root(dom)
112 {
113 }
114
115 bool ShouldBeExported(clang::NamedDecl* decl)
116 {
117 auto exported = false;
118
119 if (clang::isa<clang::TagDecl>(decl))
120 {
121 for (const auto attr: decl->attrs())
122 {
123 if (const auto anot = clang::dyn_cast<clang::AnnotateTypeAttr>(attr);
124 !anot || anot->getAnnotation() != ExportAttrInfo::AttrMangling)
125 continue;
126
127 exported = true;
128 break;
129 }
130 }
131 else
132 {
133 for (const auto attr: decl->attrs())
134 {
135 if (const auto anot = clang::dyn_cast<clang::AnnotateAttr>(attr);
136 !anot || anot->getAnnotation() != ExportAttrInfo::AttrMangling)
137 continue;
138
139 exported = true;
140 break;
141 }
142 }
143
144 return exported;
145 }
146
147 bool LilacASTVisitor::IsDuplicated(clang::NamedDecl* decl, const std::string& tag)
148 {
149 const auto ns = GetNamespaceDOM(decl);
150 if (!ns) return true;
151
152 auto skip = false;
153 for (auto& child: ns->children())
154 {
155 const auto name = clang::isa<clang::FunctionDecl>(decl)
156 ? clang::ASTNameGenerator(decl->getASTContext()).getName(decl)
157 : decl->getNameAsString();
158
159 if (child.tag().view() != tag ||
160 child.attr()["name"].view() != name)
161 continue;
162 skip = true;
163 break;
164 }
165
166 return skip;
167 }
168
169 // ReSharper disable once CppDFAConstantFunctionResult
170 bool LilacASTVisitor::TraverseEnumDecl(clang::EnumDecl* decl)
171 {
172 if (!ShouldBeExported(decl))
173 return true;
174 if (IsDuplicated(decl, "enum"))
175 return true;
176
177 std::vector<frxml::dom> children;
178
179 EnumVisitor visitor{
180 [&](const clang::EnumConstantDecl* constant)
181 {
182 const auto value = constant->getValue();
183 const auto valueStr = decl->getIntegerType()->isSignedIntegerType()
184 ? std::to_string(value.getSExtValue())
185 : std::to_string(value.getZExtValue());
186
187 children.push_back(frxml::dom::element(
188 "constant",
189 {
190 { "name", constant->getNameAsString() },
191 { "value", valueStr }
192 }));
193 }
194 };
195 visitor.TraverseDecl(decl);
196
197 auto kind = clang::BuiltinType::Kind::Int;
198
199 if (const auto underlyingT = decl->getIntegerType(); !underlyingT.isNull())
200 {
201 const auto type = underlyingT->getUnqualifiedDesugaredType();
202 if (!type->isBuiltinType())
203 {
204 static auto err = m_Diag.getCustomDiagID(
205 Level::Error,
206 "Couldn't use non-builtin type as enumeration type");
207 m_Sema.Diag(decl->getLocation(), err);
208
209 return true;
210 }
211
212 kind = clang::cast<clang::BuiltinType>(type)->getKind();
213 }
214
215 const auto ns = GetNamespaceDOM(decl);
216 if (!ns) return true;
217 ns->children().push_back(frxml::dom::element(
218 "enum",
219 {
220 { "name", decl->getNameAsString() },
221 { "type", GetBuiltinTypeName(kind) }
222 },
223 children
224 ));
225 return true;
226 }
227
229 clang::Sema& sema,
230 const clang::SourceLocation loc,
231 const clang::Type* type,
232 std::string& name)
233 {
234 type = type->getUnqualifiedDesugaredType();
235 int nRef;
236 for (nRef = 0; type->isPointerOrReferenceType(); ++nRef)
237 type = type->getPointeeType()->getUnqualifiedDesugaredType();
238
239 if (type->isBuiltinType())
240 {
241 name = GetBuiltinTypeName(clang::cast<clang::BuiltinType>(type)->getKind()) + std::string(nRef, '*');
242 return true;
243 }
244
245 if (type->isFunctionType())
246 {
247 static auto err = sema.getDiagnostics().getCustomDiagID(
248 clang::DiagnosticsEngine::Error,
249 "Function types cannot be exported");
250 sema.Diag(loc, err);
251
252 return false;
253 }
254
255 if (!type->getAsRecordDecl())
256 {
257 static auto err = sema.getDiagnostics().getCustomDiagID(
258 clang::DiagnosticsEngine::Error,
259 "Exported types must be built-in or convertible to record declaration");
260 sema.Diag(loc, err);
261
262 return false;
263 }
264
265 if (const auto parent = type->getAsRecordDecl()->getParent();
266 parent &&
267 parent->getDeclKind() != clang::Decl::Namespace &&
268 parent->getDeclKind() != clang::Decl::TranslationUnit)
269 {
270 static auto err = sema.getDiagnostics().getCustomDiagID(
271 clang::DiagnosticsEngine::Error,
272 "Exported types must be in namespace or global");
273 sema.Diag(loc, err);
274
275 return false;
276 }
277
278 std::stack<std::string> stk;
279 for (
280 auto decl = type->getAsRecordDecl()->getParent();
281 decl != nullptr && decl->getDeclKind() == clang::Decl::Namespace;
282 decl = decl->getParent())
283 {
284 stk.push(clang::cast<clang::NamespaceDecl>(decl)->getNameAsString());
285 }
286
287 std::stringstream ss;
288 for (; !stk.empty(); stk.pop())
289 {
290 ss << stk.top() << '/';
291 }
292 ss << type->getAsRecordDecl()->getNameAsString();
293 ss << std::string(nRef, '*');
294
295 name = ss.str();
296 return true;
297 }
298
299 bool RecordParameters(clang::Sema& sema, frxml::dom& dom, clang::FunctionDecl* fn)
300 {
301 for (const auto parameter: fn->parameters())
302 {
303 std::string type;
304 if (!GetTypeName(
305 sema,
306 parameter->getLocation(),
307 parameter->getType()->getUnqualifiedDesugaredType(),
308 type))
309 {
310 return false;
311 }
312
313 dom.children().push_back(frxml::dom::element(
314 "param",
315 {
316 { "name", parameter->getNameAsString() },
317 { "type", type }
318 }
319 ));
320 }
321
322 return true;
323 }
324
325 std::optional<frxml::dom> RecordFunction(
326 clang::Sema& sema,
327 clang::FunctionDecl* fn)
328 {
329 static std::map<clang::Decl::Kind, std::string> tagMap = {
330 { clang::Decl::CXXMethod, "method" },
331 { clang::Decl::CXXConstructor, "ctor" },
332 { clang::Decl::CXXDestructor, "dtor" }
333 };
334 std::string tag = "function";
335 if (!fn->isStatic() && tagMap.contains(fn->getKind()))
336 tag = tagMap[fn->getKind()];
337
338
339 std::string virtuality = "none";
340 if (const auto method = clang::dyn_cast<clang::CXXMethodDecl>(fn); method && method->isVirtual())
341 {
342 virtuality = "virtual";
343 for (const auto attr: method->attrs())
344 if (attr->getKind() == clang::attr::Final)
345 virtuality = "final";
346 }
347
348
349 const auto proto = fn->getType()->getAs<clang::FunctionProtoType>();
350 const auto callconv = clang::FunctionType::getNameForCallConv(proto->getCallConv());
351
352 clang::ASTNameGenerator ang(fn->getASTContext());
353
354 auto dom = frxml::dom::element(
355 tag,
356 {
357 { "mangle", ang.getName(fn) },
358 { "callconv", callconv.str() },
359 { "virtual", virtuality }
360 });
361
362 if (fn->getKind() != clang::Decl::CXXConstructor &&
363 fn->getKind() != clang::Decl::CXXDestructor)
364 {
365 std::string ret;
366 if (!GetTypeName(sema, fn->getLocation(), fn->getReturnType().getTypePtr(), ret))
367 return std::nullopt;
368
369 dom.attr().emplace("name", fn->getNameAsString());
370 dom.attr().emplace("return", ret);
371 }
372
373 if (!RecordParameters(sema, dom, fn))
374 return std::nullopt;
375
376 return dom;
377 }
378
379 // ReSharper disable once CppDFAConstantFunctionResult
380 bool LilacASTVisitor::TraverseCXXRecordDecl(clang::CXXRecordDecl* decl)
381 {
382 if (!ShouldBeExported(decl))
383 return true;
384 if (IsDuplicated(decl, "record"))
385 return true;
386
387 std::vector<frxml::dom> children;
388
389 static auto recordErr = m_Sema.getDiagnostics().getCustomDiagID(
390 Level::Error,
391 "CXXMethod cannot be exported");
392
393 // Record member functions including constructors/destructor
394 CXXRecordVisitor visitor{
395 [&](clang::CXXMethodDecl* method)
396 {
397 if (!ShouldBeExported(method))
398 return;
399
400 const auto dom = RecordFunction(m_Sema, method);
401 if (!dom)
402 {
403 m_Sema.Diag(method->getLocation(), recordErr);
404 return;
405 }
406
407 children.push_back(dom.value());
408 }
409 };
410 visitor.TraverseDecl(decl);
411
412 // Record implicit destructor
413 if (const auto dtor = decl->getDestructor())
414 {
415 const auto dom = RecordFunction(m_Sema, dtor);
416 if (!dom)
417 {
418 m_Sema.Diag(dtor->getLocation(), recordErr);
419 return false;
420 }
421
422 children.push_back(dom.value());
423 }
424
425 const auto ns = GetNamespaceDOM(decl);
426 if (!ns) return true;
427
428 const auto typeInfo = decl->getASTContext().getTypeInfo(decl->getTypeForDecl());
429
430 const auto size = typeInfo.Width;
431 const auto align = typeInfo.Align;
432 if (size % 8 || align % 8)
433 {
434 static auto err = m_Diag.getCustomDiagID(
435 Level::Error,
436 "A size and alignment of record must be multiple of bytes");
437 m_Sema.Diag(decl->getLocation(), err);
438 }
439
440 ns->children().push_back(frxml::dom::element(
441 "record",
442 {
443 { "name", decl->getNameAsString() },
444 { "size", std::to_string(size / 8) },
445 { "align", std::to_string(align / 8) }
446 },
447 children
448 ));
449 return true;
450 }
451
452 // ReSharper disable once CppDFAConstantFunctionResult
453 bool LilacASTVisitor::TraverseFunctionDecl(clang::FunctionDecl* decl)
454 {
455 if (!ShouldBeExported(decl))
456 return true;
457 if (clang::isa<clang::CXXMethodDecl>(decl))
458 return true;
459
460 if (IsDuplicated(decl, "function"))
461 return true;
462
463 const auto dom = RecordFunction(m_Sema, decl);
464 if (!dom)
465 {
466 static auto err = m_Sema.getDiagnostics().getCustomDiagID(
467 Level::Error,
468 "FunctionDecl cannot be exported");
469 m_Sema.Diag(decl->getLocation(), err);
470 return false;
471 }
472
473 const auto ns = GetNamespaceDOM(decl);
474 if (!ns) return true;
475
476 ns->children().push_back(dom.value());
477 return true;
478 }
479
480 LilacASTVisitor::EnumVisitor::EnumVisitor(std::function<void(clang::EnumConstantDecl* constant)> delegate)
481 : m_Delegate(std::move(delegate))
482 {
483 }
484
485 // ReSharper disable once CppMemberFunctionMayBeConst
487 {
488 m_Delegate(decl);
489 return true;
490 }
491
492 LilacASTVisitor::CXXRecordVisitor::CXXRecordVisitor(std::function<void(clang::CXXMethodDecl*)> delegate)
493 : m_Delegate(std::move(delegate))
494 {
495 }
496
497 // ReSharper disable once CppMemberFunctionMayBeConst
499 {
500 m_Delegate(decl);
501 return true;
502 }
503}
static constexpr std::string AttrMangling
Definition exportattr.h:37
void InitializeSema(clang::Sema &sema) override
LilacASTConsumer(frxml::dom &dom)
void HandleTranslationUnit(clang::ASTContext &context) override
A recursive AST visitor that traverses AST of an enumeration.
bool TraverseCXXMethodDecl(clang::CXXMethodDecl *decl)
CXXRecordVisitor(std::function< void(clang::CXXMethodDecl *)> delegate)
A recursive AST visitor that traverses AST of an enumeration.
EnumVisitor(std::function< void(clang::EnumConstantDecl *constant)>)
bool TraverseEnumConstantDecl(clang::EnumConstantDecl *decl)
A recursive AST visitor that serializes C/C++ interface data into DOM object.
bool TraverseCXXRecordDecl(clang::CXXRecordDecl *decl)
bool TraverseEnumDecl(clang::EnumDecl *decl)
LilacASTVisitor(clang::Sema &sema, frxml::dom &dom)
bool TraverseFunctionDecl(clang::FunctionDecl *decl)
std::unique_ptr< clang::ASTConsumer > CreateASTConsumer(clang::CompilerInstance &, llvm::StringRef) override
frxml::dom & GetDOMRoot()
bool ShouldBeExported(clang::NamedDecl *decl)
bool GetTypeName(clang::Sema &sema, const clang::SourceLocation loc, const clang::Type *type, std::string &name)
std::optional< frxml::dom > RecordFunction(clang::Sema &sema, clang::FunctionDecl *fn)
constexpr std::string GetBuiltinTypeName(clang::BuiltinType::Kind kind)
Definition typemap.h:27
bool RecordParameters(clang::Sema &sema, frxml::dom &dom, clang::FunctionDecl *fn)