npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ts-emutils

v0.0.1

Published

Typescript and C++ utility code for WebAssembly target

Downloads

55

Readme

ts-emutils

Npm

:zap: Rationale

This simple npm package provides utility TypeScript and C++ code for emscripten WebAssembly toolchain. It is designed for performance and simplicity.

C++ library is header-only and dependency-free. Just include headers into your pch.h (precompiled header) or dependent C++ source file directly and that's it.

:dvd: Compilation

This library is designed with std=gnu++2a compiler option in mind, so be sure to configure your em++ with this standard or higher.

You have to add the following methods to EXTRA_EXPORTED_RUNTIME_METHODS command-line option when compiling with em++:

  • lengthBytesUTF8,
  • stringToUTF8,
  • stackSave,
  • stackRestore,
  • stackAlloc

There are 2 modules exported by this package:

  • injected
  • static

injected module

This is a JavaScript file that you have to include after your emscripten glue code via post-js parameter when compiling with em++ or emcc:

--post-js ./node_modules/emutils/build/injected/index.js

The path build/injected/index.js to the bundle is guaranteed not to change within one major version of emutils package.

After that all the exports will reside in Module.Emu object.

import { EmuModule } from "emutils";

const Module: EmuModule = require('../my-wasm-emscripten-js-glue-code');

Module.onRuntimeInitialized = () => {
    const Emu = Module.Emu

    const emuStrArr = new Emu.Utf8StrArr(["my string1", "my string2"]);

    try {
        /* ... */
    } finally {
        emuStrArr.delete();
    }
};

You may have noticed the ugly try {} finally {} block you are forced to use in order to prevent memory leaks. Since JavaScript objects have indeterminate lifetimes and there is no way to hook into their destruction code, all memory management stuff is manual.

However there is a good shortcut for writing these try {} finally {} blocks that requires some babel transpiling. See using.macro package to learn how to set up the following using() syntax in your project.

const emuStrArr = new Emu.Utf8StrArr(["my string1", "my string2"]);

try {
    /* ... */
} finally {
    emuStrArr.delete();
}

 ↓ ↓ ↓ ↓ ↓ ↓

import using from "using.macro";

const const emuStrArr = using (new Emu.Utf8StrArr(["my string1", "my string2"]));

// the following code will be wrapped into try {} finally {} automatically

Heap and Stack

These two guys represent your WebAssembly stack and heap accordingly.

They provide two methods alloc(alignment, size) and allocArray(bytesPerElement, size) where the latter is just a shorthand for .alloc(bytesPerElement, bytesPerElement * size)

alloc() is ridiculously simple, it returns you a pointer to the memory on heap or stack of the size you requested.

There is some type safety here that you may rely on thanks to TypeScript that will help you to pass the proper alignment and size of primitive type you want to allocate for.

import {i32} from "emutils";

const {Heap, HeapPtr} = Module.Emu;

// points to heap memory block of 42 items of i32 type (32 bit integers)
const myI32BlobPtr: HeapPtr<i32> = Heap.alloc<i32>(4, 42);

// never forget to free the memory! or try `using()`
myI32BlobPtr.delete();

HeapPtr is a wraper over a pair of pointers to the heap, where one of them points to the actual aligned memory block you requested and which can be obtained via .ptr property. The second one points to the original memory block, returned by the memory allocator that manages emscripten's heap and it can be read via .originalPtr. This class is designed to save the original pointer that should be passed to Module._free() when deallocating the memory, because the pointer returned by the allocator may not always be properly aligned, so we do allocate some extra memory to ensure the alignment by our own means.

The Stack is a bit simpler

import {ptr, i32, f64} from "emutils";
const {Stack} = Module.Emu;

const sp = Module.stackSave();
try {
    const i32Ptr: ptr<i32> = Stack.alloc<i32>(4, 42); // 42 integers
    const f64Ptr: ptr<f64> = Stack.alloc<f64>(8, 112); // 112 floating points (doubles)
} finally {
    // no need to free each pointer one by one
    // just restore the stack to its original position
    // you may create special usingStack() macro for this if you want
    Module.stackRestore(sp);
}

write*() and read*()

There are a bunch of write*() and read*() (* is the type name) helper functions to write and read values from raw pointers accordingly. These all abstract away interaction with Module.HEAP* array views and provide the ultimate type safety.

There are also writeArray*() variants if you wonder.

import {ptr, f32} from "emutils";
const { writePtr, readPtr, writeI32, readI32, Heap } = Module.Emu;

