From 60b043ef0cb81038002afefad538d4cde5dcd2a7 Mon Sep 17 00:00:00 2001 From: Raphael Amorim Date: Wed, 20 May 2026 11:19:48 +0200 Subject: [PATCH] extends vec methods and add capacity --- .gitignore | 4 +- CMakeLists.txt | 2 + Makefile | 26 +++++++-- std/collections.jam | 70 ++++++++++++++++++++++- tests/unit/test_vec.jam | 124 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index b1b3a0a..9e2a2ba 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ abi_tests codegen_error_tests init_tests output -diagnostic_tests \ No newline at end of file +diagnostic_tests +analyzer_tests +decl_tests \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 817e1c4..b60b324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,11 @@ add_definitions(${LLVM_DEFINITIONS}) add_executable(jam src/abi.cpp + src/analyzer.cpp src/astgen.cpp src/cabi.cpp src/codegen.cpp + src/decl.cpp src/diagnostics.cpp src/drop_registry.cpp src/init_analysis.cpp diff --git a/Makefile b/Makefile index 2530ead..7231a9c 100644 --- a/Makefile +++ b/Makefile @@ -44,10 +44,12 @@ build: clang++ -c ./src/drop_registry.cpp -o ./drop_registry.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) clang++ -c ./src/abi.cpp -o ./abi.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) clang++ -c ./src/diagnostics.cpp -o ./diagnostics.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) + clang++ -c ./src/decl.cpp -o ./decl.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) + clang++ -c ./src/analyzer.cpp -o ./analyzer.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) clang++ -c ./src/astgen.cpp -o ./astgen.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) clang++ -c ./src/jir_codegen.cpp -o ./jir_codegen.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) clang++ -c ./src/jir_verify.cpp -o ./jir_verify.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) - clang++ -o ./jam.out ./jam_llvm.o ./main.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` + clang++ -o ./jam.out ./jam_llvm.o ./main.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./decl.o ./analyzer.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` cmake-build: @echo "Building with CMake..." @@ -87,7 +89,7 @@ uninstall: rm -rf $(LIBDIR)/jam clean: - rm -f ./jam_llvm.o ./main.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./astgen.o ./jir_codegen.o ./jir_verify.o ./jam.out + rm -f ./jam_llvm.o ./main.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./decl.o ./analyzer.o ./astgen.o ./jir_codegen.o ./jir_verify.o ./jam.out rm -rf build/ info: @@ -116,14 +118,14 @@ test-init: build @echo "" @echo "Building and running init-analyzer C++ tests..." clang++ -c ./tests/cpp/test_init_analysis.cpp -o ./test_init_analysis.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) - clang++ -o ./init_tests ./test_init_analysis.o ./jam_llvm.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` + clang++ -o ./init_tests ./test_init_analysis.o ./jam_llvm.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./decl.o ./analyzer.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` ./init_tests test-abi: build @echo "" @echo "Building and running ABI classifier C++ tests..." clang++ -c ./tests/cpp/test_abi.cpp -o ./test_abi.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) - clang++ -o ./abi_tests ./test_abi.o ./jam_llvm.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` + clang++ -o ./abi_tests ./test_abi.o ./jam_llvm.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./decl.o ./analyzer.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` ./abi_tests test-codegen-errors: build @@ -147,7 +149,21 @@ test-diagnostics: build clang++ -o ./diagnostic_tests ./test_diagnostics.o ./diagnostic_tests -test: test-unit test-init test-abi test-codegen-errors test-jir test-diagnostics +test-decl: build + @echo "" + @echo "Building and running DeclTable C++ tests..." + clang++ -c ./tests/cpp/test_decl_table.cpp -o ./test_decl_table.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) + clang++ -o ./decl_tests ./test_decl_table.o ./decl.o + ./decl_tests + +test-analyzer: build + @echo "" + @echo "Building and running Analyzer C++ tests..." + clang++ -c ./tests/cpp/test_analyzer.cpp -o ./test_analyzer.o `$(LLVM_CONFIG) --cxxflags` -fexceptions $(OPTFLAGS) + clang++ -o ./analyzer_tests ./test_analyzer.o ./jam_llvm.o ./lexer.o ./parser.o ./codegen.o ./target.o ./cabi.o ./module_resolver.o ./symbol_table.o ./number_literal.o ./init_analysis.o ./drop_registry.o ./abi.o ./diagnostics.o ./decl.o ./analyzer.o ./astgen.o ./jir_codegen.o ./jir_verify.o `$(LLVM_CONFIG) --ldflags --libs --libfiles --system-libs` + ./analyzer_tests + +test: test-unit test-init test-abi test-codegen-errors test-jir test-diagnostics test-decl test-analyzer fmt: format diff --git a/std/collections.jam b/std/collections.jam index d520354..c8cedc6 100644 --- a/std/collections.jam +++ b/std/collections.jam @@ -34,7 +34,7 @@ pub fn Vec(T: type) type { else if (s <= 1024) { cap = 4; } var bytes: u64 = (cap as u64) * s; var raw: *mut[] u8 = malloc(bytes); - return { ptr: raw as *mut[] T, length: 0, capacity: cap }; + return Self { ptr: raw as *mut[] T, length: 0, capacity: cap }; } // Pre-allocate `cap` slots up front. Useful when the caller @@ -46,7 +46,7 @@ pub fn Vec(T: type) type { if (actualCap == 0) { actualCap = 1; } var bytes: u64 = (actualCap as u64) * @sizeOf(T); var raw: *mut[] u8 = malloc(bytes); - return { ptr: raw as *mut[] T, length: 0, capacity: actualCap }; + return Self { ptr: raw as *mut[] T, length: 0, capacity: actualCap }; } fn push(self: mut Self, value: T) { @@ -74,6 +74,72 @@ pub fn Vec(T: type) type { return Option(T).Some(self.ptr[i]); } + // Reset length to zero without releasing the buffer. Lets + // callers reuse one Vec across many iterations of a hot + // loop (per-frame audio drain, per-sector decode scratch) + // and pay malloc exactly once, at construction. + fn clear(self: mut Self) { + self.length = 0; + } + + // Shrink to `newLen`. No-op when `newLen >= length`, so + // `truncate(0)` is the same as `clear()` and `truncate(N)` + // is safe regardless of current length. Capacity is left + // alone — Vec never shrinks its backing buffer. + fn truncate(self: mut Self, newLen: u32) { + if (newLen < self.length) { + self.length = newLen; + } + } + + // Set length explicitly, clamped to `capacity`. The use + // case is "I just filled the first N slots via `.ptr` (a + // memcpy, an extern fill, a manual loop) and need the Vec + // bookkeeping to reflect that." Callers who need room + // first must `reserve` before writing. + fn setLen(self: mut Self, newLen: u32) { + if (newLen > self.capacity) { + self.length = self.capacity; + } else { + self.length = newLen; + } + } + + // Ensure room for `additional` more elements beyond the + // current length. Doubles capacity until the bound is hit, + // then `realloc`s once — cheaper than letting `push` grow + // log(N) times when the final size is roughly known. No-op + // when the existing capacity already covers the request. + fn reserve(self: mut Self, additional: u32) { + var needed: u32 = self.length + additional; + if (needed <= self.capacity) { + return; + } + var newCap: u32 = self.capacity; + while (newCap < needed) { + newCap = newCap * 2; + } + var newBytes: u64 = (newCap as u64) * @sizeOf(T); + self.ptr = realloc(self.ptr as *mut[] u8, newBytes) as *mut[] T; + self.capacity = newCap; + } + + // Bulk-append every element of `other`. One `reserve` up + // front means the copy loop never re-grows; -O3 should + // vectorize the loop for primitive element types. The + // input is `[]T`, so a string literal slices into a + // Vec(u8) and a `[N]T` array decays in the same call. + fn appendSlice(self: mut Self, other: []T) { + var n: u32 = other.len as u32; + self.reserve(n); + var i: u32 = 0; + while (i < n) { + self.ptr[self.length + i] = other.ptr[i]; + i = i + 1; + } + self.length = self.length + n; + } + // Internal grow: doubles capacity via `realloc`. realloc // tries to grow in place against allocator metadata first // (jemalloc / mimalloc / glibc all do this opportunistically diff --git a/tests/unit/test_vec.jam b/tests/unit/test_vec.jam index 9d600de..311b93c 100644 --- a/tests/unit/test_vec.jam +++ b/tests/unit/test_vec.jam @@ -124,3 +124,127 @@ tfn withCapacityZeroClampsToOne() { v.push(42); assert(unwrapI32(v.get(0), 0), 42); } + +tfn clearResetsLength() { + var v: VecI32 = VecI32.empty(); + v.push(1); + v.push(2); + v.push(3); + v.clear(); + assert(v.len() as i32, 0); + // Buffer is reusable: pushing again works without reallocating. + v.push(99); + assert(v.len() as i32, 1); + assert(unwrapI32(v.get(0), 0), 99); +} + +tfn clearKeepsCapacity() { + var v: VecI32 = VecI32.withCapacity(50); + v.push(7); + v.clear(); + // clear() must not free or shrink — capacity survives. + assert(v.capacity as i32, 50); +} + +tfn truncateShrinks() { + var v: VecI32 = VecI32.empty(); + v.push(10); + v.push(20); + v.push(30); + v.push(40); + v.truncate(2); + assert(v.len() as i32, 2); + assert(unwrapI32(v.get(0), -1), 10); + assert(unwrapI32(v.get(1), -1), 20); + // Index 2 is past the new length even though the byte is still + // there in the buffer. + var sentinel: i32 = -42; + assert(unwrapI32(v.get(2), sentinel), sentinel); +} + +tfn truncateNoOpWhenLarger() { + var v: VecI32 = VecI32.empty(); + v.push(1); + v.push(2); + v.truncate(99); + assert(v.len() as i32, 2); +} + +tfn setLenSetsExactLength() { + var v: VecI32 = VecI32.withCapacity(10); + // Caller fills the buffer through `.ptr` then announces how + // much they wrote. + v.ptr[0] = 100; + v.ptr[1] = 200; + v.ptr[2] = 300; + v.setLen(3); + assert(v.len() as i32, 3); + assert(unwrapI32(v.get(2), 0), 300); +} + +tfn setLenClampsToCapacity() { + var v: VecI32 = VecI32.withCapacity(5); + // Overshooting must not silently corrupt — clamp to capacity. + v.setLen(99); + assert(v.len() as i32, 5); +} + +tfn reserveGrowsCapacity() { + // Empty VecI32 starts at capacity 4; doubling to cover an + // extra 100 elements lands at 128 (4 → 8 → 16 → 32 → 64 → 128). + var v: VecI32 = VecI32.empty(); + v.reserve(100); + assert(v.capacity as i32, 128); + assert(v.len() as i32, 0); +} + +tfn reserveNoOpWhenEnough() { + var v: VecI32 = VecI32.withCapacity(100); + v.reserve(50); + // length 0 + additional 50 ≤ capacity 100, so no growth. + assert(v.capacity as i32, 100); +} + +tfn reserveAccountsForCurrentLength() { + var v: VecI32 = VecI32.withCapacity(10); + v.push(1); + v.push(2); + // length 2 + additional 9 = 11 > 10, so grow once (10 → 20). + v.reserve(9); + assert(v.capacity as i32, 20); + assert(v.len() as i32, 2); +} + +tfn appendSliceFromStringLiteral() { + var v: VecU8 = VecU8.empty(); + var s: []u8 = "hi"; + v.appendSlice(s); + assert(v.len() as i32, 2); + // 'h' == 104, 'i' == 105. + assert(unwrapU8(v.get(0), 0) as i32, 104); + assert(unwrapU8(v.get(1), 0) as i32, 105); +} + +tfn appendSliceTriggersGrow() { + // VecU8 starts at capacity 8; appending 20 bytes forces grow + // through 8 → 16 → 32. Pre-reserved path means only one + // realloc, not log(N). + var v: VecU8 = VecU8.empty(); + var s: []u8 = "abcdefghijklmnopqrst"; + v.appendSlice(s); + assert(v.len() as i32, 20); + assert(v.capacity as i32, 32); + assert(unwrapU8(v.get(0), 0) as i32, 97); // 'a' + assert(unwrapU8(v.get(19), 0) as i32, 116); // 't' +} + +tfn appendSliceAfterExistingData() { + var v: VecU8 = VecU8.empty(); + v.push(65); // 'A' + v.push(66); // 'B' + var s: []u8 = "CD"; + v.appendSlice(s); + assert(v.len() as i32, 4); + assert(unwrapU8(v.get(0), 0) as i32, 65); + assert(unwrapU8(v.get(3), 0) as i32, 68); +} -- 2.51.2