diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7542038..fb4505b 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -35,6 +35,16 @@ jobs: enable_windows_checks: false swift_flags: --package-path WebGPUDemo + dom-ref-types: + name: Build DOMRefTypes Demo + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + enable_embedded_wasm_sdk_build: true + enable_linux_checks: false + enable_macos_checks: false + enable_windows_checks: false + swift_flags: --package-path DOMRefTypes + soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 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..9766f7b --- /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..723b93f --- /dev/null +++ b/DOMRefTypes/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 6.2 +// 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..eff843c --- /dev/null +++ b/DOMRefTypes/Sources/RefsTest/Entrypoint.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024-2025 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 +// +//===----------------------------------------------------------------------===// + +@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..60d4232 --- /dev/null +++ b/DOMRefTypes/Sources/RefsTest/SwiftBindings.swift @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024-2025 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 +// +//===----------------------------------------------------------------------===// + +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..568440c --- /dev/null +++ b/DOMRefTypes/Sources/externref/bridge.c @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024-2025 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 +// +//===----------------------------------------------------------------------===// + +#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..e811bf8 --- /dev/null +++ b/DOMRefTypes/Sources/externref/dom.h @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024-2025 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 +// +//===----------------------------------------------------------------------===// + +#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..7ef1863 --- /dev/null +++ b/DOMRefTypes/Sources/externref/include/refs.h @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024-2025 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 +// +//===----------------------------------------------------------------------===// + +#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..914705c --- /dev/null +++ b/DOMRefTypes/index.html @@ -0,0 +1,29 @@ + + + + + + Swift for WebAssembly Examples + + + + +

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(); diff --git a/README.md b/README.md index db8c8b2..06e093b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ The repository is split into multiple subdirectories: WebAssembly for application plugins; - The [`WebGPUDemo`](./WebGPUDemo) package demonstrates use of WebAssembly in the browser, with Swift bindings to the [WebGPU](https://developer.apple.com/videos/play/wwdc2025/236) API. +- The [`DOMRefTypes`](./DOMRefTypes) package demonstrates use of WebAssembly + reference types for high performance bridging to DOM Web APIs. This specific + package is currently provided for advanced use cases only. ## Contributing to this repository