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.
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/aleksisch/llvm-shared-builds/releases/tag/v22.1.5
- 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
sudo apt-get install -y libclang-22-dev clang-22 llvm-22-devbrew install llvm@22Note: The LLVM 22.0.0 release on the LLVM website does not ship developer headers.
Consider building it from scratch.
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/includeThis rewrites bindings/llvm_func.das, bindings/llvm_struct.das,
bindings/llvm_enum.das, bindings/llvm_const.das.
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.
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.
- By default, the
dllis stored in.jitted_scripts/. - This can be changed using
jit_output_path.
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).
-exe mode usually emits a host object via LLVM and links it with clang/clang-cl.
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.
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).
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_runtimeThis drops liblibDaScript_runtime.a (wasm32) into web/output/lib/.
Re-run only when runtime sources change.
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.aEquivalent script-level option: options jit_runtime_lib = "...". CLI wins
when both are set.
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.dasCLI 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 --.
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.