Skip to content

Commit 038e9e8

Browse files
WITOverlayGenerator: Support Embedded target build
1 parent 0f9ef18 commit 038e9e8

File tree

6 files changed

+374
-220
lines changed

6 files changed

+374
-220
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
166166
.testTarget(
167167
name: "WITOverlayGeneratorTests",
168168
dependencies: ["WITOverlayGenerator", "WasmKit", "WasmKitWASI"],
169-
exclude: ["Fixtures", "Compiled", "Generated"],
169+
exclude: ["Fixtures", "Compiled", "Generated", "EmbeddedSupport"],
170170
plugins: [.plugin(name: "GenerateOverlayForTesting")]
171171
),
172172
.plugin(

Sources/WITOverlayGenerator/GuestGenerators/GuestPrelude.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ let guestPrelude = """
44
// Generated by the wit-overlay-generator
55
66
#if arch(wasm32)
7+
#if canImport(WASILibc)
78
import WASILibc
9+
#endif
810
@_implementationOnly import _CabiShims
911
1012
fileprivate enum Prelude {
@@ -76,12 +78,14 @@ let guestPrelude = """
7678
_ array: [Element], elementSize: Int, elementAlignment: Int,
7779
storeElement: (Element, UnsafeMutableRawPointer) -> Void
7880
) -> (pointer: UInt, length: UInt) {
79-
let newBuffer = malloc(elementSize * array.count)
81+
let newBuffer = UnsafeMutableRawPointer.allocate(
82+
byteCount: elementSize * array.count, alignment: elementAlignment
83+
)
8084
for (i, element) in array.enumerated() {
81-
storeElement(element, newBuffer!.advanced(by: i * elementSize))
85+
storeElement(element, newBuffer.advanced(by: i * elementSize))
8286
}
8387
return (
84-
newBuffer.map { UInt(bitPattern: $0) } ?? 0, UInt(array.count)
88+
UInt(bitPattern: newBuffer), UInt(array.count)
8589
)
8690
}
8791
@@ -129,9 +133,12 @@ let guestPrelude = """
129133
130134
// TODO: use `@_expose(wasm)`
131135
// NOTE: multiple objects in a component can have cabi_realloc definition so use `@_weakLinked` here.
136+
#if canImport(WASILibc)
132137
@_weakLinked
133138
@_cdecl("cabi_realloc")
134-
func cabi_realloc(old: UnsafeMutableRawPointer, oldSize: UInt, align: UInt, newSize: UInt) -> UnsafeMutableRawPointer {
135-
realloc(old, Int(newSize))
139+
func cabi_realloc(old: UnsafeMutableRawPointer?, oldSize: UInt, align: UInt, newSize: UInt) -> UnsafeMutableRawPointer? {
140+
@_extern(c, "realloc") func realloc(_ old: UnsafeMutableRawPointer?, _ newSize: Int) -> UnsafeMutableRawPointer?
141+
return realloc(old, Int(newSize))
136142
}
143+
#endif
137144
"""
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// This file is used to provide a "just enough" implementation of some C standard library functions
2+
// required by Embedded target builds.
3+
#include <stdint.h>
4+
#include <stddef.h>
5+
#define WASM_PAGE_SIZE 0x10000
6+
7+
size_t __builtin_wasm_memory_grow(int32_t index, size_t delta);
8+
void *__builtin_memcpy(void *dest, const void *src, size_t n);
9+
10+
static void* alignedAlloc(size_t alignment, size_t size) {
11+
size_t basePageSize = __builtin_wasm_memory_grow(0, (size + 0xffff) / 0x10000);
12+
if (basePageSize == (size_t)-1) {
13+
return NULL;
14+
}
15+
size_t base = basePageSize * WASM_PAGE_SIZE;
16+
base = (base + alignment - 1) & -alignment;
17+
return (void*)base;
18+
}
19+
20+
/// NOTE: always allocates a new memory page by `memory.grow`
21+
int posix_memalign(void** memptr, size_t alignment, size_t size) {
22+
void* ptr = alignedAlloc(alignment, size);
23+
if (ptr == NULL) {
24+
return -1;
25+
}
26+
*memptr = ptr;
27+
return 0;
28+
}
29+
30+
/// NOTE: always allocates a new memory page by `memory.grow` and copies the old data
31+
void* cabi_realloc(void* old, size_t oldSize, size_t align, size_t newSize) {
32+
if (old != NULL) {
33+
void* new = alignedAlloc(align, newSize);
34+
if (new != NULL) {
35+
__builtin_memcpy(new, old, oldSize < newSize ? oldSize : newSize);
36+
}
37+
return new;
38+
} else {
39+
return alignedAlloc(align, newSize);
40+
}
41+
}
42+
43+
void *memmove(void *dest, const void *src, size_t n) {
44+
return __builtin_memcpy(dest, src, n);
45+
}
46+
47+
void *memcpy(void *dest, const void *src, size_t n) {
48+
// `memory.copy` is safe even if `src` and `dest` overlap
49+
// > Copying takes place as if an intermediate buffer were used, allowing the destination and source to overlap.
50+
// > https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md
51+
return __builtin_memcpy(dest, src, n);
52+
}
53+
54+
/// NOTE: does nothing as we don't manage memory chunks
55+
void free(void *ptr) {}
56+
57+
/// NOTE: just returns the input character as is, no output is produced
58+
int putchar(int c) {
59+
return c;
60+
}
61+
62+
/// NOTE: fills the buffer with a constant value
63+
void arc4random_buf(void *buf, size_t n) {
64+
for (size_t i = 0; i < n; i++) {
65+
((uint8_t *)buf)[i] = (uint8_t)42;
66+
}
67+
}

