aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS/PITFALLS/core-language.md2
-rw-r--r--AGENTS/PITFALLS/exceptions-and-c-interop.md4
-rw-r--r--AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md4
-rw-r--r--AGENTS/SUBSYSTEMS/RUNTIME_CHECKS_EXCEPTIONS.md4
-rw-r--r--lib/CodeGen/ArrayParameters.cc4
-rw-r--r--lib/CodeGen/ConstructorGen.cc47
6 files changed, 40 insertions, 25 deletions
diff --git a/AGENTS/PITFALLS/core-language.md b/AGENTS/PITFALLS/core-language.md
index 60fbd11..0ddba05 100644
--- a/AGENTS/PITFALLS/core-language.md
+++ b/AGENTS/PITFALLS/core-language.md
@@ -13,7 +13,7 @@ source, most also by compiling and running test programs.
- **Arrays cross function boundaries only as parameters.** `ArrayTable` is scoped per
function, so referencing another function's array fails cleanly as
"Undeclared array". Whole arrays pass as `ARRAY OF T` / `ARRAY[l:u,...] OF T`
- parameters (BYVAL copies the entire buffer and never frees it); a whole-array
+ parameters (BYVAL copies the entire buffer; the copy is freed when the callee ends); a whole-array
argument against a scalar parameter is still a compile error. FUNCTIONs cannot
RETURN arrays. A whole-array variable is also rejected as an operand or as a
value of its element type.
diff --git a/AGENTS/PITFALLS/exceptions-and-c-interop.md b/AGENTS/PITFALLS/exceptions-and-c-interop.md
index 104be41..b057d18 100644
--- a/AGENTS/PITFALLS/exceptions-and-c-interop.md
+++ b/AGENTS/PITFALLS/exceptions-and-c-interop.md
@@ -12,8 +12,8 @@ Part of the [pitfalls index](../PITFALLS.md).
- Uncaught exceptions also print the generic termination message and return 1.
- Native compilation links the C++ runtime automatically; manually linking emitted
`.ll` files must use LLVM 23's `clang++` (`clang++-23` on Debian/Ubuntu).
-- Owned STRING values follow class cleanup during unwinding; raw array buffers are
- not freed and files are not automatically closed.
+- Owned STRING values follow class cleanup during unwinding; array buffers are freed
+ and files are not automatically closed.
- Check the native nightly job results when changing platform-specific code
(CI coverage: [ARCH/BUILD.md](../ARCH/BUILD.md)).
diff --git a/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md b/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md
index 727a1af..3b9ec58 100644
--- a/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md
+++ b/AGENTS/SUBSYSTEMS/CLASS_LIFETIME.md
@@ -37,6 +37,10 @@ Part of the [subsystem notes](../SUBSYSTEMS.md). Builds on [classes](CLASSES.md)
- Each record retains the original address, an entry-initialized Alive flag and, for
arrays, a runtime element count in an entry slot. Array buffer addresses also live in
entry slots so deferred cleanup never references branch-local bound SSA values.
+- Every array registers, even when its elements need no destruction: cleanup runs the
+ element-destroy loop only when needed, then `free`s the buffer and nulls the slot, so
+ re-DECLARE, RETURN, unwinding and BYVAL copies all release it. BYREF arrays are never
+ registered.
- Repeated DECLARE destroys its previous live incarnation first. Alive becomes true only
after construction or copy completes and is cleared before destruction.
- Rebinding a reference slot with NEW does not change the original address tracked for
diff --git a/AGENTS/SUBSYSTEMS/RUNTIME_CHECKS_EXCEPTIONS.md b/AGENTS/SUBSYSTEMS/RUNTIME_CHECKS_EXCEPTIONS.md
index fdd10fb..0d14d3a 100644
--- a/AGENTS/SUBSYSTEMS/RUNTIME_CHECKS_EXCEPTIONS.md
+++ b/AGENTS/SUBSYSTEMS/RUNTIME_CHECKS_EXCEPTIONS.md
@@ -43,8 +43,8 @@ Part of the [subsystem notes](../SUBSYSTEMS.md). Related: [class lifetime](CLASS
propagates and interrupts remaining cleanup instead of terminating.
- Main records only its normal return, leaving its final uncaught-exception handler free
of automatic object destruction.
-- Owned STRING values follow class cleanup. Raw array buffers are not freed and files
- are not automatically closed.
+- Owned STRING values follow class cleanup. Array buffers are freed with their
+ scope (also when unwinding) and files are not automatically closed.
### Runtime and platforms
diff --git a/lib/CodeGen/ArrayParameters.cc b/lib/CodeGen/ArrayParameters.cc
index 57dc523..319f1b5 100644
--- a/lib/CodeGen/ArrayParameters.cc
+++ b/lib/CodeGen/ArrayParameters.cc
@@ -27,8 +27,8 @@ void ArrayHandler::bindArrayParameter(const std::string &Name,
Value *Ptr = DataPtr;
if (MakeCopy) {
// BYVAL: whole-buffer copy so callee writes stay invisible to the
- // caller. The copy is malloc'd and never freed, consistent with the
- // project-wide string policy.
+ // caller. The copy is malloc'd and registered below, so it is freed
+ // when the callee ends.
Value *TotalBytes = Builder->CreateMul(TotalElements, Meta.ElementSizeC,
Name + "_copy_bytes");
Value *Copy = Builder->CreateCall(MallocFunc, TotalBytes, Name + "_copy");
diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc
index 15d9300..8ca0258 100644
--- a/lib/CodeGen/ConstructorGen.cc
+++ b/lib/CodeGen/ConstructorGen.cc
@@ -256,7 +256,8 @@ bool CodeGen::emitDestroy(const std::string &TypeName, Value *Addr, int Line) {
size_t CodeGen::registerStackObject(Value *Addr, const std::string &TypeName, int Line,
bool IsArray) {
- if (!typeNeedsDestruction(TypeName)) return size_t(-1);
+ // Arrays always register: their buffer is freed when the scope ends.
+ if (!IsArray && !typeNeedsDestruction(TypeName)) return size_t(-1);
auto *F = Builder->GetInsertBlock()->getParent();
IRBuilder<> Entry(&F->getEntryBlock(), F->getEntryBlock().begin());
auto *Alive = Entry.CreateAlloca(Entry.getInt1Ty(), nullptr, "object_alive");
@@ -291,23 +292,33 @@ void CodeGen::emitStackObjectCleanup(const StackObject &Object) {
emitDestroy(Object.TypeName, Object.Addr, Object.Line);
} else {
Value *Base = Builder->CreateLoad(PointerType::getUnqual(*TheContext), Object.Addr);
- Value *Count = Builder->CreateLoad(Builder->getInt64Ty(), Object.Count);
- auto *Preheader = Builder->GetInsertBlock();
- auto *Loop = BasicBlock::Create(*TheContext, "destroy_elements", F);
- auto *Body = BasicBlock::Create(*TheContext, "destroy_element", F);
- Builder->CreateBr(Loop);
- Builder->SetInsertPoint(Loop);
- auto *Index = Builder->CreatePHI(Builder->getInt64Ty(), 2);
- Index->addIncoming(Count, Preheader);
- Builder->CreateCondBr(Builder->CreateICmpNE(Index, Builder->getInt64(0)), Body, Done);
- Builder->SetInsertPoint(Body);
- Value *Previous = Builder->CreateSub(Index, Builder->getInt64(1));
- Value *Element = Builder->CreateGEP(resolveType(Object.TypeName)->LLVMType, Base, Previous);
- emitDestroy(Object.TypeName, Element, Object.Line);
- Index->addIncoming(Previous, Builder->GetInsertBlock());
- Builder->CreateBr(Loop);
- Builder->SetInsertPoint(Done);
- return;
+ auto *Free = BasicBlock::Create(*TheContext, "free_array", F);
+ if (typeNeedsDestruction(Object.TypeName)) {
+ Value *Count = Builder->CreateLoad(Builder->getInt64Ty(), Object.Count);
+ auto *Preheader = Builder->GetInsertBlock();
+ auto *Loop = BasicBlock::Create(*TheContext, "destroy_elements", F);
+ auto *Body = BasicBlock::Create(*TheContext, "destroy_element", F);
+ Builder->CreateBr(Loop);
+ Builder->SetInsertPoint(Loop);
+ auto *Index = Builder->CreatePHI(Builder->getInt64Ty(), 2);
+ Index->addIncoming(Count, Preheader);
+ Builder->CreateCondBr(Builder->CreateICmpNE(Index, Builder->getInt64(0)), Body, Free);
+ Builder->SetInsertPoint(Body);
+ Value *Previous = Builder->CreateSub(Index, Builder->getInt64(1));
+ Value *Element = Builder->CreateGEP(resolveType(Object.TypeName)->LLVMType, Base, Previous);
+ emitDestroy(Object.TypeName, Element, Object.Line);
+ Index->addIncoming(Previous, Builder->GetInsertBlock());
+ Builder->CreateBr(Loop);
+ } else {
+ Builder->CreateBr(Free);
+ }
+ // Release the buffer; the slot is nulled so a later cleanup of the
+ // same (inactive) object can never free it twice.
+ Builder->SetInsertPoint(Free);
+ FunctionCallee FreeFunc = TheModule->getOrInsertFunction(
+ "free", FunctionType::get(Builder->getVoidTy(), {PointerType::getUnqual(*TheContext)}, false));
+ Builder->CreateCall(FreeFunc, {Base});
+ Builder->CreateStore(ConstantPointerNull::get(PointerType::getUnqual(*TheContext)), Object.Addr);
}
Builder->CreateBr(Done);
Builder->SetInsertPoint(Done);