Skip to content

Commit 881d877

Browse files
committed
[WebAssembly] Add new export_name clang attribute for controlling wasm export names
This is equivalent to the existing `import_name` and `import_module` attributes which control the import names in the final wasm binary produced by lld. This maps the existing This attribute currently requires a string rather than using the symbol name for a couple of reasons: 1. Avoid confusion with static and dynamic linking which is based on symbol name. Exporting a function from a wasm module using this directive is orthogonal to both static and dynamic linking. 2. Avoids name mangling. Differential Revision: https://reviews.llvm.org/D70520
1 parent 8db5143 commit 881d877

File tree

21 files changed

+217
-15
lines changed

21 files changed

+217
-15
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,14 @@ def BPFPreserveAccessIndex : InheritableAttr,
16001600
let LangOpts = [COnly];
16011601
}
16021602

1603+
def WebAssemblyExportName : InheritableAttr,
1604+
TargetSpecificAttr<TargetWebAssembly> {
1605+
let Spellings = [Clang<"export_name">];
1606+
let Args = [StringArgument<"ExportName">];
1607+
let Documentation = [WebAssemblyExportNameDocs];
1608+
let Subjects = SubjectList<[Function], ErrorDiag>;
1609+
}
1610+
16031611
def WebAssemblyImportModule : InheritableAttr,
16041612
TargetSpecificAttr<TargetWebAssembly> {
16051613
let Spellings = [Clang<"import_module">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4101,6 +4101,21 @@ For more information see
41014101
or `msvc documentation <https://docs.microsoft.com/pl-pl/cpp/cpp/selectany>`_.
41024102
}]; }
41034103

