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

  1. Compile the .das file as normal.

  2. Run the AOT tool: daslang -aot script.das output.cpp

  3. Build the generated .cpp alongside your application and link libDaScript.

  4. 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]) and ExprSafeAt (array?[index]). Must provide at(value, index, context) and at(value, index, count, context) methods.

das_iterator<T>

Used for for loop iteration. Must provide main_loop and 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

  1. Define a C++ abstract base class with virtual methods.

  2. Generate a “tutorial adapter” class that maps each virtual method to a das_invoke_function call.

  3. Create a dual-inheritance adapter: struct Adapter : BaseClass, TutorialAdapter.

  4. In the daslang script, derive a class from the adapter type and override methods.

  5. 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 method

  • compileBuiltinModule — compiles an embedded .das module as a built-in

  • class_info(*classPtr) — gets StructInfo for 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

  1. Call the generator-returning function with evalWithCatch, passing a Sequence * as the third argument:

    Sequence it;
    ctx.evalWithCatch(fnTest, nullptr, &it);
    
  2. Step through values with builtin_iterator_iterate:

    int32_t value = 0;
    while (builtin_iterator_iterate(it, &value, &ctx)) {
        // process value
    }
    
  3. 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 Standalone class.

  • Deterministic startup — all code is linked at build time.

Pipeline:

  1. Compile-time: daslang utils/aot/main.das -- -ctx script.das output_dir/

  2. This generates script.das.h and script.das.cpp.

  3. Build both alongside your host application.

  4. At runtime, create the Standalone context 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 — forbid unsafe blocks

  • no_global_variables = true — forbid module-level variables

  • no_global_heap = true — forbid heap allocations for globals

  • no_init = true — forbid [init] functions

  • threadlock_context = true — enable context mutex

FsFileAccess can be locked to restrict file access:

  1. Pre-introduce allowed files with das_fileaccess_introduce_file.

  2. Lock with das_fileaccess_lock to 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 require statements 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.