Skip to content

Update cxx operator implementation #58910

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 21 commits into from
Jun 2, 2022

Conversation

Huddie
Copy link
Contributor

@Huddie Huddie commented May 13, 2022

This PR aims to improve the CXX class member operator implementation by making it a more true mapping from the CXX world.

Previous Impl

Previously if a class in CPP has an operator member function

class Foo {
  Foo operator+(Foo a, Foo b);
}

it would get mapped into static method inside the generated Foo class

struct Foo {
 static func +(lhs: inout Foo, rhs: Foo) -> Foo
} 

This achieves the correct result but the operator is a static function and therefore the Foo class doesn't really have the same methods on it that it did in CPP

New Impl

This change maps the member operator function to a member function in the swift class & also create a separate swift operator static function which calls the member function

struct Foo {
 func __operatorPlus(rhs: Foo) -> Foo { ... }
 static func +(lhs: inout Foo, rhs: Foo) -> Foo  { 
   return lhs.__operatorPlus(rhs)
 }
}

A few windows tests need tor enabled in a follow up

Huge thanks to @zoecarver !

@Huddie Huddie added the c++ interop Feature: Interoperability with C++ label May 13, 2022
@Huddie Huddie requested a review from zoecarver May 13, 2022 22:33
Copy link
Contributor

@zoecarver zoecarver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! Thank you so much Ehud! I only have a few minor comments.

Also, either in this PR or another (maybe better to do another one), would you mind re-enabling these tests on Windows (they should work now) and adding the test from this PR: https://github.com/apple/swift/pull/32869/files ?

And as we've discussed offline, I think this will allow us to get rid of a lot of special-case code throughout silgen! Thanks again :)

// For operators we have to look up static member functions in addition to the
// top-level function lookup below.
auto declBaseName = (SwiftContext.LangOpts.EnableCXXInterop && name.isOperator())
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(std::string{name.getBaseName().getIdentifier()})))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(std::string{name.getBaseName().getIdentifier()})))
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(name.getBaseName().getIdentifier().str())))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do .str().str() to go from identifier to StringRef to std::string

Or should I make getOperatorNameForToken take take StringRef ?

auto d = makeOperator(MD, cxxMethod);
Impl.addAlternateDecl(MD, d);

Impl.markUnavailable(MD, "use - operator");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update this to use getOperatorNameForToken?

@@ -1843,15 +1856,9 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
case clang::OverloadedOperatorKind::OO_GreaterEqual:
case clang::OverloadedOperatorKind::OO_AmpAmp:
case clang::OverloadedOperatorKind::OO_PipePipe:
baseName = clang::getOperatorSpelling(op);
baseName = isa<clang::CXXMethodDecl>(functionDecl) ? StringRef{"__operator" + std::string{getOperatorName(op)}} : clang::getOperatorSpelling(op);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here std::string is a temporary, so you can't get a "ref" to it (StringRef is basically a pointer to the temporary memory). Beyond that, I think we'll need to make baseName a std::string or allocate an identifier (like you did above), because it's declared/used out of this scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is happening in the other cases here? Like:

baseName = "callAsFunction";

Is this not equivalent to:

baseName = StringRef{"callAsFunction"};

?

Is this an rValue that gets moved into baseName?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String literals are sort of like globals, they last for the duration of the program, so you don't have to worry about their lifetime. std::strings on the other hand are destroyed at the end of the scope if they're locals, otherwise at the end of the expression of they're temporaries. A StringRef doesn't store anything, it's a reference to a char *. If you take the reference of a "global" that's always fine. If you take the reference of a local/temporary, that's where we run into problems, because the string may have already been destroyed.

@zoecarver
Copy link
Contributor

Also, would you mind running this whole PR through git-clang-format. You can just run git-clang-format main.

@zoecarver
Copy link
Contributor

@swift-ci please test.

@@ -6,24 +6,21 @@ import MemberInline
public func sub(_ lhs: inout LoadableIntWrapper, _ rhs: LoadableIntWrapper) -> LoadableIntWrapper { lhs - rhs }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an XFAIL at the top of this file and in a few of these operator tests. I suspect you didn't see these locally because your repo is out of date. Anyway, you'll also need to re-enable operators once you pull from main. Let me know if you have any questions about this.

