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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Task-specific instructions are split into skill files under `skills/`. You MUST
| `skills/internal/documentation_rst.md` | Editing RST in `doc/source/`, `//!` doc-comments in `daslib/*.das`, tutorial RST pages |
| `skills/internal/tutorials.md` | Anything that looks like a tutorial - they live under `/tutorials/<area>/`, NEVER `modules/<X>/tutorial/` |
| `skills/internal/tutorial_prose.md` | WRITING or revising general-reader doc/tutorial prose (`documentation_rst.md` is mechanics, this is the words) |
| `skills/cpp_integration.md` | Embedding daslang in C++; binding types/functions/enums |
| `skills/cpp_integration.md` | Embedding daslang in C++; binding types/functions/enums; shipping without the compiler (`libDaScriptNano`) |
| `skills/internal/cpp_codebase_notes.md` | Working on daslang's own C++ - where inference/builtins/errors/parser live, AST function flags |
| `skills/internal/clang_bind_build.md` | Enabling `dasClangBind` / bumping the libclang SDK / running any `bind_*.das` self-binder |
| `skills/daslib_modules.md` | Working with `daslib/` modules or extending the stdlib |
Expand Down Expand Up @@ -258,6 +258,7 @@ Most layout is obvious from `ls`. The non-obvious ones:

- `skills/daslang/` - the **distributable, SDK-free daslang language skill** for third-party AI agents (`SKILL.md` + `references/`), NOT a repo task skill. Every example is probe-verified; its `README.md` carries the editing rules. Grammar/stdlib/default changes update it in the same arc
- `daslib/aot_cpp.das` - the AOT C++ emitter lives here, NOT in C++
- `nano/` - `libDaScriptNano`, the runtime with no compiler in it: a shadow include root whose four headers win over `include/`, so thirteen `src/` sources compile against a minimal `Context` unmodified. A source that needs an edit to build there is CARVED upstream, never forked (`nano/ARCHITECTURE.md`); every directory linking it must clear its inherited include dirs, which `nano/REVIEW.das` checks
- `tests/aot/CMakeLists.txt` - register new test directories here for AOT compilation. Two AOT binaries: `test_aot_subset` (tests/language only, in ALL - the per-PR CI compile gate) and full `test_aot` (`EXCLUDE_FROM_ALL`, ~1080 AOT TUs - nightly CI + `preflight --full` only, via `--target test_aot`/`run_tests_aot`)
- `dastest/` - test framework (used by both `tests/` and external repos)
- `utils/detect-dupe/` (in-repo dupe finder) and `utils/find-dupe/` (Claude judge; needs `daspkg install --root utils/find-dupe` + `ANTHROPIC_API_KEY`) - both also MCP tools
Expand Down
68 changes: 68 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -934,8 +934,10 @@ include/vecmath/dag_vecMath.h
include/vecmath/dag_vecMathDecl.h
include/vecmath/dag_vecMath_common.h
include/vecmath/dag_vecMath_const.h
include/vecmath/dag_vecMath_double.h
include/vecmath/dag_vecMath_neon.h
include/vecmath/dag_vecMath_pc_sse.h
include/vecmath/dag_vecMath_scalar.h
include/vecmath/dag_vecMath_trig.h
)
list(SORT VECMATH_SRC)
Expand Down Expand Up @@ -1038,6 +1040,7 @@ src/misc/env_cfg.cpp
src/misc/das_common.cpp
src/misc/gc_node.cpp
src/misc/globals.cpp
src/misc/hal.cpp
src/misc/sysos.cpp
src/misc/string_writer.cpp
src/misc/memory_model.cpp
Expand Down Expand Up @@ -1107,6 +1110,7 @@ src/misc/network.cpp
src/simulate/hash.cpp
src/simulate/debug_info.cpp
src/simulate/runtime_string.cpp
src/simulate/escape_string.cpp
src/simulate/runtime_iterator.cpp
src/simulate/runtime_array.cpp
src/simulate/runtime_table.cpp
Expand All @@ -1116,6 +1120,8 @@ src/simulate/simulate.cpp
src/simulate/simulate_exceptions.cpp
src/simulate/simulate_gc.cpp
src/simulate/simulate_gc_pod.cpp
src/simulate/builtin_array_ops.cpp
src/simulate/builtin_runtime_ops.cpp
src/simulate/aot_library.cpp
src/simulate/simulate_tracking.cpp
src/simulate/simulate_visit.cpp
Expand All @@ -1124,6 +1130,7 @@ src/simulate/simulate_fn_hash.cpp
src/simulate/simulate_instrument.cpp
include/daScript/simulate/cast.h
include/daScript/simulate/annotation_arguments.h
src/simulate/annotation_arguments.cpp
include/daScript/simulate/code_of_policies.h
include/daScript/simulate/hash.h
include/daScript/simulate/heap.h
Expand Down Expand Up @@ -1229,6 +1236,11 @@ list(SORT DAS_LIB_SRC)
SOURCE_GROUP_FILES("daslib" DSA_LIB_SRC)
list(SORT DAS_LIB_SRC)

