summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnders Leino <aleino@nvidia.com>2024-10-11 13:13:04 +0300
committerGitHub <noreply@github.com>2024-10-11 18:13:04 +0800
commitdfab34e4bf508fc517d4d645ebb3b6b1179a5003 (patch)
tree0a75a9618ffdbd0604145cade8dbaff7b2af01cd
parent5fa35fcce532267a2ae5779dee9ff4d07fab6bf4 (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.txt16
-rw-r--r--CMakePresets.json6
-rw-r--r--docs/building.md4
-rw-r--r--source/compiler-core/slang-diagnostic-sink.cpp3
-rw-r--r--source/slang-wasm/slang-wasm-bindings.cpp59
-rw-r--r--source/slang-wasm/slang-wasm.cpp182
-rw-r--r--source/slang-wasm/slang-wasm.h107
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