Skip to content

[Explicit Module Builds] Adopt dependency scanner Link Libraries support #1622

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
Jun 6, 2024
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
17 changes: 16 additions & 1 deletion Sources/CSwiftScan/include/swiftscan_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include <stdint.h>

#define SWIFTSCAN_VERSION_MAJOR 0
#define SWIFTSCAN_VERSION_MINOR 6
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 check if we bump this, we are not missing any APIs that in the range of 7-9?

#define SWIFTSCAN_VERSION_MINOR 10

//=== Public Scanner Data Types -------------------------------------------===//

Expand All @@ -41,6 +41,7 @@ typedef enum {

typedef struct swiftscan_module_details_s *swiftscan_module_details_t;
typedef struct swiftscan_dependency_info_s *swiftscan_dependency_info_t;
typedef struct swiftscan_link_library_info_s *swiftscan_link_library_info_t;
typedef struct swiftscan_dependency_graph_s *swiftscan_dependency_graph_t;
typedef struct swiftscan_import_set_s *swiftscan_import_set_t;
typedef struct swiftscan_diagnostic_info_s *swiftscan_diagnostic_info_t;
Expand All @@ -60,6 +61,10 @@ typedef struct {
swiftscan_dependency_info_t *modules;
size_t count;
} swiftscan_dependency_set_t;
typedef struct {
swiftscan_link_library_info_t *link_libraries;
size_t count;
} swiftscan_link_library_set_t;

//=== Batch Scan Input Specification --------------------------------------===//

Expand Down Expand Up @@ -110,9 +115,19 @@ typedef struct {
(*swiftscan_module_info_get_source_files)(swiftscan_dependency_info_t);
swiftscan_string_set_t *
(*swiftscan_module_info_get_direct_dependencies)(swiftscan_dependency_info_t);
swiftscan_link_library_set_t *
(*swiftscan_module_info_get_link_libraries)(swiftscan_dependency_graph_t);
swiftscan_module_details_t
(*swiftscan_module_info_get_details)(swiftscan_dependency_info_t);

//=== Link Library Info Functions ------------------------------------===//
swiftscan_string_ref_t
(*swiftscan_link_library_info_get_link_name)(swiftscan_link_library_info_t);
bool
(*swiftscan_link_library_info_get_is_framework)(swiftscan_link_library_info_t);
bool
(*swiftscan_link_library_info_get_should_force_load)(swiftscan_link_library_info_t);

//=== Dependency Module Info Details Functions ----------------------------===//
swiftscan_dependency_info_kind_t
(*swiftscan_module_detail_get_kind)(swiftscan_module_details_t);
Expand Down
8 changes: 8 additions & 0 deletions Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3076,6 +3076,14 @@ extension Driver {
diagnosticsEngine.emit(.error_hermetic_seal_requires_lto)
}
}

if parsedOptions.hasArgument(.explicitAutoLinking) {
if !parsedOptions.hasArgument(.driverExplicitModuleBuild) {
diagnosticsEngine.emit(.error(Error.optionRequiresAnother(Option.explicitAutoLinking.spelling,
Option.driverExplicitModuleBuild.spelling)),
location: nil)
}
}
}

private static func validateSanitizerAddressUseOdrIndicatorFlag(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
/// that specify said explicit module dependencies.
@_spi(Testing) public struct ExplicitDependencyBuildPlanner {
/// The module dependency graph.
private let dependencyGraph: InterModuleDependencyGraph
@_spi(Testing) public let dependencyGraph: InterModuleDependencyGraph

/// A set of direct and transitive dependencies for every module in the dependency graph
private let reachabilityMap: [ModuleDependencyId : Set<ModuleDependencyId>]
Expand Down Expand Up @@ -421,6 +421,29 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
}
}

