Skip to content
Open
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
8 changes: 7 additions & 1 deletion Sources/SwiftDriver/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3560,7 +3560,7 @@ extension Triple {
return GenericUnixToolchain.self
case .freeBSD, .haiku, .openbsd:
return GenericUnixToolchain.self
case .wasi:
case .wasi, .emscripten:
return WebAssemblyToolchain.self
case .win32:
return WindowsToolchain.self
Expand Down Expand Up @@ -3666,6 +3666,12 @@ extension Driver {
compilerExecutableDir: compilerExecutableDir,
toolDirectory: toolDir)

// Pass the target triple to WebAssemblyToolchain for output filename decisions
if let wasmToolchain = toolchain as? WebAssemblyToolchain,
let target = explicitTarget {
wasmToolchain.targetTriple = target
}

let frontendOverride = try FrontendOverride(&parsedOptions, diagnosticsEngine)
return (toolchain, frontendOverride.prefixArgs)
}
Expand Down
164 changes: 108 additions & 56 deletions Sources/SwiftDriver/Jobs/WebAssemblyToolchain+LinkerSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,50 +33,71 @@ extension WebAssemblyToolchain {
case .dynamicLibrary:
throw Error.dynamicLibrariesUnsupportedForTarget(targetTriple.triple)
case .executable:
if !targetTriple.triple.isEmpty {
commandLine.appendFlag("-target")
commandLine.appendFlag(targetTriple.triple)
}
let isEmscripten = targetTriple.os == .emscripten

// Select the linker to use.
if let linkerArg = parsedOptions.getLastArgument(.useLd)?.asSingle {
commandLine.appendFlag("-fuse-ld=\(linkerArg)")
}
// emcc already targets Emscripten, so skip --target and linker selection flags.
if !isEmscripten {
if !targetTriple.triple.isEmpty {
commandLine.appendFlag("-target")
commandLine.appendFlag(targetTriple.triple)
}

// Select the linker to use.
if let linkerArg = parsedOptions.getLastArgument(.useLd)?.asSingle {
commandLine.appendFlag("-fuse-ld=\(linkerArg)")
}

if let arg = parsedOptions.getLastArgument(.ldPath)?.asSingle {
commandLine.append(.joinedOptionAndPath("--ld-path=", try VirtualPath(path: arg)))
if let arg = parsedOptions.getLastArgument(.ldPath)?.asSingle {
commandLine.append(.joinedOptionAndPath("--ld-path=", try VirtualPath(path: arg)))
}
}

// Configure the toolchain.
//
// By default use the system `clang` to perform the link. We use `clang` for
// the driver here because we do not wish to select a particular C++ runtime.
// Furthermore, until C++ interop is enabled, we cannot have a dependency on
// C++ code from pure Swift code. If linked libraries are C++ based, they
// should properly link C++. In the case of static linking, the user can
// explicitly specify the C++ runtime to link against. This is particularly
// important for platforms like android where as it is a Linux platform, the
// default C++ runtime is `libstdc++` which is unsupported on the target but
// as the builds are usually cross-compiled from Linux, libstdc++ is going to
// be present. This results in linking the wrong version of libstdc++
// generating invalid binaries. It is also possible to use different C++
// runtimes than the default C++ runtime for the platform (e.g. libc++ on
// Windows rather than msvcprt). When C++ interop is enabled, we will need to
// surface this via a driver flag. For now, opt for the simpler approach of
// just using `clang` and avoid a dependency on the C++ runtime.
var clangPath = try getToolPath(.clang)
if let toolsDirPath = parsedOptions.getLastArgument(.toolsDirectory) {
// FIXME: What if this isn't an absolute path?
let toolsDir = try AbsolutePath(validating: toolsDirPath.asSingle)

// If there is a clang in the toolchain folder, use that instead.
if let tool = lookupExecutablePath(filename: "clang", searchPaths: [toolsDir]) {
clangPath = tool
// For Emscripten targets, use `emcc` (the Emscripten compiler driver) as the
// linker. emcc wraps wasm-ld and also generates JavaScript runtime glue.
//
// For other WebAssembly targets (WASI etc), use `clang` to perform the link.
// We use `clang` for the driver here because we do not wish to select a
// particular C++ runtime. Furthermore, until C++ interop is enabled, we
// cannot have a dependency on C++ code from pure Swift code. If linked
// libraries are C++ based, they should properly link C++. In the case of
// static linking, the user can explicitly specify the C++ runtime to link
// against. This is particularly important for platforms like android where as
// it is a Linux platform, the default C++ runtime is `libstdc++` which is
// unsupported on the target but as the builds are usually cross-compiled from
// Linux, libstdc++ is going to be present. This results in linking the wrong
// version of libstdc++ generating invalid binaries. It is also possible to
// use different C++ runtimes than the default C++ runtime for the platform
// (e.g. libc++ on Windows rather than msvcprt). When C++ interop is enabled,
// we will need to surface this via a driver flag. For now, opt for the simpler
// approach of just using `clang` and avoid a dependency on the C++ runtime.
var linkerPath: AbsolutePath
if isEmscripten {
linkerPath = try getToolPath(.emcc)
if let toolsDirPath = parsedOptions.getLastArgument(.toolsDirectory) {
let toolsDir = try AbsolutePath(validating: toolsDirPath.asSingle)
if let tool = lookupExecutablePath(filename: "emcc", searchPaths: [toolsDir]) {
linkerPath = tool
}
// emcc manages its own tool search paths and doesn't support
// the -B flag like clang does, so don't forward it.
}
} else {
linkerPath = try getToolPath(.clang)
if let toolsDirPath = parsedOptions.getLastArgument(.toolsDirectory) {
// FIXME: What if this isn't an absolute path?
let toolsDir = try AbsolutePath(validating: toolsDirPath.asSingle)

// Look for binutils in the toolchain folder.
commandLine.appendFlag("-B")
commandLine.appendPath(toolsDir)
// If there is a clang in the toolchain folder, use that instead.
if let tool = lookupExecutablePath(filename: "clang", searchPaths: [toolsDir]) {
linkerPath = tool
}

// Look for binutils in the toolchain folder.
commandLine.appendFlag("-B")
commandLine.appendPath(toolsDir)
}
}

guard !parsedOptions.hasArgument(.noStaticStdlib, .noStaticExecutable) else {
Expand Down Expand Up @@ -114,13 +135,16 @@ extension WebAssemblyToolchain {
}
commandLine.append(contentsOf: inputFiles)

// Prefer -sysroot as the native sysroot path if provided.
if let sysroot = parsedOptions.getLastArgument(.sysroot)?.asSingle {
commandLine.appendFlag("--sysroot")
try commandLine.appendPath(VirtualPath(path: sysroot))
} else if let path = targetInfo.sdkPath?.path {
commandLine.appendFlag("--sysroot")
commandLine.appendPath(VirtualPath.lookup(path))
// emcc manages its own sysroot, so skip --sysroot for Emscripten.
if !isEmscripten {
// Prefer -sysroot as the native sysroot path if provided.
if let sysroot = parsedOptions.getLastArgument(.sysroot)?.asSingle {
commandLine.appendFlag("--sysroot")
try commandLine.appendPath(VirtualPath(path: sysroot))
} else if let path = targetInfo.sdkPath?.path {
commandLine.appendFlag("--sysroot")
commandLine.appendPath(VirtualPath.lookup(path))
}
}

// Add the runtime library link paths.
Expand All @@ -135,7 +159,9 @@ extension WebAssemblyToolchain {
let embeddedLibrariesPath: VirtualPath = runtimeResourcePath.appending(
components: "embedded", targetTriple.triple
)
commandLine.append(.flag("-Xlinker"))
if !isEmscripten {
commandLine.append(.flag("-Xlinker"))
}
commandLine.append(.joinedOptionAndPath("-L", embeddedLibrariesPath))
} else {
// Link the standard library and dependencies.
Expand All @@ -153,8 +179,10 @@ extension WebAssemblyToolchain {
commandLine.appendFlag(optArg)
}

// Explicitly pass the target to the linker
commandLine.appendFlag("--target=\(targetTriple.triple)")
// Explicitly pass the target to the linker (not needed for emcc)
if !isEmscripten {
commandLine.appendFlag("--target=\(targetTriple.triple)")
}

// WebAssembly doesn't reserve low addresses as its ABI. But without
// "extra inhabitants" of the pointer representation, runtime performance
Expand All @@ -165,18 +193,31 @@ extension WebAssemblyToolchain {
// synchronized with `SWIFT_ABI_WASM32_LEAST_VALID_POINTER` defined in
// apple/swift's runtime library.
let SWIFT_ABI_WASM32_LEAST_VALID_POINTER = 4096
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("--global-base=\(SWIFT_ABI_WASM32_LEAST_VALID_POINTER)")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("--table-base=\(SWIFT_ABI_WASM32_LEAST_VALID_POINTER)")
if isEmscripten {
// Use emcc settings instead of raw wasm-ld flags. Setting
// -sGLOBAL_BASE prevents emcc from auto-enabling --stack-first,
// which would place the stack at address 0 and violate Swift's
// assumption that addresses below LEAST_VALID_POINTER are unused.
commandLine.appendFlag("-sGLOBAL_BASE=\(SWIFT_ABI_WASM32_LEAST_VALID_POINTER)")
commandLine.appendFlag("-sTABLE_BASE=\(SWIFT_ABI_WASM32_LEAST_VALID_POINTER)")
} else {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("--global-base=\(SWIFT_ABI_WASM32_LEAST_VALID_POINTER)")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("--table-base=\(SWIFT_ABI_WASM32_LEAST_VALID_POINTER)")
}

// Set slightly higher than the default (64K) stack size so that basic
// workflows like Swift Testing can run within this limited stack space.
let SWIFT_WASM_DEFAULT_STACK_SIZE = 1024 * 128
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("-z")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("stack-size=\(SWIFT_WASM_DEFAULT_STACK_SIZE)")
if isEmscripten {
commandLine.appendFlag("-sSTACK_SIZE=\(SWIFT_WASM_DEFAULT_STACK_SIZE)")
} else {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("-z")
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag("stack-size=\(SWIFT_WASM_DEFAULT_STACK_SIZE)")
}

// Delegate to Clang for sanitizers. It will figure out the correct linker
// options.
Expand Down Expand Up @@ -215,12 +256,23 @@ extension WebAssemblyToolchain {
from: &parsedOptions
)
addLinkedLibArgs(to: &commandLine, parsedOptions: &parsedOptions)
try addExtraClangLinkerArgs(to: &commandLine, parsedOptions: &parsedOptions)
if isEmscripten {
// emcc supports -Xlinker for passing flags to wasm-ld, but
// -Xclang-linker is clang-specific and must not be forwarded.
for linkerOpt in parsedOptions.arguments(for: .Xlinker) {
commandLine.appendFlag(.Xlinker)
commandLine.appendFlag(linkerOpt.argument.asSingle)
}
// -Xemcc-linker passes arguments directly to emcc.
try commandLine.appendAllArguments(.XemccLinker, from: &parsedOptions)
} else {
try addExtraClangLinkerArgs(to: &commandLine, parsedOptions: &parsedOptions)
}

// This should be the last option, for convenience in checking output.
commandLine.appendFlag(.o)
commandLine.appendPath(outputFile)
return try resolvedTool(.clang, pathOverride: clangPath)
return try resolvedTool(.clang, pathOverride: linkerPath)
case .staticLibrary:
// We're using 'ar' as a linker
commandLine.appendFlag("crs")
Expand Down
2 changes: 2 additions & 0 deletions Sources/SwiftDriver/Toolchains/DarwinToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public final class DarwinToolchain: Toolchain {
return try lookup(executable: "swift-help")
case .swiftAPIDigester:
return try lookup(executable: "swift-api-digester")
case .emcc:
return try lookup(executable: "emcc")
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/SwiftDriver/Toolchains/GenericUnixToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public final class GenericUnixToolchain: Toolchain {
return try lookup(executable: "swift-help")
case .swiftAPIDigester:
return try lookup(executable: "swift-api-digester")
case .emcc:
return try lookup(executable: "emcc")
}
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/SwiftDriver/Toolchains/Toolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ public enum Tool: Hashable {
case dwarfdump
case swiftHelp
case swiftAPIDigester
case emcc

/// Returns a value indicating whether or not the tool supports passing arguments via response
/// files.
public func supportsResponseFiles(in toolchain: Toolchain) -> Bool {
switch self {
case .swiftCompiler, .clang, .clangxx, .swiftAutolinkExtract, .swiftAPIDigester:
case .swiftCompiler, .clang, .clangxx, .swiftAutolinkExtract, .swiftAPIDigester, .emcc:
return true

case .dsymutil, .lldb, .dwarfdump, .swiftHelp:
Expand Down
26 changes: 26 additions & 0 deletions Sources/SwiftDriver/Toolchains/WebAssemblyToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import SwiftOptions

import struct TSCBasic.AbsolutePath
import struct TSCBasic.Diagnostic
import class TSCBasic.DiagnosticsEngine
import protocol TSCBasic.DiagnosticData
import protocol TSCBasic.FileSystem
import var TSCBasic.localFileSystem
Expand Down Expand Up @@ -60,6 +62,8 @@ public final class WebAssemblyToolchain: Toolchain {

public let dummyForTestingObjectFormat = Triple.ObjectFormat.wasm

public var targetTriple: Triple?

public init(env: ProcessEnvironmentBlock, executor: DriverExecutor, fileSystem: FileSystem = localFileSystem, compilerExecutableDir: AbsolutePath? = nil, toolDirectory: AbsolutePath? = nil) {
self.env = env
self.executor = executor
Expand All @@ -71,6 +75,9 @@ public final class WebAssemblyToolchain: Toolchain {
public func makeLinkerOutputFilename(moduleName: String, type: LinkOutputType) -> String {
switch type {
case .executable:
if targetTriple?.os == .emscripten {
return "\(moduleName).js"
}
return moduleName
case .dynamicLibrary:
// Wasm doesn't support dynamic libraries yet, but we'll report the error later.
Expand Down Expand Up @@ -123,6 +130,8 @@ public final class WebAssemblyToolchain: Toolchain {
return try lookup(executable: "swift-help")
case .swiftAPIDigester:
return try lookup(executable: "swift-api-digester")
case .emcc:
return try lookup(executable: "emcc")
}
}

Expand Down Expand Up @@ -161,4 +170,21 @@ public final class WebAssemblyToolchain: Toolchain {
targetInfo: FrontendTargetInfo) throws -> ProcessEnvironmentBlock {
throw Error.interactiveModeUnsupportedForTarget(targetInfo.target.triple.triple)
}

public func validateArgs(_ parsedOptions: inout ParsedOptions,
targetTriple: Triple, targetVariantTriple: Triple?,
compilerOutputType: FileType?,
diagnosticsEngine: DiagnosticsEngine) throws {
if targetTriple.os == .emscripten {
for arg in parsedOptions.arguments(for: .XclangLinker) {
diagnosticsEngine.emit(
.warning_xclang_linker_unsupported_for_emscripten(arg.argument.asSingle))
}
} else {
for arg in parsedOptions.arguments(for: .XemccLinker) {
diagnosticsEngine.emit(
.warning_xemcc_linker_unsupported_for_non_emscripten(arg.argument.asSingle))
}
}
}
}
2 changes: 2 additions & 0 deletions Sources/SwiftDriver/Toolchains/WindowsToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ extension WindowsToolchain.ToolchainValidationError {
return try lookup(executable: "llvm-dwarfdump")
case .swiftHelp:
return try lookup(executable: "swift-help")
case .emcc:
return try lookup(executable: "emcc")
}
}

Expand Down
8 changes: 8 additions & 0 deletions Sources/SwiftDriver/Utilities/Diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,12 @@ extension Diagnostic.Message {
static var warning_ignoring_opt_record_path_with_file_map: Diagnostic.Message {
.warning("ignoring '-save-optimization-record-path' because output file map contains optimization record entries")
}

static func warning_xclang_linker_unsupported_for_emscripten(_ value: String) -> Diagnostic.Message {
.warning("'-Xclang-linker \(value)' is not supported for Emscripten targets; use '-Xemcc-linker' to pass flags to emcc")
}

static func warning_xemcc_linker_unsupported_for_non_emscripten(_ value: String) -> Diagnostic.Message {
.warning("'-Xemcc-linker \(value)' is only supported for Emscripten targets")
}
}
4 changes: 3 additions & 1 deletion Sources/SwiftDriver/Utilities/Triple+Platforms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -404,14 +404,16 @@ extension Triple {
return "haiku"
case .wasi:
return "wasi"
case .emscripten:
return "emscripten"
case .noneOS:
return nil

// Explicitly spell out the remaining cases to force a compile error when
// Triple updates
case .ananas, .cloudABI, .dragonFly, .fuchsia, .kfreebsd, .lv2, .netbsd,
.solaris, .minix, .rtems, .nacl, .cnk, .aix, .cuda, .nvcl, .amdhsa,
.elfiamcu, .mesa3d, .contiki, .amdpal, .hermitcore, .hurd, .emscripten:
.elfiamcu, .mesa3d, .contiki, .amdpal, .hermitcore, .hurd:
return nil
}
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/SwiftOptions/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ extension Option {
public static let Wwarning: Option = Option("-Wwarning", .separate, attributes: [.helpHidden, .frontend], metaVar: "<diagnostic_group>", helpText: "Treat this warning group as warning", group: .warningTreating)
public static let Xcc: Option = Option("-Xcc", .separate, attributes: [.frontend, .synthesizeInterface], metaVar: "<arg>", helpText: "Pass <arg> to the C/C++/Objective-C compiler")
public static let XclangLinker: Option = Option("-Xclang-linker", .separate, attributes: [.helpHidden], metaVar: "<arg>", helpText: "Pass <arg> to Clang when it is used for linking.")
public static let XemccLinker: Option = Option("-Xemcc-linker", .separate, attributes: [.helpHidden], metaVar: "<arg>", helpText: "Pass <arg> to emcc when it is used for linking.")
public static let Xfrontend: Option = Option("-Xfrontend", .separate, attributes: [.helpHidden], metaVar: "<arg>", helpText: "Pass <arg> to the Swift frontend")
public static let Xlinker: Option = Option("-Xlinker", .separate, attributes: [.doesNotAffectIncrementalBuild], helpText: "Specifies an option which should be passed to the linker")
public static let Xllvm: Option = Option("-Xllvm", .separate, attributes: [.helpHidden, .frontend], metaVar: "<arg>", helpText: "Pass <arg> to LLVM.")
Expand Down Expand Up @@ -2053,6 +2054,7 @@ extension Option {
Option.Wwarning,
Option.Xcc,
Option.XclangLinker,
Option.XemccLinker,
Option.Xfrontend,
Option.Xlinker,
Option.Xllvm,
Expand Down
Loading
Loading