From 053d9d13f1c65f896ee557ad112a5c6b8df306c8 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 4 Aug 2025 16:17:19 +0100 Subject: [PATCH 01/12] Add `DOMRefTypes` example --- DOMRefTypes/.editorconfig | 3 + DOMRefTypes/.sourcekit-lsp/config.json | 5 ++ DOMRefTypes/Package.swift | 37 ++++++++++ DOMRefTypes/Sources/RefsTest/Entrypoint.swift | 9 +++ .../Sources/RefsTest/SwiftBindings.swift | 73 +++++++++++++++++++ DOMRefTypes/Sources/externref/bridge.c | 63 ++++++++++++++++ DOMRefTypes/Sources/externref/dom.h | 44 +++++++++++ DOMRefTypes/Sources/externref/include/refs.h | 28 +++++++ DOMRefTypes/index.html | 29 ++++++++ DOMRefTypes/index.js | 48 ++++++++++++ 10 files changed, 339 insertions(+) create mode 100644 DOMRefTypes/.editorconfig create mode 100644 DOMRefTypes/.sourcekit-lsp/config.json create mode 100644 DOMRefTypes/Package.swift create mode 100644 DOMRefTypes/Sources/RefsTest/Entrypoint.swift create mode 100644 DOMRefTypes/Sources/RefsTest/SwiftBindings.swift create mode 100644 DOMRefTypes/Sources/externref/bridge.c create mode 100644 DOMRefTypes/Sources/externref/dom.h create mode 100644 DOMRefTypes/Sources/externref/include/refs.h create mode 100644 DOMRefTypes/index.html create mode 100644 DOMRefTypes/index.js diff --git a/DOMRefTypes/.editorconfig b/DOMRefTypes/.editorconfig new file mode 100644 index 0000000..76a93c0 --- /dev/null +++ b/DOMRefTypes/.editorconfig @@ -0,0 +1,3 @@ +[*] +indent_style = space +indent_size = 2 diff --git a/DOMRefTypes/.sourcekit-lsp/config.json b/DOMRefTypes/.sourcekit-lsp/config.json new file mode 100644 index 0000000..9274168 --- /dev/null +++ b/DOMRefTypes/.sourcekit-lsp/config.json @@ -0,0 +1,5 @@ +{ + "swiftPM": { + "swiftSDK": "swift-DEVELOPMENT-SNAPSHOT-2025-08-02-a_wasm-embedded" + } +} diff --git a/DOMRefTypes/Package.swift b/DOMRefTypes/Package.swift new file mode 100644 index 0000000..9e49025 --- /dev/null +++ b/DOMRefTypes/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +let linkerSettings: [LinkerSetting] = [ + .unsafeFlags([ + "-Xclang-linker", "-mexec-model=reactor", + "-Xlinker", "--export-if-defined=__main_argc_argv", + ]), +] + +let package = Package( + name: "Guest", + targets: [ + .target( + name: "externref", + ), + .executableTarget( + name: "RefsTest", + dependencies: ["externref"], + linkerSettings: linkerSettings, + ), + ] +) diff --git a/DOMRefTypes/Sources/RefsTest/Entrypoint.swift b/DOMRefTypes/Sources/RefsTest/Entrypoint.swift new file mode 100644 index 0000000..c1a07d1 --- /dev/null +++ b/DOMRefTypes/Sources/RefsTest/Entrypoint.swift @@ -0,0 +1,9 @@ +@main +struct Entrypoint { + static func main() { + var h1 = Document.global.createElement(name: JSString("h1")) + let body = Document.global.body + body.append(child: h1) + h1.innerHTML = JSString("Hello, world!") + } +} diff --git a/DOMRefTypes/Sources/RefsTest/SwiftBindings.swift b/DOMRefTypes/Sources/RefsTest/SwiftBindings.swift new file mode 100644 index 0000000..9b4a5da --- /dev/null +++ b/DOMRefTypes/Sources/RefsTest/SwiftBindings.swift @@ -0,0 +1,73 @@ +import externref + +struct JSObject: ~Copyable { + fileprivate let ref: ExternRefIndex + + deinit { + freeExternRef(ref) + } +} + +struct JSArray: ~Copyable { + private let ref: ExternRefIndex + + init() { + self.ref = emptyArray() + } + + func append(_ object: borrowing JSObject) { + arrayPush(ref, object.ref) + } +} + +struct JSString: ~Copyable { + fileprivate let ref: ExternRefIndex + + deinit { + freeExternRef(self.ref) + } +} + +extension JSString { + init(_ string: StaticString) { + self.ref = bridgeString(string.utf8Start, string.utf8CodeUnitCount) + } + +} + +struct HTMLElement: ~Copyable { + fileprivate let ref: ExternRefIndex + + func append(child: borrowing HTMLElement) { + appendChild(self.ref, child.ref) + } + + static let innerHTMLName = JSString("innerHTML") + + var innerHTML: JSString { + + get { + JSString(ref: getProp(self.ref, Self.innerHTMLName.ref)) + } + + set { + setProp(self.ref, Self.innerHTMLName.ref, newValue.ref) + } + } +} + +struct Document: ~Copyable { + fileprivate let object: JSObject + + static let global = Document(object: JSObject(ref: getDocument())) + + static let bodyName = JSString("body") + + func createElement(name: borrowing JSString) -> HTMLElement { + .init(ref: externref.createElement(name.ref)) + } + + var body: HTMLElement { + HTMLElement(ref: getProp(self.object.ref, Self.bodyName.ref)) + } +} diff --git a/DOMRefTypes/Sources/externref/bridge.c b/DOMRefTypes/Sources/externref/bridge.c new file mode 100644 index 0000000..53ab329 --- /dev/null +++ b/DOMRefTypes/Sources/externref/bridge.c @@ -0,0 +1,63 @@ +#include "dom.h" +#include "refs.h" +#include + +static __externref_t table[0]; + +typedef __externref_t (*__funcref funcref_t)(__externref_t); +static funcref_t ftable[0]; + +static int nextAvailableTableIndex = 0; +static const int defaultTableGrowSize = 256; + +void freeExternRef(ExternRefIndex ref) { + __builtin_wasm_table_set(table, ref.index, __builtin_wasm_ref_null_extern()); +} + +ExternRefIndex tableAppend(__externref_t ref) { + ExternRefIndex idx = { .index = nextAvailableTableIndex++ }; + + if (idx.index >= __builtin_wasm_table_size(table)) { + __builtin_wasm_table_grow(table, __builtin_wasm_ref_null_extern(), defaultTableGrowSize); + } + + __builtin_wasm_table_set(table, idx.index, ref); + + return idx; +} + +ExternRefIndex createElement(ExternRefIndex name) { + return tableAppend(createElementJS(__builtin_wasm_table_get(table, name.index))); +} + +ExternRefIndex getDocument() { + return tableAppend(getDocumentJS()); +} + +ExternRefIndex getProp(ExternRefIndex self, ExternRefIndex name) { + return tableAppend(getPropJS(__builtin_wasm_table_get(table, self.index), __builtin_wasm_table_get(table, name.index))); +} + +int getIntProp(ExternRefIndex self, ExternRefIndex name) { + return getIntPropJS(__builtin_wasm_table_get(table, self.index), __builtin_wasm_table_get(table, name.index)); +} + +void setProp(ExternRefIndex self, ExternRefIndex name, ExternRefIndex val) { + setPropJS(__builtin_wasm_table_get(table, self.index), __builtin_wasm_table_get(table, name.index), __builtin_wasm_table_get(table, val.index)); +} + +void appendChild(ExternRefIndex self, ExternRefIndex child) { + appendChildJS(__builtin_wasm_table_get(table, self.index), __builtin_wasm_table_get(table, child.index)); +} + +ExternRefIndex bridgeString(const uint8_t *str, size_t bytes) { + return tableAppend(bridgeStringJS(str, bytes)); +} + +ExternRefIndex emptyArray() { + return tableAppend(emptyArrayJS()); +} + +void arrayPush(ExternRefIndex self, ExternRefIndex element) { + arrayPushJS(__builtin_wasm_table_get(table, self.index), __builtin_wasm_table_get(table, element.index)); +} diff --git a/DOMRefTypes/Sources/externref/dom.h b/DOMRefTypes/Sources/externref/dom.h new file mode 100644 index 0000000..09b9a51 --- /dev/null +++ b/DOMRefTypes/Sources/externref/dom.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +__attribute__((import_module("js"))) +__attribute__((import_name("getDocument"))) __externref_t +getDocumentJS(void); + +__attribute__((import_module("js"))) +__attribute__((import_name("emptyDictionary"))) __externref_t +emptyDictionaryJS(void); + +__attribute__((import_module("js"))) +__attribute__((import_name("emptyArray"))) __externref_t +emptyArrayJS(void); + +__attribute__((import_module("js"))) +__attribute__((import_name("arrayPush"))) void +arrayPushJS(__externref_t self, __externref_t element); + +__attribute__((import_module("js"))) +__attribute__((import_name("bridgeString"))) __externref_t +bridgeStringJS(const uint8_t *str, uint32_t bytes); + +__attribute__((import_module("js"))) +__attribute__((import_name("setProp"))) void +setPropJS(__externref_t self, __externref_t name, __externref_t val); + +__attribute__((import_module("js"))) +__attribute__((import_name("getProp"))) __externref_t +getPropJS(__externref_t self, __externref_t name); + + +__attribute__((import_module("js"))) +__attribute__((import_name("getIntProp"))) int +getIntPropJS(__externref_t self, __externref_t name); + +__attribute__((import_module("document"))) +__attribute__((import_name("createElement"))) __externref_t +createElementJS(__externref_t name); + +__attribute__((import_module("document"))) +__attribute__((import_name("appendChild"))) void +appendChildJS(__externref_t self, __externref_t child); diff --git a/DOMRefTypes/Sources/externref/include/refs.h b/DOMRefTypes/Sources/externref/include/refs.h new file mode 100644 index 0000000..0a921b3 --- /dev/null +++ b/DOMRefTypes/Sources/externref/include/refs.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct ExternRefIndex { + int index; +} ExternRefIndex; + +void freeExternRef(ExternRefIndex); + +ExternRefIndex createElement(ExternRefIndex name); +ExternRefIndex getDocument(void); +ExternRefIndex getProp(ExternRefIndex self, ExternRefIndex name); +int getIntProp(ExternRefIndex self, ExternRefIndex name); +void setProp(ExternRefIndex self, ExternRefIndex name, ExternRefIndex val); +void appendChild(ExternRefIndex self, ExternRefIndex child); +ExternRefIndex bridgeString(const uint8_t *str, size_t bytes); +ExternRefIndex emptyArray(void); +void arrayPush(ExternRefIndex self, ExternRefIndex element); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ diff --git a/DOMRefTypes/index.html b/DOMRefTypes/index.html new file mode 100644 index 0000000..37a314e --- /dev/null +++ b/DOMRefTypes/index.html @@ -0,0 +1,29 @@ + + + + + + Swift Audio Workstation + + + + +

