Skip to content

[wasm] Add @_expose(wasm) attribute for top-level functions #68524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,16 @@ in the generated C++ binding header.
The optional "cxxName" string will be used as the name of
the generated C++ declaration.

### `_expose(wasm[, <"wasmExportName">])`

Indicates that a particular function declaration should be
exported from the linked WebAssembly.

The optional "wasmExportName" string will be used as the
the export name.

It's the equivalent of clang's `__attribute__((export_name))`.

## `@_fixed_layout`

Same as `@frozen` but also works for classes.
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ SIMPLE_DECL_ATTR(_alwaysEmitConformanceMetadata, AlwaysEmitConformanceMetadata,
OnProtocol | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
132)
DECL_ATTR(_expose, Expose,
OnFunc | OnNominalType | OnVar | OnConstructor | LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
OnFunc | OnNominalType | OnVar | AllowMultipleAttributes | OnConstructor | LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
133)
SIMPLE_DECL_ATTR(_spiOnly, SPIOnly,
OnImport | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
Expand Down
20 changes: 16 additions & 4 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ class DeclAttribute : public AttributeBase {
kind : 1
);

SWIFT_INLINE_BITFIELD(ExposeAttr, DeclAttribute, NumExposureKindBits,
kind : NumExposureKindBits
);

SWIFT_INLINE_BITFIELD(SynthesizedProtocolAttr, DeclAttribute, 1,
isUnchecked : 1
);
Expand Down Expand Up @@ -2309,15 +2313,23 @@ class BackDeployedAttr : public DeclAttribute {
/// header used by C/C++ to interoperate with Swift.
class ExposeAttr : public DeclAttribute {
public:
ExposeAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit)
: DeclAttribute(DAK_Expose, AtLoc, Range, Implicit), Name(Name) {}
ExposeAttr(StringRef Name, SourceLoc AtLoc, SourceRange Range,
ExposureKind Kind, bool Implicit)
: DeclAttribute(DAK_Expose, AtLoc, Range, Implicit), Name(Name) {
Bits.ExposeAttr.kind = static_cast<unsigned>(Kind);
}

ExposeAttr(StringRef Name, bool Implicit)
: ExposeAttr(Name, SourceLoc(), SourceRange(), Implicit) {}
ExposeAttr(StringRef Name, ExposureKind Kind, bool Implicit)
: ExposeAttr(Name, SourceLoc(), SourceRange(), Kind, Implicit) {}

/// The exposed declaration name.
const StringRef Name;

/// Returns the kind of exposure.
ExposureKind getExposureKind() const {
return static_cast<ExposureKind>(Bits.ExposeAttr.kind);
}

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DAK_Expose;
}
Expand Down
9 changes: 9 additions & 0 deletions include/swift/AST/AttrKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ enum class EffectsKind : uint8_t {
enum : unsigned { NumEffectsKindBits =
countBitsUsed(static_cast<unsigned>(EffectsKind::Last_EffectsKind)) };

/// This enum represents the possible values of the @_expose attribute.
enum class ExposureKind: uint8_t {
Cxx,
Wasm,
Last_ExposureKind = Wasm
};

enum : unsigned { NumExposureKindBits =
countBitsUsed(static_cast<unsigned>(ExposureKind::Last_ExposureKind)) };

enum DeclAttrKind : unsigned {
#define DECL_ATTR(_, NAME, ...) DAK_##NAME,
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,9 @@ ERROR(section_not_at_top_level,none,
ERROR(section_empty_name,none,
"@_section section name cannot be empty", ())

ERROR(expose_wasm_not_at_top_level_func,none,
"@_expose attribute with 'wasm' can only be applied to global functions", ())

ERROR(expose_only_non_other_attr,none,
"@_expose attribute cannot be applied to an '%0' declaration", (StringRef))
ERROR(expose_inside_unexposed_decl,none,
Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ class SILFunction
/// Name of a section if @_section attribute was used, otherwise empty.
StringRef Section;

/// Name of a Wasm export if @_expose(wasm) attribute was used, otherwise
/// empty.
StringRef WasmExportName;

/// Has value if there's a profile for this function
/// Contains Function Entry Count
ProfileCounter EntryCount;
Expand Down Expand Up @@ -1264,6 +1268,10 @@ class SILFunction
StringRef section() const { return Section; }
void setSection(StringRef value) { Section = value; }

/// Return Wasm export name if @_expose(wasm) was used, otherwise empty
StringRef wasmExportName() const { return WasmExportName; }
void setWasmExportName(StringRef value) { WasmExportName = value; }

/// Returns true if this function belongs to a declaration that returns
/// an opaque result type with one or more availability conditions that are
/// allowed to produce a different underlying type at runtime.
Expand Down
14 changes: 11 additions & 3 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1122,14 +1122,22 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
Printer << "@_cdecl(\"" << cast<CDeclAttr>(this)->Name << "\")";
break;

case DAK_Expose:
case DAK_Expose: {
Printer.printAttrName("@_expose");
Printer << "(Cxx";
auto Attr = cast<ExposeAttr>(this);
switch (Attr->getExposureKind()) {
case ExposureKind::Wasm:
Printer << "(wasm";
break;
case ExposureKind::Cxx:
Printer << "(Cxx";
break;
}
if (!cast<ExposeAttr>(this)->Name.empty())
Printer << ", \"" << cast<ExposeAttr>(this)->Name << "\"";
Printer << ")";
break;

}
case DAK_Section:
Printer.printAttrName("@_section");
Printer << "(\"" << cast<SectionAttr>(this)->Name << "\")";
Expand Down
6 changes: 3 additions & 3 deletions lib/AST/SwiftNameTranslation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ swift::cxx_translation::getNameForCxx(const ValueDecl *VD,
CustomNamesOnly_t customNamesOnly) {
ASTContext& ctx = VD->getASTContext();

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

if (customNamesOnly)
Expand Down
5 changes: 5 additions & 0 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3554,6 +3554,11 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(
addUsedGlobal(fn);
if (!f->section().empty())
fn->setSection(f->section());
if (!f->wasmExportName().empty()) {
llvm::AttrBuilder attrBuilder(getLLVMContext());
attrBuilder.addAttribute("wasm-export-name", f->wasmExportName());
fn->addFnAttrs(attrBuilder);
}

// If `hasCReferences` is true, then the function is either marked with
// @_silgen_name OR @_cdecl. If it is the latter, it must have a definition
Expand Down
31 changes: 26 additions & 5 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3015,12 +3015,24 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
}

bool ParseSymbolName = true;

ExposureKind ExpKind;
if (DK == DAK_Expose) {
if (Tok.isNot(tok::identifier) || Tok.getText() != "Cxx") {
auto diagnoseExpectOption = [&]() {
diagnose(Tok.getLoc(), diag::attr_expected_option_such_as, AttrName,
"Cxx");
if (Tok.isNot(tok::identifier))
return makeParserSuccess();
ParseSymbolName = false;
};
if (Tok.isNot(tok::identifier)) {
diagnoseExpectOption();
return makeParserSuccess();
}
if (Tok.getText() == "Cxx") {
ExpKind = ExposureKind::Cxx;
} else if (Tok.getText() == "wasm") {
ExpKind = ExposureKind::Wasm;
} else {
diagnoseExpectOption();
DiscardAttribute = true;
}
consumeToken(tok::identifier);
Expand Down Expand Up @@ -3076,10 +3088,19 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
else if (DK == DAK_CDecl)
Attributes.add(new (Context) CDeclAttr(AsmName.value(), AtLoc,
AttrRange, /*Implicit=*/false));
else if (DK == DAK_Expose)
else if (DK == DAK_Expose) {
for (auto *EA : Attributes.getAttributes<ExposeAttr>()) {
// A single declaration cannot have two @_exported attributes with
// the same exposure kind.
if (EA->getExposureKind() == ExpKind) {
diagnose(Loc, diag::duplicate_attribute, false);
break;
}
}
Attributes.add(new (Context) ExposeAttr(
AsmName ? AsmName.value() : StringRef(""), AtLoc, AttrRange,
/*Implicit=*/false));
ExpKind, /*Implicit=*/false));
}
else
llvm_unreachable("out of sync with switch");
}
Expand Down
6 changes: 4 additions & 2 deletions lib/PrintAsClang/DeclAndTypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2825,8 +2825,10 @@ static bool hasExposeAttr(const ValueDecl *VD, bool isExtension = false) {
// Clang decls don't need to be explicitly exposed.
if (VD->hasClangNode())
return true;
if (VD->getAttrs().hasAttribute<ExposeAttr>())
return true;
for (auto *EA : VD->getAttrs().getAttributes<ExposeAttr>()) {
if (EA->getExposureKind() == ExposureKind::Cxx)
return true;
}
if (const auto *NMT = dyn_cast<NominalTypeDecl>(VD->getDeclContext()))
return hasExposeAttr(NMT);
if (const auto *ED = dyn_cast<ExtensionDecl>(VD->getDeclContext())) {
Expand Down
18 changes: 18 additions & 0 deletions lib/SIL/IR/SILFunctionBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,24 @@ void SILFunctionBuilder::addFunctionAttributes(
if (Attrs.hasAttribute<SILGenNameAttr>() || Attrs.hasAttribute<CDeclAttr>())
F->setHasCReferences(true);

for (auto *EA : Attrs.getAttributes<ExposeAttr>()) {
bool shouldExportDecl = true;
if (Attrs.hasAttribute<CDeclAttr>()) {
// If the function is marked with @cdecl, expose only C compatible
// thunk function.
shouldExportDecl = constant.isNativeToForeignThunk();
}
if (EA->getExposureKind() == ExposureKind::Wasm && shouldExportDecl) {
// A wasm-level exported function must be retained if it appears in a
// compilation unit.
F->setMarkedAsUsed(true);
if (EA->Name.empty())
F->setWasmExportName(F->getName());
else
F->setWasmExportName(EA->Name);
}
}

if (Attrs.hasAttribute<UsedAttr>())
F->setMarkedAsUsed(true);

Expand Down
71 changes: 41 additions & 30 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2026,36 +2026,47 @@ void AttributeChecker::visitCDeclAttr(CDeclAttr *attr) {
}

void AttributeChecker::visitExposeAttr(ExposeAttr *attr) {
auto *VD = cast<ValueDecl>(D);
// Expose cannot be mixed with '@_cdecl' declarations.
if (VD->getAttrs().hasAttribute<CDeclAttr>())
diagnose(attr->getLocation(), diag::expose_only_non_other_attr, "@_cdecl");

// Nested exposed declarations are expected to be inside
// of other exposed declarations.
bool hasExpose = true;
const ValueDecl *decl = VD;
while (const NominalTypeDecl *NMT =
dyn_cast<NominalTypeDecl>(decl->getDeclContext())) {
decl = NMT;
hasExpose = NMT->getAttrs().hasAttribute<ExposeAttr>();
}
if (!hasExpose) {
diagnose(attr->getLocation(), diag::expose_inside_unexposed_decl, decl);
}

// Verify that the declaration is exposable.
auto repr = cxx_translation::getDeclRepresentation(VD);
if (repr.isUnsupported())
diagnose(attr->getLocation(),
cxx_translation::diagnoseRepresenationError(*repr.error, VD));

// Verify that the name mentioned in the expose
// attribute matches the supported name pattern.
if (!attr->Name.empty()) {
if (isa<ConstructorDecl>(VD) && !attr->Name.startswith("init"))
diagnose(attr->getLocation(), diag::expose_invalid_name_pattern_init,
attr->Name);
switch (attr->getExposureKind()) {
case ExposureKind::Wasm: {
// Only top-level func decls are currently supported.
if (!isa<FuncDecl>(D) || D->getDeclContext()->isTypeContext())
diagnose(attr->getLocation(), diag::expose_wasm_not_at_top_level_func);
break;
}
case ExposureKind::Cxx: {
auto *VD = cast<ValueDecl>(D);
// Expose cannot be mixed with '@_cdecl' declarations.
if (VD->getAttrs().hasAttribute<CDeclAttr>())
diagnose(attr->getLocation(), diag::expose_only_non_other_attr, "@_cdecl");

// Nested exposed declarations are expected to be inside
// of other exposed declarations.
bool hasExpose = true;
const ValueDecl *decl = VD;
while (const NominalTypeDecl *NMT =
dyn_cast<NominalTypeDecl>(decl->getDeclContext())) {
decl = NMT;
hasExpose = NMT->getAttrs().hasAttribute<ExposeAttr>();
}
if (!hasExpose) {
diagnose(attr->getLocation(), diag::expose_inside_unexposed_decl, decl);
}

// Verify that the declaration is exposable.
auto repr = cxx_translation::getDeclRepresentation(VD);
if (repr.isUnsupported())
diagnose(attr->getLocation(),
cxx_translation::diagnoseRepresenationError(*repr.error, VD));

// Verify that the name mentioned in the expose
// attribute matches the supported name pattern.
if (!attr->Name.empty()) {
if (isa<ConstructorDecl>(VD) && !attr->Name.startswith("init"))
diagnose(attr->getLocation(), diag::expose_invalid_name_pattern_init,
attr->Name);
}
break;
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5795,10 +5795,11 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
}

case decls_block::Expose_DECL_ATTR: {
unsigned kind;
bool isImplicit;
serialization::decls_block::ExposeDeclAttrLayout::readRecord(
scratch, isImplicit);
Attr = new (ctx) ExposeAttr(blobData, isImplicit);
scratch, kind, isImplicit);
Attr = new (ctx) ExposeAttr(blobData, (ExposureKind)kind, isImplicit);
break;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 807; // keypath accessor
const uint16_t SWIFTMODULE_VERSION_MINOR = 808; // @_expose(wasm)

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down Expand Up @@ -2317,6 +2317,7 @@ namespace decls_block {
>;

using ExposeDeclAttrLayout = BCRecordLayout<Expose_DECL_ATTR,
BCFixed<1>, // exposure kind
BCFixed<1>, // implicit flag
BCBlob // declaration name
>;
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3092,7 +3092,7 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
auto *theAttr = cast<ExposeAttr>(DA);
auto abbrCode = S.DeclTypeAbbrCodes[ExposeDeclAttrLayout::Code];
ExposeDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
theAttr->isImplicit(), theAttr->Name);
(unsigned)theAttr->getExposureKind(), theAttr->isImplicit(), theAttr->Name);
return;
}

Expand Down
Loading