# libDaScriptNano - the minimal runtime. Added BEFORE the include_directories
# below on purpose: nano picks its own header search order, and inheriting this
# directory's would put the full include/ ahead of the headers nano shadows.
add_subdirectory(nano)

include_directories(include)
include_directories(3rdparty/fmt/include)

Expand Down Expand Up @@ -1615,6 +1627,10 @@ if (NOT ${DAS_TOOLS_DISABLED})
# Build daslang utilities as standalone executables (-exe mode)
add_subdirectory(utils)

# Standalone-AOT examples on libDaScriptNano. They need daslang to generate
# their C++, which is why they live here and not beside the nano library.
add_subdirectory(examples/standalone)

endif()

# This list should be significantly reduced, most of the files, except aot related should be private.
Expand Down Expand Up @@ -1796,6 +1812,46 @@ install(FILES ${DAS_BUILTIN_HEADERS}
DESTINATION include/daScript/builtin
)

# libDaScriptNano ships as sources, not as a built library: an embedder cross-
# compiles it for their own target with their own flags, which is the whole
# point of it. Its CMakeLists is self-contained given DASLANG_NANO_ROOT.
install(DIRECTORY ${PROJECT_SOURCE_DIR}/nano/
DESTINATION nano
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.cpp"
PATTERN "*.md"
PATTERN "CMakeLists.txt"
# REVIEW.md reviews a change to this repo's copy of nano; an SDK carries
# the library, not the process of changing it. (REVIEW.das is not matched
# by any pattern above, so it never ships either.)
REGEX "/REVIEW\\.md$" EXCLUDE
)
# The shared runtime sources nano compiles. They live under src/, which the SDK
# otherwise does not carry; nano/CMakeLists.txt names them one by one, and
# nano/REVIEW.das checks that this list still covers that one.
install(FILES
${PROJECT_SOURCE_DIR}/src/misc/hal.cpp
${PROJECT_SOURCE_DIR}/src/misc/memory_model.cpp
DESTINATION src/misc
)
install(FILES
${PROJECT_SOURCE_DIR}/src/simulate/annotation_arguments.cpp
${PROJECT_SOURCE_DIR}/src/simulate/aot_library.cpp
${PROJECT_SOURCE_DIR}/src/simulate/builtin_array_ops.cpp
${PROJECT_SOURCE_DIR}/src/simulate/builtin_runtime_ops.cpp
${PROJECT_SOURCE_DIR}/src/simulate/data_walker.cpp
${PROJECT_SOURCE_DIR}/src/simulate/debug_info.cpp
${PROJECT_SOURCE_DIR}/src/simulate/escape_string.cpp
${PROJECT_SOURCE_DIR}/src/simulate/heap.cpp
${PROJECT_SOURCE_DIR}/src/simulate/runtime_array.cpp
${PROJECT_SOURCE_DIR}/src/simulate/runtime_iterator.cpp
${PROJECT_SOURCE_DIR}/src/simulate/runtime_table.cpp
${PROJECT_SOURCE_DIR}/src/simulate/simulate_gc_pod.cpp
${PROJECT_SOURCE_DIR}/src/simulate/standalone_ctx_utils.cpp
DESTINATION src/simulate
)

