diff --git a/source/wasm/build.sh b/source/wasm/build.sh index f02ae525c3..55dfd89eb1 100755 --- a/source/wasm/build.sh +++ b/source/wasm/build.sh @@ -15,6 +15,7 @@ # limitations under the License. set -e +set -x NUM_CORES=$(nproc) echo "Detected $NUM_CORES cores for building" @@ -23,18 +24,45 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" VERSION=$(sed -n '0,/^v20/ s/^v\(20[0-9.]*\).*/\1/p' $DIR/../../CHANGES).${GITHUB_RUN_NUMBER:-0} echo "Version: $VERSION" -build() { +make_package() { + type=$1 + pkg=$2 + + mkdir -p out/$type/$pkg + + # copy other js files + cp source/wasm/$pkg.d.ts out/$type/$pkg + sed -e 's/\("version"\s*:\s*\).*/\1"'$VERSION'",/' source/wasm/package-$pkg.json > out/$type/$pkg/package.json + + cp source/wasm/README.md out/$type/$pkg + cp LICENSE out/$type/$pkg + + cp build/$type/$pkg.js out/$type/$pkg + + gzip -9 -k -f out/$type/$pkg/$pkg.js + if [ -e build/$type/$pkg.wasm ] ; then + cp build/$type/$pkg.wasm out/$type/$pkg + gzip -9 -k -f out/$type/$pkg/$pkg.wasm + fi + wc -c out/$type/$pkg/* +} + + +build() { type=$1 shift args=$@ mkdir -p build/$type + pushd build/$type echo $args emcmake cmake \ -DCMAKE_BUILD_TYPE=Release \ $args \ ../.. + emmake make -j $(( $NUM_CORES )) SPIRV-Tools-static + emmake make -j $(( $NUM_CORES )) SPIRV-Tools-opt echo Building js interface emcc \ @@ -47,21 +75,21 @@ build() { -s MODULARIZE \ -Oz + emcc \ + --bind \ + -I../../include \ + -std=c++11 \ + ../../source/wasm/spirv-tools-opt.cpp \ + source/libSPIRV-Tools.a \ + source/opt/libSPIRV-Tools-opt.a \ + -o spirv-tools-opt.js \ + -s MODULARIZE \ + -Oz + popd - mkdir -p out/$type - - # copy other js files - cp source/wasm/spirv-tools.d.ts out/$type/ - sed -e 's/\("version"\s*:\s*\).*/\1"'$VERSION'",/' source/wasm/package.json > out/$type/package.json - cp source/wasm/README.md out/$type/ - cp LICENSE out/$type/ - - cp build/$type/spirv-tools.js out/$type/ - gzip -9 -k -f out/$type/spirv-tools.js - if [ -e build/$type/spirv-tools.wasm ] ; then - cp build/$type/spirv-tools.wasm out/$type/ - gzip -9 -k -f out/$type/spirv-tools.wasm - fi + + make_package $type spirv-tools + make_package $type spirv-tools-opt } if [ ! -d external/spirv-headers ] ; then @@ -75,4 +103,3 @@ build web\ -DSPIRV_SKIP_TESTS=ON\ -DSPIRV_SKIP_EXECUTABLES=ON -wc -c out/*/* diff --git a/source/wasm/package-spirv-tools-opt.json b/source/wasm/package-spirv-tools-opt.json new file mode 100644 index 0000000000..dd13e5f3b8 --- /dev/null +++ b/source/wasm/package-spirv-tools-opt.json @@ -0,0 +1,17 @@ +{ + "name": "spirv-tools-opt", + "version": "VERSION", + "license": "Apache-2.0", + "main": "spirv-tools-opt", + "types": "spirv-tools-opt.d.ts", + "files": [ + "*.wasm", + "*.js", + "*.d.ts" + ], + "repository": { + "type": "git", + "url": "https://github.com/KhronosGroup/SPIRV-Tools" + }, + "homepage": "https://github.com/KhronosGroup/SPIRV-Tools" +} diff --git a/source/wasm/package-spirv-tools.json b/source/wasm/package-spirv-tools.json new file mode 100644 index 0000000000..7827353810 --- /dev/null +++ b/source/wasm/package-spirv-tools.json @@ -0,0 +1,17 @@ +{ + "name": "spirv-tools", + "version": "VERSION", + "license": "Apache-2.0", + "main": "spirv-tools", + "types": "spirv-tools.d.ts", + "files": [ + "*.wasm", + "*.js", + "*.d.ts" + ], + "repository": { + "type": "git", + "url": "https://github.com/KhronosGroup/SPIRV-Tools" + }, + "homepage": "https://github.com/KhronosGroup/SPIRV-Tools" +} diff --git a/source/wasm/spirv-tools-opt.cpp b/source/wasm/spirv-tools-opt.cpp new file mode 100644 index 0000000000..ae3d27c405 --- /dev/null +++ b/source/wasm/spirv-tools-opt.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/optimizer.hpp" + +#include +#include +#include +#include +#include + +#include +#include +using namespace emscripten; + + +void print_msg_to_stderr (spv_message_level_t, const char*, + const spv_position_t&, const char* m) { + std::cerr << "error: " << m << std::endl; +} + +std::vector aligned_copy(const std::string& str) { + std::vector result(str.size()/4); + std::memcpy(result.data(), str.data(), str.size()); + return result; +} + +struct Optimizer { + public: + Optimizer(unsigned env) : optimizer_(static_cast(env)) { + optimizer_.SetMessageConsumer(print_msg_to_stderr); + } + void registerPerformancePasses() { optimizer_.RegisterPerformancePasses(); } + void registerSizePasses() { optimizer_.RegisterSizePasses(); } + std::string run(std::string const& binary) { + auto copy = aligned_copy(binary); + std::vector optimized_binary; + optimizer_.Run(copy.data(), copy.size(), &optimized_binary); + const auto num_output_bytes = optimized_binary.size()*4; + return std::string(reinterpret_cast(optimized_binary.data()),num_output_bytes); + } + + ::spvtools::Optimizer optimizer_; +}; + +std::string optimizePerformance(unsigned env, std::string binary) { + Optimizer o(env); + o.registerPerformancePasses(); + return o.run(binary); +} + +std::string optimizeSize(unsigned env, std::string binary) { + Optimizer o(env); + o.registerSizePasses(); + return o.run(binary); +} + +EMSCRIPTEN_BINDINGS(SpirvToolsOpt) { + function("optimizePerformance",&optimizePerformance); + function("optimizeSize",&optimizeSize); + +#include "spv_env.inc" +} diff --git a/source/wasm/spirv-tools-opt.d.ts b/source/wasm/spirv-tools-opt.d.ts new file mode 100644 index 0000000000..67a401c537 --- /dev/null +++ b/source/wasm/spirv-tools-opt.d.ts @@ -0,0 +1,47 @@ +// Copyright (c) 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +declare interface SpirvToolsOpt { + optimizePerformance(env:number,binary:Uint8Array): Uint8Array; + optimizeSize(env:number,binary:Uint8Array): Uint8Array; + + // Target environment, passed to the constructor. + SPV_ENV_UNIVERSAL_1_0: number; + SPV_ENV_VULKAN_1_0: number; + SPV_ENV_UNIVERSAL_1_1: number; + SPV_ENV_OPENCL_2_1: number; + SPV_ENV_OPENCL_2_2: number; + SPV_ENV_OPENGL_4_0: number; + SPV_ENV_OPENGL_4_1: number; + SPV_ENV_OPENGL_4_2: number; + SPV_ENV_OPENGL_4_3: number; + SPV_ENV_OPENGL_4_5: number; + SPV_ENV_UNIVERSAL_1_2: number; + SPV_ENV_OPENCL_1_2: number; + SPV_ENV_OPENCL_EMBEDDED_1_2: number; + SPV_ENV_OPENCL_2_0: number; + SPV_ENV_OPENCL_EMBEDDED_2_0: number; + SPV_ENV_OPENCL_EMBEDDED_2_1: number; + SPV_ENV_OPENCL_EMBEDDED_2_2: number; + SPV_ENV_UNIVERSAL_1_3: number; + SPV_ENV_VULKAN_1_1: number; + SPV_ENV_WEBGPU_0: number; + SPV_ENV_UNIVERSAL_1_4: number; + SPV_ENV_VULKAN_1_1_SPIRV_1_4: number; + SPV_ENV_UNIVERSAL_1_5: number; + SPV_ENV_VULKAN_1_2: number; + SPV_ENV_UNIVERSAL_1_6: number; +} + +export default function (): Promise; diff --git a/source/wasm/spirv-tools.cpp b/source/wasm/spirv-tools.cpp index 33f2f05fb0..9fa729bc71 100644 --- a/source/wasm/spirv-tools.cpp +++ b/source/wasm/spirv-tools.cpp @@ -39,47 +39,22 @@ std::string dis(std::string const& buffer, uint32_t env, uint32_t options) { return disassembly; } -emscripten::val as(std::string const& source, uint32_t env, uint32_t options) { +std::string as(std::string const& source, uint32_t env, uint32_t options) { spvtools::SpirvTools core(static_cast(env)); core.SetMessageConsumer(print_msg_to_stderr); std::vector spirv; if (!core.Assemble(source, &spirv, options)) spirv.clear(); - const uint8_t* ptr = reinterpret_cast(spirv.data()); - return emscripten::val(emscripten::typed_memory_view(spirv.size() * 4, - ptr)); + // Copy the data out. + const auto* ptr = reinterpret_cast(spirv.data()); + return std::string(ptr,spirv.size()*4); } EMSCRIPTEN_BINDINGS(my_module) { function("dis", &dis); function("as", &as); - - constant("SPV_ENV_UNIVERSAL_1_0", static_cast(SPV_ENV_UNIVERSAL_1_0)); - constant("SPV_ENV_VULKAN_1_0", static_cast(SPV_ENV_VULKAN_1_0)); - constant("SPV_ENV_UNIVERSAL_1_1", static_cast(SPV_ENV_UNIVERSAL_1_1)); - constant("SPV_ENV_OPENCL_2_1", static_cast(SPV_ENV_OPENCL_2_1)); - constant("SPV_ENV_OPENCL_2_2", static_cast(SPV_ENV_OPENCL_2_2)); - constant("SPV_ENV_OPENGL_4_0", static_cast(SPV_ENV_OPENGL_4_0)); - constant("SPV_ENV_OPENGL_4_1", static_cast(SPV_ENV_OPENGL_4_1)); - constant("SPV_ENV_OPENGL_4_2", static_cast(SPV_ENV_OPENGL_4_2)); - constant("SPV_ENV_OPENGL_4_3", static_cast(SPV_ENV_OPENGL_4_3)); - constant("SPV_ENV_OPENGL_4_5", static_cast(SPV_ENV_OPENGL_4_5)); - constant("SPV_ENV_UNIVERSAL_1_2", static_cast(SPV_ENV_UNIVERSAL_1_2)); - constant("SPV_ENV_OPENCL_1_2", static_cast(SPV_ENV_OPENCL_1_2)); - constant("SPV_ENV_OPENCL_EMBEDDED_1_2", static_cast(SPV_ENV_OPENCL_EMBEDDED_1_2)); - constant("SPV_ENV_OPENCL_2_0", static_cast(SPV_ENV_OPENCL_2_0)); - constant("SPV_ENV_OPENCL_EMBEDDED_2_0", static_cast(SPV_ENV_OPENCL_EMBEDDED_2_0)); - constant("SPV_ENV_OPENCL_EMBEDDED_2_1", static_cast(SPV_ENV_OPENCL_EMBEDDED_2_1)); - constant("SPV_ENV_OPENCL_EMBEDDED_2_2", static_cast(SPV_ENV_OPENCL_EMBEDDED_2_2)); - constant("SPV_ENV_UNIVERSAL_1_3", static_cast(SPV_ENV_UNIVERSAL_1_3)); - constant("SPV_ENV_VULKAN_1_1", static_cast(SPV_ENV_VULKAN_1_1)); - constant("SPV_ENV_WEBGPU_0", static_cast(SPV_ENV_WEBGPU_0)); - constant("SPV_ENV_UNIVERSAL_1_4", static_cast(SPV_ENV_UNIVERSAL_1_4)); - constant("SPV_ENV_VULKAN_1_1_SPIRV_1_4", static_cast(SPV_ENV_VULKAN_1_1_SPIRV_1_4)); - constant("SPV_ENV_UNIVERSAL_1_5", static_cast(SPV_ENV_UNIVERSAL_1_5)); - constant("SPV_ENV_VULKAN_1_2", static_cast(SPV_ENV_VULKAN_1_2)); - constant("SPV_ENV_UNIVERSAL_1_6", - static_cast(SPV_ENV_UNIVERSAL_1_6)); + +#include "spv_env.inc" constant("SPV_BINARY_TO_TEXT_OPTION_NONE", static_cast(SPV_BINARY_TO_TEXT_OPTION_NONE)); constant("SPV_BINARY_TO_TEXT_OPTION_PRINT", static_cast(SPV_BINARY_TO_TEXT_OPTION_PRINT)); diff --git a/source/wasm/spv_env.inc b/source/wasm/spv_env.inc new file mode 100644 index 0000000000..5589663b81 --- /dev/null +++ b/source/wasm/spv_env.inc @@ -0,0 +1,40 @@ +// Copyright (c) 2020 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + + constant("SPV_ENV_UNIVERSAL_1_0", static_cast(SPV_ENV_UNIVERSAL_1_0)); + constant("SPV_ENV_VULKAN_1_0", static_cast(SPV_ENV_VULKAN_1_0)); + constant("SPV_ENV_UNIVERSAL_1_1", static_cast(SPV_ENV_UNIVERSAL_1_1)); + constant("SPV_ENV_OPENCL_2_1", static_cast(SPV_ENV_OPENCL_2_1)); + constant("SPV_ENV_OPENCL_2_2", static_cast(SPV_ENV_OPENCL_2_2)); + constant("SPV_ENV_OPENGL_4_0", static_cast(SPV_ENV_OPENGL_4_0)); + constant("SPV_ENV_OPENGL_4_1", static_cast(SPV_ENV_OPENGL_4_1)); + constant("SPV_ENV_OPENGL_4_2", static_cast(SPV_ENV_OPENGL_4_2)); + constant("SPV_ENV_OPENGL_4_3", static_cast(SPV_ENV_OPENGL_4_3)); + constant("SPV_ENV_OPENGL_4_5", static_cast(SPV_ENV_OPENGL_4_5)); + constant("SPV_ENV_UNIVERSAL_1_2", static_cast(SPV_ENV_UNIVERSAL_1_2)); + constant("SPV_ENV_OPENCL_1_2", static_cast(SPV_ENV_OPENCL_1_2)); + constant("SPV_ENV_OPENCL_EMBEDDED_1_2", static_cast(SPV_ENV_OPENCL_EMBEDDED_1_2)); + constant("SPV_ENV_OPENCL_2_0", static_cast(SPV_ENV_OPENCL_2_0)); + constant("SPV_ENV_OPENCL_EMBEDDED_2_0", static_cast(SPV_ENV_OPENCL_EMBEDDED_2_0)); + constant("SPV_ENV_OPENCL_EMBEDDED_2_1", static_cast(SPV_ENV_OPENCL_EMBEDDED_2_1)); + constant("SPV_ENV_OPENCL_EMBEDDED_2_2", static_cast(SPV_ENV_OPENCL_EMBEDDED_2_2)); + constant("SPV_ENV_UNIVERSAL_1_3", static_cast(SPV_ENV_UNIVERSAL_1_3)); + constant("SPV_ENV_VULKAN_1_1", static_cast(SPV_ENV_VULKAN_1_1)); + constant("SPV_ENV_WEBGPU_0", static_cast(SPV_ENV_WEBGPU_0)); + constant("SPV_ENV_UNIVERSAL_1_4", static_cast(SPV_ENV_UNIVERSAL_1_4)); + constant("SPV_ENV_VULKAN_1_1_SPIRV_1_4", static_cast(SPV_ENV_VULKAN_1_1_SPIRV_1_4)); + constant("SPV_ENV_UNIVERSAL_1_5", static_cast(SPV_ENV_UNIVERSAL_1_5)); + constant("SPV_ENV_VULKAN_1_2", static_cast(SPV_ENV_VULKAN_1_2)); + constant("SPV_ENV_UNIVERSAL_1_6", static_cast(SPV_ENV_UNIVERSAL_1_6)); diff --git a/test/wasm/test-opt.js b/test/wasm/test-opt.js new file mode 100644 index 0000000000..135f310fed --- /dev/null +++ b/test/wasm/test-opt.js @@ -0,0 +1,73 @@ +// Copyright (c) 2020 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const spirvTools = require("../../out/web/spirv-tools/spirv-tools"); +const spirvToolsOpt = require("../../out/web/spirv-tools-opt/spirv-tools-opt"); + +const test = async () => { + const spv = await spirvTools(); + + const source = ` + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + OpName %i "i" + OpName %i2 "i2" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_2 = OpConstant %int 2 + %main = OpFunction %void None %3 + %5 = OpLabel + %i = OpVariable %_ptr_Function_int Function + %i2 = OpVariable %_ptr_Function_int Function + OpStore %i %int_2 + %11 = OpLoad %int %i + %12 = OpLoad %int %i + %13 = OpIAdd %int %11 %12 + OpStore %i2 %13 + OpReturn + OpFunctionEnd `; + + + const asResult = spv.as( + source, + spv.SPV_ENV_UNIVERSAL_1_0, + spv.SPV_TEXT_TO_BINARY_OPTION_NONE + ); + console.log(`as returned ${asResult.length} bytes`); + + console.log(`\nNow optimize for performance ... `); + // Optimize for performance. + const opt = await spirvToolsOpt(); + const optResult = opt.optimizePerformance(opt.SPV_ENV_VULKAN_1_0,asResult); + + + console.log(`optimizePerformance returned ${optResult.length} bytes`); + + // disassemble + const disResult = spv.dis( + optResult, + spv.SPV_ENV_UNIVERSAL_1_0, + spv.SPV_BINARY_TO_TEXT_OPTION_INDENT | + spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + spv.SPV_BINARY_TO_TEXT_OPTION_COLOR + ); + console.log("dis:\n", disResult); + +}; + +test(); diff --git a/test/wasm/test.js b/test/wasm/test.js index 7f0d8f3cf8..5214ee19fa 100644 --- a/test/wasm/test.js +++ b/test/wasm/test.js @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -const spirvTools = require("../../out/web/spirv-tools"); +const spirvTools = require("../../out/web/spirv-tools/spirv-tools"); const fs = require("fs"); const util = require("util"); const readFile = util.promisify(fs.readFile); @@ -43,12 +43,13 @@ const test = async () => { %int = OpTypeInt 32 1 %spec = OpSpecConstant %int 0 %const = OpConstant %int 42`; + const asResult = spv.as( source, spv.SPV_ENV_UNIVERSAL_1_3, spv.SPV_TEXT_TO_BINARY_OPTION_NONE ); - console.log(`as returned ${asResult.byteLength} bytes`); + console.log(`as returned ${asResult.length} bytes`); // re-disassemble const disResult = spv.dis(