diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-17 05:30:57 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-17 05:30:57 +0000 |
| commit | 4dd85fd6ecd9a67e5c2b68380873e6e114714355 (patch) | |
| tree | b9dad1d0845f4c2ae7e6efed171fbee40ccd1f31 | |
| parent | e782898e420c7f2abd83b97c160194d0e70598dd (diff) | |
feat(memory): add tracked MALLOC and FREE builtins
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
27 files changed, 412 insertions, 14 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md index 83a1923..edfa281 100644 --- a/AGENTS/ARCH.md +++ b/AGENTS/ARCH.md @@ -297,6 +297,7 @@ do not modify the real release for local verification. | `lib/CodeGen/Designator.cc`, `TypeCheckGen.cc` | TYPE pre-pass and lvalue access; type admission and user-kind binary operators | | `lib/CodeGen/CTypeGen.cc` | CodeGen methods for native C scalar conversions, casts, arithmetic and input/output | | `lib/CodeGen/NativeTypeSystem.cc` | Builtin registration, native C type imports/aliases and scalar promotion | +| `lib/CodeGen/MemoryHandler.cc` | Explicit raw memory allocation and release with a live-address tracking list | | `lib/CodeGen/FileHandler.cc`, `FileRuntimeIO.cc` | File runtime setup/lookup/open and call entry points; read/write/close/EOF helper bodies | | `lib/CodeGen/ArrayHandler.cc`, `ArrayParameters.cc`, `ArrayOutput.cc` | Array allocation/indexing, parameter binding/arguments and whole/subarray output | | `lib/CodeGen/*Handler.cc`, `FunctionGen.cc`, `RuntimeCheck.cc`, `TypeSystem.cc` | Helpers owned by CodeGen (including exception emission) | diff --git a/AGENTS/CONVENTIONS.md b/AGENTS/CONVENTIONS.md index bb1b836..75289da 100644 --- a/AGENTS/CONVENTIONS.md +++ b/AGENTS/CONVENTIONS.md @@ -89,12 +89,10 @@ remain unsupported. **Strings are malloc'd and never freed — by design.** Every string-producing operation (`&`, MID/LEFT/RIGHT/LCASE/UCASE, NUM_TO_STR, CHR, READFILE, CHAR/number→STRING coercion) mallocs a fresh buffer; string literals and BOOLEAN→STRING coercion instead -return pointers to shared module globals, so a STRING value may alias a global. `free` -is never declared or called. Don't "fix" leaks piecemeal; it's a global design decision. +return pointers to shared module globals, so a STRING value may alias a global. `MemoryHandler` declares `free` for explicit user memory only; it must never free internal STRING buffers. Don't "fix" leaks piecemeal; it's a global design decision. Heap-string operations live in `StringHandler` (including `&` concatenation, `emitConcat`), `StringConversionHandler` (NUM_TO_STR/STR_TO_NUM buffers) and -`FileHandler` (READFILE line buffers — its `__cps_file_read` helper owns the project's -only `realloc`); `CodeGen` also keeps its own `MallocFunc` for the char→STRING coercion +`FileHandler` (READFILE line buffers — its `__cps_file_read` helper grows line buffers with `realloc`); `CodeGen` also keeps its own `MallocFunc` for the char→STRING coercion and INPUT STRING buffers — declared in `SetupExternalFunctions`, never fetched with a bare `getFunction("malloc")`. @@ -159,15 +157,13 @@ The LLVM ABI flattens each rank-R array parameter into a data `ptr` plus callback CodeGen injects into FunctionGen). BYVAL arrays are whole malloc+memcpy copies at callee entry; BYREF binds the caller's buffer. -**Builtins are plain identifiers backed by one table.** All 12 builtins -(LENGTH/MID/RIGHT/LEFT/LCASE/UCASE/ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM/EOF) parse as +**Builtins are plain identifiers backed by one table.** Builtins +(LENGTH/MID/RIGHT/LEFT/LCASE/UCASE/ASC/CHR/IS_NUM/NUM_TO_STR/STR_TO_NUM/EOF/MALLOC/FREE) parse as ordinary calls — no lexer/parser support, and the names are not reserved words. The inline constexpr `Builtins` table in the internal `lib/CodeGen/Builtins.h` ({Name, Arity, ReturnTypeName}) is -the single source of truth: `emitExpr`'s `CallExprAST` branch checks arity against it +the single source of truth: `emitCall` checks arity against it before dispatching to the per-builtin emit branches (still an if-chain, ahead of the -user-function fallback), and `getExprTypeInfo` reads return types from it. EOF is the -odd one out: its intercept consumes `CallExprAST::getLine()` (runtime diagnostics) and -it is the only builtin that touches mutable runtime state and can throw at run time. +user-function fallback), and `getExprTypeInfo` reads return types from it. EOF and the memory builtins consume the call source line for runtime diagnostics; they touch mutable runtime state and can throw. **Program exceptions use the C++ ABI; compiler diagnostics do not.** `ExceptionHandler` emits anonymous integer exceptions (an internal placeholder), diff --git a/AGENTS/EXTENDING.md b/AGENTS/EXTENDING.md index 5e104c8..b3f481a 100644 --- a/AGENTS/EXTENDING.md +++ b/AGENTS/EXTENDING.md @@ -17,10 +17,9 @@ already terminated the block. **New builtin function:** add a `{Name, Arity, ReturnTypeName}` row to the shared `Builtins` table in the internal `lib/CodeGen/Builtins.h` (single source of truth — arity checking in -`emitExpr`, the return type in `getExprTypeInfo`, and the reject-user-redefinition check +`emitCall`, the return type in `getExprTypeInfo`, and the reject-user-redefinition check all read it), then implement IR emission as a method on the fitting handler -(String/StringConversion) or inline, and add the name intercept in `emitExpr`'s -CallExprAST section — null-check every emitted argument before use, like the existing +(String/StringConversion) or inline, and add the name intercept in `CodeGen::emitCall` — null-check every emitted argument before use, like the existing branches do. Builtins are plain identifiers — nothing is needed in lexer/parser. **New binary operator:** token (if multi-char) → one line in `BinopPrecedence` diff --git a/AGENTS/SUBSYSTEMS.md b/AGENTS/SUBSYSTEMS.md index e58574e..6b4793c 100644 --- a/AGENTS/SUBSYSTEMS.md +++ b/AGENTS/SUBSYSTEMS.md @@ -98,7 +98,7 @@ CodeGen, AST nodes or the symbol table; `CodeGen::emitStmt`'s four file branches intercept) evaluate/coerce operands and hand over `llvm::Value*`s plus the statement line. Calls into throwing helpers go through ExceptionHandler. On first file use, `ensureRuntime()` synthesizes the whole runtime once — the -only module-level helper functions in the project: internal-linkage `__cps_file_find/ +file module-level helpers: internal-linkage `__cps_file_find/ open/read/write/close/eof` plus a 16-slot open-file table (`__cps_file_names/handles/ modes` parallel globals; mode byte 0 = free). Files are keyed by filename *content* (strcmp scan), consistent with STRING comparison; the name pointer is stored as-is @@ -107,3 +107,5 @@ malloc/realloc buffer per line and strips one trailing `\r`; fopen modes are tex `"r"/"w"/"a"` (Windows CRT translates CRLF). File-free programs get byte-identical IR — nothing is declared or emitted eagerly. Helper bodies are built with the *shared* Builder (RuntimeCheck emits through the same one), saving/restoring the insert block. + +**Explicit memory** (`MemoryHandler`): lazily emits internal allocation/find/release helpers and a linked list of live allocation start addresses. Only explicit MALLOC allocations enter the list; STRING/ARRAY internals retain their existing ownership. FREE validates non-null addresses, unlinks and frees the tracking node, releases the block and returns the original address. Zero-byte allocation returns null. Node allocation precedes the user allocation; if the latter fails, its node is freed before the diagnostic and anonymous throw. Helpers use RuntimeCheck and ExceptionHandler, including source-line arguments and per-function unwind preparation. Sizes accept INTEGER and C integers and convert to native size_t. Address reuse can make stale aliases indistinguishable from current allocations; dereferences are not checked against this list. diff --git a/CMakeLists.txt b/CMakeLists.txt index 893ec50..7451188 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ add_library(CPSCodeGen STATIC lib/CodeGen/StringConversionHandler.cc lib/CodeGen/TypeSystem.cc lib/CodeGen/NativeTypeSystem.cc + lib/CodeGen/MemoryHandler.cc lib/CodeGen/FileHandler.cc lib/CodeGen/FileRuntimeIO.cc ) diff --git a/doc/language/cpsc-language-overview.7.md b/doc/language/cpsc-language-overview.7.md index 8838b7f..1c77bb5 100644 --- a/doc/language/cpsc-language-overview.7.md +++ b/doc/language/cpsc-language-overview.7.md @@ -64,6 +64,8 @@ Closing and auxiliary keywords are documented with their main constructs: THEN/E - [cpsc-function-num_to_str(7)](function/cpsc-function-num_to_str.7.md): convert a number to text. - [cpsc-function-str_to_num(7)](function/cpsc-function-str_to_num.7.md): convert text to a real number. - [cpsc-function-eof(7)](function/cpsc-function-eof.7.md): test for the end of a text file. +- [cpsc-function-malloc(7)](function/cpsc-function-malloc.7.md): allocate raw memory. +- [cpsc-function-free(7)](function/cpsc-function-free.7.md): release an explicit allocation. # UNSUPPORTED FEATURES diff --git a/doc/language/function/cpsc-function-free.7.md b/doc/language/function/cpsc-function-free.7.md new file mode 100644 index 0000000..298199d --- /dev/null +++ b/doc/language/function/cpsc-function-free.7.md @@ -0,0 +1,32 @@ +% CPSC-FUNCTION-FREE(7) + +# NAME + +cpsc-function-free - release an explicit memory allocation + +# SYNOPSIS + +```text +FREE(pointer) +``` + +# DESCRIPTION + +FREE is a compiler extension. Its argument and return type are C void*. Explicitly cast other pointer types to a void* alias before passing them. A null pointer is accepted and returned. A non-null pointer must be the start of an active explicit MALLOC allocation; internal STRING/ARRAY storage, stack addresses, interior addresses and already released allocations are rejected with a source-line diagnostic on stdout and an anonymous exception. + +FREE returns its input address after releasing the allocation. It does not clear the caller's variable. The returned address and all aliases are invalid for memory access. Calling FREE again on that address throws unless a subsequent allocation has reused the address. Tracking cannot distinguish stale aliases once an address is reused. + +The result can be discarded with a standalone call. No user destructor is called. + +# EXAMPLE + +```text +TYPE Raw = C_TYPE("void*") +DECLARE Memory : Raw +Memory <- MALLOC(8) +FREE(Memory) +``` + +# SEE ALSO + +[cpsc-function-malloc(7)](cpsc-function-malloc.7.md), [cpsc-keyword-function(7)](../keyword/cpsc-keyword-function.7.md), [cpsc-keyword-try(7)](../keyword/cpsc-keyword-try.7.md). diff --git a/doc/language/function/cpsc-function-malloc.7.md b/doc/language/function/cpsc-function-malloc.7.md new file mode 100644 index 0000000..742367c --- /dev/null +++ b/doc/language/function/cpsc-function-malloc.7.md @@ -0,0 +1,37 @@ +% CPSC-FUNCTION-MALLOC(7) + +# NAME + +cpsc-function-malloc - allocate uninitialized raw memory + +# SYNOPSIS + +```text +MALLOC(bytes) +``` + +# DESCRIPTION + +MALLOC is a compiler extension returning a C void* to the beginning of an uninitialized allocation. The size is in bytes and accepts INTEGER or a C integer, converted to the host size_t using C integer conversion rules. Negative values are not separately rejected. MALLOC(0) returns null without allocating. A nonzero allocation failure prints the source line and reason to stdout, then throws an anonymous exception catchable with TRY/CATCH. Allocation tracking failures behave the same way. + +Declare a void* alias with C_TYPE and explicitly cast the result to a C or pseudocode pointer before dereferencing. MALLOC does not initialize fields or call constructors; it is unrelated to the CIE class NEW construct. The caller must supply enough bytes and initialize values before reading them. User memory is not automatically released. + +Only allocations from explicit memory builtins are tracked. Internal STRING and ARRAY buffers remain outside this ownership system. Tracking validates FREE addresses, not memory accesses, bounds or stale aliases after address reuse. + +# EXAMPLE + +```text +TYPE Raw = C_TYPE("void*") +TYPE IntPointer = ^INTEGER +DECLARE Memory : Raw +DECLARE Pointer : IntPointer +Memory <- MALLOC(8) +Pointer <- (IntPointer)Memory +Pointer^ <- 42 +OUTPUT Pointer^ +FREE(Memory) +``` + +# SEE ALSO + +[cpsc-function-free(7)](cpsc-function-free.7.md), [cpsc-keyword-c_type(7)](../keyword/cpsc-keyword-c_type.7.md), [cpsc-keyword-try(7)](../keyword/cpsc-keyword-try.7.md). diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 8f84004..94aacb7 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -22,6 +22,7 @@ namespace cps { class ArrayHandler; class FileHandler; +class MemoryHandler; class ExceptionHandler; class CastExprAST; @@ -52,6 +53,7 @@ class CodeGen { std::unique_ptr<StringHandler> StrHandler; std::unique_ptr<StringConversionHandler> StrConvHandler; std::unique_ptr<FileHandler> Files; + std::unique_ptr<MemoryHandler> Memory; llvm::FunctionCallee PrintfFunc; llvm::FunctionCallee ScanfFunc; @@ -106,6 +108,7 @@ class CodeGen { bool emitLValue(ExprAST *E, LValueInfo &Out); // The rvalue read: load + the STRING null->"" guard. llvm::Value *loadFromLValue(const LValueInfo &LV); + llvm::Value *emitAllocationSize(ExprAST *Expr); llvm::Value *emitCall(const std::string &Callee, const std::vector<std::unique_ptr<ExprAST>> &ArgExprs, int Line); bool marshalCallArgs(const std::string &Callee, diff --git a/include/cps/MemoryHandler.h b/include/cps/MemoryHandler.h new file mode 100644 index 0000000..fd2c93e --- /dev/null +++ b/include/cps/MemoryHandler.h @@ -0,0 +1,42 @@ +#pragma once +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Module.h" + +namespace cps { + +class TypeSystem; +class RuntimeCheck; +class ExceptionHandler; + +// Only explicit memory builtins own entries in this runtime's allocation list. +// Internal STRING/ARRAY storage retains its existing lifetime rules. +class MemoryHandler { + llvm::LLVMContext &Context; + llvm::IRBuilder<> &Builder; + llvm::Module &Module; + TypeSystem &Types; + RuntimeCheck &Checker; + ExceptionHandler &Exceptions; + llvm::IntegerType *SizeTy = nullptr; + llvm::StructType *NodeTy = nullptr; + llvm::GlobalVariable *Head = nullptr; + llvm::FunctionCallee Malloc, Free; + llvm::Function *AllocateFn = nullptr, *ReleaseFn = nullptr, *FindFn = nullptr; + llvm::Value *AllocationError = nullptr, *TrackingError = nullptr, *InvalidAddress = nullptr; + + void ensureRuntime(); + llvm::Function *createHelper(const char *Name, llvm::ArrayRef<llvm::Type *> Args); + void buildFind(); + void buildAllocate(); + void buildRelease(); + +public: + MemoryHandler(llvm::LLVMContext &C, llvm::IRBuilder<> &B, llvm::Module &M, + TypeSystem &TS, RuntimeCheck &RC, ExceptionHandler &EH) + : Context(C), Builder(B), Module(M), Types(TS), Checker(RC), Exceptions(EH) {} + + llvm::Value *emitAllocate(llvm::Value *Size, int Line); + llvm::Value *emitRelease(llvm::Value *Address, int Line); +}; + +} // namespace cps diff --git a/lib/CodeGen/Builtins.h b/lib/CodeGen/Builtins.h index e7f60e4..69361cf 100644 --- a/lib/CodeGen/Builtins.h +++ b/lib/CodeGen/Builtins.h @@ -26,6 +26,8 @@ inline constexpr BuiltinInfo Builtins[] = { {"NUM_TO_STR", 1, "STRING"}, {"STR_TO_NUM", 1, "REAL"}, {"EOF", 1, "BOOLEAN"}, + {"MALLOC", 1, "C_TYPE(\"void*\")"}, + {"FREE", 1, "C_TYPE(\"void*\")"}, }; inline const BuiltinInfo *findBuiltin(const std::string &Name) { diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index dff4009..ffabf8d 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -1,6 +1,7 @@ #include "cps/CodeGen.h" #include "cps/ArrayHandler.h" #include "cps/FileHandler.h" +#include "cps/MemoryHandler.h" #include "cps/ExceptionHandler.h" #include "Builtins.h" #include "llvm/IR/Instructions.h" @@ -61,6 +62,9 @@ CodeGen::CodeGen() { Files = std::make_unique<FileHandler>(*TheContext, *Builder, *TheModule, *RuntimeChecker, *Exceptions); + Memory = std::make_unique<MemoryHandler>(*TheContext, *Builder, *TheModule, *Types, + *RuntimeChecker, *Exceptions); + // Array parameters are bound into ArrayHandler through this inversion so // FunctionGen never sees the array subsystem. FuncGen->setArrayParamBinder( diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index fa2581f..0a24c8d 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -1,9 +1,12 @@ #include "cps/CodeGen.h" #include "cps/ArrayHandler.h" #include "cps/FileHandler.h" +#include "cps/MemoryHandler.h" #include "cps/Lexer.h" #include "cps/TypeAST.h" #include "Builtins.h" +#include <climits> +#include <cstddef> using namespace llvm; using namespace cps; @@ -184,6 +187,16 @@ Value *CodeGen::emitCall(const std::string &Callee, return nullptr; } } + if (Name == "MALLOC") { + Value *Size = emitAllocationSize(ArgExprs[0].get()); + if (!Size) return nullptr; + return Memory->emitAllocate(Size, Line); + } + if (Name == "FREE") { + Value *Address = emitCoercedExpr(ArgExprs[0].get(), resolveType("C_TYPE(\"void*\")")); + if (!Address) return nullptr; + return Memory->emitRelease(Address, Line); + } if (Name == "LENGTH") { Value *Str = emitCoercedExpr(ArgExprs[0].get(), resolveType("STRING")); if (!Str) return nullptr; @@ -279,3 +292,19 @@ Value *CodeGen::emitCall(const std::string &Callee, if (!marshalCallArgs(Name, ArgExprs, Line, Args)) return nullptr; return FuncGen->emitCall(Name, Args); } + +Value *CodeGen::emitAllocationSize(ExprAST *Expr) { + SaveAndRestore<int> Location(DiagnosticLine, Expr->getLine() ? Expr->getLine() : DiagnosticLine); + const TypeInfo *Info = getExprTypeInfo(Expr); + if (isWholeArrayVar(Expr) || !Info || + !(Info->Kind == TypeKind::Integer || + (Info->Kind == TypeKind::CScalar && Info->isIntegral()))) { + reportError("Allocation size must be INTEGER or a C integer"); + return nullptr; + } + Value *Size = emitExpr(Expr); + if (!Size) return nullptr; + auto *SizeTy = Type::getIntNTy(*TheContext, sizeof(std::size_t) * CHAR_BIT); + return Builder->CreateIntCast(Size, SizeTy, + Info->Kind == TypeKind::Integer || Info->CSigned, "allocation_size"); +} diff --git a/lib/CodeGen/MemoryHandler.cc b/lib/CodeGen/MemoryHandler.cc new file mode 100644 index 0000000..fc6d982 --- /dev/null +++ b/lib/CodeGen/MemoryHandler.cc @@ -0,0 +1,120 @@ +#include "cps/MemoryHandler.h" +#include "cps/ExceptionHandler.h" +#include "cps/RuntimeCheck.h" +#include "cps/TypeSystem.h" +#include <climits> +#include <cstddef> + +using namespace llvm; +using namespace cps; + +Function *MemoryHandler::createHelper(const char *Name, ArrayRef<Type *> Args) { + auto *F = Function::Create(FunctionType::get(PointerType::getUnqual(Context), Args, false), + Function::InternalLinkage, Name, Module); + Exceptions.prepareFunction(F); + Builder.SetInsertPoint(BasicBlock::Create(Context, "entry", F)); + return F; +} + +void MemoryHandler::ensureRuntime() { + if (AllocateFn) return; + IRBuilderBase::InsertPointGuard Guard(Builder); + auto *Ptr = PointerType::getUnqual(Context); + auto *I32 = Type::getInt32Ty(Context); + SizeTy = Type::getIntNTy(Context, sizeof(std::size_t) * CHAR_BIT); + NodeTy = StructType::create(Context, {Ptr, Ptr}, "__cps_memory_node"); + Head = new GlobalVariable(Module, Ptr, false, GlobalValue::InternalLinkage, + ConstantPointerNull::get(Ptr), "__cps_memory_head"); + Malloc = Module.getOrInsertFunction("malloc", FunctionType::get(Ptr, {SizeTy}, false)); + Free = Module.getOrInsertFunction("free", FunctionType::get(Type::getVoidTy(Context), {Ptr}, false)); + AllocationError = Builder.CreateGlobalString("[Fatal] line %d: Memory allocation failed\n", + "err_allocation", 0, &Module); + TrackingError = Builder.CreateGlobalString("[Fatal] line %d: Allocation tracking failed\n", + "err_tracking", 0, &Module); + InvalidAddress = Builder.CreateGlobalString("[Fatal] line %d: Invalid memory address (not an active allocation)\n", + "err_memory_address", 0, &Module); + FindFn = createHelper("__cps_memory_find", {Ptr, I32}); + buildFind(); + AllocateFn = createHelper("__cps_memory_allocate", {SizeTy, I32}); + buildAllocate(); + ReleaseFn = createHelper("__cps_memory_release", {Ptr, I32}); + buildRelease(); +} + +// Return the address of the list link that owns the matching node. This also +// lets release unlink the head or an interior node through the same operation. +void MemoryHandler::buildFind() { + auto *Ptr = PointerType::getUnqual(Context); + Value *Address = FindFn->getArg(0), *Line = FindFn->getArg(1); + auto *Entry = Builder.GetInsertBlock(); + auto *Loop = BasicBlock::Create(Context, "search", FindFn); + auto *Found = BasicBlock::Create(Context, "found", FindFn); + Builder.CreateBr(Loop); + Builder.SetInsertPoint(Loop); + auto *Link = Builder.CreatePHI(Ptr, 2, "link"); + Link->addIncoming(Head, Entry); + Value *Node = Builder.CreateLoad(Ptr, Link, "node"); + Checker.emitCheck(Builder.CreateIsNull(Node), InvalidAddress, {Line}); + Value *Stored = Builder.CreateLoad(Ptr, Builder.CreateStructGEP(NodeTy, Node, 0)); + Value *NextLink = Builder.CreateStructGEP(NodeTy, Node, 1); + Link->addIncoming(NextLink, Builder.GetInsertBlock()); + Builder.CreateCondBr(Builder.CreateICmpEQ(Stored, Address), Found, Loop); + Builder.SetInsertPoint(Found); + Builder.CreateRet(Link); +} + +void MemoryHandler::buildAllocate() { + auto *Ptr = PointerType::getUnqual(Context); + Value *Size = AllocateFn->getArg(0), *Line = AllocateFn->getArg(1); + auto *Zero = BasicBlock::Create(Context, "zero_size", AllocateFn); + auto *Allocate = BasicBlock::Create(Context, "allocate", AllocateFn); + Builder.CreateCondBr(Builder.CreateICmpEQ(Size, ConstantInt::get(SizeTy, 0)), Zero, Allocate); + Builder.SetInsertPoint(Zero); + Builder.CreateRet(ConstantPointerNull::get(Ptr)); + Builder.SetInsertPoint(Allocate); + Value *NodeSize = Builder.CreateIntCast(Types.getSizeOfConstant(NodeTy), SizeTy, false); + Value *Node = Builder.CreateCall(Malloc, {NodeSize}, "new_node"); + Checker.emitCheck(Builder.CreateIsNull(Node), TrackingError, {Line}); + Value *Address = Builder.CreateCall(Malloc, {Size}, "allocation"); + Value *Failed = Builder.CreateIsNull(Address); + auto *Cleanup = BasicBlock::Create(Context, "cleanup_node", AllocateFn); + auto *Checked = BasicBlock::Create(Context, "check_allocation", AllocateFn); + Builder.CreateCondBr(Failed, Cleanup, Checked); + Builder.SetInsertPoint(Cleanup); + Builder.CreateCall(Free, {Node}); + Builder.CreateBr(Checked); + Builder.SetInsertPoint(Checked); + Checker.emitCheck(Failed, AllocationError, {Line}); + Builder.CreateStore(Address, Builder.CreateStructGEP(NodeTy, Node, 0)); + Builder.CreateStore(Builder.CreateLoad(Ptr, Head), Builder.CreateStructGEP(NodeTy, Node, 1)); + Builder.CreateStore(Node, Head); + Builder.CreateRet(Address); +} + +void MemoryHandler::buildRelease() { + auto *Ptr = PointerType::getUnqual(Context); + Value *Address = ReleaseFn->getArg(0), *Line = ReleaseFn->getArg(1); + auto *Done = BasicBlock::Create(Context, "done", ReleaseFn); + auto *Release = BasicBlock::Create(Context, "release", ReleaseFn); + Builder.CreateCondBr(Builder.CreateIsNull(Address), Done, Release); + Builder.SetInsertPoint(Release); + Value *Link = Exceptions.emitCall(FindFn, {Address, Line}, "link"); + Value *Node = Builder.CreateLoad(Ptr, Link); + Value *Next = Builder.CreateLoad(Ptr, Builder.CreateStructGEP(NodeTy, Node, 1)); + Builder.CreateStore(Next, Link); + Builder.CreateCall(Free, {Address}); + Builder.CreateCall(Free, {Node}); + Builder.CreateBr(Done); + Builder.SetInsertPoint(Done); + Builder.CreateRet(Address); +} + +Value *MemoryHandler::emitAllocate(Value *Size, int Line) { + ensureRuntime(); + return Exceptions.emitCall(AllocateFn, {Size, Builder.getInt32(Line)}, "allocated"); +} + +Value *MemoryHandler::emitRelease(Value *Address, int Line) { + ensureRuntime(); + return Exceptions.emitCall(ReleaseFn, {Address, Builder.getInt32(Line)}, "released"); +} diff --git a/tests/i/memory-allocation/case.json b/tests/i/memory-allocation/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/i/memory-allocation/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/i/memory-allocation/expected.txt b/tests/i/memory-allocation/expected.txt new file mode 100644 index 0000000..330c9ba --- /dev/null +++ b/tests/i/memory-allocation/expected.txt @@ -0,0 +1,14 @@ +TRUE +TRUE +42 +11:22 +TRUE +[Fatal] line 34: Invalid memory address (not an active allocation) +double free caught +[Fatal] line 40: Invalid memory address (not an active allocation) +stack address caught +[Fatal] line 48: Invalid memory address (not an active allocation) +interior address caught +[Fatal] line 54: Memory allocation failed +allocation failure caught +99 diff --git a/tests/i/memory-allocation/notes.md b/tests/i/memory-allocation/notes.md new file mode 100644 index 0000000..c0d4286 --- /dev/null +++ b/tests/i/memory-allocation/notes.md @@ -0,0 +1,5 @@ +# Trace notes + +Human review: pending. + +Explicit memory allocation is a compiler extension. Zero size and FREE(null) return null. An INTEGER allocation stores 42; a two-INTEGER record stores 11 and 22. FREE returns the input address. Freeing the middle, head and tail nodes empties the allocation list. Repeated free, a stack address and a record field's interior address are caught before libc is called. The negative size converts to SIZE_MAX, whose allocation fails. A final allocation still stores 99. Sizes 8 and 16 cover the fixed i64 representation. diff --git a/tests/i/memory-allocation/program.cps b/tests/i/memory-allocation/program.cps new file mode 100644 index 0000000..85a24dc --- /dev/null +++ b/tests/i/memory-allocation/program.cps @@ -0,0 +1,63 @@ +TYPE Raw = C_TYPE("void*") +TYPE IntPointer = ^INTEGER +TYPE Pair + DECLARE First : INTEGER + DECLARE Second : INTEGER +ENDTYPE +TYPE PairPointer = ^Pair +DECLARE Empty : Raw +DECLARE P : Raw +DECLARE Q : Raw +DECLARE R : Raw +DECLARE Number : INTEGER +DECLARE Pointer : IntPointer +DECLARE PairAddress : PairPointer +DECLARE Result : Raw +OUTPUT MALLOC(0) = Empty +OUTPUT FREE(Empty) = Empty +P <- malloc(8) +Pointer <- (IntPointer)P +Pointer^ <- 42 +OUTPUT Pointer^ +Q <- MALLOC((C_INT)16) +PairAddress <- (PairPointer)Q +PairAddress^.First <- 11 +PairAddress^.Second <- 22 +OUTPUT PairAddress^.First, ":", PairAddress^.Second +R <- MALLOC(1) +// Remove a middle node, then the head, then the tail. +Result <- FREE(Q) +OUTPUT Result = Q +FREE(R) +FREE(P) +TRY + FREE(P) +CATCH + OUTPUT "double free caught" +ENDTRY +Pointer <- ^Number +TRY + FREE((Raw)Pointer) +CATCH + OUTPUT "stack address caught" +ENDTRY +Q <- MALLOC(16) +PairAddress <- (PairPointer)Q +Pointer <- ^PairAddress^.Second +TRY + FREE((Raw)Pointer) +CATCH + OUTPUT "interior address caught" +ENDTRY +FREE(Q) +TRY + MALLOC(-1) +CATCH + OUTPUT "allocation failure caught" +ENDTRY +// Allocation still works after all caught failures. +P <- MALLOC(8) +Pointer <- (IntPointer)P +Pointer^ <- 99 +OUTPUT Pointer^ +FREE(P) diff --git a/tests/iii/memory-call-arity/case.json b/tests/iii/memory-call-arity/case.json new file mode 100644 index 0000000..3159635 --- /dev/null +++ b/tests/iii/memory-call-arity/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "FREE\\ expects\\ 1\\ arg", + "line": 1 + } + ] +} diff --git a/tests/iii/memory-call-arity/notes.md b/tests/iii/memory-call-arity/notes.md new file mode 100644 index 0000000..456445e --- /dev/null +++ b/tests/iii/memory-call-arity/notes.md @@ -0,0 +1,5 @@ +# Rejection notes + +Human review: pending. + +The explicit memory builtin rejects the invalid argument before emitting a runtime call. diff --git a/tests/iii/memory-call-arity/program.cps b/tests/iii/memory-call-arity/program.cps new file mode 100644 index 0000000..2ae44d4 --- /dev/null +++ b/tests/iii/memory-call-arity/program.cps @@ -0,0 +1 @@ +FREE() diff --git a/tests/iii/memory-pointer-type/case.json b/tests/iii/memory-pointer-type/case.json new file mode 100644 index 0000000..557c4b9 --- /dev/null +++ b/tests/iii/memory-pointer-type/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Cannot\\ implicitly\\ convert", + "line": 4 + } + ] +} diff --git a/tests/iii/memory-pointer-type/notes.md b/tests/iii/memory-pointer-type/notes.md new file mode 100644 index 0000000..456445e --- /dev/null +++ b/tests/iii/memory-pointer-type/notes.md @@ -0,0 +1,5 @@ +# Rejection notes + +Human review: pending. + +The explicit memory builtin rejects the invalid argument before emitting a runtime call. diff --git a/tests/iii/memory-pointer-type/program.cps b/tests/iii/memory-pointer-type/program.cps new file mode 100644 index 0000000..2c1abeb --- /dev/null +++ b/tests/iii/memory-pointer-type/program.cps @@ -0,0 +1,4 @@ +TYPE Raw = C_TYPE("void*") +TYPE P = ^INTEGER +DECLARE Pointer : P +FREE(Pointer) diff --git a/tests/iii/memory-size-type/case.json b/tests/iii/memory-size-type/case.json new file mode 100644 index 0000000..9753757 --- /dev/null +++ b/tests/iii/memory-size-type/case.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "reason": "Allocation\\ size\\ must\\ be\\ INTEGER\\ or\\ a\\ C\\ integer", + "line": 1 + } + ] +} diff --git a/tests/iii/memory-size-type/notes.md b/tests/iii/memory-size-type/notes.md new file mode 100644 index 0000000..456445e --- /dev/null +++ b/tests/iii/memory-size-type/notes.md @@ -0,0 +1,5 @@ +# Rejection notes + +Human review: pending. + +The explicit memory builtin rejects the invalid argument before emitting a runtime call. diff --git a/tests/iii/memory-size-type/program.cps b/tests/iii/memory-size-type/program.cps new file mode 100644 index 0000000..0215fef --- /dev/null +++ b/tests/iii/memory-size-type/program.cps @@ -0,0 +1 @@ +MALLOC(1.5) |
