Skip to content

Commit ae9e0ed

Browse files
committed
Add initial WebAssembly toolchain implementation
1 parent 750869d commit ae9e0ed

File tree

8 files changed

+414
-17
lines changed

8 files changed

+414
-17
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,12 @@ add_library(SwiftDriver
6969
Jobs/Toolchain+LinkerSupport.swift
7070
Jobs/VerifyDebugInfoJob.swift
7171
Jobs/VerifyModuleInterfaceJob.swift
72+
Jobs/WebAssemblyToolchain+LinkerSupport.swift
7273

7374
Toolchains/DarwinToolchain.swift
7475
Toolchains/GenericUnixToolchain.swift
7576
Toolchains/Toolchain.swift
77+
Toolchains/WebAssemblyToolchain.swift
7678

7779
Utilities/Bits.swift
7880
Utilities/Bitstream.swift

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,6 +1844,8 @@ extension Triple {
18441844
return GenericUnixToolchain.self
18451845
case .freeBSD, .haiku:
18461846
return GenericUnixToolchain.self
1847+
case .wasi:
1848+
return WebAssemblyToolchain.self
18471849
case .win32:
18481850
fatalError("Windows target not supported yet")
18491851
default:

Sources/SwiftDriver/Jobs/AutolinkExtractJob.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
//===----------------------------------------------------------------------===//
1212
import TSCBasic
1313

14-
// On ELF platforms there's no built in autolinking mechanism, so we
14+
// On ELF/WASM platforms there's no built in autolinking mechanism, so we
1515
// pull the info we need from the .o files directly and pass them as an
1616
// argument input file to the linker.
1717
// FIXME: Also handle Cygwin and MinGW
1818
extension Driver {
1919
@_spi(Testing) public var isAutolinkExtractJobNeeded: Bool {
20-
targetTriple.objectFormat == .elf && lto == nil
20+
[.elf, .wasm].contains(targetTriple.objectFormat) && lto == nil
2121
}
2222

2323
mutating func autolinkExtractJob(inputs: [TypedVirtualPath]) throws -> Job? {

Sources/SwiftDriver/Jobs/Toolchain+LinkerSupport.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extension Toolchain {
2424
let resourceDirBase: VirtualPath
2525
if let resourceDir = parsedOptions.getLastArgument(.resourceDir) {
2626
resourceDirBase = try VirtualPath(path: resourceDir.asSingle)
27-
} else if !triple.isDarwin,
27+
} else if !triple.isDarwin && triple.os != .wasi,
2828
let sdk = parsedOptions.getLastArgument(.sdk),
2929
let sdkPath = try? VirtualPath(path: sdk.asSingle) {
3030
resourceDirBase = sdkPath
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//===--------------- WebAssemblyToolchain+LinkerSupport.swift -------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
import TSCBasic
13+
import SwiftOptions
14+
15+
extension WebAssemblyToolchain {
16+
public func addPlatformSpecificLinkerArgs(
17+
to commandLine: inout [Job.ArgTemplate],
18+
parsedOptions: inout ParsedOptions,
19+
linkerOutputType: LinkOutputType,
20+
inputs: [TypedVirtualPath],
21+
outputFile: VirtualPath,
22+
shouldUseInputFileList: Bool,
23+
lto: LTOKind?,
24+
sanitizers: Set<Sanitizer>,
25+
targetInfo: FrontendTargetInfo
26+
) throws -> AbsolutePath {
27+
let targetTriple = targetInfo.target.triple
28+
switch linkerOutputType {
29+
case .dynamicLibrary:
30+
throw Error.dynamicLibrariesUnsupportedForTarget(targetTriple.triple)
31+
case .executable:
32+
if !targetTriple.triple.isEmpty {
33+
commandLine.appendFlag("-target")
34+
commandLine.appendFlag(targetTriple.triple)
35+
}
36+
37+
// Select the linker to use.
38+
if let linkerArg = parsedOptions.getLastArgument(.useLd)?.asSingle {
39+
commandLine.appendFlag("-fuse-ld=\(linkerArg)")
40+
}
41+
42+
// Configure the toolchain.
43+
//
44+
// By default use the system `clang` to perform the link. We use `clang` for
45+
// the driver here because we do not wish to select a particular C++ runtime.
46+
// Furthermore, until C++ interop is enabled, we cannot have a dependency on
47+
// C++ code from pure Swift code. If linked libraries are C++ based, they
48+
// should properly link C++. In the case of static linking, the user can
49+
// explicitly specify the C++ runtime to link against. This is particularly
50+
// important for platforms like android where as it is a Linux platform, the
51+
// default C++ runtime is `libstdc++` which is unsupported on the target but
52+
// as the builds are usually cross-compiled from Linux, libstdc++ is going to
53+
// be present. This results in linking the wrong version of libstdc++
54+
// generating invalid binaries. It is also possible to use different C++
55+
// runtimes than the default C++ runtime for the platform (e.g. libc++ on
56+
// Windows rather than msvcprt). When C++ interop is enabled, we will need to
57+
// surface this via a driver flag. For now, opt for the simpler approach of
58+
// just using `clang` and avoid a dependency on the C++ runtime.
59+
var clangPath = try getToolPath(.clang)
60+
if let toolsDirPath = parsedOptions.getLastArgument(.toolsDirectory) {
61+
// FIXME: What if this isn't an absolute path?
62+
let toolsDir = try AbsolutePath(validating: toolsDirPath.asSingle)
63+
64+
// If there is a clang in the toolchain folder, use that instead.
65+
if let tool = lookupExecutablePath(filename: "clang", searchPaths: [toolsDir]) {
66+
clangPath = tool
67+
}
68+
}
69+
70+
guard !parsedOptions.hasArgument(.noStaticStdlib, .noStaticExecutable) else {
71+
throw Error.dynamicLibrariesUnsupportedForTarget(targetTriple.triple)
72+
}
73+
74+
let runtimePaths = try runtimeLibraryPaths(
75+
for: targetTriple,
76+
parsedOptions: &parsedOptions,
77+
sdkPath: targetInfo.sdkPath?.path,
78+
isShared: false
79+
)
80+
81+
let resourceDirPath = try computeResourceDirPath(
82+
for: targetTriple,
83+
parsedOptions: &parsedOptions,
84+
isShared: false
85+
)
86+
87+
let swiftrtPath = resourceDirPath
88+
.appending(
89+
components: targetTriple.archName, "swiftrt.o"
90+
)
91+
commandLine.appendPath(swiftrtPath)
92+
93+
let inputFiles: [Job.ArgTemplate] = inputs.compactMap { input in
94+
// Autolink inputs are handled specially
95+
if input.type == .autolink {
96+
return .responseFilePath(input.file)
97+
} else if input.type == .object {
98+
return .path(input.file)
99+
} else {
100+
return nil
101+
}
102+
}
103+
commandLine.append(contentsOf: inputFiles)
104+
105+
if let path = targetInfo.sdkPath?.path {
106+
commandLine.appendFlag("--sysroot")
107+
commandLine.appendPath(path)
108+
}
109+
110+
// Add the runtime library link paths.
111+
for path in runtimePaths {
112+
commandLine.appendFlag(.L)
113+
commandLine.appendPath(path)
114+
}
115+
116+
// Link the standard library.
117+
let linkFilePath: VirtualPath = resourceDirPath
118+
.appending(components: "static-executable-args.lnk")
119+
120+
guard try fileSystem.exists(linkFilePath) else {
121+
fatalError("\(linkFilePath) not found")
122+
}
123+
commandLine.append(.responseFilePath(linkFilePath))
124+
125+
// Explicitly pass the target to the linker
126+
commandLine.appendFlag("--target=\(targetTriple.triple)")
127+
128+
// Delegate to Clang for sanitizers. It will figure out the correct linker
129+
// options.
130+
guard sanitizers.isEmpty else {
131+
fatalError("WebAssembly does not support sanitizers, but a runtime library was found")
132+
}
133+
134+
guard !parsedOptions.hasArgument(.profileGenerate) else {
135+
throw Error.profilingUnsupportedForTarget(targetTriple.triple)
136+
}
137+
138+
// Run clang++ in verbose mode if "-v" is set
139+
try commandLine.appendLast(.v, from: &parsedOptions)
140+
141+
// These custom arguments should be right before the object file at the
142+
// end.
143+
try commandLine.append(
144+
contentsOf: parsedOptions.arguments(in: .linkerOption)
145+
)
146+
try commandLine.appendAllArguments(.Xlinker, from: &parsedOptions)
147+
try commandLine.appendAllArguments(.XclangLinker, from: &parsedOptions)
148+
149+
// This should be the last option, for convenience in checking output.
150+
commandLine.appendFlag(.o)
151+
commandLine.appendPath(outputFile)
152+
return clangPath
153+
case .staticLibrary:
154+
// We're using 'ar' as a linker
155+
commandLine.appendFlag("crs")
156+
commandLine.appendPath(outputFile)
157+
158+
commandLine.append(contentsOf: inputs.map { .path($0.file) })
159+
return try getToolPath(.staticLinker(lto))
160+
}
161+
}
162+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//===-------- WebAssemblyToolchain.swift - Swift WASM Toolchain -----------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
import TSCBasic
13+
import SwiftOptions
14+
15+
/// Toolchain for WebAssembly-based systems.
16+
@_spi(Testing) public final class WebAssemblyToolchain: Toolchain {
17+
@_spi(Testing) public enum Error: Swift.Error, DiagnosticData {
18+
case interactiveModeUnsupportedForTarget(String)
19+
case dynamicLibrariesUnsupportedForTarget(String)
20+
case sanitizersUnsupportedForTarget(String)
21+
case profilingUnsupportedForTarget(String)
22+
23+
public var description: String {
24+
switch self {
25+
case .interactiveModeUnsupportedForTarget(let triple):
26+
return "interactive mode is unsupported for target '\(triple)'; use 'swiftc' instead"
27+
case .dynamicLibrariesUnsupportedForTarget(let triple):
28+
return "dynamic libraries are unsupported for target '\(triple)'"
29+
case .sanitizersUnsupportedForTarget(let triple):
30+
return "sanitizers are unsupported for target '\(triple)'"
31+
case .profilingUnsupportedForTarget(let triple):
32+
return "profiling is unsupported for target '\(triple)'"
33+
}
34+
}
35+
}
36+
37+
public let env: [String: String]
38+
39+
/// The executor used to run processes used to find tools and retrieve target info.
40+
public let executor: DriverExecutor
41+
42+
/// The file system to use for queries.
43+
public let fileSystem: FileSystem
44+
45+
/// Doubles as path cache and point for overriding normal lookup
46+
private var toolPaths = [Tool: AbsolutePath]()
47+
48+
public let toolDirectory: AbsolutePath?
49+
50+
public init(env: [String: String], executor: DriverExecutor, fileSystem: FileSystem = localFileSystem, toolDirectory: AbsolutePath? = nil) {
51+
self.env = env
52+
self.executor = executor
53+
self.fileSystem = fileSystem
54+
self.toolDirectory = toolDirectory
55+
}
56+
57+
public func makeLinkerOutputFilename(moduleName: String, type: LinkOutputType) -> String {
58+
switch type {
59+
case .executable:
60+
return moduleName
61+
case .dynamicLibrary:
62+
// WASM doesn't support dynamic libraries yet, but we'll report the error later.
63+
return ""
64+
case .staticLibrary:
65+
return "lib\(moduleName).a"
66+
}
67+
}
68+
69+
/// Retrieve the absolute path for a given tool.
70+
public func getToolPath(_ tool: Tool) throws -> AbsolutePath {
71+
// Check the cache
72+
if let toolPath = toolPaths[tool] {
73+
return toolPath
74+
}
75+
let path = try lookupToolPath(tool)
76+
// Cache the path
77+
toolPaths[tool] = path
78+
return path
79+
}
80+
81+
private func lookupToolPath(_ tool: Tool) throws -> AbsolutePath {
82+
switch tool {
83+
case .swiftCompiler:
84+
return try lookup(executable: "swift-frontend")
85+
case .staticLinker(nil):
86+
return try lookup(executable: "ar")
87+
case .staticLinker(.llvmFull),
88+
.staticLinker(.llvmThin):
89+
return try lookup(executable: "llvm-ar")
90+
case .dynamicLinker:
91+
// FIXME: This needs to look in the tools_directory first.
92+
return try lookup(executable: "clang")
93+
case .clang:
94+
return try lookup(executable: "clang")
95+
case .swiftAutolinkExtract:
96+
return try lookup(executable: "swift-autolink-extract")
97+
case .dsymutil:
98+
return try lookup(executable: "dsymutil")
99+
case .lldb:
100+
return try lookup(executable: "lldb")
101+
case .dwarfdump:
102+
return try lookup(executable: "dwarfdump")
103+
case .swiftHelp:
104+
return try lookup(executable: "swift-help")
105+
}
106+
}
107+
108+
public func overrideToolPath(_ tool: Tool, path: AbsolutePath) {
109+
toolPaths[tool] = path
110+
}
111+
112+
public func defaultSDKPath(_ target: Triple?) throws -> AbsolutePath? {
113+
return nil
114+
}
115+
116+
public var shouldStoreInvocationInDebugInfo: Bool { false }
117+
118+
public func runtimeLibraryName(
119+
for sanitizer: Sanitizer,
120+
targetTriple: Triple,
121+
isShared: Bool
122+
) throws -> String {
123+
throw Error.sanitizersUnsupportedForTarget(targetTriple.triple)
124+
}
125+
126+
public func platformSpecificInterpreterEnvironmentVariables(env: [String : String], parsedOptions: inout ParsedOptions, sdkPath: VirtualPath?, targetTriple: Triple) throws -> [String : String] {
127+
throw Error.interactiveModeUnsupportedForTarget(targetTriple.triple)
128+
}
129+
}

Sources/SwiftDriver/Utilities/Triple+Platforms.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,15 @@ extension Triple {
289289
return "ps4"
290290
case .haiku:
291291
return "haiku"
292+
case .wasi:
293+
return "wasi"
292294

293295
// Explicitly spell out the remaining cases to force a compile error when
294296
// Triple updates
295297
case .ananas, .cloudABI, .dragonFly, .fuchsia, .kfreebsd, .lv2, .netbsd,
296298
.openbsd, .solaris, .minix, .rtems, .nacl, .cnk, .aix, .cuda, .nvcl,
297299
.amdhsa, .elfiamcu, .mesa3d, .contiki, .amdpal, .hermitcore, .hurd,
298-
.wasi, .emscripten:
300+
.emscripten:
299301
return nil
300302
}
301303
}

0 commit comments

Comments
 (0)