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 @@ -1587,6 +1587,14 @@ let LangOpts = [COnly]; } +def WebAssemblyExportName : InheritableAttr, + TargetSpecificAttr { + let Spellings = [Clang<"export_name">]; + let Args = [StringArgument<"ExportName">]; + let Documentation = [WebAssemblyExportNameDocs]; + 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 @@ -4023,10 +4023,25 @@ or `msvc documentation `_. }]; } +def WebAssemblyExportNameDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +Clang supports the ``__attribute__((export_name()))`` +attribute for the WebAssembly target. This attribute may be attached to a +function declaration, where it modifies how the symbol is to be exported +from the linked WebAssembly. + +WebAssembly functions are exported via string name. By default when a symbol +is exported, the export name for C/C++ symbols are the same as their C/C++ +symbol names. This attribute can be used to override the default behavior, and +request a specific string name be used instead. + }]; +} + def WebAssemblyImportModuleDocs : Documentation { let Category = DocCatFunction; let Content = [{ -Clang supports the ``__attribute__((import_module()))`` +Clang supports the ``__attribute__((import_module()))`` attribute for the WebAssembly target. This attribute may be attached to a function declaration, where it modifies how the symbol is to be imported within the WebAssembly linking environment. @@ -4036,14 +4051,14 @@ name, which typically identifies a field from that module to import. By default, module names for C/C++ symbols are assigned automatically by the linker. This attribute can be used to override the default behavior, and -reuqest a specific module name be used instead. +request a specific module name be used instead. }]; } def WebAssemblyImportNameDocs : Documentation { let Category = DocCatFunction; let Content = [{ -Clang supports the ``__attribute__((import_name()))`` +Clang supports the ``__attribute__((import_name()))`` attribute for the WebAssembly target. This attribute may be attached to a function declaration, where it modifies how the symbol is to be imported within the WebAssembly linking environment. @@ -4053,7 +4068,7 @@ name, which typically identifies a field from that module to import. By default, field names for C/C++ symbols are the same as their C/C++ symbol names. This attribute can be used to override the default behavior, and -reuqest a specific field name be used instead. +request a specific field name be used instead. }]; } 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 @@ -778,6 +778,12 @@ B.addAttribute("wasm-import-name", Attr->getImportName()); Fn->addAttributes(llvm::AttributeList::FunctionIndex, B); } + if (const auto *Attr = FD->getAttr()) { + llvm::Function *Fn = cast(GV); + llvm::AttrBuilder B; + B.addAttribute("wasm-export-name", Attr->getExportName()); + Fn->addAttributes(llvm::AttributeList::FunctionIndex, B); + } } 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 @@ -5755,6 +5755,28 @@ Rec->addAttr(::new (S.Context) BPFPreserveAccessIndexAttr(S.Context, AL)); } +static void handleWebAssemblyExportNameAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (!isFunctionOrMethod(D)) { + S.Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type) + << "'export_name'" << ExpectedFunction; + return; + } + + auto *FD = cast(D); + if (FD->isThisDeclarationADefinition()) { + S.Diag(D->getLocation(), diag::err_alias_is_definition) << FD << 0; + return; + } + + StringRef Str; + SourceLocation ArgLoc; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc)) + return; + + FD->addAttr(::new (S.Context) + WebAssemblyExportNameAttr(S.Context, AL, Str)); +} + static void handleWebAssemblyImportModuleAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (!isFunctionOrMethod(D)) { S.Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type) @@ -6634,6 +6656,9 @@ case ParsedAttr::AT_BPFPreserveAccessIndex: handleBPFPreserveAccessIndexAttr(S, D, AL); break; + case ParsedAttr::AT_WebAssemblyExportName: + handleWebAssemblyExportNameAttr(S, D, AL); + break; case ParsedAttr::AT_WebAssemblyImportModule: handleWebAssemblyImportModuleAttr(S, D, AL); break; diff --git a/clang/test/CodeGen/wasm-export-name.c b/clang/test/CodeGen/wasm-export-name.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/wasm-export-name.c @@ -0,0 +1,11 @@ +// RUN: %clang_cc1 -triple wasm32-unknown-unknown-wasm -emit-llvm -o - %s | FileCheck %s + +int __attribute__((export_name("bar"))) foo(void); + +int foo(void) { + return 43; +} + +// CHECK: define i32 @foo() [[A:#[0-9]+]] + +// CHECK: attributes [[A]] = {{{.*}} "wasm-export-name"="bar" {{.*}}} diff --git a/lld/test/wasm/export-name.ll b/lld/test/wasm/export-name.ll new file mode 100644 --- /dev/null +++ b/lld/test/wasm/export-name.ll @@ -0,0 +1,28 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: wasm-ld -o %t.wasm %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +target triple = "wasm32-unknown-unknown" + +define void @foo() #0 { + ret void +} + +define void @_start() { + call void @foo() + ret void +} + +attributes #0 = { "wasm-export-name"="bar" } + +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: bar +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 diff --git a/lld/test/wasm/import-names.ll b/lld/test/wasm/import-name.ll rename from lld/test/wasm/import-names.ll rename to lld/test/wasm/import-name.ll diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h --- a/lld/wasm/InputChunks.h +++ b/lld/wasm/InputChunks.h @@ -130,6 +130,9 @@ void writeTo(uint8_t *sectionStart) const override; StringRef getName() const override { return function->SymbolName; } StringRef getDebugName() const override { return function->DebugName; } + StringRef getExportName() const { + return function ? function->ExportName : ""; + } uint32_t getComdat() const override { return function->Comdat; } uint32_t getFunctionInputOffset() const { return getInputSectionOffset(); } uint32_t getFunctionCodeOffset() const { return function->CodeOffset; } diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -519,6 +519,10 @@ StringRef name = sym->getName(); WasmExport export_; if (auto *f = dyn_cast(sym)) { + StringRef exportName = f->function->getExportName(); + if (!exportName.empty()) { + name = exportName; + } export_ = {name, WASM_EXTERNAL_FUNCTION, f->getFunctionIndex()}; } else if (auto *g = dyn_cast(sym)) { // TODO(sbc): Remove this check once to mutable global proposal is diff --git a/llvm/include/llvm/BinaryFormat/Wasm.h b/llvm/include/llvm/BinaryFormat/Wasm.h --- a/llvm/include/llvm/BinaryFormat/Wasm.h +++ b/llvm/include/llvm/BinaryFormat/Wasm.h @@ -131,6 +131,7 @@ uint32_t CodeSectionOffset; uint32_t Size; uint32_t CodeOffset; // start of Locals and Body + StringRef ExportName; // from the "export" section StringRef SymbolName; // from the "linking" section StringRef DebugName; // from the "name" section uint32_t Comdat; // from the "comdat info" section @@ -179,6 +180,7 @@ uint32_t Flags; StringRef ImportModule; // For undefined symbols the module of the import StringRef ImportName; // For undefined symbols the name of the import + StringRef ExportName; // For symbols to be exported from the final module union { // For function or global symbols, the index in function or global index // space. diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h --- a/llvm/include/llvm/MC/MCSymbolWasm.h +++ b/llvm/include/llvm/MC/MCSymbolWasm.h @@ -21,6 +21,7 @@ mutable bool IsUsedInGOT = false; Optional ImportModule; Optional ImportName; + Optional ExportName; wasm::WasmSignature *Signature = nullptr; Optional GlobalType; Optional EventType; @@ -78,6 +79,7 @@ } void setImportModule(StringRef Name) { ImportModule = Name; } + bool hasImportName() const { return ImportName.hasValue(); } const StringRef getImportName() const { if (ImportName.hasValue()) { return ImportName.getValue(); @@ -86,6 +88,10 @@ } void setImportName(StringRef Name) { ImportName = Name; } + bool hasExportName() const { return ExportName.hasValue(); } + const StringRef getExportName() const { return ExportName.getValue(); } + void setExportName(StringRef Name) { ExportName = Name; } + void setUsedInGOT() const { IsUsedInGOT = true; } bool isUsedInGOT() const { return IsUsedInGOT; } diff --git a/llvm/include/llvm/Object/Wasm.h b/llvm/include/llvm/Object/Wasm.h --- a/llvm/include/llvm/Object/Wasm.h +++ b/llvm/include/llvm/Object/Wasm.h @@ -280,6 +280,7 @@ uint32_t StartFunction = -1; bool HasLinkingSection = false; bool HasDylinkSection = false; + bool SeenCodeSection = false; wasm::WasmLinkingData LinkingData; uint32_t NumImportedGlobals = 0; uint32_t NumImportedFunctions = 0; diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp --- a/llvm/lib/MC/WasmObjectWriter.cpp +++ b/llvm/lib/MC/WasmObjectWriter.cpp @@ -1324,6 +1324,14 @@ Comdats[C->getName()].emplace_back( WasmComdatEntry{wasm::WASM_COMDAT_FUNCTION, Index}); } + + if (WS.hasExportName()) { + wasm::WasmExport Export; + Export.Name = WS.getExportName(); + Export.Kind = wasm::WASM_EXTERNAL_FUNCTION; + Export.Index = Index; + Exports.push_back(Export); + } } else { // An import; the index was assigned above. Index = WasmIndices.find(&WS)->second; @@ -1452,8 +1460,10 @@ Flags |= wasm::WASM_SYMBOL_EXPORTED; } } - if (WS.getName() != WS.getImportName()) + if (WS.hasImportName()) Flags |= wasm::WASM_SYMBOL_EXPLICIT_NAME; + if (WS.hasExportName()) + Flags |= wasm::WASM_SYMBOL_EXPORTED; wasm::WasmSymbolInfo Info; Info.Name = WS.getName(); diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp --- a/llvm/lib/Object/WasmObjectFile.cpp +++ b/llvm/lib/Object/WasmObjectFile.cpp @@ -343,7 +343,7 @@ Error WasmObjectFile::parseNameSection(ReadContext &Ctx) { llvm::DenseSet Seen; - if (Functions.size() != FunctionTypes.size()) { + if (FunctionTypes.size() && !SeenCodeSection) { return make_error("Names must come after code section", object_error::parse_failed); } @@ -389,7 +389,7 @@ Error WasmObjectFile::parseLinkingSection(ReadContext &Ctx) { HasLinkingSection = true; - if (Functions.size() != FunctionTypes.size()) { + if (FunctionTypes.size() && !SeenCodeSection) { return make_error( "Linking data must come after code section", object_error::parse_failed); @@ -940,6 +940,7 @@ Error WasmObjectFile::parseFunctionSection(ReadContext &Ctx) { uint32_t Count = readVaruint32(Ctx); FunctionTypes.reserve(Count); + Functions.resize(Count); uint32_t NumTypes = Signatures.size(); while (Count--) { uint32_t Type = readVaruint32(Ctx); @@ -1029,9 +1030,11 @@ Ex.Index = readVaruint32(Ctx); switch (Ex.Kind) { case wasm::WASM_EXTERNAL_FUNCTION: - if (!isValidFunctionIndex(Ex.Index)) + + if (!isDefinedFunctionIndex(Ex.Index)) return make_error("Invalid function export", object_error::parse_failed); + getDefinedFunction(Ex.Index).ExportName = Ex.Name; break; case wasm::WASM_EXTERNAL_GLOBAL: if (!isValidGlobalIndex(Ex.Index)) @@ -1132,6 +1135,7 @@ } Error WasmObjectFile::parseCodeSection(ReadContext &Ctx) { + SeenCodeSection = true; CodeSection = Sections.size(); uint32_t FunctionCount = readVaruint32(Ctx); if (FunctionCount != FunctionTypes.size()) { @@ -1139,14 +1143,14 @@ object_error::parse_failed); } - while (FunctionCount--) { - wasm::WasmFunction Function; + for (uint32_t i = 0; i < FunctionCount; i++) { + wasm::WasmFunction& Function = Functions[i]; const uint8_t *FunctionStart = Ctx.Ptr; uint32_t Size = readVaruint32(Ctx); const uint8_t *FunctionEnd = Ctx.Ptr + Size; Function.CodeOffset = Ctx.Ptr - FunctionStart; - Function.Index = NumImportedFunctions + Functions.size(); + Function.Index = NumImportedFunctions + i; Function.CodeSectionOffset = FunctionStart - Ctx.Start; Function.Size = FunctionEnd - FunctionStart; @@ -1165,7 +1169,6 @@ Function.Comdat = UINT32_MAX; Ctx.Ptr += BodySize; assert(Ctx.Ptr == FunctionEnd); - Functions.push_back(Function); } if (Ctx.Ptr != Ctx.End) return make_error("Code section ended prematurely", 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 @@ -712,6 +712,12 @@ return expect(AsmToken::EndOfStatement, "EOL"); } + /* + if (DirectiveID.getString() == ".export") { + TOut.emitExportName(WasmSym, Name); + } + */ + if (DirectiveID.getString() == ".eventtype") { 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 @@ -48,6 +48,9 @@ /// .import_name virtual void emitImportName(const MCSymbolWasm *Sym, StringRef ImportName) = 0; + /// .export_name + virtual void emitExportName(const MCSymbolWasm *Sym, + StringRef ExportName) = 0; protected: void emitValueType(wasm::ValType Type); @@ -68,6 +71,7 @@ void emitEventType(const MCSymbolWasm *Sym) override; void emitImportModule(const MCSymbolWasm *Sym, StringRef ImportModule) override; void emitImportName(const MCSymbolWasm *Sym, StringRef ImportName) override; + void emitExportName(const MCSymbolWasm *Sym, StringRef ExportName) override; }; /// This part is for Wasm object output @@ -85,6 +89,8 @@ StringRef ImportModule) override {} void emitImportName(const MCSymbolWasm *Sym, StringRef ImportName) override {} + void emitExportName(const MCSymbolWasm *Sym, + StringRef ExportName) override {} }; /// This part is for null output @@ -101,6 +107,7 @@ void emitEventType(const MCSymbolWasm *) override {} void emitImportModule(const MCSymbolWasm *, StringRef) override {} void emitImportName(const MCSymbolWasm *, StringRef) override {} + void emitExportName(const MCSymbolWasm *, StringRef) 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 @@ -94,6 +94,12 @@ << ImportName << '\n'; } +void WebAssemblyTargetAsmStreamer::emitExportName(const MCSymbolWasm *Sym, + StringRef ExportName) { + OS << "\t.export_name\t" << Sym->getName() << ", " + << ExportName << '\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 @@ -290,6 +290,12 @@ // FIXME: clean up how params and results are emitted (use signatures) getTargetStreamer()->emitFunctionType(WasmSym); + if (F.hasFnAttribute("wasm-export-name")) { + StringRef Name = F.getFnAttribute("wasm-export-name").getValueAsString(); + WasmSym->setExportName(Name); + getTargetStreamer()->emitExportName(WasmSym, Name); + } + // Emit the function index. if (MDNode *Idx = F.getMetadata("wasm.index")) { assert(Idx->getNumOperands() == 1); diff --git a/llvm/test/CodeGen/WebAssembly/export-name.ll b/llvm/test/CodeGen/WebAssembly/export-name.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/WebAssembly/export-name.ll @@ -0,0 +1,12 @@ +; 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-unknown" + +define void @test() #0 { + ret void +} + +attributes #0 = { "wasm-export-name"="foo" } + +; CHECK: .export_name test, foo diff --git a/llvm/test/MC/WebAssembly/export-name.ll b/llvm/test/MC/WebAssembly/export-name.ll new file mode 100644 --- /dev/null +++ b/llvm/test/MC/WebAssembly/export-name.ll @@ -0,0 +1,24 @@ +; RUN: llc -filetype=obj %s -o - | obj2yaml | FileCheck %s + +target triple = "wasm32-unknown-unknown" + +define void @test() #0 { + ret void +} + +attributes #0 = { "wasm-export-name"="foo" } + +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: foo +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 + +; CHECK: Name: linking +; CHECK-NEXT: Version: 2 +; CHECK-NEXT: SymbolTable: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Name: test +; CHECK-NEXT: Flags: [ EXPORTED ] +; CHECK-NEXT: Function: 0