diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -414,6 +414,10 @@ def TargetX86 : TargetArch<["x86"]>; def TargetAnyX86 : TargetArch<["x86", "x86_64"]>; def TargetWebAssembly : TargetArch<["wasm32", "wasm64"]>; +def TargetEmscripten : TargetSpec { + let Arches = ["wasm32", "wasm64"]; + let OSes = ["Emscripten"]; +} def TargetWindows : TargetSpec { let OSes = ["Win32"]; } @@ -1946,6 +1950,13 @@ let Subjects = SubjectList<[Function], ErrorDiag>; } +def WebAssemblyExported : InheritableAttr, + TargetSpecificAttr { + let Spellings = [Clang<"exported">]; + let Documentation = [WebAssemblyExportedDocs]; + let Subjects = SubjectList<[Function], ErrorDiag>; +} + def WebAssemblyImportModule : InheritableAttr, TargetSpecificAttr { let Spellings = [Clang<"import_module">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5527,6 +5527,16 @@ }]; } +def WebAssemblyExportedDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +Clang supports the ``__attribute__((exported))`` +attribute for the WebAssembly target. This attribute may be attached to a +function declaration, where it causes the symbol to be exported from the +linked WebAssembly module. + }]; +} + def WebAssemblyImportModuleDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp --- a/clang/lib/CodeGen/TargetInfo.cpp +++ b/clang/lib/CodeGen/TargetInfo.cpp @@ -873,24 +873,25 @@ CodeGen::CodeGenModule &CGM) const override { TargetCodeGenInfo::setTargetAttributes(D, GV, CGM); if (const auto *FD = dyn_cast_or_null(D)) { + llvm::Function *Fn = cast(GV); if (const auto *Attr = FD->getAttr()) { - llvm::Function *Fn = cast(GV); llvm::AttrBuilder B(GV->getContext()); B.addAttribute("wasm-import-module", Attr->getImportModule()); Fn->addFnAttrs(B); } if (const auto *Attr = FD->getAttr()) { - llvm::Function *Fn = cast(GV); llvm::AttrBuilder B(GV->getContext()); B.addAttribute("wasm-import-name", Attr->getImportName()); Fn->addFnAttrs(B); } if (const auto *Attr = FD->getAttr()) { - llvm::Function *Fn = cast(GV); llvm::AttrBuilder B(GV->getContext()); B.addAttribute("wasm-export-name", Attr->getExportName()); Fn->addFnAttrs(B); } + if (const auto *Attr = FD->getAttr()) { + Fn->addFnAttr("wasm-exported"); + } } if (auto *FD = dyn_cast_or_null(D)) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7557,6 +7557,26 @@ D->addAttr(UsedAttr::CreateImplicit(S.Context)); } +static void handleWebAssemblyExportedAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (!isFunctionOrMethod(D)) { + S.Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type) + << "'exported'" << ExpectedFunction; + return; + } + + if (S.Context.getTargetInfo().getTriple().isOSEmscripten()) { + } + + auto *FD = cast(D); + if (FD->isThisDeclarationADefinition()) { + S.Diag(D->getLocation(), diag::err_alias_is_definition) << FD << 0; + return; + } + + D->addAttr(::new (S.Context) WebAssemblyExportedAttr(S.Context, AL)); + D->addAttr(UsedAttr::CreateImplicit(S.Context)); +} + WebAssemblyImportModuleAttr * Sema::mergeImportModuleAttr(Decl *D, const WebAssemblyImportModuleAttr &AL) { auto *FD = cast(D); @@ -8736,6 +8756,9 @@ case ParsedAttr::AT_WebAssemblyExportName: handleWebAssemblyExportNameAttr(S, D, AL); break; + case ParsedAttr::AT_WebAssemblyExported: + handleWebAssemblyExportedAttr(S, D, AL); + break; case ParsedAttr::AT_WebAssemblyImportModule: handleWebAssemblyImportModuleAttr(S, D, AL); break; diff --git a/clang/test/CodeGen/WebAssembly/wasm-exported.c b/clang/test/CodeGen/WebAssembly/wasm-exported.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/WebAssembly/wasm-exported.c @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -triple wasm32-unknown-emscripten -emit-llvm -o - %s | FileCheck %s +// +// This attribute is currently limited to `wasm32-unknown-emscripten` +// RUN: not %clang_cc1 -Werror -triple wasm32-unknown-unknown -emit-llvm -o - %s 2>&1 | FileCheck %s -check-prefix=MISSING + +int __attribute__((exported)) foo(void); + +int foo(void) { + return 43; +} + +// MISSING: error: unknown attribute 'exported' ignored + +// CHECK: @llvm.used = appending global [1 x ptr] [ptr @foo], section "llvm.metadata" + +// CHECK: define i32 @foo() [[A:#[0-9]+]] + +// CHECK: attributes [[A]] = {{{.*}} "wasm-exported" {{.*}}} diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp @@ -897,6 +897,15 @@ return expect(AsmToken::EndOfStatement, "EOL"); } + if (DirectiveID.getString() == ".export") { + auto SymName = expectIdent(); + if (SymName.empty()) + return true; + auto WasmSym = cast(Ctx.getOrCreateSymbol(SymName)); + WasmSym->setExported(); + TOut.emitExport(WasmSym); + } + if (DirectiveID.getString() == ".export_name") { auto SymName = expectIdent(); if (SymName.empty()) diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.h @@ -51,6 +51,8 @@ /// .export_name virtual void emitExportName(const MCSymbolWasm *Sym, StringRef ExportName) = 0; + /// .export + virtual void emitExport(const MCSymbolWasm *Sym) = 0; protected: void emitValueType(wasm::ValType Type); @@ -72,6 +74,7 @@ void emitImportModule(const MCSymbolWasm *Sym, StringRef ImportModule) override; void emitImportName(const MCSymbolWasm *Sym, StringRef ImportName) override; void emitExportName(const MCSymbolWasm *Sym, StringRef ExportName) override; + void emitExport(const MCSymbolWasm *Sym) override; }; /// This part is for Wasm object output @@ -91,6 +94,7 @@ StringRef ImportName) override {} void emitExportName(const MCSymbolWasm *Sym, StringRef ExportName) override {} + void emitExport(const MCSymbolWasm *Sym) override {} }; /// This part is for null output @@ -108,6 +112,7 @@ void emitImportModule(const MCSymbolWasm *, StringRef) override {} void emitImportName(const MCSymbolWasm *, StringRef) override {} void emitExportName(const MCSymbolWasm *, StringRef) override {} + void emitExport(const MCSymbolWasm *) override {} }; } // end namespace llvm diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp @@ -114,6 +114,10 @@ << ExportName << '\n'; } +void WebAssemblyTargetAsmStreamer::emitExport(const MCSymbolWasm *Sym) { + OS << "\t.export\t" << Sym->getName() << '\n'; +} + void WebAssemblyTargetAsmStreamer::emitIndIdx(const MCExpr *Value) { OS << "\t.indidx \t" << *Value << '\n'; } diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp @@ -376,6 +376,12 @@ getTargetStreamer()->emitImportName(Sym, Name); } + if (F.hasFnAttribute("wasm-exported")) { + auto *Sym = cast(getSymbol(&F)); + Sym->setExported(); + getTargetStreamer()->emitExport(Sym); + } + if (F.hasFnAttribute("wasm-export-name")) { auto *Sym = cast(getSymbol(&F)); StringRef Name = F.getFnAttribute("wasm-export-name").getValueAsString(); diff --git a/llvm/test/CodeGen/WebAssembly/export.ll b/llvm/test/CodeGen/WebAssembly/export.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/export.ll @@ -0,0 +1,16 @@ +; RUN: llc < %s -asm-verbose=false -wasm-keep-registers | FileCheck %s + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-emscripten" + +define void @test() #0 { + ret void +} + +declare void @test2() #1 + +attributes #0 = { "wasm-exported" } +attributes #1 = { "wasm-exported" } + +; CHECK: .export test +; CHECK: .export test2