const i32Ptr = using (Heap.alloc<i32>(4, 4)); // single 4-byte-aligned 4-byte integer
const ptrPtr = using (Heap.alloc<ptr>(4, 4)); // single 8-byte-aligned 4-byte pointer

writeI32(i32Ptr, 42);
writePtr(ptrPtr, i32Ptr);

assert(readI32(i32Ptr) === 42);
assert(readPtr(ptrPtr) === i32Ptr);
assert(readI32(readPtr(ptrPtr) as ptr<i32>) === 42); // double indirection

class Utf8StrArr

This class represents a linear fixed-with array of utf8 strings. It wraps a memory block which contains a representation of C++ Emu::Utf8StrArr class counterpart that is located in include/emutils/utf8-str-arr.h

It currently provides almost no methods, because it is designed for performance and the way it is layout in memory is the single purpose of its creation. When you make Utf8StrArr you allocate only one memory block on WebAssembly heap. After that you pass the pointer returned by .getRawPtr() method to your C++ code.

Your C++ code shoud accept int32_t instead of a pointer type, since emscripten doesn't let you pass raw pointers between JavaScript and C++ code so easily.

So this looks like this:

// TypeScript

const myStrArr = using (new Module.Emu.Utf8StrArr(["my string1", "my string2"]));

Module.myCppFn(myStrArr.getRawPtr());
// C++

#include <emutils/utf8-str-arr.h>

void MyCppFn(const int32_t strArrPtr) {
    const Emu::Utf8StrArr strArr{strArrPtr};
    const std::string_view firstString = strArr[0]; // "my string1"
}

The API of C++ counterpart is very intuitive, it just mimics std::array<std::string_view>.

class HeapArray, class StackArray and C++ class Emu::RawArray

All these classes are similarly to Utf8StrArr just wrappers over a memory block. HeapArray should be explicitly freed via .delete() and StackArray is disposed via a call to Module.stackRestore(sp).

Both HeapArray<T> and StackArray<T> provide you with a simple and type safe interface to allocating numeric arrays on emscripten's heap and stack.

They give you a whole bunch of static .alloc*(jsArray) factory methods that convert javascript vanilla arrays or typed arrays to HeapArray and StackArray.

There is .getRawPtr() method that returns a pointer (HeapPtr from HeapArray and ptr from StackArray) to the underlying memory block.

In order to access this array of numbers from C++ side you shoud use Emu::RawArray

// TypeScript

const myRawArray = using (new Module.Emu.HeapArray<f64>([3.14, 2.71, 42.0]));

Module.myCppFn(myRawArray.getRawPtr());
// C++

#include <emutils/raw-array.h>

void MyCppFn(const int32_t rawArrPtr) {
    // Cannot pass `void*` here due to the following issue:
    // https://github.com/emscripten-core/emscripten/issues/9448
    // As a workaround just use a noop placement-new operator here:

    const auto& rawArr { *new (reinterpret_cast<void*>(rawArrPtr)) Emu::RawArray<double> };
    const double firstDouble = rawArr[0];
}

interface StdVector<T> and Emu::RegisterStdVector(name)

Emu::RegisterStdVector resides in the header file: emutils/std/vector.h

This is a utility function that registers std::vector<T> types to use in your WebAssembly interface.

StdVector<T> is a simple TypeScript interface that provides you with methods typings around your registered std::vector<T> types.

static module

This is the functionallity you get from "emutils" package itself.

import {i32, nullptr, ptr, StdVector} from "emutils";

type MyStdVectorOfI32Ptrs = StdVector<ptr<i32>>;
const myVector: MyStdVectorOfI32Ptrs = new Module.StdVector();
myVector.pushBack(nullptr);

These are only some utility types like i8, i16, i32, u8, u16, u32, ptr<T>, f32, f64 that give you type safety and some other interfaces like Delete that represents a resource handle with .delete() method and StdVector<> which represents an interface of std::vector<T> that you will register from C++ side via Emu::RegisterStdVector from emutils/std/vector.h.

Development prerequisites

Emscripten

Follow these installation guidelines to install emsdk on you PC at some /path/to/emsdk. Be sure not to skip CMake installation.

Add the following line to your '~/.bashrc' file in order to add emscripten tools to your $PATH on bash terminal startup:

source "/path/to/emsdk/emsdk_env.sh" &> /dev/null

Premake5

Download the latest verison of premake5 and put the executable at some /path/to/premake5

Append the following line to your ~/.bashrc

export PATH="/path/to/premake5:$PATH"

npm run build - build the project