// For operators we have to look up static member functions in addition to the
// top-level function lookup below.
auto declBaseName = (SwiftContext.LangOpts.EnableCXXInterop && name.isOperator())
? DeclBaseName(SwiftContext.getIdentifier("__operator" + getOperatorNameForToken(std::string{name.getBaseName().getIdentifier()})))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also nit: Seems to be missing clang-format
You can use either git-clang-format tool that is on ${PATH_TO_SWIFT}/llvm-project/clang/tools/clang-format and you can add it to your path, and run on the git directory after git add .
Or clang-format -lines 1630:1660 lib/Sema/CSFix.h -i for example to run at lines in specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Will do!

importDeclReal(entry->getMostRecentDecl(), CurrentVersion))) {
consumer.foundDecl(decl, DeclVisibilityKind::VisibleAtTopLevel);
declFound = true;
for (auto entry : table.lookupMemberOperators(declBaseName)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like those two loops are exact the same code(just over different lists), could this be abstracted into a function/lambda in some way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think so! Just gotta rebase and fix some tests first.

@zoecarver zoecarver requested a review from egorzhdan May 20, 2022 18:42
@Huddie Huddie force-pushed the update-cxx-operator-impl branch from 33c7002 to e7fe6f0 Compare May 22, 2022 01:29
@Huddie
Copy link
Contributor Author

Huddie commented May 22, 2022

@swift-ci please smoke test

@Huddie
Copy link
Contributor Author

Huddie commented May 24, 2022

@swift-ci please smoke test

@zoecarver
Copy link
Contributor

Is this ready for re-review?

@Huddie
Copy link
Contributor Author

Huddie commented May 25, 2022

Is this ready for re-review?

Yes please :)

@Huddie
Copy link
Contributor Author

Huddie commented May 26, 2022

@swift-ci please smoke test

@Huddie Huddie requested a review from LucianoPAlmeida May 26, 2022 16:26
@@ -799,7 +799,7 @@ SwiftLookupTable::lookupMemberOperators(SerializedSwiftName baseName) {
SmallVector<clang::NamedDecl *, 4> result;

// Find the lookup table entry for this base name.
auto known = findOrCreate(LookupTable, baseName,
auto known = findOrCreate(LookupTable, baseName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: spacing

// Support for importing operators is temporarily disabled: rdar://91070109
if (decl->getDeclName().getNameKind() == clang::DeclarationName::CXXOperatorName &&
decl->getDeclName().getCXXOverloadedOperator() != clang::OO_Subscript)
return nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one thing we will probably want to keep disabled is templated operators. Those cause issues with ambiguity. But we can probably disable those in a follow up patch, given this patch is pretty big already.

Copy link
Contributor

@zoecarver zoecarver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to do a final review pass but this generally looks good to me. Thanks again, Ehud!

@@ -870,6 +870,7 @@ void IterableDeclContext::addMember(Decl *member, Decl *hint, bool insertAtHead)
}
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were those changes intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope lol fixing

if (isVisibleFromModule(ModuleFilter, VD))
NextConsumer.foundDecl(VD, Reason, dynamicLookupInfo);
if (!VD->hasClangNode() || isVisibleFromModule(ModuleFilter, VD))
NextConsumer.foundDecl(VD, Reason, dynamicLookupInfo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably just run git-clang-formater will fix all formatting issues =]

);

topLevelStaticFuncDecl->setAccess(AccessLevel::Public);
topLevelStaticFuncDecl->setImplicit();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woudn't createImplicit already set topLevelStaticFuncDecl as implicit already making this redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Just checked

ctx,
StaticSpellingKind::None,
opDeclName, SourceLoc(),
false, false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
false, false,
/*Async=*/false, /*Throws=*/ false,

@@ -1843,15 +1856,9 @@ ImportedName NameImporter::importNameImpl(const clang::NamedDecl *D,
case clang::OverloadedOperatorKind::OO_GreaterEqual:
case clang::OverloadedOperatorKind::OO_AmpAmp:
case clang::OverloadedOperatorKind::OO_PipePipe:
baseName = clang::getOperatorSpelling(op);
baseName = isa<clang::CXXMethodDecl>(functionDecl) ? swiftCtx.getIdentifier("__operator" + std::string{getOperatorName(op)}).str() : swiftCtx.getIdentifier(clang::getOperatorSpelling(op)).str();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: just a suggestion for simplification :)

Suggested change
baseName = isa<clang::CXXMethodDecl>(functionDecl) ? swiftCtx.getIdentifier("__operator" + std::string{getOperatorName(op)}).str() : swiftCtx.getIdentifier(clang::getOperatorSpelling(op)).str();
auto operatorName = isa<clang::CXXMethodDecl>(functionDecl) ? "__operator" + std::string{getOperatorName(op)} ? getOperatorSpelling(op);
baseName = swiftCtx.getIdentifier(operatorName).str();

Copy link
Contributor Author

@Huddie Huddie May 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

baseName is defined before the switch (both the nested operator switch and outer switch) and used for all cases (not just operators)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand, the implication suggestion was just to do ternary on the argument of swiftCtx.getIdentifier, but it was just a nit...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see! I misread (on GH phone) I thought you were just renaming baseName to operatorName

Ya this looks neat! Thanks

@Huddie
Copy link
Contributor Author

Huddie commented May 27, 2022

@swift-ci please test

@Huddie
Copy link
Contributor Author

Huddie commented May 27, 2022

@zoecarver Ready for another pass!

@@ -2868,7 +2880,7 @@ class FilteringVisibleDeclConsumer : public swift::VisibleDeclConsumer {

void foundDecl(ValueDecl *VD, DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo) override {
if (isVisibleFromModule(ModuleFilter, VD))
if (!VD->hasClangNode() || isVisibleFromModule(ModuleFilter, VD))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need !VD->hasClangNode() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! Basically it can happen that we try looking up the pure swift function that we synthesize which doesn't have a clang node (since we add it as an altDecl to the original clang operator that we import). And if that happens it will call isVisibleFromModule which assume it has clangNode which our pure swift function does not have. So this guards against that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's when VD isn't something we imported, but something we found via alternativeDecls. I think it would be a good idea to add a comment saying that. Maybe also add a comment to the top of the consumer that says it only filters imported decls/decls that have an associated clang node.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, does that mean that operators are effectively visible from Swift even if their module isn't imported? E.g.

// Foo.h
static MyClass operator+(MyClass a, MyClass b) { /* ... */ };
// A.swift
@_implementationOnly import Foo
// B.swift
import A
// + shouldn't be visible here

Could we instead add a clang decl to the synthesized functions to avoid a special case here? That's how this is handled for synthesized subscripts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@egorzhdan is that done in subscripts result->addMember(subscript) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happens in SwiftDeclConverter::makeSubscript, SubscriptDecl *subscript is created with a getterImpl->getClangNode() parameter.

Copy link
Contributor Author

@Huddie Huddie May 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm.. I was using createImplicit which doesn't have that option. But maybe createImported which does would be better here? And pass in the clangNode? And then call ->setImplicit() after to make it implicit?@zoecarver ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels a bit weird just cause this function technically wasn't imported.


Impl.addAlternateDecl(MD, opFuncDecl);

Impl.markUnavailable(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formatting seems really weird.

Copy link
Contributor

@zoecarver zoecarver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still some formatting issues but please land this once those are fixed. The patch looks good to me.

@Huddie
Copy link
Contributor Author

Huddie commented May 27, 2022

@swift-ci please test

2 similar comments
@zoecarver
Copy link
Contributor

@swift-ci please test

@zoecarver
Copy link
Contributor

@swift-ci please test

Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, other than a couple of minor code formatting issues. Thanks!

There is a potential issue when an operator is declared in a different module from the struct, however, that never worked even with the previous implementation and can be fixed later.

@Huddie
Copy link
Contributor Author

Huddie commented Jun 1, 2022

@swift-ci please test

@egorzhdan
Copy link
Contributor

@swift-ci please smoke test

@Huddie Huddie requested a review from LucianoPAlmeida June 1, 2022 18:44
@egorzhdan
Copy link
Contributor

@swift-ci please smoke test

@egorzhdan
Copy link
Contributor

@swift-ci please smoke test

Copy link
Contributor

@zoecarver zoecarver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 it!

@zoecarver
Copy link
Contributor

@swift-ci please test

@Huddie
Copy link
Contributor Author

Huddie commented Jun 1, 2022

@swift-ci please test macos

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants