From 6e8f71f401afc94e99aa71cea7710ae71c1e0840 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Mon, 23 Mar 2026 00:35:18 -0700 Subject: [PATCH 1/4] Fix ast-grep detection when spawned without venv activated When the MCP server is spawned by an external process (e.g. Claude Code), the Python venv is not activated, so bare `sg` is not on PATH. Detection now tries .venv/Scripts/sg (Windows) and .venv/bin/sg (Linux/macOS) as fallbacks. All sg invocations use the detected path and cd to project root so sgconfig.yml is found. Co-Authored-By: Claude Opus 4.6 (1M context) --- utils/mcp/main.das | 2 +- utils/mcp/tools/common.das | 2 ++ utils/mcp/tools/grep_usage.das | 37 ++++++++++++++++++++++++++++------ utils/mcp/tools/outline.das | 9 ++++++--- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/utils/mcp/main.das b/utils/mcp/main.das index a6022b1168..097d770498 100644 --- a/utils/mcp/main.das +++ b/utils/mcp/main.das @@ -27,7 +27,7 @@ def main() { let sg_check = detect_ast_grep() if (!empty(sg_check)) { ast_grep_available = true - log_write("ast-grep detected — grep_usage tool enabled") + log_write("ast-grep detected ({sg_check}) — grep_usage tool enabled") } else { log_write("WARNING: ast-grep not found — grep_usage tool disabled") } diff --git a/utils/mcp/tools/common.das b/utils/mcp/tools/common.das index 8ecdccd069..c6bda2697b 100644 --- a/utils/mcp/tools/common.das +++ b/utils/mcp/tools/common.das @@ -11,6 +11,8 @@ require math public require strings public require fio public +var ast_grep_cmd : string + struct ContentItem { @rename = "type" _type : string text : string diff --git a/utils/mcp/tools/grep_usage.das b/utils/mcp/tools/grep_usage.das index 984c547008..a5c6aa1106 100644 --- a/utils/mcp/tools/grep_usage.das +++ b/utils/mcp/tools/grep_usage.das @@ -6,22 +6,45 @@ require common public require daslib/json require daslib/json_boost -def detect_ast_grep() : string { - var found = "" +def private try_ast_grep(cmd : string) : bool { + var found = false unsafe { - popen("sg --version") <| $(f) { + popen("{cmd} --version") <| $(f) { if (f == null) { return } let line = fgets(f) if (find(line, "ast-grep") >= 0) { - found = "sg" + found = true } } } return found } +def detect_ast_grep() : string { + // try bare "sg" first (works when .venv is activated or sg is on PATH) + if (try_ast_grep("sg")) { + return "sg" + } + // try .venv paths (not activated): Scripts/ on Windows, bin/ on Linux/macOS + let das_root = get_das_root() + for (subdir in ["Scripts", "bin"]) { + let venv_sg = "{das_root}/.venv/{subdir}/sg" + if (try_ast_grep(venv_sg)) { + return venv_sg + } + } + return "" +} + +def get_ast_grep_cmd() : string { + if (empty(ast_grep_cmd)) { + ast_grep_cmd = detect_ast_grep() + } + return ast_grep_cmd +} + def json_to_int(v : JsonValue?) : int { if (v == null) { return 0 @@ -60,9 +83,11 @@ def do_grep_usage(symbol, directory, context_lines_str, glob_filter : string) : let das_root = get_das_root() let search_dir = resolve_path(!empty(directory) ? directory : ".") // build ast-grep command - var cmd = "sg run -p \"{symbol}\" -l daslang --json \"{search_dir}\"" + let sg = get_ast_grep_cmd() + let cd_root = "cd \"{das_root}\" && " + var cmd = "{cd_root}{sg} run -p \"{symbol}\" -l daslang --json \"{search_dir}\"" if (!empty(glob_filter)) { - cmd = "sg run -p \"{symbol}\" -l daslang --globs \"{glob_filter}\" --json \"{search_dir}\"" + cmd = "{cd_root}{sg} run -p \"{symbol}\" -l daslang --globs \"{glob_filter}\" --json \"{search_dir}\"" } var raw_json : string unsafe { diff --git a/utils/mcp/tools/outline.das b/utils/mcp/tools/outline.das index cd07114512..9322882460 100644 --- a/utils/mcp/tools/outline.das +++ b/utils/mcp/tools/outline.das @@ -3,6 +3,7 @@ options no_unused_function_arguments = false options no_unused_block_arguments = false require common public +require grep_usage public require daslib/json require daslib/json_boost require daslib/strings_boost @@ -230,12 +231,14 @@ def do_outline(file, glob_filter : string) : string { // glob mode: scan directory target = das_root } - var cmd = "sg scan -r \"{rules_path}\" --json \"{target}\"" + let sg = get_ast_grep_cmd() + let cd_root = "cd \"{das_root}\" && " + var cmd = "{cd_root}{sg} scan -r \"{rules_path}\" --json \"{target}\"" if (!empty(glob_filter) && !empty(file)) { // both file and glob — use glob as filter - cmd = "sg scan -r \"{rules_path}\" --globs \"{glob_filter}\" --json \"{resolve_path(file)}\"" + cmd = "{cd_root}{sg} scan -r \"{rules_path}\" --globs \"{glob_filter}\" --json \"{resolve_path(file)}\"" } elif (!empty(glob_filter)) { - cmd = "sg scan -r \"{rules_path}\" --globs \"{glob_filter}\" --json \"{das_root}\"" + cmd = "{cd_root}{sg} scan -r \"{rules_path}\" --globs \"{glob_filter}\" --json \"{das_root}\"" } var raw_json : string unsafe { From 7ed4d6516386997dbce46d4e1ad41bea86788723 Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Thu, 19 Mar 2026 23:51:53 +0300 Subject: [PATCH 2/4] jit: one more exception to intrinsic We need this hack, because we have only functions to generate intrinsics, not functions to check their existance. Usually intrinsics are not generic so if this function present we always will replace it with intrinsic. But if it is generic we handle only part of the types. From now on we have assertion, so such bugs can't happen in runtime. --- modules/dasLLVM/daslib/llvm_jit_intrin.das | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/dasLLVM/daslib/llvm_jit_intrin.das b/modules/dasLLVM/daslib/llvm_jit_intrin.das index b9147eb1d5..18dc83286c 100644 --- a/modules/dasLLVM/daslib/llvm_jit_intrin.das +++ b/modules/dasLLVM/daslib/llvm_jit_intrin.das @@ -121,7 +121,19 @@ def public has_intrinsic(expr : smart_ptr) { var result = false assume argType = expr.arguments[0]._type let call_name = "{expr.func._module.name}::{expr.func.name}" + + // We skip some intrinsics below, because g_intrin_lookup + // is not enough and `pfun` can return null sometimes. + // But we don't have full picture without arguments. + // Anyway, assertion in lookup_intrinsic guarantees consistency. if (call_name == "$::length" && !argType.isGoodArrayType) { + // table + // $::dasvector`smart_ptr`Variable& + return false + } + if (!expr.arguments |> empty() && argType.isHandle) { + // intrinsics can't have handle types: + // int64(clock) return false } g_intrin_lookup |> get(call_name) <| $(pfun) { @@ -136,6 +148,8 @@ def public lookup_intinsic(g_ctx : LLVMContextRef; g_builder : LLVMOpaqueBuilder g_intrin_lookup |> get(call_name) <| $(pfun) { result = pfun |> invoke(JitCtx(ctx = g_ctx, builder = g_builder, types = types), expr, arguments) } + // Check initrinsic consistency + verify(result != null == has_intrinsic(expr)) return result } From 1e40786f2ac10dc86fa02bec4b68cbc9e0da26a2 Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Mon, 23 Mar 2026 16:50:40 +0300 Subject: [PATCH 3/4] jit: complain on no_jit in exe mode In exe mode all das functions should be jitted. Before this commit if functions was marked as no_jit it was possible to still compile. --- modules/dasLLVM/daslib/llvm_exe.das | 35 +++++++++++++++++++++++---- modules/dasLLVM/daslib/llvm_macro.das | 2 +- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/dasLLVM/daslib/llvm_exe.das b/modules/dasLLVM/daslib/llvm_exe.das index 8ba4401762..574e61482e 100644 --- a/modules/dasLLVM/daslib/llvm_exe.das +++ b/modules/dasLLVM/daslib/llvm_exe.das @@ -454,9 +454,38 @@ def collect_external_functions(standalone_context : LLVMOpaqueValue?; ctx : LLVM // Creates main function that initializes a standalone JIT context, registers // all compiled functions, runs init scripts, then calls the program entry point. -def public inject_main(program_context : Context?; ctx : LLVMContextRef; funcs : array; +def public inject_main(program_context : Context?; ctx : LLVMContextRef; prog : Program?; entry_point : string; mod : LLVMOpaqueModule?; var types : PrimitiveTypes?, var uids : UidNodes?; strict : bool) : LLVMOpaqueValue? { + + let builder = LLVMCreateBuilder() + defer <| ${ + LLVMDisposeBuilder(builder) + } + // Collect ALL used functions from the program (not just JIT-compiled ones from `funcs`). + // There shouldn't be any `das` functions in standalone_exe, otherwise it + // will crash. + var funcs : array + var has_no_jit = false + prog |> for_each_module <| $(m) { + if (m.name == "llvm_func" || m.name == "llvm_macro") { + return + } + m |> for_each_function("") <| $(fun) { + if (fun.flags.used) { + if (!fun.flags.builtIn && fun.moreFlags.requestNoJit) { + has_no_jit = true + to_log(LOG_ERROR, "LLVM EXE: function '{fun.name}' is marked no_jit but standalone exe requires all functions to be JIT-compiled\n") + } + funcs |> emplace(fun) + } + } + } + if (strict && has_no_jit) { + to_log(LOG_ERROR, "Cannot build standalone exe: some functions are no_jit in strict mode\n") + return null + } + var start_fn_name = "" var no_return = true for (fn in funcs) { @@ -483,10 +512,6 @@ def public inject_main(program_context : Context?; ctx : LLVMContextRef; funcs : ) ) let main_fn = LLVMAddFunctionWithType(mod, "main", main_fn_type) - let builder = LLVMCreateBuilder() - defer <| ${ - LLVMDisposeBuilder(builder) - } let entry = LLVMAppendBasicBlockInContext(ctx, main_fn, "entry") LLVMPositionBuilderAtEnd(builder, entry) diff --git a/modules/dasLLVM/daslib/llvm_macro.das b/modules/dasLLVM/daslib/llvm_macro.das index a820d569c9..177c937fa6 100644 --- a/modules/dasLLVM/daslib/llvm_macro.das +++ b/modules/dasLLVM/daslib/llvm_macro.das @@ -185,7 +185,7 @@ class JIT_LLVM : AstSimulateMacro { } } if (gen_exe) { - let fn = inject_main(ctx, g_ctx, funcs, prog, exe_main, g_mod, g_prim_t, uids, exe_strict) + let fn = inject_main(ctx, g_ctx, prog, exe_main, g_mod, g_prim_t, uids, exe_strict) if (fn == null) { finalize_jit(use_dll, gen_exe, g_dynamic_lib_handle) // JIT failed. Couldn't create executable. From c3b39d7f058e80535ef479a678a8927a2346e117 Mon Sep 17 00:00:00 2001 From: Churkin Aleksey Date: Mon, 23 Mar 2026 17:11:03 +0300 Subject: [PATCH 4/4] jit: support shared variables - Support to shared variables in JIT. - Load external modules on standalone exe startup. --- modules/dasLLVM/daslib/llvm_jit.das | 6 +++++- src/builtin/module_jit.cpp | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/dasLLVM/daslib/llvm_jit.das b/modules/dasLLVM/daslib/llvm_jit.das index 905abbb71c..8bd512d085 100644 --- a/modules/dasLLVM/daslib/llvm_jit.das +++ b/modules/dasLLVM/daslib/llvm_jit.das @@ -6334,8 +6334,9 @@ def public generate_globals_initialization_fn(ctx : Context?; prog : Program?; } visitor.ffunc = globals_fn let entry = LLVMAppendBasicBlockInContext(g_ctx, globals_fn, "entry") + let body = LLVMAppendBasicBlockInContext(g_ctx, globals_fn, "body") visitor.function_entry = entry - LLVMPositionBuilderAtEnd(visitor.g_builder, entry) + LLVMPositionBuilderAtEnd(visitor.g_builder, body) let ctx_param = LLVMGetParam(globals_fn, 0u) prog |> for_each_module <| $(mod) { mod |> for_each_global() <| $(glob) { @@ -6369,6 +6370,9 @@ def public generate_globals_initialization_fn(ctx : Context?; prog : Program?; } } LLVMBuildRetVoid(visitor.g_builder) + // terminate entry block with branch to body (entry only has allocas from at_function_entry) + LLVMPositionBuilderAtEnd(visitor.g_builder, entry) + LLVMBuildBr(visitor.g_builder, body) visitor.adapter := null unsafe { delete visitor diff --git a/src/builtin/module_jit.cpp b/src/builtin/module_jit.cpp index 1daab36db4..2bb4dc3679 100644 --- a/src/builtin/module_jit.cpp +++ b/src/builtin/module_jit.cpp @@ -4,6 +4,11 @@ #include "daScript/misc/performance_time.h" #include "daScript/misc/sysos.h" +#ifdef DAS_ENABLE_DYN_INCLUDES +#include "daScript/ast/dyn_modules.h" +#include "daScript/simulate/fs_file_info.h" +#endif + #include "daScript/ast/ast.h" // astTypeInfo #include "daScript/ast/ast_handle.h" // addConstant #include "daScript/ast/ast_interop.h" // addExtern @@ -210,11 +215,13 @@ extern "C" { policies.debugger = false; context.setup(totalVariables, globalStringHeapSize, policies, {}); context.globalsSize = 32000; + context.sharedOwner = true; for (int i = 0; i < totalVariables; i++) { globalVariables[i] = GlobalVariable{}; } context.allocateGlobalsAndShared(); memset(context.globals, 0, context.globalsSize); + memset(context.shared, 0, context.sharedSize); // Instead of copying everything like in standalone contexts // Let's add only things we really need. @@ -961,5 +968,12 @@ static void init() { extern "C" { DAS_API void jit_initialize_modules () { init(); +#ifdef DAS_ENABLE_DYN_INCLUDES + das::daScriptEnvironment::ensure(); + auto access = das::make_smart(); + das::TextPrinter tout; + das::require_dynamic_modules(access, das::getDasRoot(), "", tout); +#endif + das::Module::Initialize(); } }