# Install all modules and main library.
install(TARGETS
libDaScript libDaScript_runtime libDaScriptDyn libDaScriptDyn_runtime libUriParser libUriParserDyn
Expand Down Expand Up @@ -2094,6 +2150,11 @@ install(DIRECTORY ${PROJECT_SOURCE_DIR}/examples/
DESTINATION ${DAS_INSTALL_EXAMPLESDIR}
PATTERN "fatman" EXCLUDE
PATTERN "modules" EXCLUDE
# standalone/ builds against a daslang TARGET in-tree and against an
# installed SDK from a bundle; the SDK copy is the .standalone.cmake below,
# renamed into place after this excludes the in-tree one.
PATTERN "standalone/CMakeLists.txt" EXCLUDE
PATTERN "CMakeLists.standalone.cmake" EXCLUDE
PATTERN "_build" EXCLUDE
PATTERN ".daspkg_cache" EXCLUDE
PATTERN ".daspkg_tmp" EXCLUDE
Expand All @@ -2109,6 +2170,13 @@ install(DIRECTORY ${PROJECT_SOURCE_DIR}/examples/
PATTERN "*.sf2" EXCLUDE
)

# The SDK's copy of the standalone examples' build: same four targets, driven by
# find_package(DAS) instead of in-tree targets.
install(FILES ${PROJECT_SOURCE_DIR}/examples/standalone/CMakeLists.standalone.cmake
DESTINATION ${DAS_INSTALL_EXAMPLESDIR}/standalone
RENAME CMakeLists.txt
)

# The Fox rig/animation is CC-BY 4.0, so its attribution must also ship at the
# bundle root beside the other licenses (the examples/gltf copy rides the blanket).
install(FILES ${PROJECT_SOURCE_DIR}/examples/gltf/GLTF_SAMPLE_ASSETS.LICENSE
Expand Down
74 changes: 74 additions & 0 deletions ci/nano_arm_build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Cross-compile libDaScriptNano and the tier-A standalone example for a
# cortex-m4, and print the size of what comes out.
#
# THIS DOES NOT PASS YET, and it is not wired into CI. It is the acceptance test
# for the freestanding port, and running it names the work: newlib has no
# posix_memalign / malloc_usable_size / madvise, its libstdc++ is built without
# threads so <mutex> declares nothing, its uint32_t is `unsigned long` (which
# makes every BitfieldAny and vec4<uint32_t> conversion ambiguous), and alloca
# needs its own include. Those live in platform.h, smart_ptr.h, arraytype.h and
# vectypes.h - shared headers every platform compiles, which is why the port is
# its own change rather than a corner of nano.
#
# Once it is green it becomes nano's drift tripwire: a change that pulls the
# compiler, fmt or the host's I/O back into the runtime fails here first.
#
# Usage: ci/nano_arm_build.sh <path-to-daslang> [out-dir]
# Toolchain: apt-get install gcc-arm-none-eabi
set -euo pipefail

DASLANG="${1:?usage: ci/nano_arm_build.sh <path-to-daslang> [out-dir]}"
OUT="${2:-build/nano-arm}"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CXX="${ARM_CXX:-arm-none-eabi-g++}"
SIZE="${ARM_SIZE:-arm-none-eabi-size}"

cd "$ROOT"
mkdir -p "$OUT/generated"

# nano/CMakeLists.txt is the one source list. Reading it here keeps this script
# from becoming a second one that drifts. The two variables it writes paths
# against mean different roots: nano's own sources sit under nano/, the shared
# ones under the repo root.
sources() {
sed -n '/^set(NANO_OWN_SRC/,/^)/p;/^set(NANO_SHARED_SRC/,/^)/p' nano/CMakeLists.txt \
| grep -o '\${[A-Z_]*}/[^ ]*\.cpp' \
| sed -e 's|^\${CMAKE_CURRENT_SOURCE_DIR}/|nano/|' \
-e 's|^\${DASLANG_NANO_ROOT}/||'
}

echo "== generating standalone C++ for examples/standalone/01_pure"
"$DASLANG" utils/aot/main.das -- -ctx \
examples/standalone/01_pure/pure_math.das "$OUT/generated/"

# -Os and no exceptions/RTTI is what an embedded target actually builds with;
# nano's das_config.h already forces DAS_ENABLE_EXCEPTIONS off, and this proves
# the compiler agrees.
ARM_FLAGS=(
-mcpu=cortex-m4 -mthumb -mfloat-abi=soft
-Os -ffunction-sections -fdata-sections
-fno-exceptions -fno-rtti -fno-threadsafe-statics
-std=c++17
-I nano/include -I include -I "$OUT/generated"
)

OBJS=()
echo "== compiling nano"
for src in $(sources); do
obj="$OUT/$(echo "$src" | tr '/' '_').o"
"$CXX" "${ARM_FLAGS[@]}" -c "$src" -o "$obj"
OBJS+=("$obj")
done

echo "== compiling the generated context and the example"
"$CXX" "${ARM_FLAGS[@]}" -c "$OUT/generated/pure_math.das.cpp" -o "$OUT/pure_math.o"
"$CXX" "${ARM_FLAGS[@]}" -c examples/standalone/01_pure/main.cpp -o "$OUT/main.o"
OBJS+=("$OUT/pure_math.o" "$OUT/main.o")

echo "== linking"
"$CXX" "${ARM_FLAGS[@]}" --specs=nosys.specs -Wl,--gc-sections \
"${OBJS[@]}" -o "$OUT/nano_01_pure.elf"

echo "== size (cortex-m4, -Os, gc-sections)"
"$SIZE" "$OUT/nano_01_pure.elf"
59 changes: 59 additions & 0 deletions examples/standalone/01_pure/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Tier A: a C++ program whose whole daslang dependency is libDaScriptNano.
//
// There is no daScript.h here, no Module, no compiler - just the header the
// standalone emitter generated. The context is a plain C++ object: construct
// it, call its methods, let it go out of scope.

#include "daScript/nano_print.h"
#include "pure_math.das.h"

#include <stdio.h>

using namespace das;

static int failures = 0;

static void expect_int ( const char * what, int have, int want ) {
if ( have != want ) {
printf("%s = %d, expected %d\n", what, have, want);
failures ++;
}
}

static void expect_float ( const char * what, float have, float want ) {
const float d = have > want ? have - want : want - have;
if ( d > 1e-5f ) {
printf("%s = %f, expected %f\n", what, double(have), double(want));
failures ++;
}
}

static void to_console ( const char * text ) {
fputs(text, stdout);
}

int main () {
// On a board this is where printk or a UART write goes. Setting it before
// the context exists means even a panic during construction is visible.
das_nano_set_print(&to_console);

pure_math::Standalone ctx;

pure_math::Vec3 a; a.x = 1.0f; a.y = 2.0f; a.z = 3.0f;
pure_math::Vec3 b; b.x = 4.0f; b.y = 5.0f; b.z = 6.0f;

expect_float("dot(a,b)", ctx.dot(a, b), 32.0f);

pure_math::Vec3 s = ctx.scale(a, 2.0f);
expect_float("scale(a,2).x", s.x, 2.0f);
expect_float("scale(a,2).z", s.z, 6.0f);

expect_float("component(a,y)", ctx.component(a, pure_math::Axis::y), 2.0f);
expect_float("weighted_sum(a)", ctx.weighted_sum(a), 1.0f*0.25f + 2.0f*0.5f + 3.0f*0.25f);

expect_int("collatz_steps(27)", ctx.collatz_steps(27), 111);
expect_int("collatz_steps(1)", ctx.collatz_steps(1), 0);

printf(failures ? "01_pure: %d failure(s)\n" : "01_pure: ok\n", failures);
return failures ? 1 : 0;
}
65 changes: 65 additions & 0 deletions examples/standalone/01_pure/pure_math.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
options gen2
options stack = 4096

// Tier A: pure POD compute. No das heap is ever touched here - no arrays, no
// tables, no strings, no closures - so the generated context needs only the
// stack and the constant data the standalone emitter puts in globals.
//
// `options stack = 4096` is honored exactly: the standalone context reserves
// this many bytes plus the headroom the global initializers need.

struct Vec3 {
x : float
y : float
z : float
}

enum Axis {
x
y
z
}

let TAPS = fixed_array<float>(0.25, 0.5, 0.25)

[export]
def dot(a, b : Vec3) : float {
return a.x * b.x + a.y * b.y + a.z * b.z
}

[export]
def scale(v : Vec3; k : float) : Vec3 {
return Vec3(x = v.x * k, y = v.y * k, z = v.z * k)
}

[export]
def component(v : Vec3; axis : Axis) : float {
if (axis == Axis.x) {
return v.x
} elif (axis == Axis.y) {
return v.y
}
return v.z
}

// A dim (fixed-size array) lives in globals or on the stack, never on the heap.
[export]
def weighted_sum(v : Vec3) : float {
let parts = fixed_array<float>(v.x, v.y, v.z)
var total = 0.0
for (p, w in parts, TAPS) {
total += p * w
}
return total
}

[export]
def collatz_steps(n : int) : int {
var steps = 0
var value = n
while (value > 1) {
value = value % 2 == 0 ? value / 2 : value * 3 + 1
steps++
}
return steps
}
Loading
Loading