4104+
def WebAssemblyExportNameDocs : Documentation {
4105+
let Category = DocCatFunction;
4106+
let Content = [{
4107+
Clang supports the ``__attribute__((export_name(<name>)))``
4108+
attribute for the WebAssembly target. This attribute may be attached to a
4109+
function declaration, where it modifies how the symbol is to be exported
4110+
from the linked WebAssembly.
4111+
4112+
WebAssembly functions are exported via string name. By default when a symbol
4113+
is exported, the export name for C/C++ symbols are the same as their C/C++
4114+
symbol names. This attribute can be used to override the default behavior, and
4115+
request a specific string name be used instead.
4116+
}];
4117+
}
4118+
41044119
def WebAssemblyImportModuleDocs : Documentation {
41054120
let Category = DocCatFunction;
41064121
let Content = [{

clang/lib/CodeGen/TargetInfo.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,12 @@ class WebAssemblyTargetCodeGenInfo final : public TargetCodeGenInfo {
779779
B.addAttribute("wasm-import-name", Attr->getImportName());
780780
Fn->addAttributes(llvm::AttributeList::FunctionIndex, B);
781781
}
782+
if (const auto *Attr = FD->getAttr<WebAssemblyExportNameAttr>()) {
783+
llvm::Function *Fn = cast<llvm::Function>(GV);
784+
llvm::AttrBuilder B;
785+
B.addAttribute("wasm-export-name", Attr->getExportName());
786+
Fn->addAttributes(llvm::AttributeList::FunctionIndex, B);
787+
}
782788
}
783789

784790
if (auto *FD = dyn_cast_or_null<FunctionDecl>(D)) {

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5754,6 +5754,28 @@ static void handleBPFPreserveAccessIndexAttr(Sema &S, Decl *D,
57545754
Rec->addAttr(::new (S.Context) BPFPreserveAccessIndexAttr(S.Context, AL));
57555755
}
57565756

5757+
static void handleWebAssemblyExportNameAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
5758+
if (!isFunctionOrMethod(D)) {
5759+
S.Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type)
5760+
<< "'export_name'" << ExpectedFunction;
5761+
return;
5762+
}
5763+
5764+
auto *FD = cast<FunctionDecl>(D);
5765+
if (FD->isThisDeclarationADefinition()) {
5766+
S.Diag(D->getLocation(), diag::err_alias_is_definition) << FD << 0;
5767+
return;
5768+
}
5769+
5770+
StringRef Str;
5771+
SourceLocation ArgLoc;
5772+
if (!S.checkStringLiteralArgumentAttr(AL, 0, Str, &ArgLoc))
5773+
return;
5774+
5775+
FD->addAttr(::new (S.Context)
5776+
WebAssemblyExportNameAttr(S.Context, AL, Str));
5777+
}
5778+
57575779
static void handleWebAssemblyImportModuleAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
57585780
if (!isFunctionOrMethod(D)) {
57595781
S.Diag(D->getLocation(), diag::warn_attribute_wrong_decl_type)
@@ -6672,6 +6694,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
66726694
case ParsedAttr::AT_BPFPreserveAccessIndex:
66736695
handleBPFPreserveAccessIndexAttr(S, D, AL);
66746696
break;
6697+
case ParsedAttr::AT_WebAssemblyExportName:
6698+
handleWebAssemblyExportNameAttr(S, D, AL);
6699+
break;
66756700
case ParsedAttr::AT_WebAssemblyImportModule:
66766701
handleWebAssemblyImportModuleAttr(S, D, AL);
66776702
break;

clang/test/CodeGen/wasm-export-name.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// RUN: %clang_cc1 -triple wasm32-unknown-unknown-wasm -emit-llvm -o - %s | FileCheck %s
2+
3+
int __attribute__((export_name("bar"))) foo(void);
4+
5+
int foo(void) {
6+
return 43;
7+
}
8+
9+
// CHECK: define i32 @foo() [[A:#[0-9]+]]
10+
11+
// CHECK: attributes [[A]] = {{{.*}} "wasm-export-name"="bar" {{.*}}}

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@
151151
// CHECK-NEXT: WarnUnusedResult (SubjectMatchRule_objc_method, SubjectMatchRule_enum, SubjectMatchRule_record, SubjectMatchRule_hasType_functionType)
152152
// CHECK-NEXT: Weak (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
153153
// CHECK-NEXT: WeakRef (SubjectMatchRule_variable, SubjectMatchRule_function)
154+
// CHECK-NEXT: WebAssemblyExportName (SubjectMatchRule_function)
154155
// CHECK-NEXT: WebAssemblyImportModule (SubjectMatchRule_function)
155156
// CHECK-NEXT: WebAssemblyImportName (SubjectMatchRule_function)
156157
// CHECK-NEXT: WorkGroupSizeHint (SubjectMatchRule_function)

lld/docs/WebAssembly.rst

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ WebAssembly-specific options:
4242
.. option:: --export-dynamic
4343

4444
When building an executable, export any non-hidden symbols. By default only
45-
the entry point and any symbols marked with --export/--export-all are
46-
exported.
45+
the entry point and any symbols marked as exports (either via the command line
46+
or via the `export-name` source attribute) are exported.
4747

4848
.. option:: --global-base=<value>
4949

@@ -116,16 +116,18 @@ Imports and Exports
116116
~~~~~~~~~~~~~~~~~~~
117117

118118
When building a shared library any symbols marked as ``visibility=default`` will
119-
be exported. When building an executable, only the entry point and symbols
120-
flagged as ``WASM_SYMBOL_EXPORTED`` are exported by default. In LLVM the
121-
``WASM_SYMBOL_EXPORTED`` flag is applied to any symbol in the ``llvm.used`` list
122-
which corresponds to ``__attribute__((used))`` in C/C++ sources.
119+
be exported.
120+
121+
When building an executable, only the entry point (``_start``) and symbols with
122+
the ``WASM_SYMBOL_EXPORTED`` flag are exported by default. In LLVM the
123+
``WASM_SYMBOL_EXPORTED`` flag is set by the ``wasm-export-name`` attribute which
124+
in turn can be set using ``__attribute__((export_name))`` clang attribute.
123125

124126
In addition, symbols can be exported via the linker command line using
125127
``--export``.
126128

127129
Finally, just like with native ELF linker the ``--export-dynamic`` flag can be
128-
used to export symbol in the executable which are marked as
130+
used to export symbols in the executable which are marked as
129131
``visibility=default``.
130132

131133
Garbage Collection

lld/test/wasm/export-name.ll

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
; RUN: llc -filetype=obj %s -o %t.o
2+
; RUN: wasm-ld -o %t.wasm %t.o
3+
; RUN: obj2yaml %t.wasm | FileCheck %s
4+
5+
target triple = "wasm32-unknown-unknown"
6+
7+
define void @foo() #0 {
8+
ret void
9+
}
10+
11+
define void @_start() {
12+
call void @foo()
13+
ret void
14+
}
15+
16+
attributes #0 = { "wasm-export-name"="bar" }
17+
18+
; CHECK: - Type: EXPORT
19+
; CHECK-NEXT: Exports:
20+
; CHECK-NEXT: - Name: memory
21+
; CHECK-NEXT: Kind: MEMORY
22+
; CHECK-NEXT: Index: 0
23+
; CHECK-NEXT: - Name: bar
24+
; CHECK-NEXT: Kind: FUNCTION
25+
; CHECK-NEXT: Index: 0
26+
; CHECK-NEXT: - Name: _start
27+
; CHECK-NEXT: Kind: FUNCTION
28+
; CHECK-NEXT: Index: 1

lld/wasm/InputChunks.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ class InputFunction : public InputChunk {
130130
void writeTo(uint8_t *sectionStart) const override;
131131
StringRef getName() const override { return function->SymbolName; }
132132
StringRef getDebugName() const override { return function->DebugName; }
133+
StringRef getExportName() const {
134+
return function ? function->ExportName : "";
135+
}
133136
uint32_t getComdat() const override { return function->Comdat; }
134137
uint32_t getFunctionInputOffset() const { return getInputSectionOffset(); }
135138
uint32_t getFunctionCodeOffset() const { return function->CodeOffset; }

lld/wasm/Writer.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,10 @@ void Writer::calculateExports() {
519519
StringRef name = sym->getName();
520520
WasmExport export_;
521521
if (auto *f = dyn_cast<DefinedFunction>(sym)) {
522+
StringRef exportName = f->function->getExportName();
523+
if (!exportName.empty()) {
524+
name = exportName;
525+
}
522526
export_ = {name, WASM_EXTERNAL_FUNCTION, f->getFunctionIndex()};
523527
} else if (auto *g = dyn_cast<DefinedGlobal>(sym)) {
524528
// TODO(sbc): Remove this check once to mutable global proposal is

llvm/include/llvm/BinaryFormat/Wasm.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ struct WasmFunction {
131131
uint32_t CodeSectionOffset;
132132
uint32_t Size;
133133
uint32_t CodeOffset; // start of Locals and Body
134+
StringRef ExportName; // from the "export" section
134135
StringRef SymbolName; // from the "linking" section
135136
StringRef DebugName; // from the "name" section
136137
uint32_t Comdat; // from the "comdat info" section
@@ -179,6 +180,7 @@ struct WasmSymbolInfo {
179180
uint32_t Flags;
180181
StringRef ImportModule; // For undefined symbols the module of the import
181182
StringRef ImportName; // For undefined symbols the name of the import
183+
StringRef ExportName; // For symbols to be exported from the final module
182184
union {
183185
// For function or global symbols, the index in function or global index
184186
// space.

llvm/include/llvm/MC/MCSymbolWasm.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class MCSymbolWasm : public MCSymbol {
2121
mutable bool IsUsedInGOT = false;
2222
Optional<std::string> ImportModule;
2323
Optional<std::string> ImportName;
24+
Optional<std::string> ExportName;
2425
wasm::WasmSignature *Signature = nullptr;
2526
Optional<wasm::WasmGlobalType> GlobalType;
2627
Optional<wasm::WasmEventType> EventType;
@@ -87,6 +88,10 @@ class MCSymbolWasm : public MCSymbol {
8788
}
8889
void setImportName(StringRef Name) { ImportName = Name; }
8990

91+
bool hasExportName() const { return ExportName.hasValue(); }
92+
const StringRef getExportName() const { return ExportName.getValue(); }
93+
void setExportName(StringRef Name) { ExportName = Name; }
94+
9095
void setUsedInGOT() const { IsUsedInGOT = true; }
9196
bool isUsedInGOT() const { return IsUsedInGOT; }
9297

llvm/include/llvm/Object/Wasm.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ class WasmObjectFile : public ObjectFile {
280280
uint32_t StartFunction = -1;
281281
bool HasLinkingSection = false;
282282
bool HasDylinkSection = false;
283+
bool SeenCodeSection = false;
283284
wasm::WasmLinkingData LinkingData;
284285
uint32_t NumImportedGlobals = 0;
285286
uint32_t NumImportedFunctions = 0;

llvm/lib/MC/WasmObjectWriter.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,14 @@ uint64_t WasmObjectWriter::writeObject(MCAssembler &Asm,
13241324
Comdats[C->getName()].emplace_back(
13251325
WasmComdatEntry{wasm::WASM_COMDAT_FUNCTION, Index});
13261326
}
1327+
1328+
if (WS.hasExportName()) {
1329+
wasm::WasmExport Export;
1330+
Export.Name = WS.getExportName();
1331+
Export.Kind = wasm::WASM_EXTERNAL_FUNCTION;
1332+
Export.Index = Index;
1333+
Exports.push_back(Export);
1334+
}
13271335
} else {
13281336
// An import; the index was assigned above.
13291337
Index = WasmIndices.find(&WS)->second;
@@ -1454,6 +1462,8 @@ uint64_t WasmObjectWriter::writeObject(MCAssembler &Asm,
14541462
}
14551463
if (WS.hasImportName())
14561464
Flags |= wasm::WASM_SYMBOL_EXPLICIT_NAME;
1465+
if (WS.hasExportName())
1466+
Flags |= wasm::WASM_SYMBOL_EXPORTED;
14571467

14581468
wasm::WasmSymbolInfo Info;
14591469
Info.Name = WS.getName();

llvm/lib/Object/WasmObjectFile.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ Error WasmObjectFile::parseDylinkSection(ReadContext &Ctx) {
343343

344344
Error WasmObjectFile::parseNameSection(ReadContext &Ctx) {
345345
llvm::DenseSet<uint64_t> Seen;
346-
if (Functions.size() != FunctionTypes.size()) {
346+
if (FunctionTypes.size() && !SeenCodeSection) {
347347
return make_error<GenericBinaryError>("Names must come after code section",
348348
object_error::parse_failed);
349349
}
@@ -389,7 +389,7 @@ Error WasmObjectFile::parseNameSection(ReadContext &Ctx) {
389389

390390
Error WasmObjectFile::parseLinkingSection(ReadContext &Ctx) {
391391
HasLinkingSection = true;
392-
if (Functions.size() != FunctionTypes.size()) {
392+
if (FunctionTypes.size() && !SeenCodeSection) {
393393
return make_error<GenericBinaryError>(
394394
"Linking data must come after code section",
395395
object_error::parse_failed);
@@ -940,6 +940,7 @@ Error WasmObjectFile::parseImportSection(ReadContext &Ctx) {
940940
Error WasmObjectFile::parseFunctionSection(ReadContext &Ctx) {
941941
uint32_t Count = readVaruint32(Ctx);
942942
FunctionTypes.reserve(Count);
943+
Functions.resize(Count);
943944
uint32_t NumTypes = Signatures.size();
944945
while (Count--) {
945946
uint32_t Type = readVaruint32(Ctx);
@@ -1029,9 +1030,11 @@ Error WasmObjectFile::parseExportSection(ReadContext &Ctx) {
10291030
Ex.Index = readVaruint32(Ctx);
10301031
switch (Ex.Kind) {
10311032
case wasm::WASM_EXTERNAL_FUNCTION:
1032-
if (!isValidFunctionIndex(Ex.Index))
1033+
1034+
if (!isDefinedFunctionIndex(Ex.Index))
10331035
return make_error<GenericBinaryError>("Invalid function export",
10341036
object_error::parse_failed);
1037+
getDefinedFunction(Ex.Index).ExportName = Ex.Name;
10351038
break;
10361039
case wasm::WASM_EXTERNAL_GLOBAL:
10371040
if (!isValidGlobalIndex(Ex.Index))
@@ -1132,21 +1135,22 @@ Error WasmObjectFile::parseStartSection(ReadContext &Ctx) {
11321135
}
11331136

11341137
Error WasmObjectFile::parseCodeSection(ReadContext &Ctx) {
1138+
SeenCodeSection = true;
11351139
CodeSection = Sections.size();
11361140
uint32_t FunctionCount = readVaruint32(Ctx);
11371141
if (FunctionCount != FunctionTypes.size()) {
11381142
return make_error<GenericBinaryError>("Invalid function count",
11391143
object_error::parse_failed);
11401144
}
11411145

1142-
while (FunctionCount--) {
1143-
wasm::WasmFunction Function;
1146+
for (uint32_t i = 0; i < FunctionCount; i++) {
1147+
wasm::WasmFunction& Function = Functions[i];
11441148
const uint8_t *FunctionStart = Ctx.Ptr;
11451149
uint32_t Size = readVaruint32(Ctx);
11461150
const uint8_t *FunctionEnd = Ctx.Ptr + Size;
11471151

11481152
Function.CodeOffset = Ctx.Ptr - FunctionStart;
1149-
Function.Index = NumImportedFunctions + Functions.size();
1153+
Function.Index = NumImportedFunctions + i;
11501154
Function.CodeSectionOffset = FunctionStart - Ctx.Start;
11511155
Function.Size = FunctionEnd - FunctionStart;
11521156

@@ -1165,7 +1169,6 @@ Error WasmObjectFile::parseCodeSection(ReadContext &Ctx) {
11651169
Function.Comdat = UINT32_MAX;
11661170
Ctx.Ptr += BodySize;
11671171
assert(Ctx.Ptr == FunctionEnd);
1168-
Functions.push_back(Function);
11691172
}
11701173
if (Ctx.Ptr != Ctx.End)
11711174
return make_error<GenericBinaryError>("Code section ended prematurely",

llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,18 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
712712
return expect(AsmToken::EndOfStatement, "EOL");
713713
}
714714

715+
if (DirectiveID.getString() == ".export_name") {
716+
auto SymName = expectIdent();
717+
if (SymName.empty())
718+
return true;
719+
if (expect(AsmToken::Comma, ","))
720+
return true;
721+
auto ExportName = expectIdent();
722+
auto WasmSym = cast<MCSymbolWasm>(Ctx.getOrCreateSymbol(SymName));
723+
WasmSym->setExportName(ExportName);
724+
TOut.emitExportName(WasmSym, ExportName);
725+
}
726+
715727
if (DirectiveID.getString() == ".import_module") {
716728
auto SymName = expectIdent();
717729
if (SymName.empty())

llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ void WebAssemblyTargetAsmStreamer::emitImportName(const MCSymbolWasm *Sym,
9494
<< ImportName << '\n';
9595
}
9696

97+
void WebAssemblyTargetAsmStreamer::emitExportName(const MCSymbolWasm *Sym,
98+
StringRef ExportName) {
99+
OS << "\t.export_name\t" << Sym->getName() << ", "
100+
<< ExportName << '\n';
101+
}
102+
97103
void WebAssemblyTargetAsmStreamer::emitIndIdx(const MCExpr *Value) {
98104
OS << "\t.indidx \t" << *Value << '\n';
99105
}

0 commit comments

Comments
 (0)