Skip to content

[cxx-interop] ClangDeclFinder to handle problematic codegen cases #39551

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

Closed
wants to merge 1 commit into from

Conversation

plotfi
Copy link
Contributor

@plotfi plotfi commented Oct 1, 2021

At the moment, header defined functions do necessarily get codegened by
C++-Interop if they are invoked purely on the call path from a
constructor. What this means is that when you have inline or template
functions that are header defined and are only invoked on a call graph
path that only originates from a C++ constructor that is invoked from
Swift, that you get LLVM IR Function declares instead of Function
defines with linkonceodr. This results in link errors unless those
functions happen to be invoked or template instantiated from another TU
that gets linked in.

This patch teaches the ClangDeclFinder to look for these cases and add
them to the list of decls that get handled by HandleTopLevelDecl.

Partly addresses SR-15272

@plotfi
Copy link
Contributor Author

plotfi commented Oct 1, 2021

@zoecarver @egorzhdan @hyp I'd appreciate any comments or considerations here. I intend to continue expanding on this patch, but this is what I have that partly works for now.

@zoecarver zoecarver added the c++ interop Feature: Interoperability with C++ label Oct 1, 2021
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.

Looks pretty good. A few comments, but I want to look into possibly factoring this into the existing ClangDeclFinder.

Thanks for the patch!

@@ -82,6 +84,100 @@ clang::Decl *getDeclWithExecutableCode(clang::Decl *decl) {
return nullptr;
}

auto walkCallGraphFromCtor(const clang::CXXConstructorDecl *toplevelCtor,
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 too bad that the existing ClangDeclFinder can't be used/extended here. I'm going to look into this API a bit more to see if we could potentially re-use anything.

@@ -0,0 +1,15 @@
inline int Answer() { return 42; }
Copy link
Contributor

Choose a reason for hiding this comment

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

General notes for the tests:

  1. Let's match the existing naming convention. TitleCase for classes and camelCase for functions.
  2. Name the functions based on what they do. So this might be named returns42 and the next one might be passThroughArg.
  3. header-defined-functions doesn't really explain what's being tested here. This is testing when a data member's initializer uses an inline function, so maybe this should go in Cxx/class/initializer-from-inline.h.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zoecarver I've addressed most of the review feedback. Ready for more.

@plotfi plotfi force-pushed the main branch 8 times, most recently from d893e96 to 57ae094 Compare October 7, 2021 03:26
@plotfi
Copy link
Contributor Author

plotfi commented Oct 7, 2021

@zoecarver Current diff handles most of the std::vector case as well. I think one big thing I was doing wrong was not picking up a decl if the method body was empty. As this point the only missing linkonceodr function is the destructor. Having some trouble figuring out what the right plumbing is to invoke CGM.EmitGlobalDefinition(GD, GV)

plotfi added a commit to plotfi/cxx-interop-test that referenced this pull request Oct 27, 2021
Until this PR lands: swiftlang/swift#39551

this support code is needed.
At the moment, header defined functions do necessarily get codegened by
C++-Interop if they are invoked purely on the call path from a
constructor. What this means is that when you have inline or template
functions that are header defined and are only invoked on a call graph
path that only originates from a C++ constructor that is invoked from
Swift, that you get LLVM IR Function declares instead of Function
defines with linkonceodr. This results in link errors unless those
functions happen to be invoked or template instantiated from another TU
that gets linked in.

This patch teaches the ClangDeclFinder to look for these cases and add
them to the list of decls that get handled by HandleTopLevelDecl.
@plotfi
Copy link
Contributor Author

plotfi commented Jan 23, 2022

#40867 is a much nicer and more complete solution. Closing.

@bulbazord
Copy link
Contributor

#40867 has been in the tree for some time so I tried the examples in the description of SR-15272, notably the one involving std::vector. I'm still getting linker errors there. I'm wondering if this patch should be revisited? I pulled this patch into my tree and tested both examples in the bug report, both of them link and run correctly (on my machine).

@zoecarver
Copy link
Contributor

#40867 has been in the tree for some time so I tried the examples in the description of SR-15272, notably the one involving std::vector. I'm still getting linker errors there. I'm wondering if this patch should be revisited? I pulled this patch into my tree and tested both examples in the bug report, both of them link and run correctly (on my machine).

I'd be interested to explore this more. Are you using a just-built Swift compiler? Do you know what APIs aren't linking? Maybe you can provide the input program you're using?

@bulbazord
Copy link
Contributor

I'd be interested to explore this more. Are you using a just-built Swift compiler? Do you know what APIs aren't linking? Maybe you can provide the input program you're using?

Sure, I'll provide as many details as I can.

I effectively merged the 2 programs referenced in SR-15272. Here's what I have (all files in the same directory):

A.h

inline int Answer() { return 42; }
struct A { int a = Answer(); };

Vec.h

#include <vector>
struct Vec {
  std::vector<int> v = {1, 2, 3, 4};
};

module.modulemap

module A {
  header "A.h"
  requires cplusplus
}

module Vec {
  header "Vec.h"
  requires cplusplus
}

test.swift

import A
import Vec

import std.vector

var a = A()
var answer = a.a

var v = Vec()
var Vector = v.v

for i in 0 ..< Vector.size() {
    print(Vector[i], terminator: " ")
}

I then built test.swift with the following command:
xcrun --toolchain default --sdk '$SDK_DIR/MacOSX12.1.sdk' $SWIFT_BUILD_DIR/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swiftc -target x86_64-apple-macosx10.9 -module-cache-path $SWIFT_BUILD_DIR/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/swift-test-results/x86_64-apple-macosx10.9/clang-module-cache -F '$XCODE_FRAMEWORKS_DIR' -toolchain-stdlib-rpath -Xlinker -rpath -Xlinker '$XCODE_FRAMEWORKS_DIR' -Xlinker -rpath -Xlinker /usr/lib/swift -Xlinker -headerpad_max_install_names -F '$SWIFT_BUILD_DIR/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/lib' -Xlinker -rpath -Xlinker '$SWIFT_BUILD_DIR/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/lib' -module-cache-path $SWIFT_BUILD_DIR/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/swift-test-results/x86_64-apple-macosx10.9/clang-module-cache -I. -Xfrontend -enable-cxx-interop -o a.out -module-name main test.swift

I replaced some parts of the longer paths to cut down on a much larger command, hopefully it makes sense.
Running this line gives me the following:

error: link command failed with exit code 1 (use -v to see invocation)
Undefined symbols for architecture x86_64:
  "__Z6Answerv", referenced from:
      __ZN1AC2Ev in test-1.o
  "__ZNSt3__1L25__construct_range_forwardINS_9allocatorIiEEKiiiivEEvRT_PT0_S7_RPT1_", referenced from:
      __ZNSt3__16vectorIiNS_9allocatorIiEEE18__construct_at_endIPKiEENS_9enable_ifIXsr27__is_cpp17_forward_iteratorIT_EE5valueEvE4typeES8_S8_m in test-1.o
  "__ZNSt3__1L7forwardINS_18__default_init_tagEEEOT_RNS_16remove_referenceIS2_E4typeE", referenced from:
      __ZNSt3__117__compressed_pairIPiNS_9allocatorIiEEEC2IDnNS_18__default_init_tagEEEOT_OT0_ in test-1.o
ld: symbol(s) not found for architecture x86_64

Let me know if there's anything else you'd like to know.

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.

3 participants