Skip to content

[wasm] Add @_extern(wasm) attribute support #69107

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 1 commit into from
Oct 12, 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
12 changes: 12 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,18 @@ the export name.

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

## `@_extern(<language>)`

Indicates that a particular declaration should be imported
from the external environment.

### `@_extern(wasm, module: <"moduleName">, name: <"fieldName">)`

Indicates that a particular declaration should be imported
through WebAssembly's import interface.

It's the equivalent of clang's `__attribute__((import_module("module"), import_name("field")))`.

## `@_fixed_layout`

Same as `@frozen` but also works for classes.
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,9 @@ DECL_ATTR(_section, Section,
DECL_ATTR(_rawLayout, RawLayout,
OnStruct | UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove,
146)
DECL_ATTR(_extern, Extern,
OnFunc | ABIStableToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove,
147)
CONTEXTUAL_SIMPLE_DECL_ATTR(final, Final,
OnClass | OnFunc | OnAccessor | OnVar | OnSubscript | DeclModifier | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove,
2)
Expand Down
22 changes: 22 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2335,6 +2335,28 @@ class ExposeAttr : public DeclAttribute {
}
};

/// Define the `@_extern` attribute, used to import external declarations in
/// the specified way to interoperate with Swift.
class ExternAttr : public DeclAttribute {
public:
ExternAttr(StringRef ModuleName, StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit)
: DeclAttribute(DAK_Extern, AtLoc, Range, Implicit),
ModuleName(ModuleName), Name(Name) {}

ExternAttr(StringRef ModuleName, StringRef Name, bool Implicit)
: ExternAttr(ModuleName, Name, SourceLoc(), SourceRange(), Implicit) {}

/// The module name to import the named declaration in it
const StringRef ModuleName;

/// The declaration name to import
const StringRef Name;

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DAK_Extern;
}
};

/// The `@_documentation(...)` attribute, used to override a symbol's visibility
/// in symbol graphs, and/or adding arbitrary metadata to it.
class DocumentationAttr: public DeclAttribute {
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsCommon.def
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ ERROR(require_const_initializer_for_const,none,
ERROR(func_decl_without_brace,PointsToFirstBadToken,
"expected '{' in body of function declaration", ())

ERROR(func_decl_no_body_expected,PointsToFirstBadToken,
"unexpected body of function declaration", ())

NOTE(convert_let_to_var,none,
"change 'let' to 'var' to make it mutable", ())

Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1867,6 +1867,8 @@ ERROR(attr_rawlayout_expected_integer_count,none,
ERROR(attr_rawlayout_expected_params,none,
"expected %1 argument after %0 argument in @_rawLayout attribute", (StringRef, StringRef))

ERROR(attr_extern_expected_label,none,
"expected %0 argument to @_extern attribute", (StringRef))
//------------------------------------------------------------------------------
// MARK: Generics parsing diagnostics
//------------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,10 @@ ERROR(section_not_at_top_level,none,
ERROR(section_empty_name,none,
"@_section section name cannot be empty", ())

// @_extern
ERROR(extern_not_at_top_level_func,none,
"@_extern attribute can only be applied to global functions", ())

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

Expand Down
4 changes: 4 additions & 0 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,10 @@ class Parser {
ParserResult<DifferentiableAttr> parseDifferentiableAttribute(SourceLoc AtLoc,
SourceLoc Loc);

/// Parse the @_extern attribute.
bool parseExternAttribute(DeclAttributes &Attributes, bool &DiscardAttribute,
StringRef AttrName, SourceLoc AtLoc, SourceLoc Loc);

/// Parse the arguments inside the @differentiable attribute.
bool parseDifferentiableAttributeArguments(
DifferentiabilityKind &diffKind,
Expand Down
21 changes: 21 additions & 0 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ class SILFunction
/// empty.
StringRef WasmExportName;

/// Name of a Wasm import module and field if @_extern(wasm) attribute
llvm::Optional<std::pair<StringRef, StringRef>> WasmImportModuleAndField;

/// Has value if there's a profile for this function
/// Contains Function Entry Count
ProfileCounter EntryCount;
Expand Down Expand Up @@ -1278,6 +1281,24 @@ class SILFunction
StringRef wasmExportName() const { return WasmExportName; }
void setWasmExportName(StringRef value) { WasmExportName = value; }

/// Return Wasm import module name if @_extern(wasm) was used otherwise empty
StringRef wasmImportModuleName() const {
if (WasmImportModuleAndField)
return WasmImportModuleAndField->first;
return StringRef();
}

/// Return Wasm import field name if @_extern(wasm) was used otherwise empty
StringRef wasmImportFieldName() const {
if (WasmImportModuleAndField)
return WasmImportModuleAndField->second;
return StringRef();
}

void setWasmImportModuleAndField(StringRef module, StringRef field) {
WasmImportModuleAndField = std::make_pair(module, field);
}

/// 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
11 changes: 11 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,15 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
Printer << ")";
break;
}

case DAK_Extern: {
auto *Attr = cast<ExternAttr>(this);
Printer.printAttrName("@_extern");
// For now, it accepts only "wasm" as its kind.
Printer << "(wasm, module: \"" << Attr->ModuleName << "\", name: \"" << Attr->Name << "\")";
break;
}

case DAK_Section:
Printer.printAttrName("@_section");
Printer << "(\"" << cast<SectionAttr>(this)->Name << "\")";
Expand Down Expand Up @@ -1708,6 +1717,8 @@ StringRef DeclAttribute::getAttrName() const {
}
case DAK_RawLayout:
return "_rawLayout";
case DAK_Extern:
return "_extern";
}
llvm_unreachable("bad DeclAttrKind");
}
Expand Down
11 changes: 9 additions & 2 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3577,11 +3577,18 @@ llvm::Function *IRGenModule::getAddrOfSILFunction(
addUsedGlobal(fn);
if (!f->section().empty())
fn->setSection(f->section());

llvm::AttrBuilder attrBuilder(getLLVMContext());
if (!f->wasmExportName().empty()) {
llvm::AttrBuilder attrBuilder(getLLVMContext());
attrBuilder.addAttribute("wasm-export-name", f->wasmExportName());
fn->addFnAttrs(attrBuilder);
}
if (!f->wasmImportFieldName().empty()) {
attrBuilder.addAttribute("wasm-import-name", f->wasmImportFieldName());
}
if (!f->wasmImportModuleName().empty()) {
attrBuilder.addAttribute("wasm-import-module", f->wasmImportModuleName());
}
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
83 changes: 83 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,83 @@ Parser::parseDifferentiableAttribute(SourceLoc atLoc, SourceLoc loc) {
parameters, whereClause));
}

bool Parser::parseExternAttribute(DeclAttributes &Attributes,
bool &DiscardAttribute, StringRef AttrName,
SourceLoc AtLoc, SourceLoc Loc) {

// Parse @_extern(<language>, ...)
if (!consumeIf(tok::l_paren)) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
DeclAttribute::isDeclModifier(DAK_Extern));
return false;
}
auto diagnoseExpectLanguage = [&]() {
diagnose(Tok.getLoc(), diag::attr_expected_option_such_as, AttrName,
"wasm");
};
if (Tok.isNot(tok::identifier)) {
diagnoseExpectLanguage();
return false;
}

