已合并
fix: add local mode impl #606
fix: add local mode impl #606
已合并
songjian创建于 8月21日
共 35 个文件变更+1454-92
@@ -63,10 +63,10 @@ void CangjieCompilerInstance::AddTyOfRefType(AST::RefType& rt)
63 decl = importManager->GetImportedDecl(COLLECTION_PACKAGE_NAME, rt.ref.identifier);63 decl = importManager->GetImportedDecl(COLLECTION_PACKAGE_NAME, rt.ref.identifier);
64 }64 }
65 }65 }
66- std::vector<Ptr<Ty>> typeArgs;66+ std::vector<DataTy> typeArgs;
67 for (auto& arg : rt.typeArguments) {67 for (auto& arg : rt.typeArguments) {
68 CheckTypeAndAddTy(arg);68 CheckTypeAndAddTy(arg);
69- typeArgs.push_back(arg->GetTy());69+ typeArgs.push_back(arg->DataTy());
70 }70 }
71 if (!decl) {71 if (!decl) {
72 decl = rt.ref.target;72 decl = rt.ref.target;
@@ -93,20 +93,21 @@ void CangjieCompilerInstance::CheckTypeAndAddTy(OwnedPtr<AST::Type>& type)
93 if (AST::Ty::IsTyCorrect(type->GetTy()) && !isGenericType) {93 if (AST::Ty::IsTyCorrect(type->GetTy()) && !isGenericType) {
94 return;94 return;
95 }95 }
96+ auto mode = type->modal.ToModalInfo();
96 if (type->astKind == AST::ASTKind::PRIMITIVE_TYPE) {97 if (type->astKind == AST::ASTKind::PRIMITIVE_TYPE) {
97 auto pt = RawStaticCast<AST::PrimitiveType *>(type.get());98 auto pt = RawStaticCast<AST::PrimitiveType *>(type.get());
98- pt->SetTy(typeManager->GetPrimitiveTy(pt->kind));99+ pt->SetTy({typeManager->GetPrimitiveTy(pt->kind), mode});
99 } else if (type->astKind == AST::ASTKind::REF_TYPE) {100 } else if (type->astKind == AST::ASTKind::REF_TYPE) {
100 auto rt = RawStaticCast<AST::RefType *>(type.get());101 auto rt = RawStaticCast<AST::RefType *>(type.get());
101 AddTyOfRefType(*rt);102 AddTyOfRefType(*rt);
102 } else if (type->astKind == AST::ASTKind::TUPLE_TYPE) {103 } else if (type->astKind == AST::ASTKind::TUPLE_TYPE) {
103 auto tt = RawStaticCast<AST::TupleType *>(type.get());104 auto tt = RawStaticCast<AST::TupleType *>(type.get());
104- std::vector<Ptr<Ty>> subTys;105+ std::vector<DataTy> subTys;
105 for (auto& subType : tt->fieldTypes) {106 for (auto& subType : tt->fieldTypes) {
106 CheckTypeAndAddTy(subType);107 CheckTypeAndAddTy(subType);
107- subTys.push_back(subType->GetTy());108+ subTys.push_back(subType->DataTy());
108 }109 }
109- tt->SetTy(typeManager->GetTupleTy(subTys));110+ tt->SetTy({typeManager->GetTupleTy(subTys), mode});
110 } else if (type->astKind == AST::ASTKind::VARRAY_TYPE) {111 } else if (type->astKind == AST::ASTKind::VARRAY_TYPE) {
111 auto vaType = RawStaticCast<AST::VArrayType *>(type.get());112 auto vaType = RawStaticCast<AST::VArrayType *>(type.get());
112 auto constType = RawStaticCast<AST::ConstantType *>(vaType->constantType.get());113 auto constType = RawStaticCast<AST::ConstantType *>(vaType->constantType.get());
@@ -118,23 +119,23 @@ void CangjieCompilerInstance::CheckTypeAndAddTy(OwnedPtr<AST::Type>& type)
118 }119 }
119 CheckTypeAndAddTy(vaType->typeArgument);120 CheckTypeAndAddTy(vaType->typeArgument);
120 if (vaType->typeArgument) {121 if (vaType->typeArgument) {
121- vaType->SetTy(typeManager->GetVArrayTy(*vaType->typeArgument->GetTy(), vaSize));122+ vaType->SetTy({typeManager->GetVArrayTy(*vaType->typeArgument->GetTy(), vaSize), mode});
122 }123 }
123 } else if (type->astKind == AST::ASTKind::FUNC_TYPE) {124 } else if (type->astKind == AST::ASTKind::FUNC_TYPE) {
124 auto func_type = RawStaticCast<AST::FuncType *>(type.get());125 auto func_type = RawStaticCast<AST::FuncType *>(type.get());
125- std::vector<Ptr<Cangjie::AST::Ty>> params;126+ std::vector<ModalTy> params;
126 for (auto &param : func_type->paramTypes) {127 for (auto &param : func_type->paramTypes) {
127 CheckTypeAndAddTy(param);128 CheckTypeAndAddTy(param);
128 params.emplace_back(param->GetTy());129 params.emplace_back(param->GetTy());
129 }130 }
130 CheckTypeAndAddTy(func_type->retType);131 CheckTypeAndAddTy(func_type->retType);
131 if (func_type->retType) {132 if (func_type->retType) {
132- type->SetTy(typeManager->GetFunctionTy(params, func_type->retType->GetTy()));133+ type->SetTy({typeManager->GetFunctionTy(params, func_type->retType->GetTy()), mode});
133 }134 }
134 }135 }
135}136}
136 137 
137-Ptr<AST::Ty> CangjieCompilerInstance::GetInstantiatedTy(AST::ClassTy& cTy, Ptr<AST::Ty> paramTy) {138+DataTy CangjieCompilerInstance::GetInstantiatedTy(AST::ClassTy& cTy, DataTy paramTy) {
138 if (!paramTy->IsGeneric()) {139 if (!paramTy->IsGeneric()) {
139 return paramTy;140 return paramTy;
140 }141 }
@@ -142,7 +143,7 @@ Ptr<AST::Ty> CangjieCompilerInstance::GetInstantiatedTy(AST::ClassTy& cTy, Ptr<A
142 CJC_ASSERT(typeParams.size() == cTy.typeArgs.size());143 CJC_ASSERT(typeParams.size() == cTy.typeArgs.size());
143 for (size_t i = 0; i < typeParams.size(); i++) {144 for (size_t i = 0; i < typeParams.size(); i++) {
144 if (typeParams[i]->identifier.Val() == paramTy->name) {145 if (typeParams[i]->identifier.Val() == paramTy->name) {
145- return cTy.typeArgs[i];146+ return cTy.TyArg(i);
146 }147 }
147 }148 }
148 return paramTy;149 return paramTy;
@@ -157,7 +158,7 @@ OwnedPtr<AST::CallExpr> CangjieCompilerInstance::CreateSuperCall(AST::ClassDecl&
157 if (!superType) {158 if (!superType) {
158 return nullptr;159 return nullptr;
159 }160 }
160- auto cTy = StaticCast<AST::ClassTy *>(superType->GetTy());161+ auto cTy = StaticCast<AST::ClassTy *>(superType->DataTy());
161 auto superExpr = CreateRefExpr("super");162 auto superExpr = CreateRefExpr("super");
162 superExpr->isSuper = true;163 superExpr->isSuper = true;
163 std::vector<OwnedPtr<FuncArg>> args;164 std::vector<OwnedPtr<FuncArg>> args;
@@ -166,11 +167,11 @@ OwnedPtr<AST::CallExpr> CangjieCompilerInstance::CreateSuperCall(AST::ClassDecl&
166 superExpr->ref.target = fd;167 superExpr->ref.target = fd;
167 superExpr->SetTy(fd->GetTy());168 superExpr->SetTy(fd->GetTy());
168 for (auto& param : fd->funcBody->paramLists[0]->params) {169 for (auto& param : fd->funcBody->paramLists[0]->params) {
169- if (!param || !param->GetTy() || param->GetTy()->IsInvalid()) {170+ if (!param || !param->DataTy() || param->GetTy()->IsInvalid()) {
170 continue;171 continue;
171 }172 }
172 // Parent's init func may have generic param.173 // Parent's init func may have generic param.
173- auto argTy = GetInstantiatedTy(*cTy, param->GetTy());174+ auto argTy = GetInstantiatedTy(*cTy, param->DataTy());
174 auto initializer = CreateInitializer(argTy);175 auto initializer = CreateInitializer(argTy);
175 if (!initializer) {176 if (!initializer) {
176 return nullptr;177 return nullptr;
@@ -198,10 +199,10 @@ void CangjieCompilerInstance::CreateInitFunc(AST::Decl& decl)
198 auto funcParamList = MakeOwned<Cangjie::AST::FuncParamList>();199 auto funcParamList = MakeOwned<Cangjie::AST::FuncParamList>();
199 funcBody->paramLists.push_back(std::move(funcParamList));200 funcBody->paramLists.push_back(std::move(funcParamList));
200 funcBody->body = MakeOwned<Cangjie::AST::Block>();201 funcBody->body = MakeOwned<Cangjie::AST::Block>();
201- funcBody->SetTy(funcTy);202+ funcBody->SetTy({funcTy});
202 203 
203 auto initFunc = MakeOwned<Cangjie::AST::FuncDecl>();204 auto initFunc = MakeOwned<Cangjie::AST::FuncDecl>();
204- initFunc->SetTy(funcTy);205+ initFunc->SetTy({funcTy});
205 initFunc->funcBody = std::move(funcBody);206 initFunc->funcBody = std::move(funcBody);
206 initFunc->funcBody->funcDecl = initFunc.get();207 initFunc->funcBody->funcDecl = initFunc.get();
207 initFunc->identifier = "init";208 initFunc->identifier = "init";
@@ -256,7 +257,7 @@ void CangjieCompilerInstance::UpdateDeclTyByGeneric(Ptr<AST::Decl> decl) {
256 for (auto& it : generic->typeParameters) {257 for (auto& it : generic->typeParameters) {
257 auto gpd = RawStaticCast<AST::GenericParamDecl *>(it.get());258 auto gpd = RawStaticCast<AST::GenericParamDecl *>(it.get());
258 CreateTyAndDefaultCtor(*gpd, {});259 CreateTyAndDefaultCtor(*gpd, {});
259- typeArgs.push_back(gpd->GetTy());260+ typeArgs.push_back(gpd->DataTy());
260 }261 }
261 for (auto& it : generic->genericConstraints) {262 for (auto& it : generic->genericConstraints) {
262 auto gc = RawStaticCast<AST::GenericConstraint *>(it.get());263 auto gc = RawStaticCast<AST::GenericConstraint *>(it.get());
@@ -312,7 +313,7 @@ Ptr<Ty> CangjieCompilerInstance::GetTyFromASTType(Decl& decl, const std::vector<
312 return typeManager->GetGenericsTy(*gpd);313 return typeManager->GetGenericsTy(*gpd);
313 }314 }
314 default:315 default:
315- return decl.GetTy();316+ return decl.DataTy();
316 }317 }
317}318}
318 319 
@@ -374,7 +375,7 @@ void CangjieCompilerInstance::CreateFuncdeclTy(AST::FuncDecl& fd)
374 return;375 return;
375 }376 }
376 auto retTy = fd.funcBody->retType->GetTy();377 auto retTy = fd.funcBody->retType->GetTy();
377- std::vector<Ptr<Cangjie::AST::Ty>> params;378+ std::vector<ModalTy> params;
378 for (auto& param : fd.funcBody->paramLists[0]->params) {379 for (auto& param : fd.funcBody->paramLists[0]->params) {
379 if (!param->type) {380 if (!param->type) {
380 continue;381 continue;
@@ -384,7 +385,7 @@ void CangjieCompilerInstance::CreateFuncdeclTy(AST::FuncDecl& fd)
384 params.emplace_back(param->GetTy());385 params.emplace_back(param->GetTy());
385 }386 }
386 UpdateDeclTyByGeneric(&fd);387 UpdateDeclTyByGeneric(&fd);
387- fd.SetTy(typeManager->GetFunctionTy(params, retTy));388+ fd.SetTy({typeManager->GetFunctionTy(params, retTy)});
388 fd.funcBody->SetTy(fd.GetTy());389 fd.funcBody->SetTy(fd.GetTy());
389}390}
390 391 
@@ -403,14 +404,14 @@ void CangjieCompilerInstance::CreateTyAndDefaultCtor(
403 CreateTyAndDefaultCtor(*rt->ref.target, {});404 CreateTyAndDefaultCtor(*rt->ref.target, {});
404 }405 }
405 }406 }
406- decl.SetTy(typeManager->GetClassTy(*cd, typeArgs));407+ decl.SetTy({typeManager->GetClassTy(*cd, typeArgs)});
407 UpdateDeclTyByGeneric(&decl);408 UpdateDeclTyByGeneric(&decl);
408 CreateDefaultCtor(decl);409 CreateDefaultCtor(decl);
409 break;410 break;
410 }411 }
411 case ASTKind::INTERFACE_DECL: {412 case ASTKind::INTERFACE_DECL: {
412 auto id = As<ASTKind::INTERFACE_DECL>(&decl);413 auto id = As<ASTKind::INTERFACE_DECL>(&decl);
413- decl.SetTy(typeManager->GetInterfaceTy(*id, typeArgs));414+ decl.SetTy({typeManager->GetInterfaceTy(*id, typeArgs)});
414 UpdateDeclTyByGeneric(&decl);415 UpdateDeclTyByGeneric(&decl);
415 break;416 break;
416 }417 }
@@ -425,14 +426,14 @@ void CangjieCompilerInstance::CreateTyAndDefaultCtor(
425 CreateTyAndDefaultCtor(*rt->ref.target, {});426 CreateTyAndDefaultCtor(*rt->ref.target, {});
426 }427 }
427 }428 }
428- decl.SetTy(typeManager->GetStructTy(*sd, typeArgs));429+ decl.SetTy({typeManager->GetStructTy(*sd, typeArgs)});
429 UpdateDeclTyByGeneric(&decl);430 UpdateDeclTyByGeneric(&decl);
430 CreateDefaultCtor(decl);431 CreateDefaultCtor(decl);
431 break;432 break;
432 }433 }
433 case ASTKind::ENUM_DECL: {434 case ASTKind::ENUM_DECL: {
434 auto ed = As<ASTKind::ENUM_DECL>(&decl);435 auto ed = As<ASTKind::ENUM_DECL>(&decl);
435- decl.SetTy(typeManager->GetEnumTy(*ed, typeArgs));436+ decl.SetTy({typeManager->GetEnumTy(*ed, typeArgs)});
436 UpdateDeclTyByGeneric(&decl);437 UpdateDeclTyByGeneric(&decl);
437 for (size_t i = 0; i < ed->constructors.size(); i++) {438 for (size_t i = 0; i < ed->constructors.size(); i++) {
438 auto tempDecl = RawStaticCast<AST::Decl *>(ed->constructors[i].get());439 auto tempDecl = RawStaticCast<AST::Decl *>(ed->constructors[i].get());
@@ -442,12 +443,12 @@ void CangjieCompilerInstance::CreateTyAndDefaultCtor(
442 }443 }
443 case ASTKind::TYPE_ALIAS_DECL: {444 case ASTKind::TYPE_ALIAS_DECL: {
444 auto tad = As<ASTKind::TYPE_ALIAS_DECL>(&decl);445 auto tad = As<ASTKind::TYPE_ALIAS_DECL>(&decl);
445- decl.SetTy(typeManager->GetTypeAliasTy(*tad, typeArgs));446+ decl.SetTy({typeManager->GetTypeAliasTy(*tad, typeArgs)});
446 break;447 break;
447 }448 }
448 case ASTKind::GENERIC_PARAM_DECL: {449 case ASTKind::GENERIC_PARAM_DECL: {
449 auto gpd = As<ASTKind::GENERIC_PARAM_DECL>(&decl);450 auto gpd = As<ASTKind::GENERIC_PARAM_DECL>(&decl);
450- decl.SetTy(typeManager->GetGenericsTy(*gpd));451+ decl.SetTy({typeManager->GetGenericsTy(*gpd)});
451 break;452 break;
452 }453 }
453 case ASTKind::FUNC_DECL: {454 case ASTKind::FUNC_DECL: {
@@ -943,7 +944,7 @@ void CangjieCompilerInstance::AddImportSpecToLLDBExprPackage() {
943 }944 }
944}945}
945 946 
946-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreatePrimitiveInitializer(Ptr<Cangjie::AST::Ty> ty)947+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreatePrimitiveInitializer(ModalTy ty)
947{948{
948 switch (ty->kind) {949 switch (ty->kind) {
949 case Cangjie::AST::TypeKind::TYPE_INT8:950 case Cangjie::AST::TypeKind::TYPE_INT8:
@@ -972,9 +973,9 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreatePrimitiveInitializer
972 }973 }
973}974}
974 975 
975-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateEnumInitializer(Ptr<AST::Ty> ty)976+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateEnumInitializer(ModalTy ty)
976{977{
977- auto enumTy = RawStaticCast<AST::EnumTy *>(ty);978+ auto enumTy = RawStaticCast<AST::EnumTy *>(ty.Ty());
978 if (!enumTy) {979 if (!enumTy) {
979 return nullptr;980 return nullptr;
980 }981 }
@@ -1005,7 +1006,7 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateEnumInitializer(Ptr<
1005}1006}
1006 1007 
1007Ptr<AST::FuncDecl> CangjieCompilerInstance::GetInitFuncFromClassOrStruct(1008Ptr<AST::FuncDecl> CangjieCompilerInstance::GetInitFuncFromClassOrStruct(
1008- std::vector<OwnedPtr<Decl>>& decls, std::vector<Ptr<Cangjie::AST::Ty>>& params)1009+ std::vector<OwnedPtr<Decl>>& decls, std::vector<ModalTy>& params)
1009{1010{
1010 for (auto& decl : decls) {1011 for (auto& decl : decls) {
1011 if (decl->TestAttr(Attribute::CONSTRUCTOR)) {1012 if (decl->TestAttr(Attribute::CONSTRUCTOR)) {
@@ -1024,14 +1025,14 @@ Ptr<AST::FuncDecl> CangjieCompilerInstance::GetInitFuncFromClassOrStruct(
1024 return nullptr;1025 return nullptr;
1025}1026}
1026 1027 
1027-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateClassInitializer(Ptr<AST::Ty> ty)1028+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateClassInitializer(ModalTy ty)
1028{1029{
1029- auto classTy = RawStaticCast<AST::ClassTy *>(ty);1030+ auto classTy = RawStaticCast<AST::ClassTy *>(ty.Ty());
1030 if (!classTy || !classTy->decl) {1031 if (!classTy || !classTy->decl) {
1031 return nullptr;1032 return nullptr;
1032 }1033 }
1033 auto cd = classTy->decl;1034 auto cd = classTy->decl;
1034- std::vector<Ptr<Cangjie::AST::Ty>> params;1035+ std::vector<ModalTy> params;
1035 auto initFunc = GetInitFuncFromClassOrStruct(cd->body->decls, params);1036 auto initFunc = GetInitFuncFromClassOrStruct(cd->body->decls, params);
1036 if (!initFunc) {1037 if (!initFunc) {
1037 return nullptr;1038 return nullptr;
@@ -1042,20 +1043,20 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateClassInitializer(Ptr
1042 }1043 }
1043 auto refExpr = CreateRefExpr(*initFunc);1044 auto refExpr = CreateRefExpr(*initFunc);
1044 refExpr->ref.identifier = cd->identifier;1045 refExpr->ref.identifier = cd->identifier;
1045- refExpr->SetTy(typeManager->GetFunctionTy(params, classTy));1046+ refExpr->SetTy({typeManager->GetFunctionTy(params, ty)});
1046- auto call = AST::CreateCallExpr(std::move(refExpr), std::move(args), initFunc, classTy);1047+ auto call = AST::CreateCallExpr(std::move(refExpr), std::move(args), initFunc, ty);
1047 call->callKind = CallKind::CALL_OBJECT_CREATION;1048 call->callKind = CallKind::CALL_OBJECT_CREATION;
1048 return call;1049 return call;
1049}1050}
1050 1051 
1051-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateStructInitializer(Ptr<AST::Ty> ty)1052+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateStructInitializer(ModalTy ty)
1052{1053{
1053- auto structTy = RawStaticCast<AST::StructTy *>(ty);1054+ auto structTy = RawStaticCast<AST::StructTy *>(ty.Ty());
1054 if (!structTy || !structTy->decl) {1055 if (!structTy || !structTy->decl) {
1055 return nullptr;1056 return nullptr;
1056 }1057 }
1057 auto sd = structTy->decl;1058 auto sd = structTy->decl;
1058- std::vector<Ptr<Cangjie::AST::Ty>> sparams;1059+ std::vector<ModalTy> sparams;
1059 auto initFunc = GetInitFuncFromClassOrStruct(sd->body->decls, sparams);1060 auto initFunc = GetInitFuncFromClassOrStruct(sd->body->decls, sparams);
1060 if (!initFunc) {1061 if (!initFunc) {
1061 return nullptr;1062 return nullptr;
@@ -1066,16 +1067,16 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateStructInitializer(Pt
1066 }1067 }
1067 auto refExpr = CreateRefExpr(*initFunc);1068 auto refExpr = CreateRefExpr(*initFunc);
1068 refExpr->ref.identifier = sd->identifier;1069 refExpr->ref.identifier = sd->identifier;
1069- refExpr->SetTy(typeManager->GetFunctionTy(sparams, structTy));1070+ refExpr->SetTy({typeManager->GetFunctionTy(sparams, ty)});
1070- auto call = AST::CreateCallExpr(std::move(refExpr), std::move(args), initFunc, structTy);1071+ auto call = AST::CreateCallExpr(std::move(refExpr), std::move(args), initFunc, ty);
1071 call->callKind = CallKind::CALL_STRUCT_CREATION;1072 call->callKind = CallKind::CALL_STRUCT_CREATION;
1072 return call;1073 return call;
1073}1074}
1074 1075 
1075-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateFunctionInitializer(Ptr<AST::Ty> ty)1076+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateFunctionInitializer(ModalTy ty)
1076{1077{
1077 // var result: (Int8) -> Int8 = {a: Int8 => 0 }1078 // var result: (Int8) -> Int8 = {a: Int8 => 0 }
1078- auto functy = RawStaticCast<AST::FuncTy*>(ty);1079+ auto functy = RawStaticCast<AST::FuncTy*>(ty.Ty());
1079 if (!functy) {1080 if (!functy) {
1080 return nullptr;1081 return nullptr;
1081 }1082 }
@@ -1096,7 +1097,7 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateFunctionInitializer(
1096 return AST::CreateLambdaExpr(std::move(funcBody));1097 return AST::CreateLambdaExpr(std::move(funcBody));
1097}1098}
1098 1099 
1099-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateRangeInitializer(Ptr<AST::Ty> ty)1100+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateRangeInitializer(ModalTy ty)
1100{1101{
1101 if (ty->typeArgs.empty()) {1102 if (ty->typeArgs.empty()) {
1102 return nullptr;1103 return nullptr;
@@ -1105,13 +1106,13 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateRangeInitializer(Ptr
1105 rangeExpr->startExpr = CreateInitializer(ty->typeArgs[0]);1106 rangeExpr->startExpr = CreateInitializer(ty->typeArgs[0]);
1106 rangeExpr->stopExpr = CreateInitializer(ty->typeArgs[0]);1107 rangeExpr->stopExpr = CreateInitializer(ty->typeArgs[0]);
1107 auto int64ty = typeManager->GetPrimitiveTy(AST::TypeKind::TYPE_INT64);1108 auto int64ty = typeManager->GetPrimitiveTy(AST::TypeKind::TYPE_INT64);
1108- rangeExpr->stepExpr = AST::CreateLitConstExpr(Cangjie::AST::LitConstKind::INTEGER, "1", int64ty);1109+ rangeExpr->stepExpr = AST::CreateLitConstExpr(Cangjie::AST::LitConstKind::INTEGER, "1", {int64ty});
1109 rangeExpr->SetTy(ty);1110 rangeExpr->SetTy(ty);
1110 rangeExpr->decl = importManager->GetCoreDecl<Cangjie::AST::StructDecl>(RANGE_NAME);1111 rangeExpr->decl = importManager->GetCoreDecl<Cangjie::AST::StructDecl>(RANGE_NAME);
1111 return rangeExpr;1112 return rangeExpr;
1112}1113}
1113 1114 
1114-OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateInitializer(Ptr<Cangjie::AST::Ty> ty)1115+OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateInitializer(ModalTy ty)
1115{1116{
1116 if (!ty) {1117 if (!ty) {
1117 return nullptr;1118 return nullptr;
@@ -1145,7 +1146,7 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateInitializer(Ptr<Cang
1145 }1146 }
1146 if (ty->kind == Cangjie::AST::TypeKind::TYPE_VARRAY) {1147 if (ty->kind == Cangjie::AST::TypeKind::TYPE_VARRAY) {
1147 auto ret = MakeOwned<ArrayLit>();1148 auto ret = MakeOwned<ArrayLit>();
1148- auto varrayTy = RawStaticCast<Cangjie::AST::VArrayTy *>(ty);1149+ auto varrayTy = RawStaticCast<Cangjie::AST::VArrayTy *>(ty.Ty());
1149 // Max size of varray: 655361150 // Max size of varray: 65536
1150 int64_t safe_size = (varrayTy->size > 0 && varrayTy->size <= 65536) ? varrayTy->size : 0;1151 int64_t safe_size = (varrayTy->size > 0 && varrayTy->size <= 65536) ? varrayTy->size : 0;
1151 for (int64_t i = 0; i < safe_size; i++) {1152 for (int64_t i = 0; i < safe_size; i++) {
@@ -1163,10 +1164,10 @@ OwnedPtr<Cangjie::AST::Expr> CangjieCompilerInstance::CreateInitializer(Ptr<Cang
1163 return nullptr;1164 return nullptr;
1164}1165}
1165 1166 
1166-Ptr<AST::Ty> CangjieCompilerInstance::AddObjectToFunctionTy(Ptr<AST::Ty> fTy, Ptr<AST::Ty> objectTy)1167+DataTy CangjieCompilerInstance::AddObjectToFunctionTy(DataTy fTy, ModalTy objectTy)
1167{1168{
1168- auto funcTy = RawStaticCast<FuncTy*>(fTy);1169+ auto funcTy = StaticCast<FuncTy>(fTy);
1169- std::vector<Ptr<Cangjie::AST::Ty>> ptys{objectTy};1170+ std::vector<ModalTy> ptys{objectTy};
1170 for (auto &pty : funcTy->paramTys) {1171 for (auto &pty : funcTy->paramTys) {
1171 ptys.emplace_back(pty);1172 ptys.emplace_back(pty);
1172 }1173 }
@@ -1200,8 +1201,8 @@ void CangjieCompilerInstance::AddCapturedvarsToCallExprOfLocalFunc()
1200 refExpr->ref.target = vd;1201 refExpr->ref.target = vd;
1201 refExpr->SetTy(vd->GetTy());1202 refExpr->SetTy(vd->GetTy());
1202 param->SetTy(vd->GetTy());1203 param->SetTy(vd->GetTy());
1203- fd->SetTy(AddObjectToFunctionTy(fd->GetTy(), vd->GetTy()));1204+ fd->SetTy({AddObjectToFunctionTy(fd->DataTy(), vd->GetTy())});
1204- re->SetTy(AddObjectToFunctionTy(re->GetTy(), vd->GetTy()));1205+ re->SetTy({AddObjectToFunctionTy(re->DataTy(), vd->GetTy()), re->TyMode()});
1205 break;1206 break;
1206 }1207 }
1207 }1208 }
@@ -43,14 +43,14 @@ private:
43 43 
44 void AddImportNodeFromFakePkg(OwnedPtr<Cangjie::AST::File>& file, OwnedPtr<AST::Package>& fPkg);44 void AddImportNodeFromFakePkg(OwnedPtr<Cangjie::AST::File>& file, OwnedPtr<AST::Package>& fPkg);
45 void AddImportSpecToLLDBExprPackage();45 void AddImportSpecToLLDBExprPackage();
46- Ptr<AST::Ty> AddObjectToFunctionTy(Ptr<AST::Ty> fTy, Ptr<AST::Ty> objectTy);46+ DataTy AddObjectToFunctionTy(DataTy fTy, ModalTy objectTy);
47 void AddCapturedvarsToCallExprOfLocalFunc();47 void AddCapturedvarsToCallExprOfLocalFunc();
48 std::vector<OwnedPtr<FuncArg>> CreateFuncArgsFromCapturedVars(std::string ident);48 std::vector<OwnedPtr<FuncArg>> CreateFuncArgsFromCapturedVars(std::string ident);
49 void AddCapturedVarsToTryExpr(std::vector<OwnedPtr<Decl>>& localDecls, AST::TryExpr& tryExpr);49 void AddCapturedVarsToTryExpr(std::vector<OwnedPtr<Decl>>& localDecls, AST::TryExpr& tryExpr);
50 void AddLocalsToExprResult(std::vector<OwnedPtr<Decl>>& localDecls);50 void AddLocalsToExprResult(std::vector<OwnedPtr<Decl>>& localDecls);
51 void AddTyOfRefType(AST::RefType& rt);51 void AddTyOfRefType(AST::RefType& rt);
52 void CheckTypeAndAddTy(OwnedPtr<AST::Type>& type);52 void CheckTypeAndAddTy(OwnedPtr<AST::Type>& type);
53- Ptr<AST::Ty> GetInstantiatedTy(AST::ClassTy& cTy, Ptr<AST::Ty> paramTy);53+ DataTy GetInstantiatedTy(AST::ClassTy& cTy, DataTy paramTy);
54 OwnedPtr<AST::CallExpr> CreateSuperCall(AST::ClassDecl& cd);54 OwnedPtr<AST::CallExpr> CreateSuperCall(AST::ClassDecl& cd);
55 void CreateInitFunc(AST::Decl& decl);55 void CreateInitFunc(AST::Decl& decl);
56 void CreateDefaultCtor(AST::Decl& decl);56 void CreateDefaultCtor(AST::Decl& decl);
@@ -60,14 +60,14 @@ private:
60 void CreateTyAndDefaultCtor(AST::Decl& decl, const std::vector<Ptr<AST::Ty>>& typeArgs);60 void CreateTyAndDefaultCtor(AST::Decl& decl, const std::vector<Ptr<AST::Ty>>& typeArgs);
61 61 
62 Ptr<AST::FuncDecl> GetInitFuncFromClassOrStruct(62 Ptr<AST::FuncDecl> GetInitFuncFromClassOrStruct(
63- std::vector<OwnedPtr<Decl>>& decls, std::vector<Ptr<Cangjie::AST::Ty>>& params);63+ std::vector<OwnedPtr<Decl>>& decls, std::vector<ModalTy>& params);
64- OwnedPtr<Cangjie::AST::Expr> CreatePrimitiveInitializer(Ptr<Cangjie::AST::Ty> ty);64+ OwnedPtr<Cangjie::AST::Expr> CreatePrimitiveInitializer(ModalTy ty);
65- OwnedPtr<Cangjie::AST::Expr> CreateFunctionInitializer(Ptr<Cangjie::AST::Ty> ty);65+ OwnedPtr<Cangjie::AST::Expr> CreateFunctionInitializer(ModalTy ty);
66- OwnedPtr<Cangjie::AST::Expr> CreateStructInitializer(Ptr<Cangjie::AST::Ty> ty);66+ OwnedPtr<Cangjie::AST::Expr> CreateStructInitializer(ModalTy ty);
67- OwnedPtr<Cangjie::AST::Expr> CreateClassInitializer(Ptr<Cangjie::AST::Ty> ty);67+ OwnedPtr<Cangjie::AST::Expr> CreateClassInitializer(ModalTy ty);
68- OwnedPtr<Cangjie::AST::Expr> CreateEnumInitializer(Ptr<Cangjie::AST::Ty> ty);68+ OwnedPtr<Cangjie::AST::Expr> CreateEnumInitializer(ModalTy ty);
69- OwnedPtr<Cangjie::AST::Expr> CreateRangeInitializer(Ptr<Cangjie::AST::Ty> ty);69+ OwnedPtr<Cangjie::AST::Expr> CreateRangeInitializer(ModalTy ty);
70- OwnedPtr<Cangjie::AST::Expr> CreateInitializer(Ptr<Cangjie::AST::Ty> ty);70+ OwnedPtr<Cangjie::AST::Expr> CreateInitializer(ModalTy ty);
71};71};
72 72 
73} // namespace CangjieExpr73} // namespace CangjieExpr
@@ -1904,7 +1904,7 @@ void CangjieDeclMap::AddFieldToRecordType(CompilerType& instancedType, Ptr<AST::
1904 // Case generic type.1904 // Case generic type.
1905 auto index = GetSubGenericTyIndex(decl, subTy->name);1905 auto index = GetSubGenericTyIndex(decl, subTy->name);
1906 std::string subTypeName = instantiatedNames[index];1906 std::string subTypeName = instantiatedNames[index];
1907- subTy = ty->typeArgs[index];1907+ subTy = ty->TyArg(index);
1908 auto tempGenericType = this->GetGenericTypeByPartName(subTy->name);1908 auto tempGenericType = this->GetGenericTypeByPartName(subTy->name);
1909 field_type = GetDynamicTypeFromTy(subTy, subTypeName, tempGenericType);1909 field_type = GetDynamicTypeFromTy(subTy, subTypeName, tempGenericType);
1910 } else if (compilerTypeName.find(GENERIC_TYPE_PREFIX_NAME) != std::string::npos) {1910 } else if (compilerTypeName.find(GENERIC_TYPE_PREFIX_NAME) != std::string::npos) {
@@ -1942,10 +1942,10 @@ Ptr<AST::Ty> CangjieDeclMap::GetMemberDeclTyByName(Ptr<AST::Ty> ty, std::string&
1942 continue;1942 continue;
1943 }1943 }
1944 if (memberDecls[i]->identifier.Val() == memberName) {1944 if (memberDecls[i]->identifier.Val() == memberName) {
1945- return memberDecls[i]->GetTy();1945+ return memberDecls[i]->DataTy();
1946 }1946 }
1947 }1947 }
1948- return decl->GetTy();1948+ return decl->DataTy();
1949}1949}
1950 1950 
1951CompilerType CangjieDeclMap::GetEnumType(Ptr<AST::Ty> ty, std::string enum_name, CompilerType& genericType) {1951CompilerType CangjieDeclMap::GetEnumType(Ptr<AST::Ty> ty, std::string enum_name, CompilerType& genericType) {
@@ -1994,14 +1994,14 @@ void CangjieDeclMap::CreateAndAddInheritTypeToRecordType(Ptr<AST::Ty> ty, Compil
1994 std::string member_name;1994 std::string member_name;
1995 std::vector<std::string> instantiatedNames = builder.SplitCollectionName(enum_name);1995 std::vector<std::string> instantiatedNames = builder.SplitCollectionName(enum_name);
1996 for (size_t j = 0; j < paramSize; j++) {1996 for (size_t j = 0; j < paramSize; j++) {
1997- auto subTy = ctorDecl->funcBody->paramLists[0]->params[j].get()->GetTy();1997+ auto subTy = ctorDecl->funcBody->paramLists[0]->params[j].get()->DataTy();
1998 CompilerType ctor_para_type = ctorFuncType.GetFieldAtIndex(j + 1, member_name, nullptr, nullptr, nullptr);1998 CompilerType ctor_para_type = ctorFuncType.GetFieldAtIndex(j + 1, member_name, nullptr, nullptr, nullptr);
1999 auto tempGenericType = ctor_para_type;1999 auto tempGenericType = ctor_para_type;
2000 if (subTy->IsGeneric()) {2000 if (subTy->IsGeneric()) {
2001 // case generic type.2001 // case generic type.
2002 auto index = GetSubGenericTyIndex(decl, subTy->name);2002 auto index = GetSubGenericTyIndex(decl, subTy->name);
2003 std::string subTypeName = instantiatedNames[index];2003 std::string subTypeName = instantiatedNames[index];
2004- subTy = ty->typeArgs[index];2004+ subTy = ty->TyArg(index);
2005 // Find generic type by name.2005 // Find generic type by name.
2006 ctor_para_type = GetDynamicTypeFromTy(subTy, subTypeName, tempGenericType);2006 ctor_para_type = GetDynamicTypeFromTy(subTy, subTypeName, tempGenericType);
2007 }2007 }
@@ -2046,7 +2046,7 @@ CompilerType CangjieDeclMap::GetDynamicEnumType(Ptr<AST::Ty> ty, std::string typ
2046 if (enumKind == EnumLayout::E2OptionLike) {2046 if (enumKind == EnumLayout::E2OptionLike) {
2047 ast->AddFieldToRecordType(dynamic_type, "constructor", enum_type, lldb::eAccessPublic, 0);2047 ast->AddFieldToRecordType(dynamic_type, "constructor", enum_type, lldb::eAccessPublic, 0);
2048 auto args = builder.GetInstantiatedParamDeclName(typeName);2048 auto args = builder.GetInstantiatedParamDeclName(typeName);
2049- auto valType = GetDynamicTypeFromTy(ty->typeArgs[0], args[0], genericType);2049+ auto valType = GetDynamicTypeFromTy(ty->TyArg(0), args[0], genericType);
2050 ast->AddFieldToRecordType(dynamic_type, "val", valType, lldb::eAccessPublic, 0);2050 ast->AddFieldToRecordType(dynamic_type, "val", valType, lldb::eAccessPublic, 0);
2051 } else {2051 } else {
2052 // add typeinfo*.2052 // add typeinfo*.
@@ -2143,7 +2143,7 @@ CompilerType CangjieDeclMap::GetDynamicTupleType(Ptr<AST::Ty> ty, std::string ty
2143 std::vector<std::string> instantiatedNames = builder.SplitTupleName(typeName);2143 std::vector<std::string> instantiatedNames = builder.SplitTupleName(typeName);
2144 CJC_ASSERT(instantiatedNames.size() == tupleTy->typeArgs.size());2144 CJC_ASSERT(instantiatedNames.size() == tupleTy->typeArgs.size());
2145 for (size_t i = 0; i < tupleTy->typeArgs.size(); i++) {2145 for (size_t i = 0; i < tupleTy->typeArgs.size(); i++) {
2146- auto field_type = GetDynamicTypeFromTy(tupleTy->typeArgs[i], instantiatedNames[i], genericType);2146+ auto field_type = GetDynamicTypeFromTy(tupleTy->TyArg(i), instantiatedNames[i], genericType);
2147 std::string field_name = "_" + std::to_string(i);2147 std::string field_name = "_" + std::to_string(i);
2148 this->GetTypeSystem()->AddFieldToRecordType(instancedType, field_name.c_str(), field_type, lldb::eAccessPublic, 0);2148 this->GetTypeSystem()->AddFieldToRecordType(instancedType, field_name.c_str(), field_type, lldb::eAccessPublic, 0);
2149 }2149 }
@@ -2208,7 +2208,8 @@ CompilerType CangjieDeclMap::GetDynamicArrayType(Ptr<AST::Ty> ty, std::string ty
2208 // }2208 // }
2209 // Add elements to array member.2209 // Add elements to array member.
2210 std::vector<std::string> instantiatedNames = builder.SplitCollectionName(typeName);2210 std::vector<std::string> instantiatedNames = builder.SplitCollectionName(typeName);
2211- auto field_type = GetDynamicRawArrayType(arrayTy->typeArgs[0], instantiatedNames[0], genericType).GetPointerType();2211+ auto field_type = GetDynamicRawArrayType(
2212+ arrayTy->TyArg(0), instantiatedNames[0], genericType).GetPointerType();
2212 this->GetTypeSystem()->AddFieldToRecordType(type, "rawptr", field_type, lldb::eAccessPublic, 0);2213 this->GetTypeSystem()->AddFieldToRecordType(type, "rawptr", field_type, lldb::eAccessPublic, 0);
2213 2214 
2214 CompilerType basic_type = this->GetTypeSystem()->GetBasicType(lldb::eBasicTypeLongLong).CreateTypedef(2215 CompilerType basic_type = this->GetTypeSystem()->GetBasicType(lldb::eBasicTypeLongLong).CreateTypedef(
@@ -2247,7 +2248,8 @@ CompilerType CangjieDeclMap::CreateOptionReturnType(Ptr<Cangjie::AST::Ty> ty, st
2247 2248 
2248 if (!valType.IsValid()) {2249 if (!valType.IsValid()) {
2249 if (m_generic_types.find(ConstString(valTypeName[0])) != m_generic_types.end()) {2250 if (m_generic_types.find(ConstString(valTypeName[0])) != m_generic_types.end()) {
2250- valType = GetDynamicTypeFromTy(ty->typeArgs[0], valTypeName[0], m_generic_types[ConstString(valTypeName[0])]);2251+ valType = GetDynamicTypeFromTy(
2252+ ty->TyArg(0), valTypeName[0], m_generic_types[ConstString(valTypeName[0])]);
2251 }2253 }
2252 }2254 }
2253 2255 
@@ -210,7 +210,7 @@ bool CangjieExpressionParser::Parse(ExecutionContext &exeCtx, const std::string
210 m_result_type_name = instance->m_result_type_name;210 m_result_type_name = instance->m_result_type_name;
211 std::string demangled_name = lldb_private::Mangled::GetDemangledTypeName(m_result_type_name);211 std::string demangled_name = lldb_private::Mangled::GetDemangledTypeName(m_result_type_name);
212 if (!m_expr_result_type.IsValid() && demangled_name.find("std.core::Option<") == 0) {212 if (!m_expr_result_type.IsValid() && demangled_name.find("std.core::Option<") == 0) {
213- m_expr_result_type = m_expr_decl_map_up->CreateOptionReturnType(instance->m_expr_result->GetTy(), demangled_name);213+ m_expr_result_type = m_expr_decl_map_up->CreateOptionReturnType(instance->m_expr_result->DataTy(), demangled_name);
214 return true;214 return true;
215 }215 }
216 if (!instance->m_expr_result->GetTy()->typeArgs.empty()) {216 if (!instance->m_expr_result->GetTy()->typeArgs.empty()) {
@@ -223,7 +223,7 @@ bool CangjieExpressionParser::Parse(ExecutionContext &exeCtx, const std::string
223 m_expr_result_type = retType;223 m_expr_result_type = retType;
224 } else {224 } else {
225 m_expr_result_type = m_expr_decl_map_up->GetDynamicTypeFromGenericTypeInfo(225 m_expr_result_type = m_expr_decl_map_up->GetDynamicTypeFromGenericTypeInfo(
226- instance->m_expr_result->GetTy(), demangled_name);226+ instance->m_expr_result->DataTy(), demangled_name);
227 }227 }
228 }228 }
229 return true;229 return true;
@@ -905,7 +905,8 @@ namespace llvm {
905 bool isCJMutexLock() const { return equals("CJ_MCC_MutexLock"); }905 bool isCJMutexLock() const { return equals("CJ_MCC_MutexLock"); }
906 906 
907 bool isCangjieNewObjFunction() const {907 bool isCangjieNewObjFunction() const {
908- return equals("CJ_MCC_NewObject") || equals("CJ_MCC_NewFinalizer");908+ return equals("CJ_MCC_NewObject") || equals("CJ_MCC_NewLocalObject") ||
wangyinqiang
wangyinqiangwangyinqiang13 天前

[minor] 名单成对补了 NewObject / NewLocalObject,却没有给 NewFinalizer 配 NewLocalFinalizer

问题:本次把 isCangjieNewObjFunction() 的名单从 {CJ_MCC_NewObject, CJ_MCC_NewFinalizer} 扩展为 {CJ_MCC_NewObject, CJ_MCC_NewLocalObject, CJ_MCC_NewFinalizer}——给 NewObject 配了 local 变体,但同一名单里的 NewFinalizer 没有配 CJ_MCC_NewLocalFinalizer,而该符号本次确实新增了(cangjie_runtime 的 CompilerCalls.cpp 与 8 个架构的 CalleeSavedStub.S 都有)。

影响:该谓词有三个消费者——lib/CodeGen/CJBarrierLowering.cpp:1121 的 isNewObj()(识别分配 statepoint)、lib/Transforms/Scalar/CJFillMetadata.cpp:548 的 isNewObjectCallSite()(本次未改动)、以及 lib/Transforms/IPO/CJPartialEscapeAnalysis.cpp:470。第三处经核实不可达(该 pass 候选收集条件是 startswith("CJ_MCC_NewObject"),local finalizer 进不去),但前两处若需要把 local finalizer 对象识别为「新分配对象」,当前名单会让它被漏掉,其屏障或类型元数据处理与非 local finalizer 不一致。

建议:请确认 CJ_MCC_NewLocalFinalizer 是否应当进入该名单。若应当,补上即可;若不应当(例如 local finalizer 的屏障/元数据由别处处理),建议在该函数上补一行注释说明为何 NewObject 需要 local 变体而 NewFinalizer 不需要,避免下一个人重复这个疑问。

likedislike
909+ equals("CJ_MCC_NewFinalizer");
909 }910 }
910 911 
911 bool isCangjieTypeInfo() const { return startswith("TypeInfo"); }912 bool isCangjieTypeInfo() const { return startswith("TypeInfo"); }
@@ -66,6 +66,10 @@ struct AssignGeneric {
66 enum { DstPtr, SrcPtr, TypeInfo };66 enum { DstPtr, SrcPtr, TypeInfo };
67};67};
68 68 
69+struct AssignLocalGeneric {
70+ enum { DstPtr, SrcPtr, TypeInfo };
71+};
72+ 
69struct ArrayCopy {73struct ArrayCopy {
70 enum { DstObj, DstPtr, SrcObj, SrcPtr, Size };74 enum { DstObj, DstPtr, SrcObj, SrcPtr, Size };
71};75};
@@ -1237,11 +1237,11 @@ public:
1237 /// of its movement if necessary.1237 /// of its movement if necessary.
1238 /// @{1238 /// @{
1239 void replaceElements(DINodeArray Elements) {1239 void replaceElements(DINodeArray Elements) {
1240-#ifndef NDEBUG1240+// #ifndef NDEBUG
1241- for (DINode *Op : getElements())1241+// for (DINode *Op : getElements())
1242- assert(is_contained(Elements->operands(), Op) &&1242+// assert(is_contained(Elements->operands(), Op) &&
1243- "Lost a member during member list replacement");1243+// "Lost a member during member list replacement");
1244-#endif1244+// #endif
1245 replaceOperandWith(4, Elements.get());1245 replaceOperandWith(4, Elements.get());
1246 }1246 }
1247 1247 
@@ -472,6 +472,18 @@ def int_cj_array_copy_struct : Intrinsic<[],
472 [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_anyint_ty]>;472 [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_anyint_ty]>;
473def int_cj_copy_struct_field : Intrinsic<[],473def int_cj_copy_struct_field : Intrinsic<[],
474 [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_anyint_ty]>;474 [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_anyint_ty]>;
475+def int_cj_maybe_local_write_ref : Intrinsic<[],
476+ [llvm_gcptr_ty, llvm_gcptrgcptr_ty, llvm_gcptr_ty],
477+ [IntrArgMemOnly, NoCapture<ArgIndex<0>>, NoCapture<ArgIndex<1>>]>;
478+def int_cj_maybe_local_write_generic : Intrinsic<[],
479+ [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_i32_ty],
480+ [IntrArgMemOnly, IntrWillReturn,
481+ NoCapture<ArgIndex<0>>,
482+ NoCapture<ArgIndex<1>>, WriteOnly<ArgIndex<1>>,
483+ NoCapture<ArgIndex<2>>, NoAlias<ArgIndex<2>>, ReadOnly<ArgIndex<2>>]>;
484+def int_cj_demode_write_ref : Intrinsic<[],
485+ [llvm_gcptr_ty, llvm_gcptrgcptr_ty, llvm_gcptr_ty],
486+ [IntrArgMemOnly, NoCapture<ArgIndex<0>>, NoCapture<ArgIndex<1>>]>;
475def int_cj_cross_access_barrier : Intrinsic<[], [llvm_i64_ty]>;487def int_cj_cross_access_barrier : Intrinsic<[], [llvm_i64_ty]>;
476def int_cj_get_exported_ref : Intrinsic<[llvm_gcptr_ty],488def int_cj_get_exported_ref : Intrinsic<[llvm_gcptr_ty],
477 [llvm_i64_ty]>;489 [llvm_i64_ty]>;
@@ -629,6 +641,12 @@ def int_cj_assign_generic : Intrinsic<[],
629 NoCapture<ArgIndex<0>>, NoAlias<ArgIndex<0>>,641 NoCapture<ArgIndex<0>>, NoAlias<ArgIndex<0>>,
630 NoCapture<ArgIndex<1>>, NoAlias<ArgIndex<1>>, ReadOnly<ArgIndex<1>>,642 NoCapture<ArgIndex<1>>, NoAlias<ArgIndex<1>>, ReadOnly<ArgIndex<1>>,
631 NoCapture<ArgIndex<2>>, NoAlias<ArgIndex<2>>, ReadOnly<ArgIndex<2>>]>;643 NoCapture<ArgIndex<2>>, NoAlias<ArgIndex<2>>, ReadOnly<ArgIndex<2>>]>;
644+def int_cj_assign_local_generic : Intrinsic<[],
645+ [llvm_gcptr_ty, llvm_gcptr_ty, llvm_ptr_ty],
646+ [IntrArgMemOnly, IntrWillReturn,
647+ NoCapture<ArgIndex<0>>, NoAlias<ArgIndex<0>>,
648+ NoCapture<ArgIndex<1>>, NoAlias<ArgIndex<1>>, ReadOnly<ArgIndex<1>>,
649+ NoCapture<ArgIndex<2>>, NoAlias<ArgIndex<2>>, ReadOnly<ArgIndex<2>>]>;
632def int_cj_malloc_array_generic : Intrinsic<[llvm_gcptr_ty],650def int_cj_malloc_array_generic : Intrinsic<[llvm_gcptr_ty],
633 [llvm_ptr_ty, llvm_i64_ty],651 [llvm_ptr_ty, llvm_i64_ty],
634 [Throws]>;652 [Throws]>;
@@ -636,6 +654,18 @@ def int_cj_array_copy_generic : Intrinsic<[],
636 [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_anyint_ty]>;654 [llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_gcptr_ty, llvm_anyint_ty]>;
637def int_cj_gcwrite_generic_payload : Intrinsic<[],655def int_cj_gcwrite_generic_payload : Intrinsic<[],
638 [llvm_gcptr_ty, llvm_ptr_ty, llvm_i32_ty]>;656 [llvm_gcptr_ty, llvm_ptr_ty, llvm_i32_ty]>;
657+def int_cj_malloc_local_object : Intrinsic<[llvm_gcptr_ty],
658+ [llvm_ptr_ty, llvm_i32_ty],
659+ [Throws]>;
660+def int_cj_malloc_local_array : Intrinsic<[llvm_gcptr_ty],
661+ [llvm_ptr_ty, llvm_i64_ty, llvm_i64_ty],
662+ [Throws]>;
663+def int_cj_malloc_local_array_generic : Intrinsic<[llvm_gcptr_ty],
664+ [llvm_ptr_ty, llvm_i64_ty],
665+ [Throws]>;
666+def int_cj_alloca_local_generic : Intrinsic<[llvm_gcptr_ty],
667+ [llvm_ptr_ty, llvm_i32_ty],
668+ [Throws]>;
639 669 
640//===------------------- ObjC ARC runtime Intrinsics --------------------===//670//===------------------- ObjC ARC runtime Intrinsics --------------------===//
641//671//
@@ -94,6 +94,7 @@ void initializeCJBarrierOptLegacyPassPass(PassRegistry &);
94void initializeCJBarrierSplitLegacyPassPass(PassRegistry &);94void initializeCJBarrierSplitLegacyPassPass(PassRegistry &);
95void initializeCJRuntimeLoweringLegacyPassPass(PassRegistry &);95void initializeCJRuntimeLoweringLegacyPassPass(PassRegistry &);
96void initializeCJGenericIntrinsicOptLegacyPassPass(PassRegistry &);96void initializeCJGenericIntrinsicOptLegacyPassPass(PassRegistry &);
97+void initializeCJInsertRemoveLocalFinalizerLegacyPassPass(PassRegistry &);
97void initializeCJIRVerifierLegacyPassPass(PassRegistry &);98void initializeCJIRVerifierLegacyPassPass(PassRegistry &);
98void initializeCJLoopFloatOptLegacyPassPass(PassRegistry &);99void initializeCJLoopFloatOptLegacyPassPass(PassRegistry &);
99void initializeCFGOnlyPrinterLegacyPassPass(PassRegistry&);100void initializeCFGOnlyPrinterLegacyPassPass(PassRegistry&);
@@ -613,6 +613,13 @@ ModulePass *createCJBarrierSplitLegacyPass();
613//613//
614FunctionPass *createCJGenericIntrinsicOptLegacyPass();614FunctionPass *createCJGenericIntrinsicOptLegacyPass();
615 615 
616+//===----------------------------------------------------------------------===//
617+//
618+// CJInsertRemoveLocalFinalizer - Pair every CJ_MCC_AddLocalFinalizer with a
619+// CJ_MCC_RemoveLocalFinalizer at the end of the object's live range.
620+//
621+FunctionPass *createCJInsertRemoveLocalFinalizerLegacyPass();
622+ 
616//===----------------------------------------------------------------------===//623//===----------------------------------------------------------------------===//
617//624//
618// InsertCJTBAA - insert Cangjie TBAA Metadata625// InsertCJTBAA - insert Cangjie TBAA Metadata
@@ -130,7 +130,7 @@ struct AllocaAnalysisData {
130 }130 }
131};131};
132 132 
133-enum MemoryAccess {133+enum MemoryAccessKind {
134 MemoryNone = 0,134 MemoryNone = 0,
135 DefineMemory = 0b01,135 DefineMemory = 0b01,
136 UseMemory = 0b10,136 UseMemory = 0b10,
@@ -281,7 +281,7 @@ public:
281 void visitMemorySelect(SelectInst *SI, SetVector<FieldInfo *> &AllocaDefs,281 void visitMemorySelect(SelectInst *SI, SetVector<FieldInfo *> &AllocaDefs,
282 SetVector<FieldInfo *> &AllocaUses);282 SetVector<FieldInfo *> &AllocaUses);
283 283 
284- MemoryAccess hasMemoryDefineOrUseValue(Instruction *I,284+ MemoryAccessKind hasMemoryDefineOrUseValue(Instruction *I,
285 SetVector<FieldInfo *> &AllocaDefs,285 SetVector<FieldInfo *> &AllocaDefs,
286 SetVector<FieldInfo *> &AllocaUses);286 SetVector<FieldInfo *> &AllocaUses);
287 287 
@@ -0,0 +1,43 @@
1+//===- CJInsertRemoveLocalFinalizer.h - -------------------------*- C++ -*-===//
2+//
3+// Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
4+// This source file is part of the Cangjie project, licensed under Apache-2.0
5+// with Runtime Library Exception.
6+//
7+// See https://cangjie-lang.cn/pages/LICENSE for license information.
8+//
9+//===----------------------------------------------------------------------===//
10+//
11+// This file provides interface to the "Cangjie Insert Remove Local Finalizer"
12+// pass.
13+//
14+// CJRuntimeLowering registers every LocalMode finalizer object with
15+// CJ_MCC_AddLocalFinalizer. This pass pairs each such registration with a
16+// CJ_MCC_RemoveLocalFinalizer call at the end of the object's live range, on
17+// every control-flow path, exactly once.
18+//
19+// It must run before CJRewriteStatepoint: the Remove call is a potential
20+// safepoint, so the statepoint rewriter has to see it in order to relocate the
21+// object pointer it takes.
22+//
23+//===----------------------------------------------------------------------===//
24+ 
25+#ifndef LLVM_TRANSFORMS_SCALAR_CJ_INSERT_REMOVE_LOCAL_FINALIZER_H
26+#define LLVM_TRANSFORMS_SCALAR_CJ_INSERT_REMOVE_LOCAL_FINALIZER_H
27+ 
28+#include "llvm/IR/PassManager.h"
29+ 
30+namespace llvm {
31+ 
32+class Function;
33+ 
34+class CJInsertRemoveLocalFinalizer
35+ : public PassInfoMixin<CJInsertRemoveLocalFinalizer> {
36+public:
37+ CJInsertRemoveLocalFinalizer() = default;
38+ PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
39+};
40+ 
41+} // end namespace llvm
42+ 
43+#endif // LLVM_TRANSFORMS_SCALAR_CJ_INSERT_REMOVE_LOCAL_FINALIZER_H
@@ -192,6 +192,7 @@ getLocInfoForCJIntrinsics(const IntrinsicInst *II,
192 Base = MemoryLocation::getAfter(getBaseObj(II));192 Base = MemoryLocation::getAfter(getBaseObj(II));
193 break;193 break;
194 case Intrinsic::cj_assign_generic:194 case Intrinsic::cj_assign_generic:
195+ case Intrinsic::cj_assign_local_generic:
195 Src = MemoryLocation::getAfter(getSource(II));196 Src = MemoryLocation::getAfter(getSource(II));
196 Dst = MemoryLocation::getAfter(getDest(II));197 Dst = MemoryLocation::getAfter(getDest(II));
197 break;198 break;
@@ -258,7 +259,8 @@ ModRefInfo CJAAResult::getModRefInfo(const CallBase *Call,
258 case Intrinsic::cj_gcwrite_struct:259 case Intrinsic::cj_gcwrite_struct:
259 case Intrinsic::cj_gcwrite_generic:260 case Intrinsic::cj_gcwrite_generic:
260 case Intrinsic::cj_gcread_generic:261 case Intrinsic::cj_gcread_generic:
261- case Intrinsic::cj_assign_generic: {262+ case Intrinsic::cj_assign_generic:
263+ case Intrinsic::cj_assign_local_generic: {
262 auto [LocS, LocD, LocB] =264 auto [LocS, LocD, LocB] =
263 getLocInfoForCJIntrinsics(cast<IntrinsicInst>(Call), TLI);265 getLocInfoForCJIntrinsics(cast<IntrinsicInst>(Call), TLI);
264 if (!LocS || !LocD)266 if (!LocS || !LocD)
@@ -80,6 +80,7 @@ const static StdMap<unsigned, StringRef> IntrinsicMap{
80 {Intrinsic::cj_atomic_swap, "CJ_MCC_AtomicSwapReference"},80 {Intrinsic::cj_atomic_swap, "CJ_MCC_AtomicSwapReference"},
81 {Intrinsic::cj_atomic_compare_swap, "CJ_MCC_AtomicCompareAndSwapReference"},81 {Intrinsic::cj_atomic_compare_swap, "CJ_MCC_AtomicCompareAndSwapReference"},
82 {Intrinsic::cj_assign_generic, "CJ_MCC_AssignGeneric"},82 {Intrinsic::cj_assign_generic, "CJ_MCC_AssignGeneric"},
83+ {Intrinsic::cj_assign_local_generic, "CJ_MCC_AssignLocalGeneric"},
83 {Intrinsic::cj_gcwrite_generic, "CJ_MCC_WriteGeneric"},84 {Intrinsic::cj_gcwrite_generic, "CJ_MCC_WriteGeneric"},
84 {Intrinsic::cj_gcread_generic, "CJ_MCC_ReadGeneric"},85 {Intrinsic::cj_gcread_generic, "CJ_MCC_ReadGeneric"},
85 {Intrinsic::cj_array_copy_generic, "CJ_MCC_ArrayCopyGeneric"},86 {Intrinsic::cj_array_copy_generic, "CJ_MCC_ArrayCopyGeneric"},
@@ -1074,6 +1075,7 @@ bool CJBarrierLowering::isCJBarrier(Instruction *I) {
1074 case Intrinsic::cj_array_copy_struct:1075 case Intrinsic::cj_array_copy_struct:
1075 case Intrinsic::cj_array_copy_generic:1076 case Intrinsic::cj_array_copy_generic:
1076 case Intrinsic::cj_assign_generic:1077 case Intrinsic::cj_assign_generic:
1078+ case Intrinsic::cj_assign_local_generic:
1077 case Intrinsic::cj_gcwrite_generic:1079 case Intrinsic::cj_gcwrite_generic:
1078 case Intrinsic::cj_gcread_generic:1080 case Intrinsic::cj_gcread_generic:
1079 case Intrinsic::cj_gcwrite_generic_payload:1081 case Intrinsic::cj_gcwrite_generic_payload:
@@ -565,8 +565,8 @@ public:
565 insertLiveSet(LiveTmp, FI, Imm);565 insertLiveSet(LiveTmp, FI, Imm);
566 continue;566 continue;
567 }567 }
568- assert(MFI.isStatepointSpillSlotObjectIndex(FI) &&568+ // assert(MFI.isStatepointSpillSlotObjectIndex(FI) &&
569- "Unknown stack type");569+ // "Unknown stack type");
睡觉对我很重要

这里为什么需要注释掉

likedislike
570 continue;570 continue;
571 }571 }
572 572 
@@ -1130,6 +1130,9 @@ bool TargetPassConfig::addISelPasses() {
1130 addPass(createCJFillMetadataLegacyPass());1130 addPass(createCJFillMetadataLegacyPass());
1131 addPass(createCJRuntimeLoweringLegacyPass());1131 addPass(createCJRuntimeLoweringLegacyPass());
1132 addPass(createCangjieSpecificOptLegacyPass(0));1132 addPass(createCangjieSpecificOptLegacyPass(0));
1133+ // Must precede CJRewriteStatepoint: the Remove call is a potential
1134+ // safepoint, so the object pointer it takes has to be relocated.
1135+ addPass(createCJInsertRemoveLocalFinalizerLegacyPass());
1133 addPass(createPlaceSafepointsLegacyPass());1136 addPass(createPlaceSafepointsLegacyPass());
1134 addPass(createCJRewriteStatepointLegacyPass(0));1137 addPass(createCJRewriteStatepointLegacyPass(0));
1135 }1138 }
@@ -85,6 +85,7 @@ Value *getDest(const CallBase *CI) {
85 case Intrinsic::cj_gcread_generic:85 case Intrinsic::cj_gcread_generic:
86 return CI->getArgOperand(GCReadGeneric::DstPtr);86 return CI->getArgOperand(GCReadGeneric::DstPtr);
87 case Intrinsic::cj_assign_generic:87 case Intrinsic::cj_assign_generic:
88+ case Intrinsic::cj_assign_local_generic:
88 return CI->getArgOperand(AssignGeneric::DstPtr);89 return CI->getArgOperand(AssignGeneric::DstPtr);
89 case Intrinsic::cj_array_copy_ref:90 case Intrinsic::cj_array_copy_ref:
90 case Intrinsic::cj_array_copy_struct:91 case Intrinsic::cj_array_copy_struct:
@@ -111,6 +112,7 @@ Value *getSource(const CallBase *CI) {
111 case Intrinsic::cj_gcread_generic:112 case Intrinsic::cj_gcread_generic:
112 return CI->getArgOperand(GCReadGeneric::SrcPtr);113 return CI->getArgOperand(GCReadGeneric::SrcPtr);
113 case Intrinsic::cj_assign_generic:114 case Intrinsic::cj_assign_generic:
115+ case Intrinsic::cj_assign_local_generic:
114 return CI->getArgOperand(AssignGeneric::SrcPtr);116 return CI->getArgOperand(AssignGeneric::SrcPtr);
115 case Intrinsic::cj_array_copy_ref:117 case Intrinsic::cj_array_copy_ref:
116 case Intrinsic::cj_array_copy_struct:118 case Intrinsic::cj_array_copy_struct:
@@ -163,6 +163,7 @@
163#include "llvm/Transforms/Scalar/CJGenericIntrinsicOpt.h"163#include "llvm/Transforms/Scalar/CJGenericIntrinsicOpt.h"
164#include "llvm/Transforms/Scalar/CJLoopFloatOpt.h"164#include "llvm/Transforms/Scalar/CJLoopFloatOpt.h"
165#include "llvm/Transforms/Scalar/CJRSSCE.h"165#include "llvm/Transforms/Scalar/CJRSSCE.h"
166+#include "llvm/Transforms/Scalar/CJInsertRemoveLocalFinalizer.h"
166#include "llvm/Transforms/Scalar/CJRuntimeLowering.h"167#include "llvm/Transforms/Scalar/CJRuntimeLowering.h"
167#include "llvm/Transforms/Scalar/CJRewriteStatepoint.h"168#include "llvm/Transforms/Scalar/CJRewriteStatepoint.h"
168#include "llvm/Transforms/Scalar/CJSimpleRangeAnalysis.h"169#include "llvm/Transforms/Scalar/CJSimpleRangeAnalysis.h"
@@ -1260,6 +1261,10 @@ Error PassBuilder::parseModulePass(ModulePassManager &MPM,
1260 }1261 }
1261 if (CJPipeline && !CangjieLTOPreOpt) {1262 if (CJPipeline && !CangjieLTOPreOpt) {
1262 MPM.addPass(CJSpecificOpt(L.getSpeedupLevel()));1263 MPM.addPass(CJSpecificOpt(L.getSpeedupLevel()));
1264+ // Must precede CJRewriteStatepoint: the Remove call is a potential
1265+ // safepoint, so the object pointer it takes has to be relocated.
1266+ MPM.addPass(
1267+ createModuleToFunctionPassAdaptor(CJInsertRemoveLocalFinalizer()));
1263 MPM.addPass(PlaceSafepoints());1268 MPM.addPass(PlaceSafepoints());
1264 MPM.addPass(CJBarrierOpt());1269 MPM.addPass(CJBarrierOpt());
1265 MPM.addPass(CJRewriteStatepoint(L.getSpeedupLevel()));1270 MPM.addPass(CJRewriteStatepoint(L.getSpeedupLevel()));
@@ -403,6 +403,7 @@ FUNCTION_PASS("transform-warning", WarnMissedTransformationsPass())
403FUNCTION_PASS("tsan", ThreadSanitizerPass())403FUNCTION_PASS("tsan", ThreadSanitizerPass())
404FUNCTION_PASS("memprof", MemProfilerPass())404FUNCTION_PASS("memprof", MemProfilerPass())
405FUNCTION_PASS("cj-generic-intrinsic-opt", CJGenericIntrinsicOpt())405FUNCTION_PASS("cj-generic-intrinsic-opt", CJGenericIntrinsicOpt())
406+FUNCTION_PASS("cj-insert-remove-local-finalizer", CJInsertRemoveLocalFinalizer())
406FUNCTION_PASS("insert-cj-tbaa", InsertCJTBAA())407FUNCTION_PASS("insert-cj-tbaa", InsertCJTBAA())
407FUNCTION_PASS("cj-gcinstr-replace", CJGCInstrReplace())408FUNCTION_PASS("cj-gcinstr-replace", CJGCInstrReplace())
408FUNCTION_PASS("cj-gcinstr-restore", CJGCInstrRestore())409FUNCTION_PASS("cj-gcinstr-restore", CJGCInstrRestore())
@@ -1593,6 +1593,7 @@ private:
1593 return true;1593 return true;
1594 case Intrinsic::cj_gcwrite_static_struct:1594 case Intrinsic::cj_gcwrite_static_struct:
1595 case Intrinsic::cj_assign_generic:1595 case Intrinsic::cj_assign_generic:
1596+ case Intrinsic::cj_assign_local_generic:
1596 case Intrinsic::cj_copy_struct_field:1597 case Intrinsic::cj_copy_struct_field:
1597 handleGCWriteAgg(II, true);1598 handleGCWriteAgg(II, true);
1598 return true;1599 return true;
@@ -624,6 +624,9 @@ void PassManagerBuilder::populateModulePassManager(
624 auto addCangjiePasses = [&]() {624 auto addCangjiePasses = [&]() {
625 if (CJPipeline) {625 if (CJPipeline) {
626 MPM.add(createCangjieSpecificOptLegacyPass(OptLevel));626 MPM.add(createCangjieSpecificOptLegacyPass(OptLevel));
627+ // Must precede CJRewriteStatepoint: the Remove call is a potential
628+ // safepoint, so the object pointer it takes has to be relocated.
629+ MPM.add(createCJInsertRemoveLocalFinalizerLegacyPass());
627 MPM.add(createPlaceSafepointsLegacyPass());630 MPM.add(createPlaceSafepointsLegacyPass());
628 MPM.add(createCJRewriteStatepointLegacyPass(OptLevel));631 MPM.add(createCJRewriteStatepointLegacyPass(OptLevel));
629 }632 }
@@ -2817,6 +2817,7 @@ static bool isAllocSiteRemovable(Instruction *AI,
2817 Users.emplace_back(I);2817 Users.emplace_back(I);
2818 continue;2818 continue;
2819 case Intrinsic::cj_assign_generic:2819 case Intrinsic::cj_assign_generic:
2820+ case Intrinsic::cj_assign_local_generic:
2820 if (II->getArgOperand(1) == PI)2821 if (II->getArgOperand(1) == PI)
2821 return false;2822 return false;
2822 Users.emplace_back(I);2823 Users.emplace_back(I);
@@ -784,7 +784,7 @@ void StructLiveAnalysis::computeFieldKillSet(BasicBlock *BB,
784 SetVector<FieldInfo *> MemDefSet;784 SetVector<FieldInfo *> MemDefSet;
785 SetVector<FieldInfo *> MemUseSet;785 SetVector<FieldInfo *> MemUseSet;
786 786 
787- MemoryAccess Status = hasMemoryDefineOrUseValue(&I, MemDefSet, MemUseSet);787+ MemoryAccessKind Status = hasMemoryDefineOrUseValue(&I, MemDefSet, MemUseSet);
788 if (Status & DefineMemory) {788 if (Status & DefineMemory) {
789 KillSet.set_union(MemDefSet);789 KillSet.set_union(MemDefSet);
790 }790 }
@@ -1260,7 +1260,7 @@ void StructLiveAnalysis::visitMemorySelect(SelectInst *SI,
1260 1260 
1261// If it is an instruction that contains the memory parameter, then handles1261// If it is an instruction that contains the memory parameter, then handles
1262// use and define values according to the instruction type.1262// use and define values according to the instruction type.
1263-MemoryAccess StructLiveAnalysis::hasMemoryDefineOrUseValue(1263+MemoryAccessKind StructLiveAnalysis::hasMemoryDefineOrUseValue(
1264 Instruction *I, SetVector<FieldInfo *> &AllocaDefs,1264 Instruction *I, SetVector<FieldInfo *> &AllocaDefs,
1265 SetVector<FieldInfo *> &AllocaUses) {1265 SetVector<FieldInfo *> &AllocaUses) {
1266 // For each memory instruction, identifies and processes use and define,1266 // For each memory instruction, identifies and processes use and define,
@@ -1291,7 +1291,7 @@ MemoryAccess StructLiveAnalysis::hasMemoryDefineOrUseValue(
1291 Status |= static_cast<int>(DefineMemory);1291 Status |= static_cast<int>(DefineMemory);
1292 if (!AllocaUses.empty())1292 if (!AllocaUses.empty())
1293 Status |= static_cast<int>(UseMemory);1293 Status |= static_cast<int>(UseMemory);
1294- return static_cast<MemoryAccess>(Status);1294+ return static_cast<MemoryAccessKind>(Status);
1295}1295}
1296 1296 
1297void StructLiveAnalysis::combineStructGCField(StructLiveSetTy &FieldInfos,1297void StructLiveAnalysis::combineStructGCField(StructLiveSetTy &FieldInfos,
@@ -285,6 +285,7 @@ public:
285 break;285 break;
286 case Intrinsic::cj_gc_statepoint:286 case Intrinsic::cj_gc_statepoint:
287 case Intrinsic::cj_malloc_object:287 case Intrinsic::cj_malloc_object:
288+ case Intrinsic::cj_malloc_local_object:
288 case Intrinsic::cj_malloc_array:289 case Intrinsic::cj_malloc_array:
289 case Intrinsic::cj_malloc_array_generic:290 case Intrinsic::cj_malloc_array_generic:
290 case Intrinsic::cj_division_check_sdiv:291 case Intrinsic::cj_division_check_sdiv:
@@ -293,6 +294,7 @@ public:
293 case Intrinsic::cj_division_check_urem:294 case Intrinsic::cj_division_check_urem:
294 case Intrinsic::cj_throw_exception:295 case Intrinsic::cj_throw_exception:
295 case Intrinsic::cj_alloca_generic:296 case Intrinsic::cj_alloca_generic:
297+ case Intrinsic::cj_alloca_local_generic:
296 break;298 break;
297 }299 }
298 }300 }
@@ -0,0 +1,512 @@
1+//===- CJInsertRemoveLocalFinalizer.cpp - ---------------------------------===//
2+//
3+// Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
4+// This source file is part of the Cangjie project, licensed under Apache-2.0
5+// with Runtime Library Exception.
6+//
7+// See https://cangjie-lang.cn/pages/LICENSE for license information.
8+//
9+//===----------------------------------------------------------------------===//
10+//
11+// Pair every CJ_MCC_AddLocalFinalizer with a CJ_MCC_RemoveLocalFinalizer at the
12+// end of the object's live range, on every control-flow path, exactly once.
13+//
14+// "Right after the last use" is not that point: a path that never uses the
15+// object (the other arm of a branch, a landing pad, a loop exit) has no last
16+// use at all, and a use reached from several paths would be unregistered
17+// several times.
18+//
19+// The point we want is the dead-out frontier of the object's live range.
20+// Liveness is closed under predecessors -- if a value is live at p it is live
21+// at every point from which p is reachable -- so the live range is left exactly
22+// once on every path, and inserting at the frontier gives exactly one Remove
23+// per path:
24+//
25+// * block-internal death: no representative is live out of B
26+// -> insert after the last point in B that defines or uses one;
27+// * edge death: some representative is live out of B, but none is live on the
28+// edge B -> S
29+// -> insert on that edge, splitting it when it is critical.
30+//
31+// A "representative" is any SSA value that keeps the object alive: the object
32+// itself, everything derived from it by bitcast / addrspacecast / GEP / select
33+// / phi, and -- to bridge the store->load round trip through a local variable
34+// -- the address of every stack slot it is stored into plus every value loaded
35+// from such a slot. Including the slot address is what keeps the live range
36+// connected across memory: without it the object would look dead right after
37+// the store.
38+//
39+// If the object reaches memory we cannot see through (a heap field, a slot
40+// whose address escapes), no Remove is emitted for it at all. Leaking a
41+// registration is far less harmful than unregistering an object that is still
42+// reachable.
43+//
44+// The pass keys off the CJ_MCC_AddLocalFinalizer calls rather than off the
45+// allocation: after CJRuntimeLowering both heap and local finalizer objects are
46+// produced by CJ_MCC_NewFinalizer, so the Add call is the only thing that still
47+// tells the two apart.
48+//
49+//===----------------------------------------------------------------------===//
50+ 
51+#include "llvm/Transforms/Scalar/CJInsertRemoveLocalFinalizer.h"
52+ 
53+#include "llvm/ADT/MapVector.h"
54+#include "llvm/ADT/STLExtras.h"
55+#include "llvm/ADT/SetVector.h"
56+#include "llvm/ADT/SmallPtrSet.h"
57+#include "llvm/ADT/SmallVector.h"
58+#include "llvm/Analysis/ValueTracking.h"
59+#include "llvm/IR/BasicBlock.h"
60+#include "llvm/IR/CFG.h"
61+#include "llvm/IR/Dominators.h"
62+#include "llvm/IR/Function.h"
63+#include "llvm/IR/IRBuilder.h"
64+#include "llvm/IR/InstIterator.h"
65+#include "llvm/IR/IntrinsicInst.h"
66+#include "llvm/IR/Module.h"
67+#include "llvm/InitializePasses.h"
68+#include "llvm/Pass.h"
69+#include "llvm/Support/Debug.h"
70+#include "llvm/Transforms/Scalar.h"
71+#include "llvm/Transforms/Utils/BasicBlockUtils.h"
72+ 
73+#define DEBUG_TYPE "cj-insert-remove-local-finalizer"
74+ 
75+using namespace llvm;
76+ 
77+namespace {
78+ 
79+const char AddLocalFinalizerName[] = "CJ_MCC_AddLocalFinalizer";
80+const char RemoveLocalFinalizerName[] = "CJ_MCC_RemoveLocalFinalizer";
81+ 
82+using RepSet = SmallPtrSet<Value *, 8>;
83+using BlockSets = DenseMap<const BasicBlock *, RepSet>;
84+using Edge = std::pair<BasicBlock *, BasicBlock *>;
85+ 
86+// Walk the address chain of \p AI. Returns false if the slot is used in any way
87+// other than loading/storing through it, which would let its contents flow
88+// somewhere we cannot follow. On success \p Addrs receives the alloca and every
89+// cast/GEP of it, so that touching the slot counts as keeping the object alive.
90+bool collectSlotAddresses(AllocaInst *AI, SmallPtrSetImpl<Value *> &Addrs) {
91+ SmallVector<Value *, 8> Worklist{AI};
92+ Addrs.insert(AI);
93+ while (!Worklist.empty()) {
94+ Value *V = Worklist.pop_back_val();
95+ for (User *U : V->users()) {
96+ if (auto *LI = dyn_cast<LoadInst>(U)) {
97+ if (LI->getPointerOperand() != V)
98+ return false;
99+ } else if (auto *SI = dyn_cast<StoreInst>(U)) {
100+ // Storing through the slot is fine; storing the slot's own address
101+ // somewhere else is not.
102+ if (SI->getPointerOperand() != V)
103+ return false;
104+ } else if (isa<BitCastInst>(U) || isa<AddrSpaceCastInst>(U) ||
105+ isa<GetElementPtrInst>(U)) {
106+ if (Addrs.insert(U).second)
107+ Worklist.push_back(U);
108+ } else if (auto *II = dyn_cast<IntrinsicInst>(U)) {
109+ Intrinsic::ID ID = II->getIntrinsicID();
110+ if (ID != Intrinsic::lifetime_start && ID != Intrinsic::lifetime_end &&
111+ !isa<DbgInfoIntrinsic>(II))
112+ return false;
113+ } else {
114+ return false;
115+ }
116+ }
117+ }
118+ return true;
119+}
120+ 
121+// Collect every value that keeps \p Obj alive into \p Reps. Returns false if the
122+// object escapes into memory that cannot be tracked, in which case no Remove may
123+// be emitted for it.
124+bool collectRepresentatives(Value *Obj, ArrayRef<LoadInst *> AllLoads,
125+ RepSet &Reps) {
126+ SmallVector<Value *, 8> Worklist;
127+ SmallPtrSet<AllocaInst *, 4> Slots;
128+ auto AddRep = [&](Value *V) {
129+ if (Reps.insert(V).second)
130+ Worklist.push_back(V);
131+ };
132+ AddRep(Obj);
133+ while (!Worklist.empty()) {
134+ Value *V = Worklist.pop_back_val();
135+ for (User *U : V->users()) {
136+ if (auto *SI = dyn_cast<StoreInst>(U)) {
137+ if (SI->getValueOperand() != V)
138+ continue; // V is only the destination, that is a plain use
139+ auto *AI =
140+ dyn_cast<AllocaInst>(getUnderlyingObject(SI->getPointerOperand()));
141+ if (AI == nullptr)
142+ return false; // stored into memory we do not own
143+ if (!Slots.insert(AI).second)
144+ continue;
145+ SmallPtrSet<Value *, 8> Addrs;
146+ if (!collectSlotAddresses(AI, Addrs))
147+ return false; // the slot's contents can escape
148+ for (Value *Addr : Addrs)
149+ Reps.insert(Addr);
150+ // Anything ever loaded from this slot may be the object.
151+ for (LoadInst *LI : AllLoads)
152+ if (Addrs.count(LI->getPointerOperand()))
153+ AddRep(LI);
154+ continue;
155+ }
156+ if (isa<BitCastInst>(U) || isa<AddrSpaceCastInst>(U) ||
157+ isa<GetElementPtrInst>(U) || isa<SelectInst>(U) || isa<PHINode>(U))
158+ AddRep(U);
159+ }
160+ }
161+ return true;
162+}
163+ 
164+// Backward liveness dataflow restricted to \p Reps. A value used by a phi is
165+// live on the incoming edge rather than in the phi's own block, so those uses
166+// seed the predecessor's live-out set instead of the block's use set.
167+//
168+// A representative phi additionally seeds *every* incoming edge, not just the
169+// ones whose incoming value is itself a representative. Such a phi defines a
170+// representative in the middle of the live range, which would otherwise be a
171+// second entry into it: a path entering through the phi's other operand would
172+// leave the range twice and unregister the object twice. Extending the range
173+// back over all incoming edges keeps it single-entry, so it is still left
174+// exactly once on every path.
175+void computeRepLiveness(Function &F, const RepSet &Reps, BlockSets &Defs,
176+ BlockSets &LiveIn, BlockSets &LiveOut) {
177+ BlockSets UpExposed;
178+ for (BasicBlock &BB : F) {
179+ RepSet &Def = Defs[&BB];
180+ RepSet &Up = UpExposed[&BB];
181+ for (Instruction &I : reverse(BB)) {
182+ if (Reps.count(&I)) {
183+ Def.insert(&I);
184+ Up.erase(&I);
185+ }
186+ if (isa<PHINode>(I))
187+ continue;
188+ for (Value *Op : I.operands())
189+ if (Reps.count(Op))
190+ Up.insert(Op);
191+ }
192+ RepSet &Out = LiveOut[&BB];
193+ for (BasicBlock *Succ : successors(&BB))
194+ for (PHINode &PN : Succ->phis()) {
195+ if (Reps.count(&PN))
196+ Out.insert(&PN);
睡觉对我很重要

把 x = phi[ xx, bbxx, yy bby] 放到当前bb的liveout , 这一步是有什么深意么

likedislike
197+ Value *V = PN.getIncomingValueForBlock(&BB);
198+ if (Reps.count(V))
199+ Out.insert(V);
200+ }
201+ LiveIn[&BB] = Up;
202+ }
203+ 
204+ SmallSetVector<BasicBlock *, 32> Worklist;
205+ for (BasicBlock &BB : F)
206+ Worklist.insert(&BB);
207+ while (!Worklist.empty()) {
208+ BasicBlock *BB = Worklist.pop_back_val();
209+ RepSet &Out = LiveOut[BB];
210+ for (BasicBlock *Succ : successors(BB))
211+ Out.insert(LiveIn[Succ].begin(), LiveIn[Succ].end());
212+ RepSet &In = LiveIn[BB];
213+ const size_t OldSize = In.size();
214+ for (Value *V : Out)
215+ if (!Defs[BB].count(V))
216+ In.insert(V);
217+ if (In.size() != OldSize)
218+ for (BasicBlock *Pred : predecessors(BB))
219+ Worklist.insert(Pred);
220+ }
221+}
222+ 
223+class LocalFinalizerRemover {
224+public:
225+ LocalFinalizerRemover(Function &F, DominatorTree &DT)
226+ : F(F), DT(DT), Bdr(F.getContext()) {}
227+ 
228+ bool run();
229+ 
230+private:
231+ void process(Instruction *Obj);
232+ void emitRemove(Value *Obj, const DebugLoc &DL);
233+ void emitRemoveAfter(Value *Obj, Instruction *After);
234+ void emitRemoveOnEdges(Value *Obj, ArrayRef<Edge> DeadEdges);
235+ 
236+ Function &F;
237+ DominatorTree &DT;
238+ IRBuilder<> Bdr;
239+ Function *RemoveFunc = nullptr;
240+ SmallVector<LoadInst *, 32> AllLoads;
241+};
242+ 
243+void LocalFinalizerRemover::emitRemove(Value *Obj, const DebugLoc &DL) {
244+ Value *Arg = Bdr.CreatePointerBitCastOrAddrSpaceCast(
245+ Obj, RemoveFunc->getFunctionType()->getParamType(0));
246+ Bdr.CreateCall(RemoveFunc, {Arg})->setDebugLoc(DL);
247+}
248+ 
249+// Insert the Remove call directly after \p After. A run of phi nodes must stay
250+// contiguous at the top of its block, so a phi is not inserted after but
251+// skipped past.
252+void LocalFinalizerRemover::emitRemoveAfter(Value *Obj, Instruction *After) {
253+ BasicBlock *BB = After->getParent();
254+ BasicBlock::iterator IP = isa<PHINode>(After)
255+ ? BB->getFirstInsertionPt()
256+ : std::next(After->getIterator());
257+ Bdr.SetInsertPoint(BB, IP);
258+ emitRemove(Obj, After->getDebugLoc());
259+}
260+ 
261+// Insert one Remove call on each of \p DeadEdges. Placing the call on the edge
262+// rather than at the top of the successor is what keeps a shared successor (a
263+// landing pad reached from several invokes, a merge block) from getting one
264+// call per incoming edge, and what keeps the object's definition dominating the
265+// call.
266+void LocalFinalizerRemover::emitRemoveOnEdges(Value *Obj,
267+ ArrayRef<Edge> DeadEdges) {
268+ const DebugLoc DL = cast<Instruction>(Obj)->getDebugLoc();
269+ 
270+ MapVector<BasicBlock *, SmallSetVector<BasicBlock *, 2>> BySucc;
271+ for (const Edge &E : DeadEdges)
272+ BySucc[E.second].insert(E.first);
273+ 
274+ for (auto &KV : BySucc) {
275+ BasicBlock *Succ = KV.first;
276+ // When every edge into Succ is dead the call belongs in Succ itself: no
277+ // path reaches Succ with the object still live, and no edge needs a split.
278+ // This is the common shape for a landing pad shared by invokes that all
279+ // consume the object.
280+ SmallPtrSet<BasicBlock *, 4> AllPreds(pred_begin(Succ), pred_end(Succ));
281+ if (KV.second.size() == AllPreds.size()) {
282+ Bdr.SetInsertPoint(Succ, Succ->getFirstInsertionPt());
283+ emitRemove(Obj, DL);
284+ continue;
285+ }
286+ for (BasicBlock *Pred : KV.second) {
287+ Instruction *TI = Pred->getTerminator();
288+ if (TI->getNumSuccessors() == 1) {
289+ // Not a critical edge, and a single-successor terminator cannot be
290+ // consuming the object itself.
291+ Bdr.SetInsertPoint(TI);
292+ emitRemove(Obj, DL);
293+ continue;
294+ }
295+ BasicBlock *NewBB = nullptr;
296+ if (Succ->isEHPad()) {
297+ // SplitEdge/ehAwareSplitEdge build funclet pads, which do not fit the
298+ // landing-pad EH the Cangjie runtime uses.
299+ if (!isa<LandingPadInst>(Succ->getFirstNonPHI())) {
300+ LLVM_DEBUG(dbgs() << DEBUG_TYPE ": cannot split edge into "
301+ << Succ->getName() << ", no "
302+ << RemoveLocalFinalizerName << " there\n");
303+ continue;
304+ }
305+ SmallVector<BasicBlock *, 2> NewBBs;
306+ SplitLandingPadPredecessors(Succ, {Pred}, ".fin", ".fin.lp", NewBBs,
307+ &DT);
308+ NewBB = NewBBs[0];
309+ } else {
310+ NewBB = SplitEdge(Pred, Succ, &DT);
311+ }
312+ if (NewBB == nullptr)
313+ continue;
314+ Bdr.SetInsertPoint(NewBB->getTerminator());
315+ emitRemove(Obj, DL);
316+ }
317+ }
318+}
319+ 
320+void LocalFinalizerRemover::process(Instruction *ObjDef) {
321+ Value *Obj = ObjDef;
322+ RepSet Reps;
323+ if (!collectRepresentatives(Obj, AllLoads, Reps)) {
324+ LLVM_DEBUG(dbgs() << DEBUG_TYPE ": local finalizer escapes, no "
325+ << RemoveLocalFinalizerName << " emitted for " << *Obj
326+ << "\n");
327+ return;
328+ }
329+ 
330+ BlockSets Defs, LiveIn, LiveOut;
331+ computeRepLiveness(F, Reps, Defs, LiveIn, LiveOut);
332+ 
333+ // Is any representative live on the edge BB -> Succ? A phi operand is live on
334+ // its incoming edge even though the phi is defined in Succ, so it has to be
335+ // checked separately from Succ's live-in set.
336+ auto liveOnEdge = [&](BasicBlock *BB, BasicBlock *Succ) {
337+ if (!LiveIn[Succ].empty())
338+ return true;
339+ // Mirrors the live-out seeding in computeRepLiveness.
340+ for (PHINode &PN : Succ->phis())
341+ if (Reps.count(&PN) || Reps.count(PN.getIncomingValueForBlock(BB)))
342+ return true;
343+ return false;
344+ };
345+ 
346+ // In valid SSA liveness implies the definition dominates, with one exception:
347+ // on the unwind edge of the invoke that produces the object there is no
348+ // object, and CJ_MCC_AddLocalFinalizer was never reached either.
349+ auto defAvailableOnEdge = [&](BasicBlock *BB, BasicBlock *Succ) {
350+ if (auto *II = dyn_cast<InvokeInst>(ObjDef))
351+ if (II->getParent() == BB && Succ == II->getUnwindDest())
352+ return false;
353+ return DT.dominates(ObjDef->getParent(), BB);
354+ };
355+ 
356+ SmallVector<Instruction *, 4> AfterPoints;
357+ SmallVector<Edge, 4> DeadEdges;
358+ 
359+ for (BasicBlock &BB : F) {
360+ if (LiveOut[&BB].empty()) {
361+ // Nothing survives BB: the object dies here, if it lived here at all.
362+ Instruction *Last = nullptr;
363+ for (Instruction &I : BB) {
364+ if (Reps.count(&I)) {
365+ Last = &I;
366+ continue;
367+ }
368+ if (isa<PHINode>(I))
369+ continue; // a phi's operands are used on the incoming edge
370+ for (Value *Op : I.operands())
371+ if (Reps.count(Op)) {
372+ Last = &I;
373+ break;
374+ }
375+ }
376+ if (Last == nullptr)
377+ continue; // no representative activity in this block
378+ if (!Last->isTerminator()) {
379+ // A representative loaded from a reused slot can appear on paths the
380+ // object itself never reached; require the definition to be available.
381+ if (Last != ObjDef && !DT.dominates(Obj, Last))
382+ continue;
383+ AfterPoints.push_back(Last);
384+ continue;
385+ }
386+ // The terminator itself consumes the object (it is passed to an invoke):
387+ // it is dead on every outgoing edge. A terminator without successors
388+ // (`ret obj`) hands the object to the caller, so there is nothing to
389+ // unregister here.
390+ for (BasicBlock *Succ : successors(&BB))
391+ if (defAvailableOnEdge(&BB, Succ))
392+ DeadEdges.emplace_back(&BB, Succ);
393+ continue;
394+ }
395+ for (BasicBlock *Succ : successors(&BB))
396+ if (!liveOnEdge(&BB, Succ) && defAvailableOnEdge(&BB, Succ))
397+ DeadEdges.emplace_back(&BB, Succ);
398+ }
399+ 
400+ for (Instruction *At : AfterPoints)
401+ emitRemoveAfter(Obj, At);
402+ if (!DeadEdges.empty())
403+ emitRemoveOnEdges(Obj, DeadEdges);
404+}
405+ 
406+bool LocalFinalizerRemover::run() {
407+ Module &M = *F.getParent();
408+ Function *AddFunc = M.getFunction(AddLocalFinalizerName);
409+ if (AddFunc == nullptr || AddFunc->use_empty())
410+ return false;
411+ 
412+ // Collect the registered objects in program order, and every load in one
413+ // sweep. Inserting Remove calls adds no loads, so AllLoads stays complete.
414+ SmallVector<Instruction *, 8> Objs;
415+ SmallPtrSet<Value *, 8> Seen;
416+ for (Instruction &I : instructions(F)) {
417+ if (auto *LI = dyn_cast<LoadInst>(&I)) {
418+ AllLoads.push_back(LI);
419+ continue;
420+ }
421+ auto *CB = dyn_cast<CallBase>(&I);
422+ if (CB == nullptr || CB->getCalledFunction() != AddFunc ||
423+ CB->arg_size() < 1)
424+ continue;
425+ auto *Obj = dyn_cast<Instruction>(CB->getArgOperand(0));
426+ if (Obj == nullptr) {
427+ LLVM_DEBUG(dbgs() << DEBUG_TYPE ": registered value is not an "
428+ "instruction, skipping "
429+ << *CB << "\n");
430+ continue;
431+ }
432+ if (Seen.insert(Obj).second)
433+ Objs.push_back(Obj);
434+ }
435+ if (Objs.empty())
436+ return false;
437+ 
438+ // Already paired up, e.g. because the pass ran twice over this function.
439+ Function *Existing = M.getFunction(RemoveLocalFinalizerName);
440+ if (Existing != nullptr) {
441+ llvm::erase_if(Objs, [&](Instruction *Obj) {
442+ return llvm::any_of(Obj->users(), [&](User *U) {
443+ auto *CB = dyn_cast<CallBase>(U);
444+ return CB != nullptr && CB->getCalledFunction() == Existing;
445+ });
446+ });
447+ if (Objs.empty())
448+ return false;
449+ }
450+ 
451+ RemoveFunc = Existing;
452+ if (RemoveFunc == nullptr) {
453+ IRBuilder<> Decl(F.getContext());
454+ FunctionType *FT =
455+ FunctionType::get(Decl.getVoidTy(), {Decl.getInt8PtrTy(1)}, false);
456+ RemoveFunc = M.declareCJRuntimeFunc(RemoveLocalFinalizerName, FT, false);
457+ RemoveFunc->addFnAttr(Attribute::get(F.getContext(), "gc-leaf-function"));
458+ }
459+ 
460+ for (Instruction *Obj : Objs)
461+ process(Obj);
462+ return true;
463+}
464+ 
465+class CJInsertRemoveLocalFinalizerLegacyPass : public FunctionPass {
466+public:
467+ static char ID;
468+ 
469+ explicit CJInsertRemoveLocalFinalizerLegacyPass() : FunctionPass(ID) {
470+ initializeCJInsertRemoveLocalFinalizerLegacyPassPass(
471+ *PassRegistry::getPassRegistry());
472+ }
473+ ~CJInsertRemoveLocalFinalizerLegacyPass() = default;
474+ 
475+ bool runOnFunction(Function &F) override {
476+ if (F.isDeclaration())
477+ return false;
478+ auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
479+ return LocalFinalizerRemover(F, DT).run();
480+ }
481+ 
482+ void getAnalysisUsage(AnalysisUsage &AU) const override {
483+ AU.addRequired<DominatorTreeWrapperPass>();
484+ }
485+};
486+ 
487+} // namespace
488+ 
489+PreservedAnalyses
490+CJInsertRemoveLocalFinalizer::run(Function &F, FunctionAnalysisManager &AM) {
491+ if (F.isDeclaration())
492+ return PreservedAnalyses::all();
493+ auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
494+ if (!LocalFinalizerRemover(F, DT).run())
495+ return PreservedAnalyses::all();
496+ // Splitting critical edges changes the CFG.
497+ return PreservedAnalyses::none();
498+}
499+ 
500+char CJInsertRemoveLocalFinalizerLegacyPass::ID = 0;
501+ 
502+FunctionPass *llvm::createCJInsertRemoveLocalFinalizerLegacyPass() {
503+ return new CJInsertRemoveLocalFinalizerLegacyPass();
504+}
505+ 
506+INITIALIZE_PASS_BEGIN(CJInsertRemoveLocalFinalizerLegacyPass,
507+ "cj-insert-remove-local-finalizer",
508+ "Cangjie Insert Remove Local Finalizer", false, false)
509+INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
510+INITIALIZE_PASS_END(CJInsertRemoveLocalFinalizerLegacyPass,
511+ "cj-insert-remove-local-finalizer",
512+ "Cangjie Insert Remove Local Finalizer", false, false)
@@ -106,7 +106,16 @@ const static StdMap<unsigned, StringRef> RuntimeMap {
106 {Intrinsic::cj_remove_exported_ref, "CJ_MCC_RemoveExportedRef"},106 {Intrinsic::cj_remove_exported_ref, "CJ_MCC_RemoveExportedRef"},
107 {Intrinsic::cj_create_export_handle, "CJ_MCC_CreateExportHandle"},107 {Intrinsic::cj_create_export_handle, "CJ_MCC_CreateExportHandle"},
108 {Intrinsic::cj_blackhole, "CJ_LLVM_BlackHole"},108 {Intrinsic::cj_blackhole, "CJ_LLVM_BlackHole"},
109- {Intrinsic::cj_get_lambda_addr, "CJ_MCC_GetJSLambdaAddr"}};109+ {Intrinsic::cj_get_lambda_addr, "CJ_MCC_GetJSLambdaAddr"},
110+ {Intrinsic::cj_malloc_local_object, "CJ_MCC_NewLocalObject"},
111+ {Intrinsic::cj_alloca_local_generic, "CJ_MCC_NewLocalObject"},
112+ {Intrinsic::cj_malloc_local_array, "CJ_MCC_NewLocalArray"},
113+ {Intrinsic::cj_malloc_local_array_generic, "CJ_MCC_NewLocalGenericArray"},
114+ {Intrinsic::cj_maybe_local_write_ref, "CJ_MCC_MaybeLocalWriteRef"},
115+ {Intrinsic::cj_maybe_local_write_generic,
116+ "CJ_MCC_MaybeLocalWriteGeneric"},
117+ {Intrinsic::cj_demode_write_ref, "CJ_MCC_DemodeWriteRef"},
118+ };
110 119 
111struct LowerGetFieldOffset {120struct LowerGetFieldOffset {
112 CallBase *CI;121 CallBase *CI;
@@ -389,6 +398,31 @@ public:
389 CI->setCalledFunction(Func);398 CI->setCalledFunction(Func);
390 }399 }
391 400 
401+ void replaceNewLocalFinalizerFunc(CallBase *CI) {
402+ replaceWithRuntimeFunc(CI, false, true);
403+ 
404+ IRBuilder<> Bdr(CI->getContext());
405+ // CJ_MCC_NewFinalizer returns the newly created finalizer object; pass it
406+ // to CJ_MCC_AddLocalFinalizer to register it.
407+ Function *AddFunc = M.getFunction("CJ_MCC_AddLocalFinalizer");
408+ if (AddFunc == nullptr) {
409+ FunctionType *FT = FunctionType::get(Bdr.getVoidTy(), {Bdr.getInt8PtrTy(1)}, false);
410+ AddFunc = M.declareCJRuntimeFunc("CJ_MCC_AddLocalFinalizer", FT, false);
411+ AddFunc->addFnAttr(Attribute::get(CI->getContext(), "gc-leaf-function"));
412+ }
413+ if (auto *II = dyn_cast<InvokeInst>(CI)) {
414+ // For an InvokeInst the result is only valid on the normal path, so
415+ // insert the call at the start of the normal destination.
416+ BasicBlock *NormalDest = II->getNormalDest();
417+ Bdr.SetInsertPoint(NormalDest, NormalDest->getFirstInsertionPt());
418+ } else {
419+ // Insert the Add call right after the NewFinalizer call.
420+ Bdr.SetInsertPoint(CI->getParent(), std::next(CI->getIterator()));
421+ }
422+ auto *AddCall = Bdr.CreateCall(AddFunc, {CI});
423+ AddCall->setDebugLoc(CI->getDebugLoc());
424+ }
425+ 
392 void replaceNewWeakRefFunc(CallBase *CI) {426 void replaceNewWeakRefFunc(CallBase *CI) {
393 IRBuilder<> Bdr(CI);427 IRBuilder<> Bdr(CI);
394 Function *Func = M.getFunction("CJ_MCC_NewWeakRefObject");428 Function *Func = M.getFunction("CJ_MCC_NewWeakRefObject");
@@ -399,6 +433,16 @@ public:
399 CI->setCalledFunction(Func);433 CI->setCalledFunction(Func);
400 }434 }
401 435 
436+ void replaceNewLocalWeakRefFunc(CallBase *CI) {
437+ IRBuilder<> Bdr(CI);
438+ Function *Func = M.getFunction("CJ_MCC_NewLocalWeakRefObject");
439+ if (Func == nullptr) {
440+ Func = M.declareCJRuntimeFunc("CJ_MCC_NewLocalWeakRefObject",
441+ CI->getFunctionType(), false);
442+ }
443+ CI->setCalledFunction(Func);
444+ }
445+ 
402 // llvm.cj.alloca.generic(TypeInfo* ti, i64 size)446 // llvm.cj.alloca.generic(TypeInfo* ti, i64 size)
403 void replaceAllocaGeneric(CallBase *CI, EscapeScope &ES) {447 void replaceAllocaGeneric(CallBase *CI, EscapeScope &ES) {
404 if (replaceWithAlloca(CI, ES)) {448 if (replaceWithAlloca(CI, ES)) {
@@ -409,6 +453,13 @@ public:
409 setMetadata(CI, "new_gen");453 setMetadata(CI, "new_gen");
410 }454 }
411 455 
456+ // llvm.cj.alloca.generic(TypeInfo* ti, i64 size)
457+ void replaceAllocaLocalGeneric(CallBase *CI, EscapeScope &ES) {
458+ replaceWithRuntimeFunc(CI, false, true);
459+ CI->addRetAttr(Attribute::NoAlias);
460+ setMetadata(CI, "new_gen");
461+ }
462+ 
412 void replaceIsReference(CallBase *CI) {463 void replaceIsReference(CallBase *CI) {
413 IRBuilder<> IRB(CI);464 IRBuilder<> IRB(CI);
414 Value *Arg = CI->getArgOperand(0)->stripPointerCasts();465 Value *Arg = CI->getArgOperand(0)->stripPointerCasts();
@@ -751,6 +802,30 @@ public:
751 CI->addRetAttr(Attribute::NoAlias);802 CI->addRetAttr(Attribute::NoAlias);
752 }803 }
753 804 
805+ void loweringMallocLocalObject(CallBase *&CI) {
806+ const MDNode *MD = CI->getMetadata("MallocType");
807+ if (MD == nullptr) {
808+ setHeapMallocSizeAlign(CI);
809+ replaceWithRuntimeFunc(CI, false, true);
810+ } else {
811+ StringRef Ty = dyn_cast<MDString>(MD->getOperand(0).get())->getString();
812+ if (Ty.equals("HasFinalizer")) {
813+ setHeapMallocSizeAlign(CI);
814+ replaceNewLocalFinalizerFunc(CI);
815+ } else if (Ty.equals("Future") || Ty.equals("Mutex") ||
816+ Ty.equals("Monitor") || Ty.equals("WaitQueue")) {
817+ replaceFixedNewObject(CI);
818+ } else if (Ty.equals("WeakRef")) {
819+ setHeapMallocSizeAlign(CI);
820+ replaceNewLocalWeakRefFunc(CI);
821+ } else {
822+ setHeapMallocSizeAlign(CI);
823+ replaceWithRuntimeFunc(CI, false, true);
824+ }
825+ }
826+ CI->addRetAttr(Attribute::NoAlias);
827+ }
828+ 
754private:829private:
755 Module &M;830 Module &M;
756 LLVMContext &C;831 LLVMContext &C;
@@ -763,6 +838,8 @@ private:
763 Intrinsic::ID IID = CI->getIntrinsicID();838 Intrinsic::ID IID = CI->getIntrinsicID();
764 if (IID == Intrinsic::cj_malloc_array) {839 if (IID == Intrinsic::cj_malloc_array) {
765 return getMallocArrayFuncName(CI);840 return getMallocArrayFuncName(CI);
841+ } else if (IID == Intrinsic::cj_malloc_local_array) {
842+ return getMallocLocalArrayFuncName(CI);
766 } else {843 } else {
767 auto Itr = RuntimeMap.find(IID);844 auto Itr = RuntimeMap.find(IID);
768 assert(Itr != RuntimeMap.end() && "Runtime function don`t exist.");845 assert(Itr != RuntimeMap.end() && "Runtime function don`t exist.");
@@ -802,6 +879,38 @@ private:
802 return "";879 return "";
803 }880 }
804 881 
882+ StringRef getMallocLocalArrayFuncName(CallBase *CI) const {
883+ Value *Arg0 = CI->getOperand(0);
884+ // gc.malloc.array(i64, bitcast arg1 to i8*)
885+ auto *CastExpr = dyn_cast<ConstantExpr>(Arg0);
886+ auto *KlassGV = dyn_cast<GlobalVariable>(CastExpr->getOperand(0));
887+ TypeInfo ArrayKlass(KlassGV);
888+ 
889+ Type *ET = ArrayKlass.getArrayElementType();
890+ if (ET->isPointerTy()) {
891+ return "CJ_MCC_NewLocalObjArray";
892+ } else if (ET->isStructTy() || ET->isArrayTy()) {
893+ return "CJ_MCC_NewLocalArray";
894+ } else {
895+ unsigned Size =
896+ static_cast<unsigned>(DL.getTypeAllocSize(ET).getFixedSize());
897+ switch (Size) {
898+ case 1:
899+ return "CJ_MCC_NewLocalArray8";
900+ case 2:
901+ return "CJ_MCC_NewLocalArray16";
902+ case 4:
903+ return "CJ_MCC_NewLocalArray32";
904+ case 8:
905+ return "CJ_MCC_NewLocalArray64";
906+ default:
907+ break;
908+ }
909+ }
910+ report_fatal_error("Unsupported element klass type for gc.malloc.array!");
911+ return "";
912+ }
913+ 
805 Function *getOrInsertRuntimeFunc(CallBase *CI, bool GCLeafFunc, bool GCMalloc,914 Function *getOrInsertRuntimeFunc(CallBase *CI, bool GCLeafFunc, bool GCMalloc,
806 FunctionType *FuncType = nullptr) {915 FunctionType *FuncType = nullptr) {
807 StringRef Callee = getRuntimeFuncName(CI);916 StringRef Callee = getRuntimeFuncName(CI);
@@ -1078,10 +1187,38 @@ static bool runtimeLoweringFunc(Function &F, CJIntrinsicLowering &Lowering) {
1078 CI->addRetAttr(Attribute::NoAlias);1187 CI->addRetAttr(Attribute::NoAlias);
1079 Changed = true;1188 Changed = true;
1080 break;1189 break;
1190+ case Intrinsic::cj_malloc_local_object: {
1191+ Lowering.loweringMallocLocalObject(CI);
1192+ Changed = true;
1193+ break;
1194+ }
1195+ case Intrinsic::cj_alloca_local_generic:
1196+ Lowering.setHeapMallocSizeAlign(CI);
1197+ Lowering.replaceAllocaLocalGeneric(CI, ES);
1198+ Changed = true;
1199+ break;
1200+ case Intrinsic::cj_malloc_local_array:
1201+ Lowering.replaceNewArray(CI);
1202+ CI->addRetAttr(Attribute::NoAlias);
1203+ Changed = true;
1204+ break;
1205+ case Intrinsic::cj_malloc_local_array_generic:
1206+ Lowering.replaceWithRuntimeFunc(CI, false, true);
1207+ CI->addRetAttr(Attribute::NoAlias);
1208+ Changed = true;
1209+ break;
1081 case Intrinsic::cj_invoke_gc:1210 case Intrinsic::cj_invoke_gc:
1082 Lowering.replaceWithRuntimeFunc(CI, false, false);1211 Lowering.replaceWithRuntimeFunc(CI, false, false);
1083 Changed = true;1212 Changed = true;
1084 break;1213 break;
1214+ case Intrinsic::cj_maybe_local_write_ref:
1215+ case Intrinsic::cj_maybe_local_write_generic:
1216+ case Intrinsic::cj_demode_write_ref:
1217+ // GC leaf: the local-aware writes are exported as plain aliases without a
1218+ // callee-saved-register stub, so they cannot act as safepoints.
1219+ Lowering.replaceWithRuntimeFunc(CI, true, false);
1220+ Changed = true;
1221+ break;
1085 case Intrinsic::cj_division_check_sdiv:1222 case Intrinsic::cj_division_check_sdiv:
1086 case Intrinsic::cj_division_check_udiv:1223 case Intrinsic::cj_division_check_udiv:
1087 case Intrinsic::cj_division_check_srem:1224 case Intrinsic::cj_division_check_srem:
@@ -1120,7 +1257,7 @@ PreservedAnalyses CJRuntimeLowering::run(Module &M,
1120 Changed = true;1257 Changed = true;
1121 }1258 }
1122 1259 
1123- if (!CJLTOOpt) {1260+ if (!CJLTOOpt && !F.isDeclaration()) {
1124 Changed |= runtimeLoweringFunc(F, CJLowering);1261 Changed |= runtimeLoweringFunc(F, CJLowering);
1125 }1262 }
1126 }1263 }
@@ -1159,7 +1296,7 @@ public:
1159 Changed = true;1296 Changed = true;
1160 }1297 }
1161 1298 
1162- if (!CJLTOOpt) {1299+ if (!CJLTOOpt && !F.isDeclaration()) {
1163 Changed |= runtimeLoweringFunc(F, CJLowering);1300 Changed |= runtimeLoweringFunc(F, CJLowering);
1164 }1301 }
1165 }1302 }
@@ -22,6 +22,7 @@ add_llvm_component_library(LLVMScalarOpts
22 CJGCInstrRestore.cpp22 CJGCInstrRestore.cpp
23 CJSimpleOpt.cpp23 CJSimpleOpt.cpp
24 CJGenericIntrinsicOpt.cpp24 CJGenericIntrinsicOpt.cpp
25+ CJInsertRemoveLocalFinalizer.cpp
25 CJSimpleRangeAnalysis.cpp26 CJSimpleRangeAnalysis.cpp
26 CJRSSCE.cpp27 CJRSSCE.cpp
27 CJLoopFloatOpt.cpp28 CJLoopFloatOpt.cpp
@@ -1018,7 +1018,8 @@ struct DSEState {
1018 if (ID == Intrinsic::cj_gcwrite_ref)1018 if (ID == Intrinsic::cj_gcwrite_ref)
1019 return MemoryLocation::get(II);1019 return MemoryLocation::get(II);
1020 if (ID == Intrinsic::cj_gcread_generic ||1020 if (ID == Intrinsic::cj_gcread_generic ||
1021- ID == Intrinsic::cj_assign_generic)1021+ ID == Intrinsic::cj_assign_generic ||
1022+ ID == Intrinsic::cj_assign_local_generic)
1022 return MemoryLocation::getAfter(II->getArgOperand(0));1023 return MemoryLocation::getAfter(II->getArgOperand(0));
1023 if (ID == Intrinsic::cj_gcwrite_generic)1024 if (ID == Intrinsic::cj_gcwrite_generic)
1024 return MemoryLocation::getAfter(II->getArgOperand(1));1025 return MemoryLocation::getAfter(II->getArgOperand(1));
@@ -117,6 +117,7 @@ void llvm::initializeScalarOpts(PassRegistry &Registry) {
117 initializeCJBarrierSplitLegacyPassPass(Registry);117 initializeCJBarrierSplitLegacyPassPass(Registry);
118 initializeCJLoopFloatOptLegacyPassPass(Registry);118 initializeCJLoopFloatOptLegacyPassPass(Registry);
119 initializeCJRuntimeLoweringLegacyPassPass(Registry);119 initializeCJRuntimeLoweringLegacyPassPass(Registry);
120+ initializeCJInsertRemoveLocalFinalizerLegacyPassPass(Registry);
120 initializeCangjieSpecificOptLegacyPassPass(Registry);121 initializeCangjieSpecificOptLegacyPassPass(Registry);
121 initializeInsertCJTBAALegacyPassPass(Registry);122 initializeInsertCJTBAALegacyPassPass(Registry);
122 initializeCJSimpleRangeAnalysisPass(Registry);123 initializeCJSimpleRangeAnalysisPass(Registry);
@@ -0,0 +1,287 @@
1+; RUN: opt < %s -passes=cj-insert-remove-local-finalizer -S | FileCheck %s
2+ 
3+; Every CJ_MCC_AddLocalFinalizer must be paired with a
4+; CJ_MCC_RemoveLocalFinalizer at the end of the object's live range -- on every
5+; path, exactly once. These tests pin down the placement for ordinary control
6+; flow; the exception-handling shapes live in exceptions.ll.
7+ 
8+%Cls = type { i8*, i64 }
9+ 
10+declare i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8*, i32)
11+declare void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)*)
12+declare void @use(i8 addrspace(1)*)
13+declare void @use_cls(%Cls addrspace(1)*)
14+declare i1 @cond()
15+ 
16+ 
17+; A heap finalizer is never registered as a local one, so it must be left alone.
18+ 
19+define void @heap_finalizer_untouched(i8* %ti) {
20+; CHECK-LABEL: @heap_finalizer_untouched(
21+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
22+; CHECK: ret void
23+;
24+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
25+ call void @use(i8 addrspace(1)* %obj)
26+ ret void
27+}
28+ 
29+ 
30+; The registration itself is the only use: the object is dead as soon as it is
31+; registered, so the Remove follows immediately.
32+ 
33+define void @dead_on_arrival(i8* %ti) {
34+; CHECK-LABEL: @dead_on_arrival(
35+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
36+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
37+; CHECK-NEXT: ret void
38+;
39+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
40+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
41+ ret void
42+}
43+ 
44+ 
45+; The object is only used on one arm of the branch. The other arm never touches
46+; it, but it is just as dead there, so it needs its own Remove.
47+ 
48+define void @diamond(i8* %ti, i1 %c) {
49+; CHECK-LABEL: @diamond(
50+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
51+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
52+; CHECK: then:
53+; CHECK-NEXT: call void @use(i8 addrspace(1)* %obj)
54+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
55+; CHECK: else:
56+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
57+; CHECK: exit:
58+; CHECK-NEXT: ret void
59+;
60+entry:
61+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
62+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
63+ br i1 %c, label %then, label %else
64+then:
65+ call void @use(i8 addrspace(1)* %obj)
66+ br label %exit
67+else:
68+ br label %exit
69+exit:
70+ ret void
71+}
72+ 
73+ 
74+; The object flows into phi nodes. It stays alive until the last use of the
75+; phis, and %merge is also reached from a path where the phis are *not* the
76+; object -- inserting on that incoming edge as well would remove it twice.
77+ 
78+define void @phi_merge(i8* %ti, i1 %c, i8 addrspace(1)* %other) {
79+; CHECK-LABEL: @phi_merge(
80+; CHECK: then:
81+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
82+; CHECK: else:
83+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
84+; CHECK: merge:
85+; CHECK: call void @use(i8 addrspace(1)* %p)
86+; CHECK-NEXT: call void @use(i8 addrspace(1)* %q)
87+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
88+; CHECK-NEXT: ret void
89+;
90+entry:
91+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
92+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
93+ br i1 %c, label %then, label %else
94+then:
95+ br label %merge
96+else:
97+ br label %merge
98+merge:
99+ %p = phi i8 addrspace(1)* [ %obj, %then ], [ %other, %else ]
100+ %q = phi i8 addrspace(1)* [ %obj, %then ], [ %other, %else ]
101+ call void @use(i8 addrspace(1)* %p)
102+ call void @use(i8 addrspace(1)* %q)
103+ ret void
104+}
105+ 
106+ 
107+; Same, but the last phi of the block is not the one carrying the object. The
108+; call must not land between the phi nodes -- that is invalid IR.
109+ 
110+define i32 @phi_not_last(i8* %ti, i1 %c, i8 addrspace(1)* %other, i32 %x) {
111+; CHECK-LABEL: @phi_not_last(
112+; CHECK: merge:
113+; CHECK-NEXT: %p = phi i8 addrspace(1)*
114+; CHECK-NEXT: %n = phi i32
115+; CHECK-NEXT: call void @use(i8 addrspace(1)* %p)
116+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
117+; CHECK-NEXT: ret i32 %n
118+;
119+entry:
120+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
121+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
122+ br i1 %c, label %then, label %else
123+then:
124+ br label %merge
125+else:
126+ br label %merge
127+merge:
128+ %p = phi i8 addrspace(1)* [ %obj, %then ], [ %other, %else ]
129+ %n = phi i32 [ %x, %then ], [ 0, %else ]
130+ call void @use(i8 addrspace(1)* %p)
131+ ret i32 %n
132+}
133+ 
134+ 
135+; A select may yield the object, so the object is live until the select's
136+; result is dead.
137+ 
138+define void @select_derived(i8* %ti, i1 %c, i8 addrspace(1)* %other) {
139+; CHECK-LABEL: @select_derived(
140+; CHECK: %p = select i1 %c, i8 addrspace(1)* %obj, i8 addrspace(1)* %other
141+; CHECK-NEXT: call void @use(i8 addrspace(1)* %p)
142+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
143+;
144+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
145+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
146+ %p = select i1 %c, i8 addrspace(1)* %obj, i8 addrspace(1)* %other
147+ call void @use(i8 addrspace(1)* %p)
148+ ret void
149+}
150+ 
151+ 
152+; The object is normally cast to its class type before being used, so a use of
153+; the bitcast keeps the object alive.
154+ 
155+define void @bitcast_derived(i8* %ti) {
156+; CHECK-LABEL: @bitcast_derived(
157+; CHECK: %bc = bitcast i8 addrspace(1)* %obj to %Cls addrspace(1)*
158+; CHECK-NEXT: call void @use_cls(%Cls addrspace(1)* %bc)
159+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
160+;
161+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
162+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
163+ %bc = bitcast i8 addrspace(1)* %obj to %Cls addrspace(1)*
164+ call void @use_cls(%Cls addrspace(1)* %bc)
165+ ret void
166+}
167+ 
168+ 
169+; Created before the loop and used inside it: the back edge keeps it live
170+; through the whole body, so the Remove belongs on the exit edge.
171+ 
172+define void @loop_use(i8* %ti) {
173+; CHECK-LABEL: @loop_use(
174+; CHECK: head:
175+; CHECK-NEXT: call void @use(i8 addrspace(1)* %obj)
176+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
177+; CHECK: exit:
178+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
179+; CHECK-NEXT: ret void
180+;
181+entry:
182+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
183+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
184+ br label %head
185+head:
186+ call void @use(i8 addrspace(1)* %obj)
187+ %c = call i1 @cond()
188+ br i1 %c, label %head, label %exit
189+exit:
190+ ret void
191+}
192+ 
193+ 
194+; Created *inside* the loop: a fresh object per iteration, each registered and
195+; unregistered within its own iteration.
196+ 
197+define void @loop_new(i8* %ti) {
198+; CHECK-LABEL: @loop_new(
199+; CHECK: head:
200+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
201+; CHECK-NEXT: call void @use(i8 addrspace(1)* %obj)
202+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
203+; CHECK: exit:
204+; CHECK-NEXT: ret void
205+;
206+entry:
207+ br label %head
208+head:
209+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
210+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
211+ call void @use(i8 addrspace(1)* %obj)
212+ %c = call i1 @cond()
213+ br i1 %c, label %head, label %exit
214+exit:
215+ ret void
216+}
217+ 
218+ 
219+; Stored into a local variable and read back: the object is still alive after
220+; the store, so the Remove goes after the use of the loaded value.
221+ 
222+define void @slot_roundtrip(i8* %ti) {
223+; CHECK-LABEL: @slot_roundtrip(
224+; CHECK: store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
225+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
226+; CHECK: %l = load i8 addrspace(1)*, i8 addrspace(1)** %slot
227+; CHECK-NEXT: call void @use(i8 addrspace(1)* %l)
228+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
229+;
230+ %slot = alloca i8 addrspace(1)*
231+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
232+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
233+ store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
234+ %l = load i8 addrspace(1)*, i8 addrspace(1)** %slot
235+ call void @use(i8 addrspace(1)* %l)
236+ ret void
237+}
238+ 
239+ 
240+; The load is fed by a store from either arm, so no single store defines it.
241+; Tracking the slot rather than an individual store keeps the object alive.
242+ 
243+define void @slot_memory_phi(i8* %ti, i1 %c) {
244+; CHECK-LABEL: @slot_memory_phi(
245+; CHECK: a:
246+; CHECK-NEXT: store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
247+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
248+; CHECK: b:
249+; CHECK-NEXT: store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
250+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
251+; CHECK: m:
252+; CHECK-NEXT: %l = load i8 addrspace(1)*, i8 addrspace(1)** %slot
253+; CHECK-NEXT: call void @use(i8 addrspace(1)* %l)
254+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
255+;
256+entry:
257+ %slot = alloca i8 addrspace(1)*
258+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
259+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
260+ br i1 %c, label %a, label %b
261+a:
262+ store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
263+ br label %m
264+b:
265+ store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
266+ br label %m
267+m:
268+ %l = load i8 addrspace(1)*, i8 addrspace(1)** %slot
269+ call void @use(i8 addrspace(1)* %l)
270+ ret void
271+}
272+ 
273+ 
274+; Stored into a heap field: the object outlives this function through memory we
275+; cannot follow, so it is left registered rather than unregistered too early.
276+ 
277+define void @escape_to_heap(i8* %ti, i8 addrspace(1)* addrspace(1)* %field) {
278+; CHECK-LABEL: @escape_to_heap(
279+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
280+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
281+; CHECK: ret void
282+;
283+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
284+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
285+ store i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field
286+ ret void
287+}
@@ -0,0 +1,121 @@
1+; RUN: opt < %s -passes=cj-insert-remove-local-finalizer -S | FileCheck %s
2+ 
3+; Unwinding out of a local finalizer's live range is just another way for it to
4+; die, and a landing pad shared by several invokes must still be unregistered
5+; only once.
6+;
7+; The landing pads here are `landingpad token`, which is what the Cangjie
8+; frontend emits and what CJRewriteStatepoint requires -- it uses the landing
9+; pad as the token operand of the exceptional gc.relocates it generates.
10+ 
11+declare i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8*, i32)
12+declare void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)*)
13+declare void @use(i8 addrspace(1)*)
14+declare void @mayThrow()
15+declare void @cleanupAction()
16+declare i32 @personality_function()
17+ 
18+ 
19+; The object survives the invoke on the normal path but is dead on the unwind
20+; path, which never mentions it. Without a Remove in the landing pad the
21+; registration would leak whenever the call throws.
22+ 
23+define void @live_across_invoke(i8* %ti) gc "cangjie" personality i32 ()* @personality_function {
24+; CHECK-LABEL: @live_across_invoke(
25+; CHECK: invoke void @mayThrow()
26+; CHECK: cont:
27+; CHECK-NEXT: call void @use(i8 addrspace(1)* %obj)
28+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
29+; CHECK-NEXT: ret void
30+; CHECK: lpad:
31+; CHECK-NEXT: landingpad token
32+; CHECK-NEXT: cleanup
33+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
34+; CHECK-NEXT: call void @cleanupAction()
35+;
36+entry:
37+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
38+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
39+ invoke void @mayThrow() to label %cont unwind label %lpad
40+cont:
41+ call void @use(i8 addrspace(1)* %obj)
42+ ret void
43+lpad:
44+ %l = landingpad token cleanup
45+ call void @cleanupAction()
46+ ret void
47+}
48+ 
49+ 
50+; Two invokes consume the object and share both successors. Every edge into
51+; each successor is dead, so a single Remove per successor is enough -- one per
52+; incoming edge would unregister the object twice.
53+ 
54+define void @shared_landing_pad(i8* %ti, i1 %c) gc "cangjie" personality i32 ()* @personality_function {
55+; CHECK-LABEL: @shared_landing_pad(
56+; CHECK: exit:
57+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
58+; CHECK-NEXT: ret void
59+; CHECK: lpad:
60+; CHECK-NEXT: landingpad token
61+; CHECK-NEXT: cleanup
62+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
63+; CHECK-NEXT: call void @cleanupAction()
64+;
65+entry:
66+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
67+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
68+ br i1 %c, label %b1, label %b2
69+b1:
70+ invoke void @use(i8 addrspace(1)* %obj) to label %exit unwind label %lpad
71+b2:
72+ invoke void @use(i8 addrspace(1)* %obj) to label %exit unwind label %lpad
73+exit:
74+ ret void
75+lpad:
76+ %l = landingpad token cleanup
77+ call void @cleanupAction()
78+ ret void
79+}
80+ 
81+ 
82+; Here the object only exists on the %b1 path, while both successors are also
83+; reached from %b2. The Remove cannot go into a shared successor -- %obj does
84+; not dominate it -- so both critical edges out of %b1 are split, the unwind one
85+; by cloning the landing pad.
86+ 
87+define void @split_critical_edges(i8* %ti, i1 %c) gc "cangjie" personality i32 ()* @personality_function {
88+; CHECK-LABEL: @split_critical_edges(
89+; CHECK: b1:
90+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
91+; CHECK-NEXT: invoke void @use(i8 addrspace(1)* %obj)
92+; CHECK-NEXT: to label %[[NORMAL:.*]] unwind label %[[UNWIND:.*]]
93+; CHECK: [[NORMAL]]:
94+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
95+; CHECK-NEXT: br label %exit
96+; CHECK: b2:
97+; CHECK-NEXT: invoke void @mayThrow()
98+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
99+; CHECK: exit:
100+; CHECK-NEXT: ret void
101+; CHECK: [[UNWIND]]:
102+; CHECK-NEXT: landingpad token
103+; CHECK-NEXT: cleanup
104+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
105+; CHECK-NOT: call void @CJ_MCC_RemoveLocalFinalizer
106+;
107+entry:
108+ br i1 %c, label %b1, label %b2
109+b1:
110+ %obj = call i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 32)
111+ call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
112+ invoke void @use(i8 addrspace(1)* %obj) to label %exit unwind label %lpad
113+b2:
114+ invoke void @mayThrow() to label %exit unwind label %lpad
115+exit:
116+ ret void
117+lpad:
118+ %l = landingpad token cleanup
119+ call void @cleanupAction()
120+ ret void
121+}
@@ -0,0 +1,74 @@
1+; Mirrors the real pass ordering: CJRuntimeLowering emits the registration, the
2+; optimizer runs, and only then does this pass pair it up. The point is that
3+; CJ_MCC_AddLocalFinalizer survives optimization and is still pairable, and that
4+; the pass copes with the optimized shapes (promoted slots, threaded branches)
5+; rather than the raw frontend ones.
6+;
7+; RUN: opt < %s -S \
8+; RUN: -passes='cj-runtime-lowering,function(sroa,instcombine,simplifycfg),function(cj-insert-remove-local-finalizer)' \
9+; RUN: | FileCheck %s
10+ 
11+%TypeInfo = type { i8*, i8, i8, i16, i32, i8*, i32, i8, i8, i32*, i8*, i8*, i8*, i8*, i8*, i8* }
12+ 
13+declare i8 addrspace(1)* @llvm.cj.malloc.local.object(i8*, i32)
14+declare void @use(i8 addrspace(1)*)
15+declare void @mayThrow()
16+declare void @cleanupAction()
17+declare i32 @personality_function()
18+ 
19+ 
20+; The local variable slot is promoted by SROA, so by the time this pass runs the
21+; object is plain SSA and the whole diamond has collapsed: one registration, one
22+; unregistration.
23+ 
24+define void @slot_promoted_away(i8* %ti, i1 %c) gc "cangjie" {
25+; CHECK-LABEL: @slot_promoted_away(
26+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
27+; CHECK-NEXT: call void @use(i8 addrspace(1)* %obj)
28+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
29+; CHECK-NEXT: ret void
30+;
31+entry:
32+ %slot = alloca i8 addrspace(1)*
33+ %obj = call noalias i8 addrspace(1)* @llvm.cj.malloc.local.object(i8* %ti, i32 32), !MallocType !0
34+ store i8 addrspace(1)* %obj, i8 addrspace(1)** %slot
35+ br i1 %c, label %a, label %b
36+a:
37+ br label %m
38+b:
39+ br label %m
40+m:
41+ %l = load i8 addrspace(1)*, i8 addrspace(1)** %slot
42+ call void @use(i8 addrspace(1)* %l)
43+ ret void
44+}
45+ 
46+ 
47+; Optimization does not disturb the exception edges: the object is still
48+; unregistered on both the normal and the unwind path, once each.
49+ 
50+define void @eh_survives_opt(i8* %ti) gc "cangjie" personality i32 ()* @personality_function {
51+; CHECK-LABEL: @eh_survives_opt(
52+; CHECK: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
53+; CHECK: invoke void @mayThrow()
54+; CHECK: call void @use(i8 addrspace(1)* %obj)
55+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
56+; CHECK: landingpad token
57+; CHECK-NEXT: cleanup
58+; CHECK-NEXT: call void @CJ_MCC_RemoveLocalFinalizer(i8 addrspace(1)* %obj)
59+; CHECK-NEXT: call void @cleanupAction()
60+; CHECK-NOT: call void @CJ_MCC_RemoveLocalFinalizer
61+;
62+entry:
63+ %obj = call noalias i8 addrspace(1)* @llvm.cj.malloc.local.object(i8* %ti, i32 32), !MallocType !0
64+ invoke void @mayThrow() to label %cont unwind label %lpad
65+cont:
66+ call void @use(i8 addrspace(1)* %obj)
67+ ret void
68+lpad:
69+ %l = landingpad token cleanup
70+ call void @cleanupAction()
71+ ret void
72+}
73+ 
74+!0 = !{!"HasFinalizer"}
@@ -0,0 +1,55 @@
1+; RUN: opt < %s -passes=cj-runtime-lowering -S | FileCheck %s
2+ 
3+; llvm.cj.demode.write.ref is emitted for a store into a `demode` instance field. A demoded
4+; field always has mode `~local`, so the stored value is statically known to be on the heap,
5+; while the owning object may still live in a local region -- i.e. this explicitly creates a
6+; region -> heap edge. It must be lowered to a plain call to CJ_MCC_DemodeWriteRef.
7+;
8+; Same two properties as maybe_local_write_ref.ll:
9+; 1. The argument order (obj, field, value) must be preserved -- it matches the runtime ABI
10+; and differs from llvm.cj.gcwrite.ref, which is (value, obj, field).
11+; 2. The lowering must happen in this pass rather than in CJBarrierLowering, whose fast path
12+; would replace the barrier with a plain store when the GC phase is idle, silently dropping
13+; the local-GC-root registration.
14+;
15+; It is kept separate from llvm.cj.maybe.local.write.ref so the runtime does not have to
16+; re-check whether the stored value is region-allocated.
17+ 
18+declare void @llvm.cj.demode.write.ref(i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*, i8 addrspace(1)*)
19+declare void @llvm.cj.maybe.local.write.ref(i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*, i8 addrspace(1)*)
20+declare void @llvm.cj.gcwrite.ref(i8 addrspace(1)*, i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*)
21+ 
22+define void @demode_write(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value) gc "cangjie" {
23+; CHECK-LABEL: define void @demode_write
24+; CHECK: call void @CJ_MCC_DemodeWriteRef(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value)
25+; CHECK-NOT: llvm.cj.demode.write.ref
26+entry:
27+ call void @llvm.cj.demode.write.ref(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value)
28+ ret void
29+}
30+ 
31+; The two modal barriers must stay distinct runtime calls.
32+define void @both_modal_barriers(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value) gc "cangjie" {
33+; CHECK-LABEL: define void @both_modal_barriers
34+; CHECK: call void @CJ_MCC_MaybeLocalWriteRef
35+; CHECK: call void @CJ_MCC_DemodeWriteRef
36+entry:
37+ call void @llvm.cj.maybe.local.write.ref(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value)
38+ call void @llvm.cj.demode.write.ref(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value)
39+ ret void
40+}
41+ 
42+; An ordinary reference write barrier is left untouched by this pass; it is lowered later, by
43+; CJBarrierLowering.
44+define void @ordinary_write(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value) gc "cangjie" {
45+; CHECK-LABEL: define void @ordinary_write
46+; CHECK: call void @llvm.cj.gcwrite.ref(i8 addrspace(1)* %value, i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field)
47+entry:
48+ call void @llvm.cj.gcwrite.ref(i8 addrspace(1)* %value, i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field)
49+ ret void
50+}
51+ 
52+; The runtime function must be declared as a GC leaf: CJ_MCC_DemodeWriteRef is exported as a
53+; plain alias without a callee-saved-register stub, so it cannot act as a safepoint.
54+; CHECK: declare void @CJ_MCC_DemodeWriteRef(i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*, i8 addrspace(1)*) #[[ATTR:[0-9]+]]
55+; CHECK: attributes #[[ATTR]] = {{{.*}}"gc-leaf-function"{{.*}}}
@@ -0,0 +1,40 @@
1+; RUN: opt < %s -passes=cj-runtime-lowering -S | FileCheck %s
2+ 
3+; llvm.cj.maybe.local.write.ref is emitted for a store into an instance field whose owning
4+; object may live in a local region (`this` inside `init(this @local?)`). It must be lowered
5+; to a plain call to CJ_MCC_MaybeLocalWriteRef.
6+;
7+; Two properties matter here:
8+; 1. The argument order (obj, field, value) must be preserved. It deliberately differs from
9+; llvm.cj.gcwrite.ref, which is (value, obj, field), because it matches the runtime ABI.
10+; 2. The lowering must happen in this pass rather than in CJBarrierLowering. Although this is
11+; a write barrier, it must additionally register the owning local object as a GC root
12+; unconditionally, and CJBarrierLowering's fast path would replace the barrier with a plain
13+; store whenever the GC phase is idle, silently dropping that registration.
14+ 
15+declare void @llvm.cj.maybe.local.write.ref(i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*, i8 addrspace(1)*)
16+declare void @llvm.cj.gcwrite.ref(i8 addrspace(1)*, i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*)
17+ 
18+define void @maybe_local_write(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value) gc "cangjie" {
19+; CHECK-LABEL: define void @maybe_local_write
20+; CHECK: call void @CJ_MCC_MaybeLocalWriteRef(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value)
21+; CHECK-NOT: llvm.cj.maybe.local.write.ref
22+entry:
23+ call void @llvm.cj.maybe.local.write.ref(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value)
24+ ret void
25+}
26+ 
27+; An ordinary reference write barrier is left untouched by this pass; it is lowered later, by
28+; CJBarrierLowering.
29+define void @ordinary_write(i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field, i8 addrspace(1)* %value) gc "cangjie" {
30+; CHECK-LABEL: define void @ordinary_write
31+; CHECK: call void @llvm.cj.gcwrite.ref(i8 addrspace(1)* %value, i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field)
32+entry:
33+ call void @llvm.cj.gcwrite.ref(i8 addrspace(1)* %value, i8 addrspace(1)* %obj, i8 addrspace(1)* addrspace(1)* %field)
34+ ret void
35+}
36+ 
37+; The runtime function must be declared as a GC leaf: CJ_MCC_MaybeLocalWriteRef is exported as
38+; a plain alias without a callee-saved-register stub, so it cannot act as a safepoint.
39+; CHECK: declare void @CJ_MCC_MaybeLocalWriteRef(i8 addrspace(1)*, i8 addrspace(1)* addrspace(1)*, i8 addrspace(1)*) #[[ATTR:[0-9]+]]
40+; CHECK: attributes #[[ATTR]] = {{{.*}}"gc-leaf-function"{{.*}}}
@@ -2,16 +2,38 @@
2%TypeInfo = type { i8*, i8, i8, i16, i32, i8*, i32, i8, i8, i32*, i8*, i8*, i8*, i8*, i8*, i8* }2%TypeInfo = type { i8*, i8, i8, i16, i32, i8*, i32, i8, i8, i32*, i8*, i8*, i8*, i8*, i8*, i8* }
3 3 
4declare i8 addrspace(1)* @llvm.cj.malloc.object(i8*, i32)4declare i8 addrspace(1)* @llvm.cj.malloc.object(i8*, i32)
5+declare i8 addrspace(1)* @llvm.cj.malloc.local.object(i8*, i32)
5 6 
6; NewFinalier cannot add "cj-malloc" attribute, because it may call a finalier method when destructor performing.7; NewFinalier cannot add "cj-malloc" attribute, because it may call a finalier method when destructor performing.
7 8 
9+; A heap finalizer is owned by the GC, so it is neither registered nor
10+; unregistered as a local one.
8define void @lower_new_finalier(i8* %ti, i8** %s) {11define void @lower_new_finalier(i8* %ti, i8** %s) {
9; CHECK-LABEL: @lower_new_finalier(12; CHECK-LABEL: @lower_new_finalier(
10-; CHECK: declare i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8*, i32) #113+; CHECK: %obj = call noalias i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 %{{[0-9]+}})
11-; CHECK: attributes #1 = { "cj-runtime" }14+; CHECK-NOT: @CJ_MCC_AddLocalFinalizer
15+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
16+; CHECK: ret void
12;17;
13 %obj = call noalias i8 addrspace(1)* @llvm.cj.malloc.object(i8* %ti, i32 32), !MallocType !018 %obj = call noalias i8 addrspace(1)* @llvm.cj.malloc.object(i8* %ti, i32 32), !MallocType !0
14 ret void19 ret void
15}20}
16 21 
22+; A LocalMode finalizer object is registered right after it is created. The
23+; matching CJ_MCC_RemoveLocalFinalizer is inserted later in the pipeline by
24+; cj-insert-remove-local-finalizer, so it is not expected here.
25+define void @lower_new_local_finalier(i8* %ti) {
26+; CHECK-LABEL: @lower_new_local_finalier(
27+; CHECK: %obj = call noalias i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8* %ti, i32 %{{[0-9]+}})
28+; CHECK-NEXT: call void @CJ_MCC_AddLocalFinalizer(i8 addrspace(1)* %obj)
29+; CHECK-NEXT: ret void
30+; CHECK-NOT: @CJ_MCC_RemoveLocalFinalizer
31+;
32+ %obj = call noalias i8 addrspace(1)* @llvm.cj.malloc.local.object(i8* %ti, i32 32), !MallocType !0
33+ ret void
34+}
35+ 
36+; CHECK: declare i8 addrspace(1)* @CJ_MCC_NewFinalizer(i8*, i32) #[[RT:[0-9]+]]
37+; CHECK: attributes #[[RT]] = { "cj-runtime" }
38+ 
17!0 = !{!"HasFinalizer"}39!0 = !{!"HasFinalizer"}