diff --git a/crates/core/src/wasm.rs b/crates/core/src/wasm.rs index 97837d4..209f3fa 100644 --- a/crates/core/src/wasm.rs +++ b/crates/core/src/wasm.rs @@ -1372,12 +1372,14 @@ impl RuntimePrelude { config.heap_start ) .expect("write WAT"); + writeln!(self.wat, " (global $__last_panic_payload (mut i32) (i32.const 0))").expect("write WAT"); } fn alloc(&mut self, config: runtime::RuntimeConfig) { let helper = helpers::ALLOC_HELPER .replace("{alignment_mask}", &(config.layout.alignment - 1).to_string()) - .replace("{alignment}", &config.layout.alignment.to_string()); + .replace("{alignment}", &config.layout.alignment.to_string()) + .replace("{allocation_failure_offset}", "64"); self.lines(&helper); } @@ -2681,6 +2683,47 @@ pub fn same() { "hi" == "hi" } assert_eq!(compare.call(&mut store, ()).expect("call compare"), -1); } + #[test] + fn runtime_allocation_grows_memory_without_moving_existing_objects() { + let instance = runtime_helper_instance( + r#" + (data (i32.const 2048) "ab") + (func $stable (export "stable") (result i32) + (local $ptr i32) + i32.const 2048 + i32.const 2 + call $__string_new + local.set $ptr + i32.const 2048 + i32.const 70000 + call $__string_new + drop + local.get $ptr) + (func $pages (export "pages") (result i32) + memory.size) +"#, + ); + let (engine, mut store, instance) = instance; + let _engine = engine; + let pages = instance + .get_typed_func::<(), i32>(&mut store, "pages") + .expect("get pages export"); + assert_eq!(pages.call(&mut store, ()).expect("call pages"), 1); + let stable = instance + .get_typed_func::<(), i32>(&mut store, "stable") + .expect("get stable export"); + let pointer = stable.call(&mut store, ()).expect("call stable") as usize; + assert_eq!(pointer, runtime::RuntimeConfig::DEFAULT.heap_start as usize); + assert!(pages.call(&mut store, ()).expect("call pages after growth") > 1); + + let memory = instance.get_memory(&mut store, "memory").expect("memory export"); + let mut bytes = [0; 16]; + memory.read(&store, pointer, &mut bytes).expect("read stable object"); + assert_eq!(u32::from_le_bytes(bytes[0..4].try_into().unwrap()), 1); + assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), 2); + assert_eq!(&bytes[8..10], b"ab"); + } + #[test] fn runtime_helpers_compare_nested_managed_values_structurally() { let instance = runtime_helper_instance( diff --git a/crates/core/src/wasm/helpers.rs b/crates/core/src/wasm/helpers.rs index af96659..7c3b55d 100644 --- a/crates/core/src/wasm/helpers.rs +++ b/crates/core/src/wasm/helpers.rs @@ -1,8 +1,41 @@ //! WAT snippets for the generated runtime prelude. pub const ALLOC_HELPER: &str = r#" + (func $__last_panic (export "__last_panic") (result i32) + global.get $__last_panic_payload + ) + (func $__allocation_fail (param $size i32) (param $heap i32) (result i32) + i32.const {allocation_failure_offset} + i32.const 10 + i32.store + i32.const {allocation_failure_offset} + i32.const 4 + i32.add + i32.const 2 + i32.store + i32.const {allocation_failure_offset} + i32.const 8 + i32.add + i32.const 1 + i32.store + i32.const {allocation_failure_offset} + i32.const 12 + i32.add + local.get $size + i64.extend_i32_u + i64.store + i32.const {allocation_failure_offset} + i32.const 20 + i32.add + local.get $heap + i64.extend_i32_u + i64.store + i32.const {allocation_failure_offset} + global.set $__last_panic_payload + unreachable + ) (func $__alloc (param $size i32) (result i32) - (local $ptr i32) + (local $ptr i32) (local $end i32) (local $pages i32) global.get $__heap local.set $ptr global.get $__heap @@ -12,6 +45,49 @@ pub const ALLOC_HELPER: &str = r#" i32.add i32.const -{alignment} i32.and + local.set $end + local.get $end + local.get $ptr + i32.lt_u + if (result i32) + local.get $size + local.get $ptr + call $__allocation_fail + return + else + i32.const 0 + end + drop + local.get $end + memory.size + i32.const 65536 + i32.mul + i32.gt_u + if + local.get $end + memory.size + i32.const 65536 + i32.mul + i32.sub + i32.const 65535 + i32.add + i32.const 16 + i32.shr_u + local.tee $pages + memory.grow + i32.const -1 + i32.eq + if (result i32) + local.get $size + local.get $ptr + call $__allocation_fail + return + else + i32.const 0 + end + drop + end + local.get $end global.set $__heap local.get $ptr ) diff --git a/docs/book.toml b/docs/book.toml index 6765de9..6694a16 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -7,3 +7,4 @@ language = "en" default-theme = "coal" preferred-dark-theme = "coal" additional-css = ["src/assets/css/custom.css"] +git-repository-url = "https://github.com/desertthunder/regulus" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index dbe71a8..3149b5c 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -7,44 +7,46 @@ - [Modules, imports, and packages](./chapter_1/mods_imports_pkgs.md) - [Functional programming in Gleam](./chapter_1/functional_programming.md) - [Values and expressions](./chapter_1/functions_values_expressions.md) - - [Custom types and records](./chapter_1/custom_types_records_tuples_lists.md) - - [Pattern matching](./chapter_1/pattern_matching.md) - - [Type system and inference model](./chapter_1/type_system_inference.md) + - [Type Checker](./chapter_1/type_system_inference.md) + - [Custom types and records](./chapter_1/custom_types_records_tuples_lists.md) + - [Pattern matching](./chapter_1/pattern_matching.md) - [Compilation](./chapter_1/compiler_notes.md) - [Lexing, parsing, and syntax trees](./chapter_2.md) - [Lexing](./chapter_2/lexing.md) - [Parsing](./chapter_2/parsing.md) - - [Concrete and abstract syntax trees](./chapter_2/syntax_trees.md) - - [Gleam's grammar](./chapter_2/gleam_grammar.md) -- [Name resolution](./chapter_3.md) + - [Syntax Trees](./chapter_2/syntax_trees.md) + - [Grammars](./chapter_2/gleam_grammar.md) +- [Name Resolution](./chapter_3.md) - [Lexical scope and shadowing](./chapter_3/lexical_scope.md) - [Symbols and scopes](./chapter_3/symbols_and_scopes.md) - [Namespaces and imports](./chapter_3/namespaces_and_imports.md) - [Patterns and visibility](./chapter_3/patterns_visibility_packages.md) - [The current resolver](./chapter_3/current_resolver.md) -- [Type checking and type inference](./chapter_4.md) +- [Type Checking & Inference](./chapter_4.md) - [Local and top-level inference](./chapter_4/local_top_level_inference.md) - [Algebraic data types](./chapter_4/algebraic_data_types.md) - [Unification and substitution](./chapter_4/unification_and_substitution.md) - [Calls, operators, and branches](./chapter_4/calls_operators_branches.md) - [Patterns and imports](./chapter_4/patterns_imports_opaque.md) - [Module interfaces](./chapter_4/module_interfaces.md) -- [Intermediate representations and lowering](./chapter_5.md) -- [Runtime value representation](./chapter_6.md) -- [WebAssembly code generation](./chapter_7.md) +- [IR & Lowering](./chapter_5.md) +- [Runtime Value Representation](./chapter_6.md) +- [WebAssembly Code Gen](./chapter_7.md) - [Modules and stack machine](./chapter_7/modules_stack_machine.md) - [Text and binary formats](./chapter_7/text_binary_format.md) - [Memory and module boundaries](./chapter_7/memory_tables_imports_exports.md) - [Wasmtime and browser](./chapter_7/running_wasmtime_browser.md) -- [Pattern matching in the compiler](./chapter_8.md) +- [Pattern matching](./chapter_8.md) - [Compiling pattern matching](./chapter_8/compiling_pattern_matching.md) - - [Binding variables from patterns](./chapter_8/binding_variables.md) + - [Binding variables](./chapter_8/binding_variables.md) - [Exhaustiveness diagnostics](./chapter_8/exhaustiveness_diagnostics.md) - [Compiler architecture](./chapter_9.md) - [Phase boundaries](./chapter_9/phase_boundaries.md) - [Pass pipelines and invariants](./chapter_9/pass_pipeline_invariants.md) - [Project input and module graph](./chapter_9/project_to_modules.md) - [Artifacts and execution](./chapter_9/artifacts_execution.md) +- [WebAssembly In-Depth (TODO)]() +- [Runtime Memory Management (TODO)]() + --> -# Internal Docs +# Inflight Work - [Specs]() - [Runtime memory](internal/specs/12_runtime_memory_and_semantics.md) - [Stdlib and host interop](internal/specs/14_stdlib_and_host_interop.md) - [CLI and build outputs](internal/specs/15_cli_and_build_outputs.md) -- [Active tasks]() +- [Task Trackers]() - [Runtime memory](internal/tasks/12_runtime_memory_and_semantics.md) - [Stdlib and host interop](internal/tasks/14_stdlib_and_host_interop.md) - [CLI and build outputs](internal/tasks/15_cli_and_build_outputs.md) diff --git a/docs/src/internal/development/architecture/runtime_representation.md b/docs/src/internal/development/architecture/runtime_representation.md index ea4cdb9..c69aaa9 100644 --- a/docs/src/internal/development/architecture/runtime_representation.md +++ b/docs/src/internal/development/architecture/runtime_representation.md @@ -35,8 +35,10 @@ Pointer, bool, nil, and ordering values use the low 32 bits and leave padding zeroed. The current runtime uses a bump allocator with a mutable heap pointer and no -freeing. This gives arena lifetime: objects live until the instance is reset. -Static objects are emitted as data segments before the dynamic heap. +freeing. Allocation checks the aligned end pointer and grows linear memory when +needed. Objects are non-moving: existing managed pointers remain stable across +`memory.grow` and live until the instance is reset. Static objects are emitted +as data segments before the dynamic heap. ## Tags @@ -72,9 +74,9 @@ and `Option` values. `Ok`, `Error`, `Some`, and `None` are constructor tags in normal tag-5 custom objects. Runtime error objects use tag 9 and are separate from the `Result.Error` constructor. -Closures store a stable function id followed by captured managed-value pointers. -Current closure captures are pointer-shaped; scalar captures must be boxed or -spilled into a managed environment before closure allocation. +Closures store a stable function id followed by captured values in 8-byte +slots. Managed captures use pointers and scalar captures use their raw slot +representation. Opaque values are owned by the dependency or host interface that created them. The backend may pass opaque pointers around and compare identity, but may not @@ -82,6 +84,9 @@ inspect payload contents without a dependency-specific helper. Panic and runtime error objects are materialized when a helper needs to report or render a failure. Direct panic paths may trap without allocating one. +Allocation failure writes a reserved tag-10 panic object before trapping. Its +reason tag is `1`, payload slot 0 is the requested allocation size, and payload +slot 1 is the heap pointer before allocation. ## ABI and ownership diff --git a/docs/src/internal/development/architecture/wasm_backend_and_runtime.md b/docs/src/internal/development/architecture/wasm_backend_and_runtime.md index e84b8d0..39169e6 100644 --- a/docs/src/internal/development/architecture/wasm_backend_and_runtime.md +++ b/docs/src/internal/development/architecture/wasm_backend_and_runtime.md @@ -28,7 +28,7 @@ The prelude currently provides: - exported linear memory - a mutable bump-allocation heap pointer -- `__alloc` for aligned allocation +- `__alloc` for aligned, checked allocation and heap growth - byte and slot copy helpers - string construction, comparison, concatenation, and inspection - bit-array construction, append, slicing, bit access, and matching @@ -71,8 +71,7 @@ The backend is complete for the current typed IR surface, not for the whole Gleam language. These areas still need design or implementation before every accepted source program can execute with full Gleam semantics: -- long-term heap growth, lifetime, and allocation failure behavior -- allocation failure and checked heap growth on every allocation path +- explicit arena reset points beyond instance reset - target-specific browser and WASI adapters - standard library and dependency-backed imports diff --git a/docs/src/internal/tasks/12_runtime_memory_and_semantics.md b/docs/src/internal/tasks/12_runtime_memory_and_semantics.md index a612107..f640a76 100644 --- a/docs/src/internal/tasks/12_runtime_memory_and_semantics.md +++ b/docs/src/internal/tasks/12_runtime_memory_and_semantics.md @@ -9,9 +9,9 @@ Complete runtime memory management and allocation failure behavior. ### Memory management - [x] Choose a resettable bump arena with checked `memory.grow`. -- [ ] Implement checked allocation and heap growth for every allocator path. -- [ ] Define allocation failure as a structured runtime panic payload. -- [ ] Keep managed objects non-moving until instance reset or arena reset. +- [x] Implement checked allocation and heap growth for every allocator path. +- [x] Define allocation failure as a structured runtime panic payload. +- [x] Keep managed objects non-moving until instance reset or arena reset. - [ ] Document host pointers as borrowed and stable until reset. - [ ] Add Wasmtime tests for growth, failed growth, and pointer stability.