Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ lib/
modules/**/*.shared_module
examples/**/*.shared_module
_aot_generated/
_llvm_aot_generated/
.vscode/
.cache/

Expand Down
54 changes: 54 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,60 @@ MACRO(DAS_AOT_LIB input genList mainTarget dasAotTool)
DAS_AOT_EXT("${input}" ${genList} ${mainTarget} ${dasAotTool} -aotlib)
ENDMACRO()

# LLVM analog of DAS_AOT_LIB: emit a native .o per .das (this module only) via the LLVM
# backend instead of the C++ proxy. Linked as external objects, so their load ctors
# self-register into the AOT library. Needs LLVM (call only under NOT DAS_LLVM_DISABLED).
MACRO(DAS_LLVM_AOT_LIB input_files genList mainTarget)
# The emit-object codegen lives in dasLLVM's daslib (loaded at runtime), so depend
# on it too -- ninja re-emits the .o when the codegen changes, not just the binary.
file(GLOB _das_llvm_aot_codegen CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/modules/dasLLVM/daslib/*.das)
set(all_outputs "")
# Emit in batches of 32 files per daslang process: the ~52-file dasLLVM compile-time load
# is paid once per batch, not per file (the way run_tests_jit's --batch amortizes it). Any
# file in a batch changing re-emits the whole batch; ninja still runs batches in parallel.
# MACRO args are text substitutions, not variables -- copy to a real var for list(LENGTH).
set(_llvm_aot_inputs ${input_files})
list(LENGTH _llvm_aot_inputs _llvm_aot_total)
set(_llvm_aot_idx 0)
set(_batch_rels "")
set(_batch_srcs "")
set(_batch_objs "")
foreach(input ${_llvm_aot_inputs})
math(EXPR _llvm_aot_idx "${_llvm_aot_idx} + 1")
get_filename_component(input_src ${input} ABSOLUTE)
get_filename_component(input_dir ${input_src} DIRECTORY)
get_filename_component(input_name ${input} NAME)
set(out_dir ${input_dir}/_llvm_aot_generated)
set(out_obj ${out_dir}/${mainTarget}_${input_name}.o)
# The compile-time path is baked into hashed constants (same reason as DAS_AOT_EXT),
# so spell the input source-root-relative to keep the AOT hash in sync with -use-aot.
file(RELATIVE_PATH input_rel ${PROJECT_SOURCE_DIR} ${input_src})
file(MAKE_DIRECTORY ${out_dir})
list(APPEND all_outputs ${out_obj})
list(APPEND ${genList} ${out_obj})
set_source_files_properties(${out_obj} PROPERTIES GENERATED TRUE EXTERNAL_OBJECT TRUE)
list(APPEND _batch_rels ${input_rel})
list(APPEND _batch_srcs ${input_src})
list(APPEND _batch_objs ${out_obj})
list(LENGTH _batch_rels _batch_n)
if(_batch_n EQUAL 32 OR _llvm_aot_idx EQUAL _llvm_aot_total)
ADD_CUSTOM_COMMAND(
OUTPUT ${_batch_objs}
DEPENDS daslang ${_batch_srcs} ${PROJECT_SOURCE_DIR}/utils/jit/main.das ${_das_llvm_aot_codegen}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
COMMENT "LLVM-AOT compiling ${_batch_n} files"
COMMAND daslang ${PROJECT_SOURCE_DIR}/utils/jit/main.das -- ${_batch_rels} --aot-object --aot-object-prefix ${mainTarget}
)
set(_batch_rels "")
set(_batch_srcs "")
set(_batch_objs "")
endif()
endforeach()
set(custom_name ${mainTarget}_genllvmaot)
ADD_CUSTOM_TARGET(${custom_name} DEPENDS ${all_outputs})
ADD_DEPENDENCIES(${mainTarget} ${custom_name})
ENDMACRO()

SET(UNITZE_BUILD_BATCH_SIZE 10)

MACRO(UNITIZE_BUILD input_dir genList)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ JIT all functions - if enabled, JIT will compile all functions in the module.
JIT debug info - if enabled, JIT will generate debug info for JIT compiled code.
JIT dll mode - if enabled, JIT will generate DLL's into JIT output folder and load them from there.
JIT exe mode - if enabled, JIT will generate standalone executable.
JIT offline AOT object mode - emit a native .o (this module only) with a load constructor registering its functions into the AOT library, for static linking into a host binary.
JIT will always emit function prologues, which allows call-stack in debuggers.
JIT output folder (where JIT compiled code will be stored).
JIT optimization level for compiled code (0-3).
Expand Down
1 change: 1 addition & 0 deletions include/daScript/ast/ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,7 @@ namespace das
bool jit_debug_info = false; // Add debug info to generate binary code
bool jit_dll_mode = true; // Create if missing and reuse DLL or JIT compile
bool jit_exe_mode = false; // Create executable
bool jit_emit_object = false; // Offline AOT: emit a .o (this module only) + a load ctor registering its functions into the AOT library, for static linking into a host
bool jit_emit_prologue = false; // Emit prologue for all functions and blocks
string jit_output_path; // Folder to store compiled dll's. By default it'll be _das_root_/.jitted_scripts
int32_t jit_opt_level = 3u; // Opt level for LLVM to codegen and IR optimizations
Expand Down
8 changes: 8 additions & 0 deletions include/daScript/simulate/aot_library.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ namespace das {
bool is_cmres;
void * fn;
vec4f (*wrappedFn)(Context*);
bool is_jit = false;
AotFactory(bool is_cmres, void *fn, vec4f(*wrappedFn)(Context*))
: is_cmres(is_cmres), fn(fn), wrappedFn(wrappedFn) {}
explicit AotFactory(void *jitFn)
: is_cmres(false), fn(jitFn), wrappedFn(nullptr), is_jit(true) {}

SimNode * operator ()(Context &ctx) const;
};
Expand All @@ -29,6 +32,11 @@ namespace das {
DAS_API AotLibrary & getGlobalAotLibrary();
DAS_API void clearGlobalAotLibrary();

// makeAotJitNode builds a SimNode_Jit (defined in module_jit.cpp, where it is visible);
SimNode * makeAotJitNode ( Context & ctx, void * publ );
// runLlvmAotGlobInits runs each linked LLVM-AOT object's glob re-resolution callback.
void runLlvmAotGlobInits ( Context & ctx );

// Test standalone context

typedef Context * ( * RegisterTestCreator ) ();
Expand Down
23 changes: 23 additions & 0 deletions modules/dasAudio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,29 @@ IF ((NOT DAS_AUDIO_INCLUDED) AND ((NOT ${DAS_AUDIO_DISABLED}) OR (NOT DEFINED DA
ADD_MODULE_DAS(strudel strudel strudel_player)
ADD_MODULE_DAS(strudel strudel strudel_live)

# AOT-able sources (for test_aot / any consumer AOT-ing dasAudio); empty when disabled.
SET(DASAUDIO_AOT_FILES
modules/dasAudio/audio/audio_boost.das
modules/dasAudio/audio/audio_wav.das
modules/dasAudio/audio/audio_record.das
modules/dasAudio/strudel/strudel_event.das
modules/dasAudio/strudel/strudel_time.das
modules/dasAudio/strudel/strudel_pattern.das
modules/dasAudio/strudel/strudel_mini.das
modules/dasAudio/strudel/strudel_sfxr.das
modules/dasAudio/strudel/strudel_modal.das
modules/dasAudio/strudel/strudel_sfx.das
modules/dasAudio/strudel/strudel_synth.das
modules/dasAudio/strudel/strudel_samples.das
modules/dasAudio/strudel/strudel_scheduler.das
modules/dasAudio/strudel/strudel_sf2.das
modules/dasAudio/strudel/strudel_sf2_voice.das
modules/dasAudio/strudel/strudel_scales.das
modules/dasAudio/strudel/strudel_midi.das
modules/dasAudio/strudel/strudel_midi_player.das
modules/dasAudio/strudel/strudel_player.das
)

install(DIRECTORY ${PROJECT_SOURCE_DIR}/modules/dasAudio/audio
DESTINATION ${DAS_INSTALL_MODULESDIR}/dasAudio
FILES_MATCHING
Expand Down
6 changes: 6 additions & 0 deletions modules/dasHV/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ IF ((NOT DAS_HV_INCLUDED) AND ((NOT ${DAS_HV_DISABLED}) OR (NOT DEFINED DAS_HV_D

ADD_MODULE_DAS(dashv dashv dashv_boost)

# AOT-able sources (for test_aot / any consumer); empty when disabled. The test-side
# fixture (_dashv_test_common) is appended by tests/aot, it is not a module source.
SET(DASHV_AOT_FILES
modules/dasHV/dashv/dashv_boost.das
)

install(DIRECTORY ${PROJECT_SOURCE_DIR}/modules/dasHV/dashv
DESTINATION ${DAS_INSTALL_MODULESDIR}/dasHV
FILES_MATCHING
Expand Down
48 changes: 48 additions & 0 deletions modules/dasLLAMA/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,54 @@ IF(NOT DAS_LLAMA_INCLUDED)
ADD_MODULE_DAS(dasllama dasllama dasllama_chat)
ADD_MODULE_DAS(dasllama dasllama dasllama)

# AOT-able sources (for test_aot / any consumer); empty when disabled. Spans dasLLVM helpers
# dasLLAMA's math kernels require, plus the dasllama module graph.
SET(DASLLAMA_AOT_FILES
modules/dasLLAMA/tests/_model_tier.das
modules/dasLLVM/daslib/aarch64_neon.das
modules/dasLLVM/daslib/x64_avx.das
modules/dasLLVM/daslib/f16_cvt.das
modules/dasLLAMA/dasllama/dasllama_math.das
modules/dasLLAMA/dasllama/dasllama_math_default.das
modules/dasLLAMA/dasllama/dasllama_math_aarch64_neon.das
modules/dasLLAMA/dasllama/dasllama_gemm_schema.das
modules/dasLLAMA/dasllama/dasllama_math_gen.das
modules/dasLLAMA/dasllama/dasllama_quant.das
modules/dasLLAMA/dasllama/dasllama_gguf.das
modules/dasLLAMA/dasllama/dasllama_unicode.das
modules/dasLLAMA/dasllama/dasllama_tokenizer.das
modules/dasLLAMA/dasllama/dasllama_bpe.das
modules/dasLLAMA/dasllama/dasllama_common.das
modules/dasLLAMA/dasllama/dasllama_prefix.das
modules/dasLLAMA/dasllama/dasllama_audio.das
modules/dasLLAMA/dasllama/dasllama_whisper.das
modules/dasLLAMA/dasllama/dasllama_qwen3a.das
modules/dasLLAMA/dasllama/dasllama_parakeet.das
modules/dasLLAMA/dasllama/dasllama_canary.das
modules/dasLLAMA/dasllama/dasllama_gemma4a.das
modules/dasLLAMA/dasllama/dasllama_vad.das
modules/dasLLAMA/dasllama/dasllama_arch_llama.das
modules/dasLLAMA/dasllama/dasllama_arch_qwen2.das
modules/dasLLAMA/dasllama/dasllama_arch_qwen3.das
modules/dasLLAMA/dasllama/dasllama_arch_phi3.das
modules/dasLLAMA/dasllama/dasllama_arch_gemma2.das
modules/dasLLAMA/dasllama/dasllama_arch_gemma3.das
modules/dasLLAMA/dasllama/dasllama_arch_gemma4.das
modules/dasLLAMA/dasllama/dasllama_arch_qwen2moe.das
modules/dasLLAMA/dasllama/dasllama_arch_qwen3moe.das
modules/dasLLAMA/dasllama/dasllama_arch_qwen35.das
modules/dasLLAMA/dasllama/dasllama_arch_gptoss.das
modules/dasLLAMA/dasllama/dasllama_arch_mistral3.das
modules/dasLLAMA/dasllama/dasllama_arch_glm4moe.das
modules/dasLLAMA/dasllama/dasllama_image.das
modules/dasLLAMA/dasllama/dasllama_transformer.das
modules/dasLLAMA/dasllama/dasllama_asr.das
modules/dasLLAMA/dasllama/dasllama_chat.das
modules/dasLLAMA/dasllama/dasllama.das
)
FILE(GLOB DASLLAMA_AOT_DEPENDS CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/modules/dasLLAMA/dasllama/*.das")
SET(DASLLAMA_AOT_DEPENDS "${DASLLAMA_AOT_DEPENDS}")

install(DIRECTORY ${PROJECT_SOURCE_DIR}/modules/dasLLAMA/dasllama
DESTINATION ${DAS_INSTALL_MODULESDIR}/dasLLAMA
FILES_MATCHING
Expand Down
2 changes: 1 addition & 1 deletion modules/dasLLVM/.das_module
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def initialize(project_path : string) {
"llvm_boost", "llvm_debug", "llvm_jit", "llvm_targets",
"llvm_dsl",
"llvm_jit_intrin", "llvm_jit_common", "llvm_dll_utils",
"llvm_exe", "llvm_macro", "llvm_jit_cli", "llvm_jit_run",
"llvm_exe", "llvm_macro", "llvm_jit_cli", "llvm_jit_run", "llvm_aot",
"aarch64_neon", // public NEON-intrinsic header (portable fallbacks + name-based JIT recognition)
"x64_avx", // public x86-64-intrinsic header (same contract, x64 mirror)
"f16_cvt", // public f16<->f32 convert header (same contract, aarch64 + x64/F16C)
Expand Down
118 changes: 118 additions & 0 deletions modules/dasLLVM/daslib/llvm_aot.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
options gen2
options relaxed_pointer_const // fixed_array<LLVMTypeRef> with const PrimitiveTypes fields needs it

module llvm_aot shared private

require llvm/daslib/llvm_boost
require llvm/daslib/llvm_jit
require llvm/daslib/llvm_jit_common
require llvm/daslib/llvm_exe
require llvm/daslib/llvm_dll_utils
require daslib/ast_boost
require daslib/rtti

//! LLVM-AOT registration pass. After the JIT visitor has generated + optimized the module (as for a
//! normal exe), this adds the offline-AOT-object registration layer so a statically-linked .o binds
//! through Program::linkCppAot: a self-registering das_aot_register ctor plus the builtin/Func-value
//! glob re-resolution ctor (built by llvm_exe's extern resolver). All the AOT-object-only "register
//! it into the host" logic lives here; run_jit calls build_llvm_aot_ctors once, in emit_aot_object mode.

// The das_aot_register ctor: for each emitted function, emit das_aot_register(aotHash, publ) so
// the linked .o self-registers into the AOT library at load; also wire the glob re-resolution callback.
def private emit_aot_register_ctor_dtor(types : PrimitiveTypes?; funcs : array<FunctionPtr>; var uids : UidNodes?; _prog : Program?; glob_ctor : LLVMOpaqueValue?) : tuple<LLVMOpaqueValue?, LLVMOpaqueValue?> {
let regType = LLVMFunctionType(types.t_void,
fixed_array<LLVMTypeRef>(types.t_int64, types.LLVMVoidPtrType()))
var regFn = LLVMGetNamedFunction(g_mod, "das_aot_register")
if (regFn == null) {
regFn = LLVMAddFunctionWithType(g_mod, "das_aot_register", regType) // linker binds it to daScript core
}
let functionType = LLVMFunctionType(types.t_void, null, 0u, 0)
// Private: each object defines it, so public linkage would clash when many link together.
let reg_ctor = LLVMAddFunctionWithType(g_mod, "das_aot_register_constructor", functionType)
let reg_dtor = LLVMAddFunctionWithType(g_mod, "das_aot_register_destructor", functionType)
set_private_linkage(reg_ctor)
set_private_linkage(reg_dtor)
var ctor_builder = LLVMCreateBuilderInContext(g_ctx)
var dtor_builder = LLVMCreateBuilderInContext(g_ctx)
LLVMPositionBuilderAtEnd(ctor_builder, LLVMAppendBasicBlockInContext(g_ctx, reg_ctor, "entry"))
LLVMPositionBuilderAtEnd(dtor_builder, LLVMAppendBasicBlockInContext(g_ctx, reg_dtor, "entry"))
for (fun in funcs) {
var publ = LLVMGetNamedFunction(g_mod, uids->get_dll_fn_name(fun).publ())
if (publ == null) {
continue
}
let aot_hash = get_function_aot_hash(fun)
var publ_ptr = LLVMBuildPointerCast(ctor_builder, publ, types.LLVMVoidPtrType(), "")
var reg_args = array<LLVMValueRef>(types.ConstI64(aot_hash), publ_ptr)
LLVMBuildCall2(ctor_builder, regType, regFn, reg_args, "")
}
// Record the glob re-resolution callback; the runtime runs it lazily at the first AotLibrary drain (not static-init).
if (glob_ctor != null) {
let giType = LLVMFunctionType(types.t_void, fixed_array<LLVMTypeRef>(types.LLVMVoidPtrType()))
var giFn = LLVMGetNamedFunction(g_mod, "das_aot_register_globinit")
if (giFn == null) {
giFn = LLVMAddFunctionWithType(g_mod, "das_aot_register_globinit", giType)
}
var gi_ptr = LLVMBuildPointerCast(ctor_builder, glob_ctor, types.LLVMVoidPtrType(), "")
var gi_args = array<LLVMValueRef>(gi_ptr)
LLVMBuildCall2(ctor_builder, giType, giFn, gi_args, "")
}
LLVMBuildRetVoid(ctor_builder)
LLVMBuildRetVoid(dtor_builder)
LLVMDisposeBuilder(ctor_builder)
LLVMDisposeBuilder(dtor_builder)
return (reg_ctor, reg_dtor)
}


// Build a load ctor that re-resolves this AOT object's builtin-accessor globals to the HOST's real
// helpers (offline objects leave them null+internal since codegen-process addresses are meaningless
// there) and points ExprAddr globals at their publ symbols. No module/function registration.
def public emit_aot_object_globinit(ctx : LLVMContextRef; mod : LLVMOpaqueModule?;
var types : PrimitiveTypes?; var funcs : array<FunctionPtr>;
var uids : UidNodes?) : LLVMOpaqueValue? {
// void das_aot_object_globinit_constructor(Context* ctx): ctx is used to
// resolve Func-value globs to the host's SimFunction* (das_aot_get_func_by_mnh).
let fnType = LLVMFunctionType(types.t_void, fixed_array<LLVMTypeRef>(types.LLVMVoidPtrType()))
let ctor = LLVMAddFunctionWithType(g_mod, "das_aot_object_globinit_constructor", fnType)
LLVMSetLinkage(ctor, LLVMLinkage.LLVMPrivateLinkage)
var b = LLVMCreateBuilderInContext(ctx)
LLVMPositionBuilderAtEnd(b, LLVMAppendBasicBlockInContext(ctx, ctor, "entry"))
// Aliases the caller's uids; do NOT delete it (deletion cascades into that shared uid, which the
// caller still uses). It's a gc_node, reclaimed by the surrounding ast_gc_guard / process teardown.
var extern_resolver = new CollectExternVisitor(null, ctx, b, mod, types, uids, false, LlvmJitMode.LLVM_AOT)
extern_resolver.aot_ctx_arg = LLVMGetParam(ctor, 0u)
make_visitor(*extern_resolver) $(adapter_resolve) {
ast_gc_guard() {
for (fn in funcs) {
visit(fn, adapter_resolve)
}
}
}
// Fill the cross-module call/ctor target globs recorded during codegen with the
// host Context's SimFunction* for each callee (by mangled-name hash).
if (!(g_aot_func_globs |> empty())) {
let ctx_arg = LLVMGetParam(ctor, 0u)
for (fg in g_aot_func_globs) {
if (fg.glob == null) {
continue
}
var simfn = build_aot_get_func_by_mnh(b, types, fg.mnh, ctx_arg)
// The glob is a pointer-to-fn-type; the SimFunction* (void*) bitcasts in.
let elemT = LLVMGlobalGetValueType(fg.glob)
var casted = LLVMBuildPointerCast(b, simfn, elemT, "")
LLVMBuildStore(b, casted, fg.glob)
}
}
LLVMBuildRetVoid(b)
LLVMDisposeBuilder(b)
return ctor
}


//! Build both offline-AOT-object ctors (the extern-resolver globinit + the das_aot_register ctor) and
//! return the (ctor, dtor) pair to append to llvm.global_ctors/dtors. Called by run_jit in emit_aot_object mode.
def public build_llvm_aot_ctors(ctx : LLVMContextRef; mod : LLVMOpaqueModule?; var types : PrimitiveTypes?; var funcs : array<FunctionPtr>; var uids : UidNodes?; prog : Program?) : tuple<LLVMOpaqueValue?, LLVMOpaqueValue?> {
let glob_ctor = emit_aot_object_globinit(ctx, mod, types, funcs, uids)
return emit_aot_register_ctor_dtor(types, funcs, uids, prog, glob_ctor)
}
Loading
Loading