4.4. Advanced Topics
This page covers advanced embedding techniques: ahead-of-time compilation, class adapters, coroutines, dynamic script generation, and standalone contexts.
4.4.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.
4.4.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 linklibDaScript.At runtime,
simulate()detects AOT-compiled functions and uses them instead of the interpreter.
See tutorial_integration_cpp_aot for a step-by-step walkthrough.
4.4.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.
4.4.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.
4.4.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.
4.4.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.
4.4.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 tutorial_integration_cpp_class_adapters for a complete, compilable example.
4.4.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.
4.4.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.
See tutorial_integration_cpp_coroutines.
4.4.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 tutorial_integration_cpp_dynamic_scripts.
4.4.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.
See tutorial_integration_cpp_context_variables.
4.4.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::standalone::Standalone ctx;
ctx.test(); // direct call, no findFunction needed
See tutorial_integration_cpp_standalone_contexts for a complete example.
4.4.6. 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 tutorial_integration_cpp_serialization and tutorial_integration_c_serialization.
4.4.7. 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.
See tutorial_integration_cpp_sandbox and tutorial_integration_c_sandbox.
4.4.8. 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
}
}
See tutorial_integration_cpp_custom_modules.