Wasm Reference Types Demo

+ + diff --git a/DOMRefTypes/index.js b/DOMRefTypes/index.js new file mode 100644 index 0000000..c029a73 --- /dev/null +++ b/DOMRefTypes/index.js @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + + +const decoder = new TextDecoder(); + +const moduleInstances = []; +function wasmMemoryAsString(i, address, byteCount) { + return decoder.decode(moduleInstances[i].exports.memory.buffer.slice(address, address + byteCount)); +} + +function wasmMemoryAsFloat32Array(i, address, byteCount) { + return new Float32Array(moduleInstances[i].exports.memory.buffer.slice(address, address + byteCount)); +} + +const importsObject = { + js: { + getDocument: () => document, + emptyDictionary: () => { return {} }, + emptyArray: () => [], + bridgeString: (address, count) => wasmMemoryAsString(0, address, count), + setProp: (self, name, val) => { self[name] = val; }, + getProp: (self, name) => self[name], + getIntProp: (self, name) => self[name], + }, + + document: { + createElement: (name) => document.createElement(name), + appendChild: (element, child) => element.appendChild(child), + } + }; + +const { instance, module } = await WebAssembly.instantiateStreaming( + fetch(".build/release/RefsTest.wasm"), + importsObject +); +moduleInstances.push(instance); + +instance.exports.__main_argc_argv(); From 21a21099b8dc5c62be7b8f3a7a14592d6c6bb004 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 4 Aug 2025 17:40:00 +0100 Subject: [PATCH 02/12] Fix formatting in `Package.swift` --- DOMRefTypes/Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DOMRefTypes/Package.swift b/DOMRefTypes/Package.swift index 9e49025..18b58c2 100644 --- a/DOMRefTypes/Package.swift +++ b/DOMRefTypes/Package.swift @@ -17,8 +17,8 @@ import PackageDescription let linkerSettings: [LinkerSetting] = [ .unsafeFlags([ - "-Xclang-linker", "-mexec-model=reactor", - "-Xlinker", "--export-if-defined=__main_argc_argv", + "-Xclang-linker", "-mexec-model=reactor", + "-Xlinker", "--export-if-defined=__main_argc_argv", ]), ] From 5c1359892e66857667738f631814fc199d87fe09 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 4 Aug 2025 17:40:38 +0100 Subject: [PATCH 03/12] Fix title in `index.html` --- DOMRefTypes/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOMRefTypes/index.html b/DOMRefTypes/index.html index 37a314e..914705c 100644 --- a/DOMRefTypes/index.html +++ b/DOMRefTypes/index.html @@ -9,7 +9,7 @@ - Swift Audio Workstation + Swift for WebAssembly Examples