if (Tok.getText() != "wasm") {
diagnoseExpectLanguage();
DiscardAttribute = true;
}
consumeToken(tok::identifier);

// Parse @_extern(wasm, module: "x", name: "y")
auto parseStringLiteralArgument = [&](StringRef fieldName, StringRef &fieldValue) {
if (!consumeIf(tok::comma) || Tok.isNot(tok::identifier) || Tok.getText() != fieldName) {
diagnose(Loc, diag::attr_extern_expected_label, fieldName);
return false;
}
consumeToken(tok::identifier);

if (!consumeIf(tok::colon)) {
diagnose(Tok.getLoc(), diag::attr_expected_colon_after_label, fieldName);
return false;
}

if (Tok.isNot(tok::string_literal)) {
diagnose(Loc, diag::attr_expected_string_literal, AttrName);
return false;
}
llvm::Optional<StringRef> importModuleName =
getStringLiteralIfNotInterpolated(Loc, ("'" + AttrName + "'").str());
consumeToken(tok::string_literal);

if (!importModuleName.has_value()) {
DiscardAttribute = true;
return false;
}
fieldValue = importModuleName.value();
return true;
};

StringRef importModuleName, importName;
if (!parseStringLiteralArgument("module", importModuleName))
DiscardAttribute = true;

if (!parseStringLiteralArgument("name", importName))
DiscardAttribute = true;

if (!consumeIf(tok::r_paren)) {
diagnose(Loc, diag::attr_expected_rparen, AttrName,
DeclAttribute::isDeclModifier(DAK_Extern));
return false;
}

auto AttrRange = SourceRange(Loc, Tok.getLoc());

if (!DiscardAttribute) {
Attributes.add(new (Context) ExternAttr(importModuleName, importName, AtLoc,
AttrRange,
/*Implicit=*/false));
}
return false;
}

// Attribute parsing error helper.
// For the given parentheses depth, skip until ')' and consume it if possible.
// If no ')' is found, produce error.
Expand Down Expand Up @@ -3168,6 +3245,12 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
break;
}

case DAK_Extern: {
if (!parseExternAttribute(Attributes, DiscardAttribute, AttrName, AtLoc, Loc))
return makeParserSuccess();
break;
}

