From 345e3d419eafb0e234aecb4a33f37efe2afbbf53 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sun, 18 Oct 2026 08:07:50 +0000 Subject: feat(classes): add copy constructors and statement-scoped temporaries Co-Authored-By: Claude Opus 5.5 --- lib/CodeGen/CodeGen.cc | 24 ++++++++++++++- lib/CodeGen/ConstructorGen.cc | 68 +++++++++++++++++++++++++++++++++++++++-- lib/CodeGen/ControlFlowGen.cc | 7 +++++ lib/CodeGen/Designator.cc | 12 +++++++- lib/CodeGen/ExceptionHandler.cc | 7 +++-- lib/CodeGen/FunctionGen.cc | 11 ++++++- lib/CodeGen/OperatorGen.cc | 26 +++++++++++++--- lib/CodeGen/StatementGen.cc | 21 +++++++++++-- lib/Parser/Parser.cc | 6 ++++ lib/Parser/ParserExpression.cc | 38 ++++++++++++++++++++--- lib/Parser/ParserFunction.cc | 6 ++++ 11 files changed, 206 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index c9d8c1a..49fc7b1 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -390,6 +390,7 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature, return false; } + std::vector Transfers; for (size_t i = 0; i < ArgExprs.size(); ++i) { if (i >= Sig->Params.size()) { Value *V = emitVariadicArgument(ArgExprs[i], Callee, i + 1); @@ -423,6 +424,26 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature, if (IsByRef) { auto *Var = dynamic_cast(ArgExpr); + bool InternalBinding = Signature.Prototype && + (sameIdentifier(Signature.Prototype->getName(), "NEW") || + Signature.Prototype->getOperator() == tok_assign); + if (!Var && InternalBinding) { + const TypeInfo *Actual = getExprTypeInfo(ArgExpr); + if (!Actual || !Actual->sameType(resolveType(TypeName))) { + reportError("BYREF argument to %s has an incompatible type", Callee.c_str()); + return false; + } + if (isObjectLValue(ArgExpr)) { + LValueInfo LV; + if (!emitLValue(ArgExpr, LV)) return false; + Out.push_back(LV.Addr); + } else { + Value *Storage = materializeOperatorReceiver(emitExpr(ArgExpr), Actual); + if (!Storage) return false; + Out.push_back(Storage); + } + continue; + } if (!Var) { reportError("BYREF argument to %s must be a variable", Callee.c_str()); return false; @@ -442,11 +463,12 @@ bool CodeGen::marshalCallArgs(const FuncSig &Signature, } Out.push_back(symbolAddress(*Info)); } else { - Value *V = emitCoercedExpr(ArgExpr, resolveType(TypeName)); + Value *V = emitOwnedValue(ArgExpr, resolveType(TypeName), Transfers); if (!V) return false; Out.push_back(V); } } + transferObjects(Transfers); return true; } diff --git a/lib/CodeGen/ConstructorGen.cc b/lib/CodeGen/ConstructorGen.cc index e1033c3..4bf5568 100644 --- a/lib/CodeGen/ConstructorGen.cc +++ b/lib/CodeGen/ConstructorGen.cc @@ -8,6 +8,70 @@ using namespace llvm; using namespace cps; +bool CodeGen::isObjectLValue(ExprAST *Expr) const { + return dynamic_cast(Expr) || dynamic_cast(Expr) || + dynamic_cast(Expr); +} + +void CodeGen::transferObjects(const std::vector &Objects) { + for (size_t Index : Objects) + if (Index != size_t(-1)) Builder->CreateStore(Builder->getFalse(), StackObjects[Index].Alive); +} + +Value *CodeGen::emitOwnedValue(ExprAST *Expr, const TypeInfo *Type, + std::vector &Transfers) { + if (!Type || !Type->IsClass) return emitCoercedExpr(Expr, Type); + const TypeInfo *Source = getExprTypeInfo(Expr); + if (!Source || !Source->sameType(Type)) return emitCoercedExpr(Expr, Type); + Value *Value = nullptr; + if (isObjectLValue(Expr)) { + LValueInfo LV; + if (!emitLValue(Expr, LV)) return nullptr; + return copyClassValue(Type, LV.Addr, Transfers); + } + Value = emitCoercedExpr(Expr, Type); + if (!Value) return nullptr; + // A pending argument owns its result until all remaining arguments succeed. + auto *Storage = materializeOperatorReceiver(Value, Type); + if (typeNeedsDestruction(Type->Name)) Transfers.push_back(StackObjects.size() - 1); + return Builder->CreateLoad(Type->LLVMType, Storage); +} + +Value *CodeGen::copyClassValue(const TypeInfo *Type, Value *Address, + std::vector &Transfers) { + const FuncSig *Copy = nullptr; + for (const FuncSig *Sig : FuncGen->getOverloads(Type->Name + ".NEW")) + if (Sig->IsOverload && Sig->Params.size() == 1 && Sig->Params[0].IsByRef && + !Sig->Params[0].IsArray && Type->sameType(resolveType(Sig->Params[0].TypeName))) + Copy = Sig; + if (!Copy) return Builder->CreateLoad(Type->LLVMType, Address); + if (!checkOperatorAccess(*Copy)) return nullptr; + auto *Storage = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), + Type->LLVMType, "copy_object"); + size_t Lifetime = registerStackObject(Storage, Type->Name, DiagnosticLine, false, true); + if (Lifetime != size_t(-1)) StackObjects[Lifetime].Temporary = true; + Builder->CreateStore(Types->getZeroValue(Type->Name), Storage); + for (const auto &[Name, Field] : Type->Record.Fields) { + if (typeNeedsConstruction(Field.TypeName) && + !emitDefaultConstruct(Field.TypeName, + Builder->CreateStructGEP(Type->LLVMType, Storage, Field.Index), DiagnosticLine)) + return nullptr; + } + if (!FuncGen->emitCall(*Copy, {Storage, Address})) return nullptr; + activateStackObject(Lifetime); + Transfers.push_back(Lifetime); + return Builder->CreateLoad(Type->LLVMType, Storage, "copied_value"); +} + +void CodeGen::cleanupTemporaries(size_t Start) { + if (!Builder->GetInsertBlock() || Builder->GetInsertBlock()->hasTerminator()) return; + // Destructor emission can itself register temporaries. Copy each record before emitting. + for (size_t I = StackObjects.size(); I > Start; --I) { + auto Object = StackObjects[I - 1]; + if (Object.Temporary) emitStackObjectCleanup(Object); + } +} + bool CodeGen::typeNeedsConstruction(const std::string &TypeName) const { if (findMethod(TypeName, "NEW")) return true; const TypeInfo *Info = resolveType(TypeName); @@ -169,8 +233,8 @@ void CodeGen::emitStackObjectCleanup(const StackObject &Object) { } void CodeGen::finishStackObjects(Function *F) { - Exceptions->fillCleanups(F, [this] { + Exceptions->fillCleanups(F, [this](bool TemporariesOnly) { for (auto It = StackObjects.rbegin(); It != StackObjects.rend(); ++It) - emitStackObjectCleanup(*It); + if (!TemporariesOnly || It->Temporary) emitStackObjectCleanup(*It); }); } diff --git a/lib/CodeGen/ControlFlowGen.cc b/lib/CodeGen/ControlFlowGen.cc index 0cf0c34..414fe19 100644 --- a/lib/CodeGen/ControlFlowGen.cc +++ b/lib/CodeGen/ControlFlowGen.cc @@ -29,6 +29,7 @@ void CodeGen::emitTryStmt(TryStmtAST *Stmt) { void CodeGen::emitIfStmt(IfStmtAST *Stmt) { Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; + cleanupTemporaries(); Function *TheFunction = Builder->GetInsertBlock()->getParent(); BasicBlock *ThenBB = BasicBlock::Create(*TheContext, "then", TheFunction); @@ -65,6 +66,7 @@ void CodeGen::emitWhileStmt(WhileStmtAST *Stmt) { Builder->SetInsertPoint(CondBB); Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; + cleanupTemporaries(); Builder->CreateCondBr(CondV, LoopBB, AfterBB); @@ -100,6 +102,7 @@ void CodeGen::emitRepeatStmt(RepeatStmtAST *Stmt) { Builder->SetInsertPoint(CondBB); Value *CondV = emitCoercedExpr(Stmt->getCond(), resolveType("BOOLEAN")); if (!CondV) return; + cleanupTemporaries(); Builder->CreateCondBr(CondV, AfterBB, LoopBB); @@ -127,6 +130,8 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) { Value *Alloca = Symbol->Storage; Builder->CreateStore(StartVal, Alloca); + cleanupTemporaries(); + // STEP is evaluated exactly once, before the loop. Value *StepVal = nullptr; if (Stmt->getStep()) { @@ -136,6 +141,7 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) { StepVal = ConstantInt::get(*TheContext, APInt(64, 1)); } + cleanupTemporaries(); BasicBlock *CondBB = BasicBlock::Create(*TheContext, "forcond", TheFunction); BasicBlock *LoopBB = BasicBlock::Create(*TheContext, "forloop", TheFunction); BasicBlock *IncBB = BasicBlock::Create(*TheContext, "forinc", TheFunction); @@ -147,6 +153,7 @@ void CodeGen::emitForStmt(ForStmtAST *Stmt) { Value *CurVar = Builder->CreateLoad(Type::getInt64Ty(*TheContext), Alloca, VarName.c_str()); Value *EndVal = emitCoercedExpr(Stmt->getEnd(), resolveType("INTEGER")); if (!EndVal) return; + cleanupTemporaries(); // The loop direction depends on the STEP value at run time. Value *StepNonNeg = Builder->CreateICmpSGE(StepVal, diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc index 5fb0ed3..b9c9a1a 100644 --- a/lib/CodeGen/Designator.cc +++ b/lib/CodeGen/Designator.cc @@ -98,7 +98,17 @@ const FuncSig *CodeGen::resolveMethod(const std::string &Class, const std::strin Value *CodeGen::emitMemberCall(MemberCallExprAST *Call) { SaveAndRestore Location(DiagnosticLine, Call->getLine()); LValueInfo Receiver; - if (!emitLValue(Call->getReceiver(), Receiver)) return nullptr; + if (isObjectLValue(Call->getReceiver())) { + if (!emitLValue(Call->getReceiver(), Receiver)) return nullptr; + } else { + const TypeInfo *Type = getExprTypeInfo(Call->getReceiver()); + if (!Type || !Type->IsClass) { + reportError("A temporary method receiver must be a class"); + return nullptr; + } + Receiver = {materializeOperatorReceiver(emitExpr(Call->getReceiver()), Type), Type->Name}; + if (!Receiver.Addr) return nullptr; + } return emitMethodCall(Receiver.TypeName, Receiver.Addr, Call->getMethod(), Call->getArgs(), Call->getLine()); } diff --git a/lib/CodeGen/ExceptionHandler.cc b/lib/CodeGen/ExceptionHandler.cc index 3f69417..c879a87 100644 --- a/lib/CodeGen/ExceptionHandler.cc +++ b/lib/CodeGen/ExceptionHandler.cc @@ -94,7 +94,7 @@ void ExceptionHandler::recordExit(Instruction *Before, bool Unwinding) { CleanupPoints[F].push_back({Before, States[F], Unwinding}); } -void ExceptionHandler::fillCleanups(Function *F, const std::function &Destroy) { +void ExceptionHandler::fillCleanups(Function *F, const std::function &Destroy) { IRBuilderBase::InsertPointGuard Guard(Builder); SaveAndRestore StateGuard(States[F]); auto &Points = CleanupPoints[F]; @@ -107,7 +107,7 @@ void ExceptionHandler::fillCleanups(Function *F, const std::function &De auto *Continuation = BB->splitBasicBlock(Point.Before->getIterator(), "after_destruction"); BB->getTerminator()->eraseFromParent(); Builder.SetInsertPoint(BB); - Destroy(); + Destroy(Point.TemporariesOnly); Builder.CreateBr(Continuation); } Points.clear(); @@ -133,7 +133,8 @@ BasicBlock *ExceptionHandler::emitUnwindDestination() { for (unsigned I = TargetDepth; I < State.CatchDepth; ++I) Builder.CreateCall(EndCatch); if (Handler) { Builder.CreateStore(Builder.CreateExtractValue(LP, 0), Handler->Exception); - Builder.CreateBr(Handler->Target); + auto *Branch = Builder.CreateBr(Handler->Target); + CleanupPoints[F].push_back({Branch, States[F], true, true}); } else { recordExit(Builder.CreateResume(LP), true); } diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index a0d0e32..37df8ff 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -91,7 +91,7 @@ void FunctionGen::createArgumentAllocas(Function *F, const std::vector TmpB(&F->getEntryBlock(), F->getEntryBlock().begin()); AllocaInst *Alloca = TmpB.CreateAlloca(ArgType, nullptr, P.Name); - // Shallow BYVAL copy. The common lifetime hook is the future copy-constructor point. + // The caller constructs class arguments; this slot takes ownership of the value. Builder.CreateStore(ArgVal, Alloca); const TypeInfo *Info = Types.resolve(P.TypeName); if (Info && Info->IsClass) { @@ -123,6 +123,15 @@ const FuncSig *FunctionGen::getSignatureByLink(const std::string &Name) const { Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { auto Registered = PrototypeLinks.find(Proto); if (Registered != PrototypeLinks.end()) return Module.getFunction(Registered->second); + if (!Proto->getOwnerClass().empty() && sameIdentifier(Proto->getName(), "NEW") && + Proto->getParams().size() == 1) { + const auto &P = Proto->getParams()[0]; + if (!P.IsArray && sameIdentifier(P.TypeName, Proto->getOwnerClass()) && + P.Mode != PassMode::ByRef) { + reportError("A copy constructor must take BYREF to its own class"); + return nullptr; + } + } if (int Op = Proto->getOperator()) { size_t Count = Proto->getParams().size() + !Proto->getOwnerClass().empty(); bool ValidCount = Op == '-' ? Count == 1 || Count == 2 diff --git a/lib/CodeGen/OperatorGen.cc b/lib/CodeGen/OperatorGen.cc index d340ed7..b449ba1 100644 --- a/lib/CodeGen/OperatorGen.cc +++ b/lib/CodeGen/OperatorGen.cc @@ -45,6 +45,7 @@ Value *CodeGen::materializeOperatorReceiver(Value *Val, const TypeInfo *Type) { size_t Object = registerStackObject(Storage, Type->Name, DiagnosticLine, false, true); Builder->CreateStore(Val, Storage); activateStackObject(Object); + if (Object != size_t(-1)) StackObjects[Object].Temporary = true; return Storage; } @@ -76,8 +77,25 @@ Value *CodeGen::emitOperatorCall(const FuncSig &Signature, ExprAST *Left, ExprAS Value *CodeGen::emitOperatorValues(const FuncSig &Signature, Value *Left, const TypeInfo *LeftType, Value *Right) { if (!checkOperatorAccess(Signature)) return nullptr; - if (!Signature.Prototype->getOwnerClass().empty()) - Left = materializeOperatorReceiver(Left, LeftType); - if (!Left) return nullptr; - return FuncGen->emitCall(Signature, {Left, Right}); + bool Member = !Signature.Prototype->getOwnerClass().empty(); + std::vector Args; + std::vector Transfers; + auto Capture = [&](Value *Val, const TypeInfo *Type) -> Value * { + auto *Slot = CreateEntryBlockAlloca(Builder->GetInsertBlock()->getParent(), + Type->LLVMType, "case_operand"); + Builder->CreateStore(Val, Slot); + return Slot; + }; + if (Member) Args.push_back(Capture(Left, LeftType)); + else { + if (LeftType->IsClass) Left = copyClassValue(LeftType, Capture(Left, LeftType), Transfers); + if (!Left) return nullptr; + Args.push_back(Left); + } + const TypeInfo *RightType = resolveType(Signature.Params[Member ? 0 : 1].TypeName); + if (RightType->IsClass) Right = copyClassValue(RightType, Capture(Right, RightType), Transfers); + if (!Right) return nullptr; + Args.push_back(Right); + transferObjects(Transfers); + return FuncGen->emitCall(Signature, Args); } diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 99a269d..c0bc223 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -10,6 +10,12 @@ using namespace llvm; using namespace cps; void CodeGen::emitStmt(StmtAST *Stmt) { + size_t Start = StackObjects.size(); + emitStmtImpl(Stmt); + cleanupTemporaries(Start); +} + +void CodeGen::emitStmtImpl(StmtAST *Stmt) { SaveAndRestore Location(DiagnosticLine, Stmt && Stmt->getLine() ? Stmt->getLine() : DiagnosticLine); if (!Stmt) return; @@ -89,12 +95,18 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } if (auto *Call = dynamic_cast(Stmt)) { - emitMemberCall(Call->getCall()); + Value *Result = emitMemberCall(Call->getCall()); + const TypeInfo *Type = getExprTypeInfo(Call->getCall()); + if (Result && Type && Type->IsClass) materializeOperatorReceiver(Result, Type); return; } if (auto *Call = dynamic_cast(Stmt)) { - emitCall(Call->getCallee(), Call->getArgs(), Call->getLine()); + Value *Result = emitCall(Call->getCallee(), Call->getArgs(), Call->getLine()); + const FuncSig *Sig = FuncGen->hasFunction(Call->getCallee()) + ? resolveCall(Call->getCallee(), Call->getArgs()) : nullptr; + const TypeInfo *Type = Sig ? resolveType(Sig->ReturnTypeName) : nullptr; + if (Result && Type && Type->IsClass) materializeOperatorReceiver(Result, Type); return; } @@ -117,9 +129,12 @@ void CodeGen::emitStmt(StmtAST *Stmt) { } Value *RetVal = nullptr; if (Ret->getRetVal()) { - RetVal = emitCoercedExpr(Ret->getRetVal(), resolveType(Sig->ReturnTypeName)); + std::vector Transfers; + RetVal = emitOwnedValue(Ret->getRetVal(), resolveType(Sig->ReturnTypeName), Transfers); + transferObjects(Transfers); if (!RetVal) return; } + cleanupTemporaries(); FuncGen->emitReturn(Ret, RetVal); return; } diff --git a/lib/Parser/Parser.cc b/lib/Parser/Parser.cc index afe0e80..48f4df4 100644 --- a/lib/Parser/Parser.cc +++ b/lib/Parser/Parser.cc @@ -299,6 +299,12 @@ std::unique_ptr Parser::ParseAssignStmt() { getNextToken(); auto Args = ParseExprList(')', true); if (!Args) return nullptr; + if (CurTok == '.') { + auto Result = ParseResultMethods(std::make_unique(Name, std::move(*Args), Line), Line); + if (!Result) return nullptr; + return std::make_unique( + std::unique_ptr(static_cast(Result.release()))); + } return std::make_unique(Name, std::move(*Args), Line); } diff --git a/lib/Parser/ParserExpression.cc b/lib/Parser/ParserExpression.cc index 6cde5bf..7558c86 100644 --- a/lib/Parser/ParserExpression.cc +++ b/lib/Parser/ParserExpression.cc @@ -56,12 +56,12 @@ std::unique_ptr Parser::ParseIdentifierExpr() { getNextToken(); auto Args = ParseExprList(')', true); if (!Args) return nullptr; - if (CurTok == '[' || CurTok == '.' || CurTok == '^') { + if (CurTok == '[' || CurTok == '^') { cps::sourceDiagnostic(Lex.getLine(), "error: record/array/pointer access on a function result is not supported at line %d\n", Lex.getLine()); return nullptr; } - return std::make_unique(IdName, std::move(*Args), Line); + return ParseResultMethods(std::make_unique(IdName, std::move(*Args), Line), Line); } std::vector Accesses; @@ -95,12 +95,13 @@ std::unique_ptr Parser::ParseMemberCall( if (!Parsed) return nullptr; Args = std::move(*Parsed); } - if (CurTok == '[' || CurTok == '.' || CurTok == '^') { + if (CurTok == '[' || CurTok == '^') { cps::sourceDiagnostic(Lex.getLine(), "error: record/array/pointer access on a function result is not supported at line %d\n", Lex.getLine()); return nullptr; } - return std::make_unique(std::move(Receiver), Method, std::move(Args), Line); + auto Result = ParseResultMethods(std::make_unique(std::move(Receiver), Method, std::move(Args), Line), Line); + return std::unique_ptr(static_cast(Result.release())); } // Postfix accessor chain after an identifier: '[' exprs ']' | '.' IDENT | '^' @@ -173,11 +174,38 @@ std::unique_ptr Parser::ParseParenExpr() { return V; } +std::unique_ptr Parser::ParseResultMethods(std::unique_ptr Expr, int Line) { + while (CurTok == '.' && !atCaseBoundary()) { + getNextToken(); + std::string Name; + if (CurTok == tok_operator) { + int Op; + Name = ParseOperatorName(Op); + if (Name.empty()) return nullptr; + } else if (CurTok == tok_identifier || CurTok == tok_delete) { + Name = CurTok == tok_delete ? "DELETE" : Lex.IdentifierStr; + getNextToken(); + } else { + sourceDiagnostic(Lex.getLine(), "error: Expected method name at line %d\n", Lex.getLine()); + return nullptr; + } + if (CurTok != '(') { + sourceDiagnostic(Lex.getLine(), "error: Expected '(' after method name at line %d\n", Lex.getLine()); + return nullptr; + } + getNextToken(); + auto Args = ParseExprList(')', true); + if (!Args) return nullptr; + Expr = std::make_unique(std::move(Expr), Name, std::move(*Args), Line); + } + return Expr; +} + std::unique_ptr Parser::ParsePrimary() { int Line = Lex.getLine(); auto Expr = ParsePrimaryImpl(); if (Expr && !Expr->getLine()) Expr->setLine(Line); - return Expr; + return Expr ? ParseResultMethods(std::move(Expr), Line) : nullptr; } std::unique_ptr Parser::ParsePrimaryImpl() { diff --git a/lib/Parser/ParserFunction.cc b/lib/Parser/ParserFunction.cc index dfb689a..65e0b85 100644 --- a/lib/Parser/ParserFunction.cc +++ b/lib/Parser/ParserFunction.cc @@ -374,6 +374,12 @@ std::unique_ptr Parser::ParseCallStmt() { Args = std::move(*Parsed); } + if (CurTok == '.') { + auto Result = ParseResultMethods(std::make_unique(Callee, std::move(Args), Line), Line); + if (!Result) return nullptr; + return std::make_unique( + std::unique_ptr(static_cast(Result.release()))); + } return std::make_unique(Callee, std::move(Args), Line); } -- cgit v1.3.1-4-g156e