public func getLinkLibraryLoadCommandFlags(_ commandLine: inout [Job.ArgTemplate]) throws {
var allLinkLibraries: [LinkLibraryInfo] = []
for (_, moduleInfo) in dependencyGraph.modules {
guard let moduleLinkLibraries = moduleInfo.linkLibraries else {
continue
}
for linkLibrary in moduleLinkLibraries {
allLinkLibraries.append(linkLibrary)
}
}

for linkLibrary in allLinkLibraries {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know the corresponding scanner change. Does the library here guaranteed to be found by linker? Also does force-linking does anything in the link here in logics? Maybe considering just pass force-linked library with full path (so linker can't drop them) and stop emitting force-linking symbols.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The library is not guaranteed to be found, no. On Darwin the linker treats load directives for auto-linked libraries and frameworks as essentially "optional".

On non-Darwin today we just end up with a collection of -l flags via swift-autolink-extract, so the behavior introduced here would be identical.

I'd like to still add this new behavior behind a flag in this PR, and consider the optionality of load directives and determining whether the linked binary exists separately.

if !linkLibrary.isFramework {
commandLine.appendFlag("-l\(linkLibrary.linkName)")
} else {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("-framework")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag(linkLibrary.linkName)
}
}
}

/// Resolve all module dependencies of the main module and add them to the lists of
/// inputs and command line flags.
public mutating func resolveMainModuleDependencies(inputs: inout [TypedVirtualPath],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,26 @@ import func TSCBasic.topologicalSort
if let currentInfo = modules[swiftModuleId],
externalModuleId.moduleName != mainModuleName {
let newExternalModuleDetails =
try SwiftPrebuiltExternalModuleDetails(compiledModulePath:
TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
isFramework: externalModuleDetails.isFramework)
SwiftPrebuiltExternalModuleDetails(compiledModulePath:
TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
isFramework: externalModuleDetails.isFramework)
let newInfo = ModuleInfo(modulePath: TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
sourceFiles: [],
directDependencies: currentInfo.directDependencies,
linkLibraries: currentInfo.linkLibraries,
details: .swiftPrebuiltExternal(newExternalModuleDetails))
Self.replaceModule(originalId: swiftModuleId, replacementId: prebuiltModuleId,
replacementInfo: newInfo, in: &modules)
} else if let currentPrebuiltInfo = modules[prebuiltModuleId] {
// Just update the isFramework bit on this prebuilt module dependency
let newExternalModuleDetails =
try SwiftPrebuiltExternalModuleDetails(compiledModulePath:
TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
isFramework: externalModuleDetails.isFramework)
SwiftPrebuiltExternalModuleDetails(compiledModulePath:
TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
isFramework: externalModuleDetails.isFramework)
let newInfo = ModuleInfo(modulePath: TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
sourceFiles: [],
directDependencies: currentPrebuiltInfo.directDependencies,
linkLibraries: currentPrebuiltInfo.linkLibraries,
details: .swiftPrebuiltExternal(newExternalModuleDetails))
Self.replaceModule(originalId: prebuiltModuleId, replacementId: prebuiltModuleId,
replacementInfo: newInfo, in: &modules)
Expand Down Expand Up @@ -231,6 +233,9 @@ extension InterModuleDependencyGraph {
let firstModuleDependencies = firstInfo.directDependencies ?? []
let secondModuleDependencies = secondInfo.directDependencies ?? []
let combinedDependencies = Array(Set(firstModuleDependencies + secondModuleDependencies))
let firstLinkLibraries = firstInfo.linkLibraries ?? []
let secondLinkLibraries = secondInfo.linkLibraries ?? []
let combinedLinkLibraries = Array(Set(firstLinkLibraries + secondLinkLibraries))

let firstModuleCapturedPCMArgs = firstDetails.capturedPCMArgs ?? Set<[String]>()
let secondModuleCapturedPCMArgs = secondDetails.capturedPCMArgs ?? Set<[String]>()
Expand All @@ -245,6 +250,7 @@ extension InterModuleDependencyGraph {
return ModuleInfo(modulePath: firstInfo.modulePath,
sourceFiles: combinedSourceFiles,
directDependencies: combinedDependencies,
linkLibraries: combinedLinkLibraries,
details: .clang(combinedModuleDetails))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ public struct BridgingHeader: Codable, Hashable {
var moduleDependencies: [String]
}

/// Linked Library
public struct LinkLibraryInfo: Codable, Hashable {
public var linkName: String
public var isFramework: Bool
public var shouldForceLoad: Bool
}

/// Details specific to Swift modules.
public struct SwiftModuleDetails: Codable, Hashable {
/// The module interface from which this module was built, if any.
Expand Down Expand Up @@ -203,6 +210,9 @@ public struct ModuleInfo: Codable, Hashable {
/// The set of direct module dependencies of this module.
public var directDependencies: [ModuleDependencyId]?

/// The set of libraries that need to be linked
public var linkLibraries: [LinkLibraryInfo]?

/// Specific details of a particular kind of module.
public var details: Details

Expand All @@ -226,10 +236,12 @@ public struct ModuleInfo: Codable, Hashable {
public init(modulePath: TextualVirtualPath,
sourceFiles: [String]?,
directDependencies: [ModuleDependencyId]?,
linkLibraries: [LinkLibraryInfo]?,
details: Details) {
self.modulePath = modulePath
self.sourceFiles = sourceFiles
self.directDependencies = directDependencies
self.linkLibraries = linkLibraries
self.details = details
}
}
Expand Down Expand Up @@ -306,7 +318,7 @@ public struct InterModuleDependencyGraph: Codable {
public var mainModule: ModuleInfo { modules[.swift(mainModuleName)]! }
}

internal extension InterModuleDependencyGraph {
@_spi(Testing) public extension InterModuleDependencyGraph {
func toJSONData() throws -> Data {
let encoder = JSONEncoder()
#if os(Linux) || os(Android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ public class InterModuleDependencyOracle {
return swiftScan.supportsDiagnosticSourceLocations
}

@_spi(Testing) public func supportsLinkLibraries() throws -> Bool {
guard let swiftScan = swiftScanLibInstance else {
fatalError("Attempting to query supported scanner API with no scanner instance.")
}
return swiftScan.supportsLinkLibraries
}

@_spi(Testing) public func getScannerDiagnostics() throws -> [ScannerDiagnosticPayload]? {
guard let swiftScan = swiftScanLibInstance else {
fatalError("Attempting to reset scanner cache with no scanner instance.")
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,12 @@ extension Driver {
commandLine.appendFlag("-frontend-parseable-output")
}

// If explicit auto-linking is enabled, ensure that compiler tasks do not produce
// auto-link load commands in resulting object files.
if parsedOptions.hasArgument(.explicitAutoLinking) {
commandLine.appendFlag(.disableAllAutolinking)
}

savedUnknownDriverFlagsForSwiftFrontend.forEach {
commandLine.appendFlag($0)
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftDriver/Jobs/LinkJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ extension Driver {
targetInfo: frontendTargetInfo
)

if parsedOptions.hasArgument(.explicitAutoLinking) {
try explicitDependencyBuildPlanner?.getLinkLibraryLoadCommandFlags(&commandLine)
}

return Job(
moduleName: moduleOutputInfo.name,
kind: .link,
Expand Down
25 changes: 25 additions & 0 deletions Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ private extension SwiftScan {
directDependencies = nil
}

var linkLibraries: [LinkLibraryInfo] = []
if supportsLinkLibraries {
let linkLibrarySetRefOrNull = api.swiftscan_module_info_get_link_libraries(moduleInfoRef)
guard let linkLibrarySetRef = linkLibrarySetRefOrNull else {
throw DependencyScanningError.missingField("dependency_graph.link_libraries")
}
// Turn the `swiftscan_dependency_set_t` into an array of `swiftscan_dependency_info_t`
// references we can iterate through in order to construct `ModuleInfo` objects.
let linkLibraryRefArray = Array(UnsafeBufferPointer(start: linkLibrarySetRef.pointee.link_libraries,
count: Int(linkLibrarySetRef.pointee.count)))
for linkLibraryRefOrNull in linkLibraryRefArray {
guard let linkLibraryRef = linkLibraryRefOrNull else {
throw DependencyScanningError.missingField("dependency_set_t.link_libraries[_]")
}
linkLibraries.append(try constructLinkLibrayInfo(from: linkLibraryRef))
}
}

guard let moduleDetailsRef = api.swiftscan_module_info_get_details(moduleInfoRef) else {
throw DependencyScanningError.missingField("modules[\(moduleId)].details")
}
Expand All @@ -128,9 +146,16 @@ private extension SwiftScan {

return (moduleId, ModuleInfo(modulePath: modulePath, sourceFiles: sourceFiles,
directDependencies: directDependencies,
linkLibraries: linkLibraries,
details: details))
}

func constructLinkLibrayInfo(from linkLibraryInfoRef: swiftscan_link_library_info_t) throws -> LinkLibraryInfo {
return LinkLibraryInfo(linkName: try toSwiftString(api.swiftscan_link_library_info_get_link_name(linkLibraryInfoRef)),
isFramework: api.swiftscan_link_library_info_get_is_framework(linkLibraryInfoRef),
shouldForceLoad: api.swiftscan_link_library_info_get_should_force_load(linkLibraryInfoRef))
}

/// From a reference to a binary-format module info details object info returned by libSwiftScan,
/// construct an instance of an `ModuleInfo`.Details as used by the driver.
/// The object returned by libSwiftScan is a union so ensure to execute dependency-specific queries.
Expand Down
12 changes: 12 additions & 0 deletions Sources/SwiftDriver/SwiftScan/SwiftScan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,13 @@ private extension String {
api.swiftscan_source_location_get_column_number != nil
}

@_spi(Testing) public var supportsLinkLibraries : Bool {
return api.swiftscan_module_info_get_link_libraries != nil &&
api.swiftscan_link_library_info_get_link_name != nil &&
api.swiftscan_link_library_info_get_is_framework != nil &&
api.swiftscan_link_library_info_get_should_force_load != nil
}

func serializeScannerCache(to path: AbsolutePath) {
api.swiftscan_scanner_cache_serialize(scanner,
path.description.cString(using: String.Encoding.utf8))
Expand Down Expand Up @@ -636,6 +643,11 @@ private extension swiftscan_functions_t {
self.swiftscan_source_location_get_line_number = try loadOptional("swiftscan_source_location_get_line_number")
self.swiftscan_source_location_get_column_number = try loadOptional("swiftscan_source_location_get_column_number")

self.swiftscan_module_info_get_link_libraries = try loadOptional("swiftscan_module_info_get_link_libraries")
self.swiftscan_link_library_info_get_link_name = try loadOptional("swiftscan_link_library_info_get_link_name")
self.swiftscan_link_library_info_get_is_framework = try loadOptional("swiftscan_link_library_info_get_is_framework")
self.swiftscan_link_library_info_get_should_force_load = try loadOptional("swiftscan_link_library_info_get_should_force_load")

// Swift Overlay Dependencies
self.swiftscan_swift_textual_detail_get_swift_overlay_dependencies =
try loadOptional("swiftscan_swift_textual_detail_get_swift_overlay_dependencies")
Expand Down
Loading