case DAK_Section: {
if (!consumeIf(tok::l_paren)) {
diagnose(Loc, diag::attr_expected_lparen, AttrName,
Expand Down
4 changes: 4 additions & 0 deletions lib/SIL/IR/SILFunctionBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ void SILFunctionBuilder::addFunctionAttributes(
}
}

if (auto *EA = Attrs.getAttribute<ExternAttr>()) {
F->setWasmImportModuleAndField(EA->ModuleName, EA->Name);
}

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

Expand Down
8 changes: 8 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {

void visitCDeclAttr(CDeclAttr *attr);
void visitExposeAttr(ExposeAttr *attr);
void visitExternAttr(ExternAttr *attr);
void visitUsedAttr(UsedAttr *attr);
void visitSectionAttr(SectionAttr *attr);

Expand Down Expand Up @@ -2070,6 +2071,13 @@ void AttributeChecker::visitExposeAttr(ExposeAttr *attr) {
}
}

void AttributeChecker::visitExternAttr(ExternAttr *attr) {
// Only top-level func decls are currently supported.
if (!isa<FuncDecl>(D) || D->getDeclContext()->isTypeContext()) {
diagnose(attr->getLocation(), diag::extern_not_at_top_level_func);
}
}

void AttributeChecker::visitUsedAttr(UsedAttr *attr) {
if (!Ctx.LangOpts.hasFeature(Feature::SymbolLinkageMarkers)) {
diagnoseAndRemoveAttr(attr, diag::section_linkage_markers_disabled);
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,7 @@ namespace {
UNINTERESTING_ATTR(Inlinable)
UNINTERESTING_ATTR(Effects)
UNINTERESTING_ATTR(Expose)
UNINTERESTING_ATTR(Extern)
UNINTERESTING_ATTR(Final)
UNINTERESTING_ATTR(MoveOnly)
UNINTERESTING_ATTR(FixedLayout)
Expand Down
14 changes: 14 additions & 0 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3247,6 +3247,16 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
// PatternBindingDecl.
}

/// Determine whether the given declaration should not have a definition.
static bool requiresNoDefinition(Decl *decl) {
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
// Function with @_extern should not have a body.
return func->getAttrs().hasAttribute<ExternAttr>();
}
// Everything else can have a definition.
return false;
}

/// Determine whether the given declaration requires a definition.
///
/// Only valid for declarations that can have definitions, i.e.,
Expand All @@ -3264,6 +3274,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
// Functions can have _silgen_name, semantics, and NSManaged attributes.
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
if (func->getAttrs().hasAttribute<SILGenNameAttr>() ||
func->getAttrs().hasAttribute<ExternAttr>() ||
func->getAttrs().hasAttribute<SemanticsAttr>() ||
func->getAttrs().hasAttribute<NSManagedAttr>())
return false;
Expand Down Expand Up @@ -3346,6 +3357,9 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
if (requiresDefinition(FD) && !FD->hasBody()) {
// Complain if we should have a body.
FD->diagnose(diag::func_decl_without_brace);
} else if (requiresNoDefinition(FD) && FD->hasBody()) {
// Complain if we have a body but shouldn't.
FD->diagnose(diag::func_decl_no_body_expected);
} else if (FD->getDeclContext()->isLocalContext()) {
// Check local function bodies right away.
(void)FD->getTypecheckedBody();
Expand Down
12 changes: 12 additions & 0 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5860,6 +5860,18 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
break;
}

case decls_block::Extern_DECL_ATTR: {
bool isImplicit;
unsigned moduleNameSize, declNameSize;
serialization::decls_block::ExternDeclAttrLayout::readRecord(
scratch, isImplicit, moduleNameSize, declNameSize);
StringRef moduleName = blobData.substr(0, moduleNameSize);
blobData = blobData.substr(moduleNameSize);
StringRef declName = blobData.substr(0, declNameSize);
Attr = new (ctx) ExternAttr(moduleName, declName, isImplicit);
break;
}

case decls_block::Documentation_DECL_ATTR: {
bool isImplicit;
uint64_t CategoryID;
Expand Down
9 changes: 8 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 = 811; // default argument isolation
const uint16_t SWIFTMODULE_VERSION_MINOR = 812; // @_extern(wasm)

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

using ExternDeclAttrLayout = BCRecordLayout<Extern_DECL_ATTR,
BCFixed<1>, // implicit flag
BCVBR<4>, // number of bytes in module name
BCVBR<4>, // number of bytes in name
BCBlob // module name and declaration name
>;

using DocumentationDeclAttrLayout = BCRecordLayout<
Documentation_DECL_ATTR,
BCFixed<1>, // implicit flag
Expand Down
Loading