diff options
| author | Anders Leino <aleino@nvidia.com> | 2024-10-11 13:13:04 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-11 18:13:04 +0800 |
| commit | dfab34e4bf508fc517d4d645ebb3b6b1179a5003 (patch) | |
| tree | 0a75a9618ffdbd0604145cade8dbaff7b2af01cd | |
| parent | 5fa35fcce532267a2ae5779dee9ff4d07fab6bf4 (diff) | |
Add slang-wasm target (#5237)
Support for exceptions is enabled, since Slang uses them for diagnostics.
The size optimization arguments ('-Os') resolves some internal emscripten error during the
slang-wasm.wasm linking step, which happens when enabling exceptions.
("parse exception: too many locals")
| -rw-r--r-- | CMakeLists.txt | 16 | ||||
| -rw-r--r-- | CMakePresets.json | 6 | ||||
| -rw-r--r-- | docs/building.md | 4 | ||||
| -rw-r--r-- | source/compiler-core/slang-diagnostic-sink.cpp | 3 | ||||
| -rw-r--r-- | source/slang-wasm/slang-wasm-bindings.cpp | 59 | ||||
| -rw-r--r-- | source/slang-wasm/slang-wasm.cpp | 182 | ||||
| -rw-r--r-- | source/slang-wasm/slang-wasm.h | 107 |
7 files changed, 372 insertions, 5 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b505cbf53..da22ec19a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -372,6 +372,22 @@ if(SLANG_ENABLE_SLANGC) endif() # +# WebAssembly bindings for Slang +# +# This is an executable target because emcmake produces .a files without bindings if you just create a static library +# https://stackoverflow.com/questions/63622009/static-library-built-with-cmake-as-a-with-emscripten-instead-of-wasm-js +slang_add_target( + source/slang-wasm + EXECUTABLE + EXCLUDE_FROM_ALL + USE_FEWER_WARNINGS + LINK_WITH_PRIVATE miniz lz4_static slang core compiler-core + INCLUDE_DIRECTORIES_PUBLIC include source/slang-wasm +) +# To generate binding code +target_link_options(slang-wasm PUBLIC "--bind") + +# # Our wrappers for glslang and llvm # if(SLANG_ENABLE_SLANG_GLSLANG) diff --git a/CMakePresets.json b/CMakePresets.json index 7b847e03e..47a733ee2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -22,7 +22,9 @@ "binaryDir": "${sourceDir}/build.em", "cacheVariables": { "SLANG_SLANG_LLVM_FLAVOR": "DISABLE", - "CMAKE_EXE_LINKER_FLAGS": "-sASSERTIONS -sALLOW_MEMORY_GROWTH" + "CMAKE_C_FLAGS_INIT": "-fwasm-exceptions -Os", + "CMAKE_CXX_FLAGS_INIT": "-fwasm-exceptions -Os", + "CMAKE_EXE_LINKER_FLAGS": "-sASSERTIONS -sALLOW_MEMORY_GROWTH -fwasm-exceptions --export=__cpp_exception" } }, { @@ -89,7 +91,7 @@ "configurePreset": "emscripten", "configuration": "Release", "targets": [ - "slang" + "slang-wasm" ] }, { diff --git a/docs/building.md b/docs/building.md index da55f57aa..fa18ef60f 100644 --- a/docs/building.md +++ b/docs/building.md @@ -75,8 +75,8 @@ source ./emsdk_env # For Windows, emsdk_env.bat popd emcmake cmake -DSLANG_GENERATORS_PATH=generators/bin --preset emscripten -G "Ninja" -# Build build.em/Release/bin/libslang.a -cmake --build --preset emscripten --target slang +# Build slang-wasm.js and slang-wasm.wasm in build.em/Release/bin +cmake --build --preset emscripten --target slang-wasm ``` **Note:** If the last build step fails, try running the command that `emcmake` outputs, directly. diff --git a/source/compiler-core/slang-diagnostic-sink.cpp b/source/compiler-core/slang-diagnostic-sink.cpp index 7e3c286eb..a4cbce22f 100644 --- a/source/compiler-core/slang-diagnostic-sink.cpp +++ b/source/compiler-core/slang-diagnostic-sink.cpp @@ -574,7 +574,8 @@ bool DiagnosticSink::diagnoseImpl(DiagnosticInfo const& info, const UnownedStrin if (info.severity >= Severity::Fatal) { // TODO: figure out a better policy for aborting compilation - SLANG_ABORT_COMPILATION(""); + std::string message(formattedMessage.begin(), formattedMessage.end()); + SLANG_ABORT_COMPILATION(message.c_str()); } return true; } diff --git a/source/slang-wasm/slang-wasm-bindings.cpp b/source/slang-wasm/slang-wasm-bindings.cpp new file mode 100644 index 000000000..4880596a9 --- /dev/null +++ b/source/slang-wasm/slang-wasm-bindings.cpp @@ -0,0 +1,59 @@ +#include <emscripten/bind.h> +#include <slang-com-ptr.h> +#include "slang-wasm.h" + +using namespace emscripten; + +EMSCRIPTEN_BINDINGS(slang) +{ + constant("SLANG_OK", SLANG_OK); + + function( + "createGlobalSession", + &slang::wgsl::createGlobalSession, + return_value_policy::take_ownership()); + + function( + "getLastError", + &slang::wgsl::getLastError); + + class_<slang::wgsl::GlobalSession>("GlobalSession") + .function( + "createSession", + &slang::wgsl::GlobalSession::createSession, + return_value_policy::take_ownership()); + + class_<slang::wgsl::Session>("Session") + .function( + "loadModuleFromSource", + &slang::wgsl::Session::loadModuleFromSource, + return_value_policy::take_ownership()) + .function( + "createCompositeComponentType", + &slang::wgsl::Session::createCompositeComponentType, + return_value_policy::take_ownership()); + + class_<slang::wgsl::ComponentType>("ComponentType") + .function( + "link", + &slang::wgsl::ComponentType::link, + return_value_policy::take_ownership()) + .function( + "getEntryPointCode", + &slang::wgsl::ComponentType::getEntryPointCode); + + class_<slang::wgsl::Module, base<slang::wgsl::ComponentType>>("Module") + .function( + "findEntryPointByName", + &slang::wgsl::Module::findEntryPointByName, + return_value_policy::take_ownership()); + + value_object<slang::wgsl::Error>("Error") + .field("type", &slang::wgsl::Error::type) + .field("result", &slang::wgsl::Error::result) + .field("message", &slang::wgsl::Error::message); + + class_<slang::wgsl::EntryPoint, base<slang::wgsl::ComponentType>>("EntryPoint"); + + register_vector<slang::wgsl::ComponentType*>("ComponentTypeList"); +} diff --git a/source/slang-wasm/slang-wasm.cpp b/source/slang-wasm/slang-wasm.cpp new file mode 100644 index 000000000..c2cbe66b6 --- /dev/null +++ b/source/slang-wasm/slang-wasm.cpp @@ -0,0 +1,182 @@ +#include <vector> +#include <string> +#include <slang.h> +#include <slang-com-ptr.h> +#include "slang-wasm.h" +#include "../core/slang-blob.h" +#include "../core/slang-exception.h" + +using namespace slang; + +namespace slang +{ +namespace wgsl +{ + +Error g_error; + +Error getLastError() +{ + Error currentError = g_error; + g_error = {}; + return currentError; +} + +GlobalSession* createGlobalSession() +{ + IGlobalSession* globalSession = nullptr; + { + SlangResult result = slang::createGlobalSession(&globalSession); + if (result != SLANG_OK) + { + g_error.type = std::string("USER"); + g_error.result = result; + return nullptr; + } + } + + return new GlobalSession(globalSession); +} + +Session* GlobalSession::createSession() +{ + ISession* session = nullptr; + { + SessionDesc sessionDesc = {}; + sessionDesc.structureSize = sizeof(sessionDesc); + constexpr SlangInt targetCount = 1; + TargetDesc targets[targetCount] = { + {.structureSize = sizeof(TargetDesc), .format = SLANG_WGSL} + }; + sessionDesc.targets = targets; + sessionDesc.targetCount = targetCount; + SlangResult result = m_interface->createSession(sessionDesc, &session); + if (result != SLANG_OK) + { + g_error.type = std::string("USER"); + g_error.result = result; + return nullptr; + } + } + + return new Session(session); +} + +Module* Session::loadModuleFromSource(const std::string& slangCode) +{ + Slang::ComPtr<IModule> module; + { + const char * name = ""; + const char * path = ""; + Slang::ComPtr<slang::IBlob> diagnosticsBlob; + Slang::ComPtr<ISlangBlob> slangCodeBlob = Slang::RawBlob::create( + slangCode.c_str(), slangCode.size()); + module = m_interface->loadModuleFromSource( + name, path, slangCodeBlob, diagnosticsBlob.writeRef()); + if (!module) + { + g_error.type = std::string("USER"); + g_error.message = std::string( + (char*)diagnosticsBlob->getBufferPointer(), + (char*)diagnosticsBlob->getBufferPointer() + + diagnosticsBlob->getBufferSize()); + return nullptr; + } + } + + return new Module(module); +} + +EntryPoint* Module::findEntryPointByName(const std::string& name) +{ + Slang::ComPtr<IEntryPoint> entryPoint; + { + SlangResult result = moduleInterface()->findEntryPointByName( + name.c_str(), entryPoint.writeRef()); + if (result != SLANG_OK) + { + g_error.type = std::string("USER"); + g_error.result = result; + return nullptr; + } + } + + return new EntryPoint(entryPoint); +} + +ComponentType* Session::createCompositeComponentType( + const std::vector<ComponentType*>& components) +{ + Slang::ComPtr<IComponentType> composite; + { + std::vector<IComponentType*> nativeComponents(components.size()); + for (size_t i = 0U; i < components.size(); i++) + nativeComponents[i] = components[i]->interface(); + SlangResult result = m_interface->createCompositeComponentType( + nativeComponents.data(), + (SlangInt)nativeComponents.size(), + composite.writeRef()); + if (result != SLANG_OK) + { + g_error.type = std::string("USER"); + g_error.result = result; + return nullptr; + } + } + + return new ComponentType(composite); +} + +ComponentType* ComponentType::link() +{ + Slang::ComPtr<IComponentType> linkedProgram; + { + Slang::ComPtr<ISlangBlob> diagnosticBlob; + SlangResult result = interface()->link( + linkedProgram.writeRef(), diagnosticBlob.writeRef()); + if (result != SLANG_OK) + { + g_error.type = std::string("USER"); + g_error.result = result; + g_error.message = std::string( + (char*)diagnosticBlob->getBufferPointer(), + (char*)diagnosticBlob->getBufferPointer() + + diagnosticBlob->getBufferSize()); + return nullptr; + } + } + + return new ComponentType(linkedProgram); +} + +std::string ComponentType::getEntryPointCode(int entryPointIndex, int targetIndex) +{ + { + Slang::ComPtr<IBlob> kernelBlob; + Slang::ComPtr<ISlangBlob> diagnosticBlob; + SlangResult result = interface()->getEntryPointCode( + entryPointIndex, + targetIndex, + kernelBlob.writeRef(), + diagnosticBlob.writeRef()); + if (result != SLANG_OK) + { + g_error.type = std::string("USER"); + g_error.result = result; + g_error.message = std::string( + (char*)diagnosticBlob->getBufferPointer(), + (char*)diagnosticBlob->getBufferPointer() + + diagnosticBlob->getBufferSize()); + return ""; + } + std::string wgslCode = std::string( + (char*)kernelBlob->getBufferPointer(), + (char*)kernelBlob->getBufferPointer() + kernelBlob->getBufferSize()); + return wgslCode; + } + + return {}; +} + +} // namespace wgsl +} // namespace slang diff --git a/source/slang-wasm/slang-wasm.h b/source/slang-wasm/slang-wasm.h new file mode 100644 index 000000000..24ce62392 --- /dev/null +++ b/source/slang-wasm/slang-wasm.h @@ -0,0 +1,107 @@ +#pragma once + +#include <slang.h> + +namespace slang +{ +namespace wgsl +{ + +class Error +{ +public: + // Can be + // "USER": User did not call the API correctly + // "INTERNAL": Slang failed due to a bug + std::string type; + std::string message; + SlangResult result; +}; + +Error getLastError(); + +class ComponentType +{ +public: + + ComponentType(slang::IComponentType* interface) : + m_interface(interface) {} + + ComponentType* link(); + + std::string getEntryPointCode(int entryPointIndex, int targetIndex); + + slang::IComponentType* interface() const {return m_interface;} + + virtual ~ComponentType() = default; + +private: + + Slang::ComPtr<slang::IComponentType> m_interface; +}; + +class EntryPoint : public ComponentType +{ +public: + + EntryPoint(slang::IEntryPoint* interface) : ComponentType(interface) {} + +private: + + slang::IEntryPoint* entryPointInterface() const { + return static_cast<slang::IEntryPoint*>(interface()); + } +}; + +class Module : public ComponentType +{ +public: + + Module(slang::IModule* interface) : ComponentType(interface) {} + + EntryPoint* findEntryPointByName(const std::string& name); + + slang::IModule* moduleInterface() const { + return static_cast<slang::IModule*>(interface()); + } +}; + +class Session +{ +public: + + Session(slang::ISession* interface) + : m_interface(interface) {} + + Module* loadModuleFromSource(const std::string& slangCode); + + ComponentType* createCompositeComponentType( + const std::vector<ComponentType*>& components); + + slang::ISession* interface() const {return m_interface;} + +private: + + Slang::ComPtr<slang::ISession> m_interface; +}; + +class GlobalSession +{ +public: + + GlobalSession(slang::IGlobalSession* interface) + : m_interface(interface) {} + + Session* createSession(); + + slang::IGlobalSession* interface() const {return m_interface;} + +private: + + Slang::ComPtr<slang::IGlobalSession> m_interface; +}; + +GlobalSession* createGlobalSession(); + +} // namespace wgsl +} // namespace slang |
