Skip to content

Latest commit

 

History

History
187 lines (162 loc) · 6.78 KB

File metadata and controls

187 lines (162 loc) · 6.78 KB

dasLLVM

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:

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 dll is 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 clang/clang-cl. When a cross-compile target is selected, dasLLVM instead:

  1. Initializes the WebAssembly LLVM target (lazy — no JIT-startup overhead when unused).
  2. Builds a TargetMachine for the requested triple and pins the module's data layout / triple so codegen sizes pointers as 32-bit.
  3. Emits a wasm32 object file via LLVMTargetMachineEmitToFile.
  4. 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:

  1. Explicit override (set cop.jit_path_to_linker programmatically).
  2. <das_root>/bin/emcc[.bat] — bundled next to daslang.
  3. emcc on PATH (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.