Skip to content

Commit 8cfcc24

Browse files
Merge pull request #68524 from kateinoigakukun/katei/expose-wasm-sym
[wasm] Add `@_expose(wasm)` attribute for top-level functions
2 parents 631ad05 + ac440c3 commit 8cfcc24

File tree

18 files changed

+243
-52
lines changed

18 files changed

+243
-52
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,16 @@ in the generated C++ binding header.
427427
The optional "cxxName" string will be used as the name of
428428
the generated C++ declaration.
429429

430+
### `_expose(wasm[, <"wasmExportName">])`
431+
432+
Indicates that a particular function declaration should be
433+
exported from the linked WebAssembly.
434+
435+
The optional "wasmExportName" string will be used as the
436+
the export name.
437+
438+
It's the equivalent of clang's `__attribute__((export_name))`.
439+
430440
## `@_fixed_layout`
431441

432442
Same as `@frozen` but also works for classes.

include/swift/AST/Attr.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ SIMPLE_DECL_ATTR(_alwaysEmitConformanceMetadata, AlwaysEmitConformanceMetadata,
398398
OnProtocol | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
399399
132)
400400
DECL_ATTR(_expose, Expose,
401-
OnFunc | OnNominalType | OnVar | OnConstructor | LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
401+
OnFunc | OnNominalType | OnVar | AllowMultipleAttributes | OnConstructor | LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
402402
133)
403403
SIMPLE_DECL_ATTR(_spiOnly, SPIOnly,
404404
OnImport | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,

include/swift/AST/Attr.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ class DeclAttribute : public AttributeBase {
172172
kind : 1
173173
);
174174

175+
SWIFT_INLINE_BITFIELD(ExposeAttr, DeclAttribute, NumExposureKindBits,
176+
kind : NumExposureKindBits
177+
);
178+
175179
SWIFT_INLINE_BITFIELD(SynthesizedProtocolAttr, DeclAttribute, 1,
176180
isUnchecked : 1
177181
);
@@ -2309,15 +2313,23 @@ class BackDeployedAttr : public DeclAttribute {
23092313
/// header used by C/C++ to interoperate with Swift.
23102314
class ExposeAttr : public DeclAttribute {
23112315
public:
2312-
ExposeAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit)
2313-
: DeclAttribute(DAK_Expose, AtLoc, Range, Implicit), Name(Name) {}
2316+
ExposeAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range,
2317+
ExposureKind Kind, bool Implicit)
2318+
: DeclAttribute(DAK_Expose, AtLoc, Range, Implicit), Name(Name) {
2319+
Bits.ExposeAttr.kind = static_cast<unsigned>(Kind);
2320+
}
23142321

2315-
ExposeAttr(StringRef Name, bool Implicit)
2316-
: ExposeAttr(Name, SourceLoc(), SourceRange(), Implicit) {}
2322+
ExposeAttr(StringRef Name, ExposureKind Kind, bool Implicit)
2323+
: ExposeAttr(Name, SourceLoc(), SourceRange(), Kind, Implicit) {}
23172324

23182325
/// The exposed declaration name.
23192326
const StringRef Name;
23202327

2328+
/// Returns the kind of exposure.
2329+
ExposureKind getExposureKind() const {
2330+
return static_cast<ExposureKind>(Bits.ExposeAttr.kind);
2331+
}
2332+
23212333
static bool classof(const DeclAttribute *DA) {
23222334
return DA->getKind() == DAK_Expose;
23232335
}

include/swift/AST/AttrKind.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ enum class EffectsKind : uint8_t {
102102
enum : unsigned { NumEffectsKindBits =
103103
countBitsUsed(static_cast<unsigned>(EffectsKind::Last_EffectsKind)) };
104104

105+
/// This enum represents the possible values of the @_expose attribute.
106+
enum class ExposureKind: uint8_t {
107+
Cxx,
108+
Wasm,
109+
Last_ExposureKind = Wasm
110+
};
111+
112+
enum : unsigned { NumExposureKindBits =
113+
countBitsUsed(static_cast<unsigned>(ExposureKind::Last_ExposureKind)) };
105114

106115
enum DeclAttrKind : unsigned {
107116
#define DECL_ATTR(_, NAME, ...) DAK_##NAME,

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,9 @@ ERROR(section_not_at_top_level,none,
18381838
ERROR(section_empty_name,none,
18391839
"@_section section name cannot be empty", ())
18401840

1841+
ERROR(expose_wasm_not_at_top_level_func,none,
1842+
"@_expose attribute with 'wasm' can only be applied to global functions", ())
1843+
18411844
ERROR(expose_only_non_other_attr,none,
18421845
"@_expose attribute cannot be applied to an '%0' declaration", (StringRef))
18431846
ERROR(expose_inside_unexposed_decl,none,

include/swift/SIL/SILFunction.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ class SILFunction
299299
/// Name of a section if @_section attribute was used, otherwise empty.
300300
StringRef Section;
301301

302+
/// Name of a Wasm export if @_expose(wasm) attribute was used, otherwise
303+
/// empty.
304+
StringRef WasmExportName;
305+
302306
/// Has value if there's a profile for this function
303307
/// Contains Function Entry Count
304308
ProfileCounter EntryCount;
@@ -1270,6 +1274,10 @@ class SILFunction
12701274
StringRef section() const { return Section; }
12711275
void setSection(StringRef value) { Section = value; }
12721276

1277+
/// Return Wasm export name if @_expose(wasm) was used, otherwise empty
1278+
StringRef wasmExportName() const { return WasmExportName; }
1279+
void setWasmExportName(StringRef value) { WasmExportName = value; }
1280+
12731281
/// Returns true if this function belongs to a declaration that returns
12741282
/// an opaque result type with one or more availability conditions that are
12751283
/// allowed to produce a different underlying type at runtime.

lib/AST/Attr.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,14 +1122,22 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
11221122
Printer << "@_cdecl(\"" << cast<CDeclAttr>(this)->Name << "\")";
11231123
break;
11241124

1125-
case DAK_Expose:
1125+
case DAK_Expose: {
11261126
Printer.printAttrName("@_expose");
1127-
Printer << "(Cxx";
1127+
auto Attr = cast<ExposeAttr>(this);
1128+
switch (Attr->getExposureKind()) {
1129+
case ExposureKind::Wasm:
1130+
Printer << "(wasm";
1131+
break;
1132+
case ExposureKind::Cxx:
1133+
Printer << "(Cxx";
1134+
break;
1135+
}
11281136
if (!cast<ExposeAttr>(this)->Name.empty())
11291137
Printer << ", \"" << cast<ExposeAttr>(this)->Name << "\"";
11301138
Printer << ")";
11311139
break;
1132-
1140+
}
11331141
case DAK_Section:
11341142
Printer.printAttrName("@_section");
11351143
Printer << "(\"" << cast<SectionAttr>(this)->Name << "\")";

lib/AST/SwiftNameTranslation.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ swift::cxx_translation::getNameForCxx(const ValueDecl *VD,
158158
CustomNamesOnly_t customNamesOnly) {
159159
ASTContext& ctx = VD->getASTContext();
160160

161-
if (const auto *Expose = VD->getAttrs().getAttribute<ExposeAttr>()) {
162-
if (!Expose->Name.empty())
163-
return Expose->Name;
161+
for (auto *EA : VD->getAttrs().getAttributes<ExposeAttr>()) {
162+
if (EA->getExposureKind() == ExposureKind::Cxx && !EA->Name.empty())
163+
return EA->Name;
164164
}
165165

166166
if (customNamesOnly)

lib/IRGen/GenDecl.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3561,6 +3561,11 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(
35613561
addUsedGlobal(fn);
35623562
if (!f->section().empty())
35633563
fn->setSection(f->section());
3564+
if (!f->wasmExportName().empty()) {
3565+
llvm::AttrBuilder attrBuilder(getLLVMContext());
3566+
attrBuilder.addAttribute("wasm-export-name", f->wasmExportName());
3567+
fn->addFnAttrs(attrBuilder);
3568+
}
35643569

35653570
// If `hasCReferences` is true, then the function is either marked with
35663571
// @_silgen_name OR @_cdecl. If it is the latter, it must have a definition

lib/Parse/ParseDecl.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3015,12 +3015,24 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
30153015
}
30163016

30173017
bool ParseSymbolName = true;
3018+
3019+
ExposureKind ExpKind;
30183020
if (DK == DAK_Expose) {
3019-
if (Tok.isNot(tok::identifier) || Tok.getText() != "Cxx") {
3021+
auto diagnoseExpectOption = [&]() {
30203022
diagnose(Tok.getLoc(), diag::attr_expected_option_such_as, AttrName,
30213023
"Cxx");
3022-
if (Tok.isNot(tok::identifier))
3023-
return makeParserSuccess();
3024+
ParseSymbolName = false;
3025+
};
3026+
if (Tok.isNot(tok::identifier)) {
3027+
diagnoseExpectOption();
3028+
return makeParserSuccess();
3029+
}
3030+
if (Tok.getText() == "Cxx") {
3031+
ExpKind = ExposureKind::Cxx;
3032+
} else if (Tok.getText() == "wasm") {
3033+
ExpKind = ExposureKind::Wasm;
3034+
} else {
3035+
diagnoseExpectOption();
30243036
DiscardAttribute = true;
30253037
}
30263038
consumeToken(tok::identifier);
@@ -3076,10 +3088,19 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
30763088
else if (DK == DAK_CDecl)
30773089
Attributes.add(new (Context) CDeclAttr(AsmName.value(), AtLoc,
30783090
AttrRange, /*Implicit=*/false));
3079-
else if (DK == DAK_Expose)
3091+
else if (DK == DAK_Expose) {
3092+
for (auto *EA : Attributes.getAttributes<ExposeAttr>()) {
3093+
// A single declaration cannot have two @_exported attributes with
3094+
// the same exposure kind.
3095+
if (EA->getExposureKind() == ExpKind) {
3096+
diagnose(Loc, diag::duplicate_attribute, false);
3097+
break;
3098+
}
3099+
}
30803100
Attributes.add(new (Context) ExposeAttr(
30813101
AsmName ? AsmName.value() : StringRef(""), AtLoc, AttrRange,
3082-
/*Implicit=*/false));
3102+
ExpKind, /*Implicit=*/false));
3103+
}
30833104
else
30843105
llvm_unreachable("out of sync with switch");
30853106
}

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2825,8 +2825,10 @@ static bool hasExposeAttr(const ValueDecl *VD, bool isExtension = false) {
28252825
// Clang decls don't need to be explicitly exposed.
28262826
if (VD->hasClangNode())
28272827
return true;
2828-
if (VD->getAttrs().hasAttribute<ExposeAttr>())
2829-
return true;
2828+
for (auto *EA : VD->getAttrs().getAttributes<ExposeAttr>()) {
2829+
if (EA->getExposureKind() == ExposureKind::Cxx)
2830+
return true;
2831+
}
28302832
if (const auto *NMT = dyn_cast<NominalTypeDecl>(VD->getDeclContext()))
28312833
return hasExposeAttr(NMT);
28322834
if (const auto *ED = dyn_cast<ExtensionDecl>(VD->getDeclContext())) {

lib/SIL/IR/SILFunctionBuilder.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,24 @@ void SILFunctionBuilder::addFunctionAttributes(
218218
if (Attrs.hasAttribute<SILGenNameAttr>() || Attrs.hasAttribute<CDeclAttr>())
219219
F->setHasCReferences(true);
220220

221+
for (auto *EA : Attrs.getAttributes<ExposeAttr>()) {
222+
bool shouldExportDecl = true;
223+
if (Attrs.hasAttribute<CDeclAttr>()) {
224+
// If the function is marked with @cdecl, expose only C compatible
225+
// thunk function.
226+
shouldExportDecl = constant.isNativeToForeignThunk();
227+
}
228+
if (EA->getExposureKind() == ExposureKind::Wasm && shouldExportDecl) {
229+
// A wasm-level exported function must be retained if it appears in a
230+
// compilation unit.
231+
F->setMarkedAsUsed(true);
232+
if (EA->Name.empty())
233+
F->setWasmExportName(F->getName());
234+
else
235+
F->setWasmExportName(EA->Name);
236+
}
237+
}
238+
221239
if (Attrs.hasAttribute<UsedAttr>())
222240
F->setMarkedAsUsed(true);
223241

lib/Sema/TypeCheckAttr.cpp

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,36 +2026,47 @@ void AttributeChecker::visitCDeclAttr(CDeclAttr *attr) {
20262026
}
20272027

20282028
void AttributeChecker::visitExposeAttr(ExposeAttr *attr) {
2029-
auto *VD = cast<ValueDecl>(D);
2030-
// Expose cannot be mixed with '@_cdecl' declarations.
2031-
if (VD->getAttrs().hasAttribute<CDeclAttr>())
2032-
diagnose(attr->getLocation(), diag::expose_only_non_other_attr, "@_cdecl");
2033-
2034-
// Nested exposed declarations are expected to be inside
2035-
// of other exposed declarations.
2036-
bool hasExpose = true;
2037-
const ValueDecl *decl = VD;
2038-
while (const NominalTypeDecl *NMT =
2039-
dyn_cast<NominalTypeDecl>(decl->getDeclContext())) {
2040-
decl = NMT;
2041-
hasExpose = NMT->getAttrs().hasAttribute<ExposeAttr>();
2042-
}
2043-
if (!hasExpose) {
2044-
diagnose(attr->getLocation(), diag::expose_inside_unexposed_decl, decl);
2045-
}
2046-
2047-
// Verify that the declaration is exposable.
2048-
auto repr = cxx_translation::getDeclRepresentation(VD);
2049-
if (repr.isUnsupported())
2050-
diagnose(attr->getLocation(),
2051-
cxx_translation::diagnoseRepresenationError(*repr.error, VD));
2052-
2053-
// Verify that the name mentioned in the expose
2054-
// attribute matches the supported name pattern.
2055-
if (!attr->Name.empty()) {
2056-
if (isa<ConstructorDecl>(VD) && !attr->Name.startswith("init"))
2057-
diagnose(attr->getLocation(), diag::expose_invalid_name_pattern_init,
2058-
attr->Name);
2029+
switch (attr->getExposureKind()) {
2030+
case ExposureKind::Wasm: {
2031+
// Only top-level func decls are currently supported.
2032+
if (!isa<FuncDecl>(D) || D->getDeclContext()->isTypeContext())
2033+
diagnose(attr->getLocation(), diag::expose_wasm_not_at_top_level_func);
2034+
break;
2035+
}
2036+
case ExposureKind::Cxx: {
2037+
auto *VD = cast<ValueDecl>(D);
2038+
// Expose cannot be mixed with '@_cdecl' declarations.
2039+
if (VD->getAttrs().hasAttribute<CDeclAttr>())
2040+
diagnose(attr->getLocation(), diag::expose_only_non_other_attr, "@_cdecl");
2041+
2042+
// Nested exposed declarations are expected to be inside
2043+
// of other exposed declarations.
2044+
bool hasExpose = true;
2045+
const ValueDecl *decl = VD;
2046+
while (const NominalTypeDecl *NMT =
2047+
dyn_cast<NominalTypeDecl>(decl->getDeclContext())) {
2048+
decl = NMT;
2049+
hasExpose = NMT->getAttrs().hasAttribute<ExposeAttr>();
2050+
}
2051+
if (!hasExpose) {
2052+
diagnose(attr->getLocation(), diag::expose_inside_unexposed_decl, decl);
2053+
}
2054+
2055+
// Verify that the declaration is exposable.
2056+
auto repr = cxx_translation::getDeclRepresentation(VD);
2057+
if (repr.isUnsupported())
2058+
diagnose(attr->getLocation(),
2059+
cxx_translation::diagnoseRepresenationError(*repr.error, VD));
2060+
2061+
// Verify that the name mentioned in the expose
2062+
// attribute matches the supported name pattern.
2063+
if (!attr->Name.empty()) {
2064+
if (isa<ConstructorDecl>(VD) && !attr->Name.startswith("init"))
2065+
diagnose(attr->getLocation(), diag::expose_invalid_name_pattern_init,
2066+
attr->Name);
2067+
}
2068+
break;
2069+
}
20592070
}
20602071
}
20612072

lib/Serialization/Deserialization.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5795,10 +5795,11 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
57955795
}
57965796

57975797
case decls_block::Expose_DECL_ATTR: {
5798+
unsigned kind;
57985799
bool isImplicit;
57995800
serialization::decls_block::ExposeDeclAttrLayout::readRecord(
5800-
scratch, isImplicit);
5801-
Attr = new (ctx) ExposeAttr(blobData, isImplicit);
5801+
scratch, kind, isImplicit);
5802+
Attr = new (ctx) ExposeAttr(blobData, (ExposureKind)kind, isImplicit);
58025803
break;
58035804
}
58045805

lib/Serialization/ModuleFormat.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 807; // keypath accessor
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 808; // @_expose(wasm)
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///
@@ -2317,6 +2317,7 @@ namespace decls_block {
23172317
>;
23182318

23192319
using ExposeDeclAttrLayout = BCRecordLayout<Expose_DECL_ATTR,
2320+
BCFixed<1>, // exposure kind
23202321
BCFixed<1>, // implicit flag
23212322
BCBlob // declaration name
23222323
>;

lib/Serialization/Serialization.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3092,7 +3092,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
30923092
auto *theAttr = cast<ExposeAttr>(DA);
30933093
auto abbrCode = S.DeclTypeAbbrCodes[ExposeDeclAttrLayout::Code];
30943094
ExposeDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
3095-
theAttr->isImplicit(), theAttr->Name);
3095+
(unsigned)theAttr->getExposureKind(), theAttr->isImplicit(), theAttr->Name);
30963096
return;
30973097
}
30983098

0 commit comments

Comments
 (0)