Tests/WITOverlayGeneratorTests/Runtime/RuntimeSmokeTests.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import WIT
55
class RuntimeSmokeTests: XCTestCase {
66
func testCallExportByGuest() throws {
77
var harness = try RuntimeTestHarness(fixture: "Smoke")
8-
let (runtime, instance) = try harness.build(link: SmokeTestWorld.link(_:))
9-
let component = SmokeTestWorld(moduleInstance: instance)
10-
_ = try component.hello(runtime: runtime)
8+
try harness.build(link: SmokeTestWorld.link(_:)) { (runtime, instance) in
9+
let component = SmokeTestWorld(moduleInstance: instance)
10+
_ = try component.hello(runtime: runtime)
11+
}
1112
}
1213
}

Tests/WITOverlayGeneratorTests/Runtime/RuntimeTestHarness.swift

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import XCTest
1717
struct RuntimeTestHarness {
1818
struct Configuration: Codable {
1919
let swiftExecutablePath: URL
20+
let wasiSwiftSDK: URL
2021

2122
var swiftCompilerExecutablePath: URL {
2223
swiftExecutablePath.deletingLastPathComponent().appendingPathComponent("swiftc")
@@ -53,9 +54,11 @@ struct RuntimeTestHarness {
5354
throw XCTSkip("""
5455
Please create 'Tests/default.json' with this or similar contents:
5556
{
56-
"swiftExecutablePath": "/Library/Developer/Toolchains/swift-wasm-5.8.0-RELEASE.xctoolchain/usr/bin/swift"
57+
"swiftExecutablePath": "$HOME/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-07-08-a.xctoolchain/usr/bin/swift",
58+
"wasiSwiftSDK": "$HOME/Library/org.swift.swiftpm/swift-sdks/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle/DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi/wasm32-unknown-wasi"
5759
}
5860
61+
5962
or specify `configuration` parameter in your test code.
6063
""")
6164
}
@@ -116,32 +119,70 @@ struct RuntimeTestHarness {
116119
}
117120

118121
/// Build up WebAssembly module from the fixture and instantiate WasmKit runtime with the module.
119-
mutating func build(link: (inout [String: HostModule]) -> Void) throws -> (Runtime, ModuleInstance) {
120-
defer { cleanupTemporaryFiles() }
121-
let compiled = try compile(inputFiles: collectGuestInputFiles())
122-
123-
let wasi = try WASIBridgeToHost(args: [compiled.path])
124-
var hostModules: [String: HostModule] = wasi.hostModules
125-
link(&hostModules)
126-
127-
let module = try parseWasm(filePath: .init(compiled.path))
128-
let runtime = Runtime(hostModules: hostModules)
129-
let instance = try runtime.instantiate(module: module)
130-
return (runtime, instance)
122+
mutating func build(
123+
link: (inout [String: HostModule]) -> Void,
124+
run: (Runtime, ModuleInstance) throws -> Void
125+
) throws {
126+
for compile in [compileForEmbedded, compileForWASI] {
127+
defer { cleanupTemporaryFiles() }
128+
let compiled = try compile(collectGuestInputFiles())
129+
130+
let wasi = try WASIBridgeToHost(args: [compiled.path])
131+
var hostModules: [String: HostModule] = wasi.hostModules
132+
link(&hostModules)
133+
134+
let module = try parseWasm(filePath: .init(compiled.path))
135+
let runtime = Runtime(hostModules: hostModules)
136+
let instance = try runtime.instantiate(module: module)
137+
try run(runtime, instance)
138+
}
139+
}
140+
141+
func compileForEmbedded(inputFiles: [String]) throws -> URL {
142+
let embeddedSupport = Self.testsDirectory.appendingPathComponent("EmbeddedSupport")
143+
144+
let libc = embeddedSupport.appendingPathComponent("MinLibc.c")
145+
let libcObjFile = Self.testsDirectory
146+
.appendingPathComponent("Compiled")
147+
.appendingPathComponent("MinLibc.o")
148+
FileManager.default.createFile(atPath: libcObjFile.deletingLastPathComponent().path, contents: nil, attributes: nil)
149+
try compileToObj(cInputFiles: [libc.path], arguments: [
150+
"-target", "wasm32-unknown-none-wasm",
151+
// Enable bulk memory operations for `realloc`
152+
"-mbulk-memory",
153+
], outputPath: libcObjFile)
154+
155+
return try compile(inputFiles: inputFiles + [libcObjFile.path], arguments: [
156+
"-target", "wasm32-unknown-none-wasm",
157+
"-enable-experimental-feature", "Embedded",
158+
"-wmo", "-Xcc", "-fdeclspec",
159+
"-Xfrontend", "-disable-stack-protector",
160+
"-Xlinker", "--no-entry", "-Xclang-linker", "-nostdlib",
161+
])
162+
}
163+
164+
func compileForWASI(inputFiles: [String]) throws -> URL {
165+
return try compile(inputFiles: inputFiles, arguments: [
166+
"-target", "wasm32-unknown-wasi",
167+
"-static-stdlib",
168+
"-Xclang-linker", "-mexec-model=reactor",
169+
"-resource-dir", configuration.wasiSwiftSDK.appendingPathComponent( "/swift.xctoolchain/usr/lib/swift_static").path,
170+
"-sdk", configuration.wasiSwiftSDK.appendingPathComponent("WASI.sdk").path,
171+
"-Xclang-linker", "-resource-dir",
172+
"-Xclang-linker", configuration.wasiSwiftSDK.appendingPathComponent("swift.xctoolchain/usr/lib/swift_static/clang").path
173+
])
131174
}
132175

133176
/// Compile the given input Swift source files into core Wasm module
134-
func compile(inputFiles: [String]) throws -> URL {
177+
func compile(inputFiles: [String], arguments: [String]) throws -> URL {
135178
let outputPath = Self.testsDirectory
136179
.appendingPathComponent("Compiled")
137180
.appendingPathComponent("\(fixtureName).core.wasm")
138181
try fileManager.createDirectory(at: outputPath.deletingLastPathComponent(), withIntermediateDirectories: true)
139182
let process = Process()
140183
process.launchPath = configuration.swiftCompilerExecutablePath.path
141-
process.arguments = inputFiles + [
142-
"-target", "wasm32-unknown-wasi",
184+
process.arguments = inputFiles + arguments + [
143185
"-I\(Self.sourcesDirectory.appendingPathComponent("_CabiShims").appendingPathComponent("include").path)",
144-
"-Xclang-linker", "-mexec-model=reactor",
145186
// TODO: Remove `--export-all` linker option by replacing `@_cdecl` with `@_expose(wasm)`
146187
"-Xlinker", "--export-all",
147188
"-o", outputPath.path
@@ -161,7 +202,9 @@ struct RuntimeTestHarness {
161202
"""
162203
}.joined(separator: "\n====================\n")
163204
let message = """
164-
Failed to execute \(process.arguments?.joined(separator: " ") ?? " ")
205+
Failed to execute \(
206+
([configuration.swiftCompilerExecutablePath.path] + (process.arguments ?? [])).joined(separator: " ")
207+
)
165208
Exit status: \(process.terminationStatus)
166209
Input files:
167210
\(fileContents)
@@ -170,4 +213,28 @@ struct RuntimeTestHarness {
170213
}
171214
return outputPath
172215
}
216+
217+
/// Compile the given input Swift source files into an object file
218+
func compileToObj(cInputFiles: [String], arguments: [String], outputPath: URL) throws {
219+
let process = Process()
220+
// Assume that clang is placed alongside swiftc
221+
process.launchPath = configuration.swiftCompilerExecutablePath
222+
.deletingLastPathComponent().appendingPathComponent("clang").path
223+
process.arguments = cInputFiles + arguments + [
224+
"-c",
225+
"-o", outputPath.path
226+
]
227+
process.environment = [:]
228+
process.launch()
229+
process.waitUntilExit()
230+
guard process.terminationStatus == 0 else {
231+
let message = """
232+
Failed to execute \(
233+
([configuration.swiftCompilerExecutablePath.path] + (process.arguments ?? [])).joined(separator: " ")
234+
)
235+
Exit status: \(process.terminationStatus)
236+
"""
237+
throw Error(description: message)
238+
}
239+
}
173240
}

0 commit comments

Comments
 (0)