-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SR-648] Add option to create statically linked binaries (take 2) #5394
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
[SR-648] Add option to create statically linked binaries (take 2) #5394
Conversation
6783268
to
f975871
Compare
/cc @jckarter could you have a nitpick of this pr if you have some time, it should be a cleaner solution than the last one |
Definitely, thanks for continuing work on this @spevans! |
@erg might also have opinions about the cmake changes here. |
Is this something we should merge once OSX support is added or should we merge the Linux part first? |
@erg osx doesnt support static binaries but it should be possible to expand it to other ELF targets eventually, its just that Linux is the only one I have to test with and I didnt want to break any of the other ELF targets at the moment |
Apple platforms don't really support fully static binaries, so focusing on Linux support seems like the right thing to do. |
468a4a8
to
f4378c1
Compare
rebased and retested on Linux and macOS @jckarter is there anything I can do to help get this PR reviewed and merged? Would it help if I broke out some of the commits into their own PRs? |
Sorry, just have been busy with other things. Let me take a look now. |
dladdr(const void* addr, Dl_info* info) | ||
{ | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not export our own definition of dladdr
. Is this still necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is needed by Errors.cpp when doing a stack backtrace. I dont currently have an implementation for this but I am thinking of adding an ELF parser to inspect the binary, but for now I just made it return 0 for not found
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors.cpp
can be changed to use a different name, swift_dladdr
or something similar, that returns 0 in the static implementation. We shouldn't be redefining standard POSIX symbols.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a commit to wrap dladdr() in _swift_dladdr() and updated the callers. I also tidied up the function visibility for exported functions using the correct macros and retested on macOS and Linux
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @spevans, this is shaping up nicely! I took a shot at refactoring all the image inspection code in #5846. It should be a lot easier to cleanly slot in an ImageInspectionStatic.cpp
file once that goes in; all you'd need is something like:
extern const char Conformances __asm__(".swift2_protocol_conformances_start");
extern const char TypeMetadataRecords __asm__(".swift2_type_metadata_start");
void swift::initializeProtocolConformanceLookup() {
// The image is statically linked, so we can just directly reference the symbol
// that signals the beginning of the section.
// ld should mash all the conformance records together for us, and we don't need
// to be concerned about dynamic loading.
uint64_t size;
memcpy(&size, Conformances, sizeof(size));
addImageProtocolConformanceBlockCallBack(Conformances + sizeof(size), size);
}
and something similar for type metadata records.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jckarter Ahh I see, you mean have a separate initialise function for each section rather than having them all share the same one, that makes it a bit easier to understand. Do you think in the future there will be many more data sections? I noticed some other ones defined in swift_sections.S but they only seemed to be used by the unit test code
#define SWIFT_TYPE_METADATA_SECTION ".swift2_type_metadata_start" | ||
|
||
#if defined(__linux__) | ||
#define SUPPORTS_STATIC_BINARIES |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Macros should be namespaced by starting with SWIFT_
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed the macros names
auto blockSize = *reinterpret_cast<const uint64_t*>(blockAddr); | ||
blockAddr += sizeof(blockSize); | ||
inspectArgs->fnAddImageBlock(blockAddr, blockSize); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There really shouldn't need to be any work done here, since there's only ever one section of interest in a static binary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theres currently two sections (protocol, metadata) although the metadata isn't usually loaded from what I saw in my tests. I added a struct to define the sections which eliminated the casts and simplified it
void _swift_initializeCallbacksForSectionData(InspectArgs *inspectArgs); | ||
} | ||
|
||
#endif /* SWIFT_RUNTIME_SECTIONDATA_H */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure any of the abstractions here really hold their weight, given how platform-specific the behavior is at nearly every level. The code is getting really confusing to follow with how conditionalized it. At a high level, we need _swift_initializeCallbacksForSectionData
to perform platform-specific registration of existing sections and observation of new dylibs getting loaded. I don't think that operation benefits from having any InspectArgs
passed down—the implementation of _swift_initializeCallbacksForSectionData()
can probably take no arguments and be factored into a per-platform implementation that knows the specific lookup operations to perform for a general platform (Mach-O, Windows, dynamic ELF, static ELF).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a go at changing the interface to remove the extra callback used just for MachO however I think that I may have introduced more complexity into the mach-o handling to provide a simpler interface.
I couldn't find a way to eliminate the InspectArgs because the 2 sections are initialised lazily
unless we explicitly want to setup callbacks for both sections at startup - Im not sure if thats acceptable though. Unless you mean when one of the sections is lazily loaded by calling _swift_initializeCallbacksForSectionData() it loads the other one at that point as well, I think that should be easy to accomodate
I did however manage to come up with a solution for loading sections in ELF objects that are dlopen'd after the initial initialise, which I think fixes rdar://problem/19045112
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, injecting a static constructor is probably the best we can do for ELF, since ld.so
doesn't appear to have any other on-image-load hooks. I might not be explaining the refactoring I had in mind well—let me take a look at cleaning this code up a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the static constructor for introducing dlopen-ed ELF images be factored into a separate PR? I don't want to block this PR on that functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it shouldnt be a problem to back out the constructor and add it as a separate PR
I made some comments on the runtime implementation. The CMake changes look vaguely reasonable, but that's not my wheelhouse. @shahmishal, @erg, or @zisko, any comments on that? |
0b76e48
to
68830ab
Compare
FYI, Apple's Thanksgiving break is next week, so I won't have a chance to look at this again till the 28th. Thanks for continuing to work on it! |
530dc22
to
254cc73
Compare
@jckarter I've rebased wrt your changes. I ended up adding a _swfit_dladdr() wrapper around dladdr() in each platform's ImageInspection*.cpp which isnt ideal but if Win32 needs it's own implentation at some point its not too bad. After this PR is merged I have two changes lined up, one to add a full dladdr() implementation for ELF static binaries and another to address the ELF dynamically loaded images metadata lookup which is dependant on some changes in this PR |
@swift-ci Please smoke test |
Great! I'll try to look at this tomorrow. |
@spevans It looks like there's a new conflict with Greg's |
- Add --libicu option to compile icu from source. This allows the configuration to be controlled so that it is enabled for shared and static building and the static files dont require the use of the dynamic loader - Add --install-libicu option to install the shared libraries into $prefix/lib/swift and the static ones into $prefix/lib/swift_static. This avoids interference with system installed versions - Dont use find_package if building ICU from source
- Add ImageInspectionStatic.cpp to lookup protocol conformance and metadata sections in static binaries - For Linux, build libswiftImageInspectionShared.a and libswiftImageInspectionStatic.a for linking with libswiftCore.a. This allows static binaries to be built without linking to libdl. libswiftImageInspectionShared (ImageInspectionELF.cpp) is automatically compiled into libswiftCore.so - Adds -static-executable option to swiftc to use along with -emit-executable that uses linker arguments in static-executable-args.lnk. This also requires a libicu to be compiled using the --libicu which has configure options that dont require libdl for accessing ICU datafiles - Static binaries only work on Linux at this time
- Create a file of linker arguments instead of a hardcoded list in ToolChains.cpp for use by -static-stdlib option
254cc73
to
f6866e7
Compare
Rebased on top of #5969 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Just a few small comments.
@@ -62,13 +62,57 @@ set(LLVM_OPTIONAL_SOURCES | |||
MutexPThread.cpp | |||
MutexWin32.cpp | |||
CygwinPort.cpp | |||
ImageInspectionELF.cpp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't ImageInspectionELF.cpp
included in the core swift_runtime_sources
above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When compiling for Linux it is removed from swift_runtime_sources so that it is not added to libswiftCore.a but instead used in libswiftImageInspectionShared.a
For other ELF targets it remains in swift_runtime_source - this may change as this static option is tested on other ELF targets
@@ -78,6 +79,7 @@ static int iteratePHDRCallback(struct dl_phdr_info *info, | |||
return 0; | |||
} | |||
|
|||
SWIFT_RUNTIME_EXPORT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions shouldn't need to be exported outside the runtime AFAICS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used because the functions are in libswiftImageInspectionShared.a but called from libswiftCore.a
Its not needed for the ImageInspectionStatic.cpp functions as this file is not compiled with
-fvisibility=hidden
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They still shouldn't need to be exported since all of the .a
files are ultimately being linked into the same binary. Visibility only affects an executable or dylib, not .o files. In a static binary, ideally, everything should be hidden besides main
, in order to maximize the power of LTO and dead symbol stripping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just commented them out and you are correct, it does still link, but it fails when building the tests. I was mistaken, the issue is actually with linking the tests with libswiftCore.so not with the .a libs (its been a while since I first looked at it!). It looks like it is fixable in the CMake files so I will add a fix for that and remove the SWIFT_RUNTIME_EXPORT
uint64_t size; | ||
const uint8_t data[0]; | ||
}; | ||
extern const SectionInfo __swift2_protocol_conformances_start; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You ought to be able to preserve the existing dotted symbol names by declaring these with an asm
attribute, e.g. extern const SectionInfo ProtocolConformancesStart asm(".swift2_protocol_conformances_start");
, which would also give the declarations a slightly friendlier source name. I'm a bit concerned about the potential of other users outside of the compiler depending on the existing symbol names.
// Used in swift_sections.S to mark the start of a section | ||
struct SectionInfo { | ||
uint64_t size; | ||
const uint8_t data[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zero-sized array fields aren't standard C++, and reaching outside the bounds of an array field through a struct pointer is undefined behavior. It'd fit a bit more within the standard to leave SectionInfo
opaque and do char* pointer manipulation:
struct alignas(uint64_t) SectionInfo;
uint64_t getSectionSize(const SectionInfo *section) {
uint64_t result;
memcpy(&result, section, sizeof(uint64_t));
return result;
}
const void *getSectionData(const SectionInfo *section) {
return reinterpret_cast<const char*>(section) + sizeof(uint64_t);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the following ok? I wanted to keep SectionInfo a struct as it will also be used in the PR which fixes the issue with libs that are dlopen()'d after startup.
extern const uint64_t protocolConformancesStart asm(".swift2_protocol_conformances_start");
extern const uint64_t typeMetadataStart asm(".swift2_type_metadata_start");
struct SectionInfo {
uint64_t size;
const char *data;
};
static SectionInfo
_getSection(const uint64_t *section) {
SectionInfo info;
info.size = *section;
info.data = reinterpret_cast<const char *>(section + 1);
return info;
}
void
swift::initializeProtocolConformanceLookup() {
auto protocolConformances = _getSection(&protocolConformancesStart);
addImageProtocolConformanceBlockCallback(protocolConformances.data,
protocolConformances.size);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping a SectionInfo struct around seems reasonable. I think it's still best to declare the section references as an opaque type, for type safety, and to avoid possible optimizations assuming protocolConformancesStart only points to a uint64_t's worth of data:
struct alignas(uint64_t) Section;
extern const Section protocolConformancesStart asm(".swift2_protocol_conformances_start");
extern const Section typeMetadataStart asm(".swift2_type_metadata_start");
struct SectionInfo {
uint64_t size;
const char *data;
};
static SectionInfo
getSectionInfo(const Section *section) {
SectionInfo info;
memcpy(&info.size, section, sizeof(uint64_t));
info.data = reinterpret_cast<const char *>(section) + sizeof(uint64_t);
return info;
}
@swift-ci Please smoke test |
@swift-ci Please smoke test Linux |
@ddunbar The |
Based on the exit status I suspect what is happening is that swift-build-tool is crashing (with SIGILL, maybe?)? I'm not sure how to diagnose... does this reproduce locally for anyone? |
Oh wait... I misread -- this is the self-hosted build that is failing, so the chances are good that swift-build itself was miscompiled and is crashing. That would explain the lack of any other diagnostics. |
- Revert the use of SWIFT_RUNTIME_EXPORT in ImageInspectionELF.cpp and fix the unittests by explicitly adding the file to the list - Revert the change of section data names
@spevans are you also building the package manager as part of your Linux builds? |
Runtime changes look great now, thanks for bearing with me! Are you able to look into the swift-build bootstrapping problem? |
@swift-ci Please smoke test |
@ddunbar I hadnt been testing with --swiftpm but Im running a test now and it seems to be working so far. I think that last commit may have fixed something as I think I stopped testing with the package manager a while ago when I saw some breakage but though it was due to something else |
Looks like swiftpm bootstraps successfully now. |
When I update to master and build with: I get this test failure, which may be related to this merge:
|
@alblue Possibly an error where the static-stdlib test is running but the --build-swift-static-stdlib was not specificied. Can you add that option and try again. I will see if that test should be running without it |
If that test is only intended to be run for static-executable targets, we may need to add a |
That test is the preexisting one and currently has:
Thats why I thought it might be a config error as I think it should only be run if --build-swift-static-stdlib (the preexisting option) was used. It may have worked in the past by accident if the lib/swift_static/linux files were there from a previous build but fails now because of the missing It may also be an issue the --reconfigure doesnt work fully and may need |
@spevans I wanted to check whether you had started a PR to add the static constructor for registering |
[pull] swiftwasm from main
This is a reworking of PR #4754 to provide a way of creating static binaries for use with the package manager. Based on the comments in the earlier PR this one doesn't intercept any libdl but builds its own version of libicu that doesn't need dlopen() etc
Main changes: