From 4cb24e8ff9e266634763f742179ca98641cdd84e Mon Sep 17 00:00:00 2001 From: Max Richter Date: Fri, 23 Jan 2026 13:40:31 +0100 Subject: [PATCH] feat: some shit --- app/src/lib/runtime/helpers.ts | 39 ++++++++++++++ app/src/lib/runtime/runtime-executor.ts | 29 +++++++---- app/src/routes/dev/+page.svelte | 15 +++++- app/src/routes/dev/helpers.ts | 48 +++++++++++++++++ app/src/routes/dev/settings.svelte.ts | 15 ++++++ nodes/max/plantarium/float/Cargo.toml | 5 +- nodes/max/plantarium/noise/Cargo.toml | 2 +- nodes/max/plantarium/random/src/lib.rs | 1 + packages/macros/src/lib.rs | 37 +++++++++----- packages/utils/src/allocator.rs | 68 +++++++++++++++++++++++++ packages/utils/src/encoding.rs | 3 ++ packages/utils/src/lib.rs | 17 ++++--- packages/utils/src/wasm-wrapper.ts | 11 ++-- 13 files changed, 249 insertions(+), 41 deletions(-) create mode 100644 app/src/lib/runtime/helpers.ts create mode 100644 app/src/routes/dev/helpers.ts create mode 100644 app/src/routes/dev/settings.svelte.ts create mode 100644 packages/utils/src/allocator.rs diff --git a/app/src/lib/runtime/helpers.ts b/app/src/lib/runtime/helpers.ts new file mode 100644 index 0000000..4d3abf9 --- /dev/null +++ b/app/src/lib/runtime/helpers.ts @@ -0,0 +1,39 @@ +export function logInt32ArrayChanges( + before: Int32Array, + after: Int32Array, + clamp = 10 +): void { + if (before.length !== after.length) { + throw new Error('Arrays must have the same length'); + } + + let rangeStart: number | null = null; + let collected: number[] = []; + + const flush = (endIndex: number) => { + if (rangeStart === null) return; + + const preview = collected.slice(0, clamp); + const suffix = collected.length > clamp ? '...' : ''; + + console.log( + `Change ${rangeStart}-${endIndex}: [${preview.join(', ')}${suffix}]` + ); + + rangeStart = null; + collected = []; + }; + + for (let i = 0; i < before.length; i++) { + if (before[i] !== after[i]) { + if (rangeStart === null) { + rangeStart = i; + } + collected.push(after[i]); + } else { + flush(i - 1); + } + } + + flush(before.length - 1); +} diff --git a/app/src/lib/runtime/runtime-executor.ts b/app/src/lib/runtime/runtime-executor.ts index e8bd863..ceec173 100644 --- a/app/src/lib/runtime/runtime-executor.ts +++ b/app/src/lib/runtime/runtime-executor.ts @@ -1,3 +1,4 @@ +import type { SettingsToStore } from '$lib/settings/app-settings.svelte'; import { RemoteNodeRegistry } from '@nodarium/registry'; import type { Graph, @@ -13,6 +14,8 @@ import { encodeFloat, type PerformanceStore } from '@nodarium/utils'; +import { DevSettingsType } from '../../routes/dev/settings.svelte'; +import { logInt32ArrayChanges } from './helpers'; import type { RuntimeNode } from './types'; const log = createLogger('runtime-executor'); @@ -79,14 +82,14 @@ export type Pointer = { perf?: PerformanceStore; - constructor( - private readonly registry: NodeRegistry, - public cache?: SyncCache - ) { - this.cache = undefined; - this.refreshView(); - log.info('MemoryRuntimeExecutor initialized'); - } + constructor( + private readonly registry: NodeRegistry, + public cache?: SyncCache + ) { + this.cache = undefined; + this.refreshView(); + log.info('MemoryRuntimeExecutor initialized'); + } private refreshView(): void { this.memoryView = new Int32Array(this.memory.buffer); @@ -409,10 +412,11 @@ export type Pointer = { const args = inputs.flatMap(p => [p.start * 4, p.end * 4]); log.info(`Executing node ${node.type}/${node.id}`); + const memoryBefore = this.memoryView.slice(0, this.offset); const bytesWritten = nodeType.execute(this.offset * 4, args); - if (bytesWritten === -1) { - throw new Error(`Failed to execute node`); - } + this.refreshView(); + const memoryAfter = this.memoryView.slice(0, this.offset); + logInt32ArrayChanges(memoryBefore, memoryAfter); this.refreshView(); const outLen = bytesWritten >> 2; @@ -427,6 +431,7 @@ export type Pointer = { ) ) { this.results[node.id] = inputs[0]; + this.allPtrs.push(this.results[node.id]); log.info(`Node ${node.id} result reused input memory`); } else { this.results[node.id] = { @@ -434,6 +439,7 @@ export type Pointer = { end: outputStart + outLen, _title: `${node.id} ->` }; + this.allPtrs.push(this.results[node.id]); this.offset += outLen; lastNodePtr = this.results[node.id]; log.info( @@ -454,6 +460,7 @@ export type Pointer = { console.error(e); } finally { this.isRunning = false; + console.log('Final Memory', [...this.memoryView.slice(0, 20)]); this.perf?.endPoint('runtime'); log.info('Executor state reset'); } diff --git a/app/src/routes/dev/+page.svelte b/app/src/routes/dev/+page.svelte index cf1ff45..512e3a3 100644 --- a/app/src/routes/dev/+page.svelte +++ b/app/src/routes/dev/+page.svelte @@ -127,8 +127,14 @@ {/each} + +

Debug Settings

+ +
{ + const index = start + i; + const ptr = currentPtrs[i]; + return { + index, + ptr: ptr?._title ?? '', + value: value + }; + }); + + // Compute column widths + const indexWidth = Math.max( + 5, + ...tableRows.map((r) => r.index.toString().length) + ); + const ptrWidth = Math.max( + 10, + ...tableRows.map((r) => r.ptr.length) + ); + const valueWidth = Math.max( + 10, + ...tableRows.map((r) => r.value.toString().length) + ); + + // Build header + let output = + `| ${'Index'.padEnd(indexWidth)} | ${'Ptr'.padEnd(ptrWidth)} | ${'Value'.padEnd(valueWidth) + } |\n` + + `|-${'-'.repeat(indexWidth)}-|-${'-'.repeat(ptrWidth)}-|-${'-'.repeat(valueWidth)}-|\n`; + + // Add rows + for (const row of tableRows) { + output += `| ${row.index.toString().padEnd(indexWidth)} | ${row.ptr.padEnd(ptrWidth)} | ${row.value.toString().padEnd(valueWidth) + } |\n`; + } + + // Copy to clipboard + navigator.clipboard + .writeText(output) + .then(() => console.log('Memory + metadata copied as table')) + .catch((err) => console.error('Failed to copy memory:', err)); +} diff --git a/app/src/routes/dev/settings.svelte.ts b/app/src/routes/dev/settings.svelte.ts new file mode 100644 index 0000000..5c8e68f --- /dev/null +++ b/app/src/routes/dev/settings.svelte.ts @@ -0,0 +1,15 @@ +import { localState } from '$lib/helpers/localState.svelte'; +import { settingsToStore } from '$lib/settings/app-settings.svelte'; + +export const DevSettingsType = { + debugNode: { + type: 'boolean', + label: 'Debug Nodes', + value: true + } +} as const; + +export let devSettings = localState( + 'dev-settings', + settingsToStore(DevSettingsType) +); diff --git a/nodes/max/plantarium/float/Cargo.toml b/nodes/max/plantarium/float/Cargo.toml index 7b24405..5a40acc 100644 --- a/nodes/max/plantarium/float/Cargo.toml +++ b/nodes/max/plantarium/float/Cargo.toml @@ -2,11 +2,14 @@ name = "float" version = "0.1.0" authors = ["Max Richter "] -edition = "2018" +edition = "2021" [lib] crate-type = ["cdylib", "rlib"] +[profile.dev] +panic = "unwind" + [dependencies] nodarium_macros = { version = "0.1.0", path = "../../../../packages/macros" } nodarium_utils = { version = "0.1.0", path = "../../../../packages/utils" } diff --git a/nodes/max/plantarium/noise/Cargo.toml b/nodes/max/plantarium/noise/Cargo.toml index 4d18f71..6873b2f 100644 --- a/nodes/max/plantarium/noise/Cargo.toml +++ b/nodes/max/plantarium/noise/Cargo.toml @@ -2,7 +2,7 @@ name = "noise" version = "0.1.0" authors = ["Max Richter "] -edition = "2018" +edition = "2021" [lib] crate-type = ["cdylib", "rlib"] diff --git a/nodes/max/plantarium/random/src/lib.rs b/nodes/max/plantarium/random/src/lib.rs index 3ef724d..83e5cc5 100644 --- a/nodes/max/plantarium/random/src/lib.rs +++ b/nodes/max/plantarium/random/src/lib.rs @@ -7,6 +7,7 @@ nodarium_definition_file!("src/input.json"); #[nodarium_execute] pub fn execute(min: (i32, i32), max: (i32, i32), seed: (i32, i32)) -> Vec { + nodarium_utils::log!("random execute start"); concat_arg_vecs(vec![ vec![1], read_i32_slice(min), diff --git a/packages/macros/src/lib.rs b/packages/macros/src/lib.rs index d444012..838adb7 100644 --- a/packages/macros/src/lib.rs +++ b/packages/macros/src/lib.rs @@ -9,12 +9,12 @@ use syn::parse_macro_input; use syn::spanned::Spanned; fn add_line_numbers(input: String) -> String { - return input + input .split('\n') .enumerate() .map(|(i, line)| format!("{:2}: {}", i + 1, line)) .collect::>() - .join("\n"); + .join("\n") } fn read_node_definition(file_path: &Path) -> NodeDefinition { @@ -72,7 +72,7 @@ pub fn nodarium_execute(_attr: TokenStream, item: TokenStream) -> TokenStream { let total_c_params = param_count * 2; let arg_names: Vec<_> = (0..total_c_params) - .map(|i| syn::Ident::new(&format!("arg{}", i), input_fn.sig.span())) + .map(|i| syn::Ident::new(&format!("arg{i}"), input_fn.sig.span())) .collect(); let mut tuple_args = Vec::new(); @@ -89,7 +89,20 @@ pub fn nodarium_execute(_attr: TokenStream, item: TokenStream) -> TokenStream { extern "C" { fn __nodarium_log(ptr: *const u8, len: usize); - fn __nodarium_log_panic(ptr: *const u8, len: usize); + } + + #[cfg(target_arch = "wasm32")] + fn init_panic_hook() { + std::panic::set_hook(Box::new(|_info| { + unsafe { + __nodarium_log(b"PANIC\0".as_ptr(), 5); + } + })); + } + + #[no_mangle] + pub extern "C" fn init_allocator() { + nodarium_utils::allocator::ALLOCATOR.init(); } #fn_vis fn #inner_fn_name(#( #input_param_names: (i32, i32) ),*) -> Vec { @@ -99,11 +112,14 @@ pub fn nodarium_execute(_attr: TokenStream, item: TokenStream) -> TokenStream { #[no_mangle] #fn_vis extern "C" fn execute(output_pos: i32, #( #arg_names: i32 ),*) -> i32 { + nodarium_utils::allocator::ALLOCATOR.init(); + #[cfg(target_arch = "wasm32")] + init_panic_hook(); nodarium_utils::log!("before_fn"); let result = #inner_fn_name( #( #tuple_args ),* ); - nodarium_utils::log!("after_fn"); + nodarium_utils::log!("after_fn: result_len={}", result.len()); let len_bytes = result.len() * 4; unsafe { @@ -137,7 +153,7 @@ fn validate_signature(fn_sig: &syn::Signature, expected_inputs: usize, def: &Nod .map(|i| i.keys().collect::>()) .unwrap_or_default(), (0..expected_inputs) - .map(|i| format!("arg{}: (i32, i32)", i)) + .map(|i| format!("arg{i}: (i32, i32)")) .collect::>() .join(", ") ); @@ -155,9 +171,7 @@ fn validate_signature(fn_sig: &syn::Signature, expected_inputs: usize, def: &Nod .to_string(); if !clean_type.contains("(") && !clean_type.contains(",") { panic!( - "Parameter {} has type '{}' but should be a tuple (i32, i32) representing (start, end) positions in memory", - i, - clean_type + "Parameter {i} has type '{clean_type}' but should be a tuple (i32, i32) representing (start, end) positions in memory", ); } } @@ -197,10 +211,7 @@ pub fn nodarium_definition_file(input: TokenStream) -> TokenStream { let full_path = Path::new(&project_dir).join(&file_path); let json_content = fs::read_to_string(&full_path).unwrap_or_else(|err| { - panic!( - "Failed to read JSON file at '{}/{}': {}", - project_dir, file_path, err - ) + panic!("Failed to read JSON file at '{project_dir}/{file_path}': {err}",) }); let _: NodeDefinition = serde_json::from_str(&json_content).unwrap_or_else(|err| { diff --git a/packages/utils/src/allocator.rs b/packages/utils/src/allocator.rs new file mode 100644 index 0000000..8a7fa7a --- /dev/null +++ b/packages/utils/src/allocator.rs @@ -0,0 +1,68 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +extern "C" { + fn __wasm_memory_size() -> usize; + fn __nodarium_manual_end() -> usize; +} + +#[allow(dead_code)] +const WASM_PAGE_SIZE: usize = 64 * 1024; + +pub struct DownwardBumpAllocator { + heap_top: AtomicUsize, +} + +impl Default for DownwardBumpAllocator { + fn default() -> Self { + Self::new() + } +} + +impl DownwardBumpAllocator { + pub const fn new() -> Self { + Self { + heap_top: AtomicUsize::new(0), + } + } + + #[allow(dead_code)] + pub fn init(&self) { + let pages = unsafe { __wasm_memory_size() }; + let mem_size = pages * WASM_PAGE_SIZE; + self.heap_top.store(mem_size, Ordering::Relaxed); + } +} + +#[global_allocator] +pub static ALLOCATOR: DownwardBumpAllocator = DownwardBumpAllocator::new(); + +unsafe impl GlobalAlloc for DownwardBumpAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let align = layout.align(); + let size = layout.size(); + + let mut current = self.heap_top.load(Ordering::Relaxed); + + loop { + let aligned = (current - size) & !(align - 1); + + let manual_end = unsafe { __nodarium_manual_end() }; + if aligned < manual_end { + return core::ptr::null_mut(); + } + + match self.heap_top.compare_exchange( + current, + aligned, + Ordering::SeqCst, + Ordering::Relaxed, + ) { + Ok(_) => return aligned as *mut u8, + Err(next) => current = next, + } + } + } + + unsafe fn dealloc(&self, _: *mut u8, _: Layout) {} +} diff --git a/packages/utils/src/encoding.rs b/packages/utils/src/encoding.rs index 470f8dc..277c50a 100644 --- a/packages/utils/src/encoding.rs +++ b/packages/utils/src/encoding.rs @@ -1,3 +1,5 @@ +use crate::log; + pub fn encode_float(f: f32) -> i32 { // Convert f32 to u32 using to_bits, then safely cast to i32 let bits = f.to_bits(); @@ -12,6 +14,7 @@ pub fn decode_float(bits: i32) -> f32 { #[inline] pub fn read_i32(ptr: i32) -> i32 { + log!("read_i32 ptr: {:?}", ptr); unsafe { let _ptr = ptr as *const i32; *_ptr diff --git a/packages/utils/src/lib.rs b/packages/utils/src/lib.rs index 912ff2c..2c0464e 100644 --- a/packages/utils/src/lib.rs +++ b/packages/utils/src/lib.rs @@ -1,3 +1,4 @@ +pub mod allocator; mod encoding; mod nodes; mod tree; @@ -11,7 +12,7 @@ extern "C" { pub fn __nodarium_log(ptr: *const u8, len: usize); } -#[cfg(debug_assertions)] +// #[cfg(debug_assertions)] #[macro_export] macro_rules! log { ($($t:tt)*) => {{ @@ -25,13 +26,13 @@ macro_rules! log { }} } -#[cfg(not(debug_assertions))] -#[macro_export] -macro_rules! log { - ($($arg:tt)*) => {{ - // This will expand to nothing in release builds - }}; -} +// #[cfg(not(debug_assertions))] +// #[macro_export] +// macro_rules! log { +// ($($arg:tt)*) => {{ +// // This will expand to nothing in release builds +// }}; +// } #[allow(dead_code)] #[rustfmt::skip] diff --git a/packages/utils/src/wasm-wrapper.ts b/packages/utils/src/wasm-wrapper.ts index 0467394..a5e37fd 100644 --- a/packages/utils/src/wasm-wrapper.ts +++ b/packages/utils/src/wasm-wrapper.ts @@ -1,11 +1,13 @@ interface NodariumExports extends WebAssembly.Exports { memory: WebAssembly.Memory; execute: (outputPos: number, ...args: number[]) => number; + init_allocator: () => void; } export function createWasmWrapper(buffer: ArrayBuffer, memory: WebAssembly.Memory) { let exports: NodariumExports; + let end = 0; const importObject = { env: { memory: memory, @@ -25,14 +27,11 @@ export function createWasmWrapper(buffer: ArrayBuffer, memory: WebAssembly.Memor const module = new WebAssembly.Module(buffer); const instance = new WebAssembly.Instance(module, importObject); exports = instance.exports as NodariumExports; + exports.init_allocator(); function execute(outputPos: number, args: number[]): number { - try { - return exports.execute(outputPos, ...args); - } catch (e) { - console.log(e); - return -1; - } + end = outputPos; + return exports.execute(outputPos, ...args); } function get_definition() {