dasLLVM
July 2, 2026 · View on GitHub
LLVM-C Bindings for daScript
This project provides LLVM-C bindings for daScript, generated for LLVM 22.1.5.
These bindings allow you to interact with LLVM's compiler infrastructure
directly from daScript.
Installation
Automatic Installation (Recommended)
During the CMake configuration process, the build system will:
- First attempt to locate a system-installed
LLVM 22 - If not found, automatically download prebuilt LLVM packages from:
https://github.com/GaijinEntertainment/daScript/releases/tag/llvm-v22.1.5
(curated z3-free builds produced by the
llvm-releaseworkflow,.github/workflows/llvm_release.yml) - Place the library into
das_root/lib/LLVM.dll.
Note that the filename is fixed and cannot be changed, regardless of your platform. The reason is bindings
Manual installation
Linux (Debian/Ubuntu)
sudo apt-get install -y libclang-22-dev clang-22 llvm-22-dev
Darwin
brew install llvm@22
Windows
Note: The LLVM 22.0.0 release on the LLVM website does not ship developer headers.
Consider building it from scratch.
Regenerating bindings
Bindings under bindings/ are generated by modules/dasClangBind/bind/bind_llvm.das
using libclang. To regenerate against a different LLVM include tree (e.g. when
bumping the version), pass --input pointing at the matching llvm-c/
include tree. --output overrides the bindings folder (defaults to
<das_root>/modules/dasLLVM/bindings). Run with --show-help (-?) for
the full flag list.
./bin/daslang modules/dasClangBind/bind/bind_llvm.das -- \
--input /usr/lib/llvm-22/include
This rewrites bindings/llvm_func.das, bindings/llvm_struct.das,
bindings/llvm_enum.das, bindings/llvm_const.das.
JIT Usage
Take your project, for example:
[jit] // Not necessary, default behaviour
def foo() {
print("Hello from JIT!")
}
[no_jit]
def bar() {
print("Hello from interpreter!")
}
[export]
def main() {
print("Hello from JIT!\n")
}
And run it:
./bin/daslang -jit main.das
This module will create binaries on-the-fly for the foo and main
functions, and use them instead of the interpreter at runtime.
Caching System
When using daslang (./bin/daslang) and jit_use_dll_mode=true
(default behaviour):
- First Run: Compiles the code to
LLVM IR, optimizes it, generates native code, and saves it as a dynamic (shared) library. - Subsequent Runs: Checks if the source code has changed. If unchanged, loads the cached DLL for instant execution.
DLL location
- By default, the
dllis stored in.jitted_scripts/. - This can be changed using
jit_output_path.
Cross-compilation (WebAssembly)
The JIT pipeline can emit a non-host target instead of running on the host.
The supported cross-target is wasm32-unknown-emscripten (the default when no
explicit triple is passed).
How it works
-exe mode usually emits a host object via LLVM and links it with the host linker (lld-link on Windows-MSVC, c++/clang elsewhere).
When a cross-compile target is selected, dasLLVM instead:
- Initializes the WebAssembly LLVM target (lazy — no JIT-startup overhead when unused).
- Builds a
TargetMachinefor the requested triple and pins the module's data layout / triple so codegen sizes pointers as 32-bit. - Emits a
wasm32object file viaLLVMTargetMachineEmitToFile. - Links via
emcc <obj> [<libDaScript_runtime.a>] -sSTANDALONE_WASM --no-entry. The runtime archive is included only when the module references runtime symbols (i.e. has external decls). Pure programs link without it.
The runtime archive is auto-located at
<das_root>/web/output/lib/liblibDaScript_runtime.a and is produced by the
existing emscripten build (web/CMakeLists.txt). If a program references
runtime symbols but the archive is missing, the link still proceeds and emits
a warning — the resulting .wasm will fail at load time on unresolved
imports for every runtime symbol that is actually used.
Linker tools
The wasm link always goes through emcc. dasLLVM resolves it in this order:
- Explicit override (set
cop.jit_path_to_linkerprogrammatically). <das_root>/bin/emcc[.bat]— bundled next todaslang.emcconPATH(typically provided by an activated emsdk).
Building libDaScript_runtime for wasm
The runtime archive needed by the emcc path is a side-effect of the
emscripten build documented in web/README.md. One-time setup (assumes
emcc is on PATH — see web/README.md for install options, e.g.
sudo apt install emscripten on Ubuntu):
emcmake cmake -S web -B web/cmake_temp -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build web/cmake_temp --target libDaScript_runtime
This drops liblibDaScript_runtime.a (wasm32) into web/output/lib/.
Re-run only when runtime sources change.
Custom runtime archive path
By default dasLLVM auto-locates the archive at
<das_root>/web/output/lib/liblibDaScript_runtime.a. To use a different
location pass --jit-runtime-lib=<path> after --:
./bin/daslang -exe -output add add.das -- \
--jit-target=wasm32-unknown-emscripten \
--jit-runtime-lib=/opt/daslang/wasm/liblibDaScript_runtime.a
Equivalent script-level option: options jit_runtime_lib = "...". CLI wins
when both are set.
Example
Write add.das:
options gen2
def add(a, b : int) : int { return a + b; }
[export] def main() : int { return add(2, 3); }
Pick a triple via either the script or the CLI (only one is required; both are optional — CLI wins when both present):
Script-level option:
options jit_target = "wasm32-unknown-emscripten"
then:
./bin/daslang -exe -output add add.das
CLI flag (no edit to the script):
./bin/daslang -exe -output add add.das -- --jit-target=wasm32-unknown-emscripten
-exe selects executable JIT mode; the triple pins codegen to wasm32 and
emcc links add.wasm next to -output. Script-side flags live after --.
Running the produced .wasm
For pure-arithmetic programs (no runtime linked):
node -e 'WebAssembly.instantiate(require("fs").readFileSync("add.wasm"),{env:new Proxy({},{get:()=>()=>0})}).then(r=>console.log(r.instance.exports.main()))'
Prints 5.
The Proxy supplies a ()=>0 stub for every wasm import — needed because
--allow-undefined leaves daslang-runtime symbols as imports.
For programs that pull in libDaScript_runtime (auto-detected, see How it
works), the output is -sSTANDALONE_WASM and the only imports are wasi
syscalls. Run under any wasi-capable host:
wasmtime add.wasm
# or with Node ≥ 20:
node --experimental-wasi-unstable-preview1 -e \
'const {WASI}=require("node:wasi");const fs=require("fs");\
const w=new WASI({version:"preview1"});\
WebAssembly.instantiate(fs.readFileSync("add.wasm"),w.getImportObject())\
.then(r=>w.start(r.instance))'
Threading is unsupported: the runtime archive is built without -sUSE_PTHREADS=1,
so jobque/channels/LockBox paths trap at runtime even though they link.