5.6. Advanced Topics
This page covers advanced embedding techniques: ahead-of-time compilation, class adapters, coroutines, dynamic script generation, and standalone contexts.
5.6.1. AOT compilation
daslang can compile scripts ahead-of-time into C++ source files that, when built alongside the host, replace the interpreter with direct native calls. This provides significant performance improvements for hot paths.
5.6.1.1. AOT pipeline overview
Compile the
.dasfile as normal.Run the AOT tool:
daslang -aot script.das output.cppBuild the generated
.cppalongside your application and linklibDaScriptandlibDaScript_runtime(the full hand-wired link set: C API Reference, section Linking).At runtime,
simulate()detects AOT-compiled functions and uses them instead of the interpreter.
See C++ Integration: AOT Compilation for a step-by-step walkthrough.
5.6.1.2. AOT annotations
[no_aot]Prevents AOT compilation for a specific function.
[hybrid]AOT function uses full ABI — slower calls, but no semantic hash dependency. Required when the module may be compiled separately from the script.
5.6.1.3. AOT template functions
By default, AOT-generated functions expect blocks to be passed as the
C++ TBlock class (see Block). If the block is small enough,
AOT can replace it with a direct call. setAotTemplate enables this:
addExtern<DAS_BIND_FUN(my_func)>(*this, lib, "my_func",
SideEffects::invoke, "my_func")
->setAotTemplate();
This generates inlined block evaluation instead of das_invoke.
5.6.1.4. AOT type customization
Handled types can customize their AOT generation through
TypeAnnotation virtual methods:
aotPreVisitGetField(writer, fieldName)Emits code before field access (e.g., dereference operator).
aotVisitGetField(writer, fieldName)Emits the field access itself.
aotPreVisitGetFieldPtr(writer, fieldName)Same as above, for pointer field access.
aotVisitGetFieldPtr(writer, fieldName)Emits pointer field access.
Index operations are covered by das_index<T, IDX> and
das_iterator<T> C++ templates. Override these templates to
provide AOT-compatible indexing and iteration for custom types.
das_index<T, IDX>Used for
ExprAt(array[index]) andExprSafeAt(array?[index]). Must provideat(value, index, context)andat(value, index, count, context)methods.das_iterator<T>Used for
forloop iteration. Must providemain_loopand associated helper methods.
5.6.2. Class adapters
Class adapters bridge C++ virtual method dispatch to daslang class method implementations. This allows daslang scripts to define classes that a C++ host can polymorphically call through base class pointers.
5.6.2.1. Pattern overview
Define a C++ abstract base class with virtual methods.
Generate a “tutorial adapter” class that maps each virtual method to a
das_invoke_functioncall.Create a dual-inheritance adapter:
struct Adapter : BaseClass, TutorialAdapter.In the daslang script, derive a class from the adapter type and override methods.
The C++ host calls virtual methods on base class pointers — dispatch reaches daslang automatically.
Key C++ APIs:
das_invoke_function<Ret>::invoke(ctx, at, fn, args...)— calls a daslang function pointer from C++getDasClassMethod(classPtr, offset)— retrieves the daslang function pointer for a virtual methodcompileBuiltinModule— compiles an embedded.dasmodule as a built-inclass_info(*classPtr)— getsStructInfofor RTTI bridging
The adapter class is typically generated by log_cpp_class_adapter
from daslib/cpp_bind, which produces the boilerplate
get_<method> / invoke_<method> static helpers.
See C++ Integration: Class Adapters for a complete, compilable example.
5.6.3. Coroutines (generators)
daslang generators (generator<T>) can be consumed from C++ through
the Sequence type. This enables coroutine-style patterns where
the host drives execution step by step.
5.6.3.1. Consuming a generator
Call the generator-returning function with
evalWithCatch, passing aSequence *as the third argument:Sequence it; ctx.evalWithCatch(fnTest, nullptr, &it);
Step through values with
builtin_iterator_iterate:int32_t value = 0; while (builtin_iterator_iterate(it, &value, &ctx)) { // process value }
Clean up with
builtin_iterator_close:builtin_iterator_close(it, &value, &ctx);
Even if the generator has finished, always call close to release
resources. If you break early, close is essential.
5.6.4. Dynamic script generation
Scripts can be constructed as strings and compiled without writing to disk, using virtual files:
TextWriter ss;
ss << "options gen2\n";
ss << "var x = 0.0f\n";
ss << "var y = 0.0f\n";
ss << "[export] def eval() : float { return x*x + y*y; }\n";
auto fAccess = make_smart<FsFileAccess>();
auto fileInfo = make_unique<TextFileInfo>(
ss.str().c_str(), uint32_t(ss.str().length()), false);
fAccess->setFileInfo("virtual.das", das::move(fileInfo));
auto program = compileDaScript("virtual.das", fAccess, tout,
dummyLibGroup);
After simulation, global variables can be located and written to directly through context memory:
int idx = ctx->findVariable("x");
float * xPtr = (float *)ctx->getVariable(idx);
*xPtr = 3.0f; // set x = 3.0
This pattern is useful for expression evaluators, configuration systems, and dynamic code generation.
See C++ Integration: Dynamic Scripts.
5.6.4.1. Context variables
ctx.findVariable(name)Returns the index of a global variable, or -1 if not found.
ctx.getVariable(index)Returns a raw pointer into the context’s global data segment.
ctx.getTotalVariables()Returns the number of global variables.
ctx.getVariableName(index)Returns the variable’s name.
ctx.getVariableSize(index)Returns the variable’s size in bytes.
5.6.5. Standalone contexts
A standalone context pre-compiles a daslang program into C++ source
files (.das.h and .das.cpp) that embed the entire program state
— function tables, variable layouts, and AOT implementations — without
requiring runtime compilation.
Benefits:
Zero compilation overhead at runtime — no parsing, no type inference, no simulation.
Direct native calls — exported functions become C++ methods on a
Standaloneclass.Deterministic startup — all code is linked at build time.
Pipeline:
Compile-time:
daslang utils/aot/main.das -- -ctx script.das output_dir/This generates
script.das.handscript.das.cpp.Build both alongside your host application.
At runtime, create the
Standalonecontext and call functions directly.
#include "standalone_ctx_generated/script.das.h"
das::ctx_standalone::Standalone ctx;
ctx.test(); // direct call, no findFunction needed
The same generated files also carry a C API: one extern "C" entry
point per export, wrapping the C++ method beside it, plus an opaque
handle and <prefix>_create / _destroy / _last_error. So one
generated pair serves a C++ host and a C host at once, and the two
surfaces are not identical — C++ reaches every export in native types,
while C carries the subset C can express (the emitter names the ones it
skips). The .das.cpp embeds the header’s text rather than including
it, so moving or editing your copy of the header cannot break the
library’s own translation unit.
See C++ Integration: Standalone Contexts for a complete example.
5.6.6. C libraries
daslang -lib compiles a daslang program to a native library with a C
API, so a host that only calls one script needs no daslang headers, no
Module, and no compiler. The LLVM backend emits the library; a
standalone context (above) emits C++ source your build compiles instead.
Pipeline:
daslang -lib script.das -output build/scriptThis writes
build/script.so(.dylib/.dll) andbuild/script.h. Add-- --jit-lib-staticfor a.a/.libarchive instead; the header records what a host then links.Include the header, link the library, call it.
Which functions cross the boundary is your choice: [export_c] marks
them one at a time, and -lib-export-all offers every public function
of the entry module whose signature C can spell, naming the ones it skips.
Every library gets the same four entry points, prefixed with the output name — one instance owns one daslang context, with its own globals and heap:
#include "script.h"
script_ctx * ctx = script_create();
if ( !ctx ) {
printf("%s\n", script_last_error(NULL));
return 1;
}
script_Vec3 v = { 1.0f, 2.0f, 3.0f };
script_Vec3 out;
script_scale(ctx, &v, 2.0f, &out); /* a struct result uses a trailing out pointer */
script_destroy(ctx);
A daslang panic never unwinds into C: the call returns zero and leaves
out untouched, and script_last_error(ctx) reports the text until
the next call on that instance clears it. A const char * a function
returns lives in that instance’s string heap, so copy it if you need it
past the next call.
The generated header asserts the layout of every structure it declares
(_Static_assert in C11, static_assert in C++), so a host built
for a different target fails to compile rather than misreading memory.
A daslang host needs no C: -- --jit-lib-bindings build/script_c.das
writes the daslang twin of the header beside it, one
[extern(cdecl, late, ...)] per entry point plus a das struct per
structure that crosses, and the host requires that file. The externs
bind late, so the bindings compile before the library exists and a
build system can generate them as an ordinary output.
Several such libraries coexist in one process, and so does a library inside a host that registered the daslang modules itself: whoever gets there first registers the runtime, and the rest bind to it. Several instances of one library are fine, on any thread.
5.6.7. Serialization
Compiled programs can be serialized to a binary blob, skipping compilation entirely on subsequent loads:
// Save
AstSerializer ser;
program->serialize(ser);
// ser.buffer contains the binary data
// Load
AstSerializer deser;
deser.buffer = saved_data;
auto restored = Program::deserialize(deser);
// simulate and run as normal
This is faster than recompilation (skips parsing and type inference) but slower than standalone contexts (still requires simulation).
See C++ Integration: Serialization and C Integration: Serialization.
5.6.8. Sandboxing
CodeOfPolicies controls what scripts are allowed to do:
no_unsafe = true— forbidunsafeblocksno_global_variables = true— forbid module-level variablesno_global_heap = true— forbid heap allocations for globalsno_init = true— forbid[init]functionsthreadlock_context = true— enable context mutex
FsFileAccess can be locked to restrict file access:
Pre-introduce allowed files with
das_fileaccess_introduce_file.Lock with
das_fileaccess_lockto prevent filesystem reads.
5.6.9. File access customization
Override FsFileAccess methods for custom file resolution:
getNewFileInfo(fname)Resolves file paths and returns file content.
getModuleInfo(req, from)Resolves
requirestatements to file paths.setFileInfo(name, info)Registers a virtual file from a string — used for dynamic script generation and embedded modules.
For project-level file resolution, implement module_get as a
function macro (.das-side):
options gen2
require daslib/ast_boost public
[function_macro(name="module_get")]
class ModuleGetMacro : AstModuleGetMacro {
def override getModule(req, from : string) : ModuleInfo {
// resolve require paths here
}
}