From: Reid Kleckner Date: Fri, 1 May 2015 22:40:25 +0000 (+0000) Subject: Re-land "[WinEH] Add an EH registration and state insertion pass for 32-bit x86" X-Git-Url: http://plrg.eecs.uci.edu/git/?p=oota-llvm.git;a=commitdiff_plain;h=2701a7ff17508dcf9971f1c84e58cc0c5d187873 Re-land "[WinEH] Add an EH registration and state insertion pass for 32-bit x86" This reverts commit r236340. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@236359 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/llvm/MC/MCAsmInfo.h b/include/llvm/MC/MCAsmInfo.h index 05da1d1871f..60a415ec68d 100644 --- a/include/llvm/MC/MCAsmInfo.h +++ b/include/llvm/MC/MCAsmInfo.h @@ -37,6 +37,7 @@ enum class EncodingType { ARM, /// Windows NT (Windows on ARM) CE, /// Windows CE ARM, PowerPC, SH3, SH4 Itanium, /// Windows x64, Windows Itanium (IA-64) + X86, /// Windows x86, uses no CFI, just EH tables MIPS = Alpha, }; } @@ -506,12 +507,13 @@ public: /// frame information to unwind. bool usesCFIForEH() const { return (ExceptionsType == ExceptionHandling::DwarfCFI || - ExceptionsType == ExceptionHandling::ARM || - ExceptionsType == ExceptionHandling::WinEH); + ExceptionsType == ExceptionHandling::ARM || usesWindowsCFI()); } bool usesWindowsCFI() const { - return ExceptionsType == ExceptionHandling::WinEH; + return ExceptionsType == ExceptionHandling::WinEH && + (WinEHEncodingType != WinEH::EncodingType::Invalid && + WinEHEncodingType != WinEH::EncodingType::X86); } bool doesDwarfUseRelocationsAcrossSections() const { diff --git a/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/lib/CodeGen/AsmPrinter/AsmPrinter.cpp index d56a1808049..f385ac3c3d5 100644 --- a/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ b/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -266,6 +266,8 @@ bool AsmPrinter::doInitialization(Module &M) { case ExceptionHandling::WinEH: switch (MAI->getWinEHEncodingType()) { default: llvm_unreachable("unsupported unwinding information encoding"); + case WinEH::EncodingType::Invalid: + break; case WinEH::EncodingType::Itanium: ES = new Win64Exception(this); break; diff --git a/lib/CodeGen/WinEHPrepare.cpp b/lib/CodeGen/WinEHPrepare.cpp index c1067c5d560..db91c02ee46 100644 --- a/lib/CodeGen/WinEHPrepare.cpp +++ b/lib/CodeGen/WinEHPrepare.cpp @@ -8,9 +8,11 @@ //===----------------------------------------------------------------------===// // // This pass lowers LLVM IR exception handling into something closer to what the -// backend wants. It snifs the personality function to see which kind of -// preparation is necessary. If the personality function uses the Itanium LSDA, -// this pass delegates to the DWARF EH preparation pass. +// backend wants for functions using a personality function from a runtime +// provided by MSVC. Functions with other personality functions are left alone +// and may be prepared by other passes. In particular, all supported MSVC +// personality functions require cleanup code to be outlined, and the C++ +// personality requires catch handler code to be outlined. // //===----------------------------------------------------------------------===// @@ -31,7 +33,6 @@ #include "llvm/IR/Module.h" #include "llvm/IR/PatternMatch.h" #include "llvm/Pass.h" -#include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" @@ -922,7 +923,7 @@ bool WinEHPrepare::prepareExceptionHandlers( if (SEHExceptionCodeSlot) { if (SEHExceptionCodeSlot->hasNUses(0)) SEHExceptionCodeSlot->eraseFromParent(); - else + else if (isAllocaPromotable(SEHExceptionCodeSlot)) PromoteMemToReg(SEHExceptionCodeSlot, *DT); } diff --git a/lib/Target/X86/CMakeLists.txt b/lib/Target/X86/CMakeLists.txt index be61b4701aa..be8e7f6b7c5 100644 --- a/lib/Target/X86/CMakeLists.txt +++ b/lib/Target/X86/CMakeLists.txt @@ -32,6 +32,7 @@ set(sources X86TargetTransformInfo.cpp X86VZeroUpper.cpp X86FixupLEAs.cpp + X86WinEHState.cpp ) if( CMAKE_CL_64 ) diff --git a/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp b/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp index e64b9635ba0..bda35f2b972 100644 --- a/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp +++ b/lib/Target/X86/MCTargetDesc/X86MCAsmInfo.cpp @@ -132,9 +132,10 @@ X86MCAsmInfoMicrosoft::X86MCAsmInfoMicrosoft(const Triple &Triple) { PrivateLabelPrefix = ".L"; PointerSize = 8; WinEHEncodingType = WinEH::EncodingType::Itanium; - ExceptionsType = ExceptionHandling::WinEH; } + ExceptionsType = ExceptionHandling::WinEH; + AssemblerDialect = AsmWriterFlavor; TextAlignFillValue = 0x90; diff --git a/lib/Target/X86/X86.h b/lib/Target/X86/X86.h index 8b0a4cf477f..62c65bacef5 100644 --- a/lib/Target/X86/X86.h +++ b/lib/Target/X86/X86.h @@ -69,6 +69,12 @@ FunctionPass *createX86FixupLEAs(); /// esp-relative movs with pushes. FunctionPass *createX86CallFrameOptimization(); +/// createX86WinEHStatePass - Return an IR pass that inserts EH registration +/// stack objects and explicit EH state updates. This pass must run after EH +/// preparation, which does Windows-specific but architecture-neutral +/// preparation. +FunctionPass *createX86WinEHStatePass(); + } // End llvm namespace #endif diff --git a/lib/Target/X86/X86TargetMachine.cpp b/lib/Target/X86/X86TargetMachine.cpp index 919072aa913..9484f678808 100644 --- a/lib/Target/X86/X86TargetMachine.cpp +++ b/lib/Target/X86/X86TargetMachine.cpp @@ -185,6 +185,7 @@ public: void addIRPasses() override; bool addInstSelector() override; bool addILPOpts() override; + bool addPreISel() override; void addPreRegAlloc() override; void addPostRegAlloc() override; void addPreEmitPass() override; @@ -220,6 +221,14 @@ bool X86PassConfig::addILPOpts() { return true; } +bool X86PassConfig::addPreISel() { + // Only add this pass for 32-bit x86. + Triple TT(TM->getTargetTriple()); + if (TT.getArch() == Triple::x86) + addPass(createX86WinEHStatePass()); + return true; +} + void X86PassConfig::addPreRegAlloc() { addPass(createX86CallFrameOptimization()); } diff --git a/lib/Target/X86/X86WinEHState.cpp b/lib/Target/X86/X86WinEHState.cpp new file mode 100644 index 00000000000..e4bbffcc55a --- /dev/null +++ b/lib/Target/X86/X86WinEHState.cpp @@ -0,0 +1,317 @@ +//===-- X86WinEHState - Insert EH state updates for win32 exceptions ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// All functions using an MSVC EH personality use an explicitly updated state +// number stored in an exception registration stack object. The registration +// object is linked into a thread-local chain of registrations stored at fs:00. +// This pass adds the registration object and EH state updates. +// +//===----------------------------------------------------------------------===// + +#include "X86.h" +#include "llvm/Analysis/LibCallSemantics.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/CodeGen/WinEHFuncInfo.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PatternMatch.h" +#include "llvm/Pass.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/Local.h" + +using namespace llvm; +using namespace llvm::PatternMatch; + +#define DEBUG_TYPE "winehstate" + +namespace { +class WinEHStatePass : public FunctionPass { +public: + static char ID; // Pass identification, replacement for typeid. + + WinEHStatePass() : FunctionPass(ID) {} + + bool runOnFunction(Function &Fn) override; + + bool doInitialization(Module &M) override; + + bool doFinalization(Module &M) override; + + void getAnalysisUsage(AnalysisUsage &AU) const override; + + const char *getPassName() const override { + return "Windows 32-bit x86 EH state insertion"; + } + +private: + void emitExceptionRegistrationRecord(Function *F); + + void linkExceptionRegistration(IRBuilder<> &Builder, Value *RegNode, + Value *Handler); + void unlinkExceptionRegistration(IRBuilder<> &Builder, Value *RegNode); + + // Module-level type getters. + Type *getEHRegistrationType(); + Type *getSEH3RegistrationType(); + Type *getSEH4RegistrationType(); + Type *getCXXEH3RegistrationType(); + + // Per-module data. + Module *TheModule = nullptr; + StructType *EHRegistrationTy = nullptr; + StructType *CXXEH3RegistrationTy = nullptr; + StructType *SEH3RegistrationTy = nullptr; + StructType *SEH4RegistrationTy = nullptr; + + // Per-function state + EHPersonality Personality = EHPersonality::Unknown; + Function *PersonalityFn = nullptr; +}; +} + +FunctionPass *llvm::createX86WinEHStatePass() { return new WinEHStatePass(); } + +char WinEHStatePass::ID = 0; + +bool WinEHStatePass::doInitialization(Module &M) { + TheModule = &M; + return false; +} + +bool WinEHStatePass::doFinalization(Module &M) { + assert(TheModule == &M); + TheModule = nullptr; + EHRegistrationTy = nullptr; + CXXEH3RegistrationTy = nullptr; + SEH3RegistrationTy = nullptr; + SEH4RegistrationTy = nullptr; + return false; +} + +void WinEHStatePass::getAnalysisUsage(AnalysisUsage &AU) const { + // This pass should only insert a stack allocation, memory accesses, and + // framerecovers. + AU.setPreservesCFG(); +} + +bool WinEHStatePass::runOnFunction(Function &F) { + // Check the personality. Do nothing if this is not an MSVC personality. + LandingPadInst *LP = nullptr; + for (BasicBlock &BB : F) { + LP = BB.getLandingPadInst(); + if (LP) + break; + } + if (!LP) + return false; + PersonalityFn = + dyn_cast(LP->getPersonalityFn()->stripPointerCasts()); + if (!PersonalityFn) + return false; + Personality = classifyEHPersonality(PersonalityFn); + if (!isMSVCEHPersonality(Personality)) + return false; + + emitExceptionRegistrationRecord(&F); + // FIXME: State insertion. + + // Reset per-function state. + PersonalityFn = nullptr; + Personality = EHPersonality::Unknown; + return true; +} + +/// Get the common EH registration subobject: +/// struct EHRegistrationNode { +/// EHRegistrationNode *Next; +/// EXCEPTION_DISPOSITION (*Handler)(...); +/// }; +Type *WinEHStatePass::getEHRegistrationType() { + if (EHRegistrationTy) + return EHRegistrationTy; + LLVMContext &Context = TheModule->getContext(); + EHRegistrationTy = StructType::create(Context, "EHRegistrationNode"); + Type *FieldTys[] = { + EHRegistrationTy->getPointerTo(0), // EHRegistrationNode *Next + Type::getInt8PtrTy(Context) // EXCEPTION_DISPOSITION (*Handler)(...) + }; + EHRegistrationTy->setBody(FieldTys, false); + return EHRegistrationTy; +} + +/// The __CxxFrameHandler3 registration node: +/// struct CXXExceptionRegistration { +/// void *SavedESP; +/// EHRegistrationNode SubRecord; +/// int32_t TryLevel; +/// }; +Type *WinEHStatePass::getCXXEH3RegistrationType() { + if (CXXEH3RegistrationTy) + return CXXEH3RegistrationTy; + LLVMContext &Context = TheModule->getContext(); + Type *FieldTys[] = { + Type::getInt8PtrTy(Context), // void *SavedESP + getEHRegistrationType(), // EHRegistrationNode SubRecord + Type::getInt32Ty(Context) // int32_t TryLevel + }; + CXXEH3RegistrationTy = + StructType::create(FieldTys, "CXXExceptionRegistration"); + return CXXEH3RegistrationTy; +} + +/// The _except_handler3 registration node: +/// struct EH3ExceptionRegistration { +/// EHRegistrationNode SubRecord; +/// void *ScopeTable; +/// int32_t TryLevel; +/// }; +Type *WinEHStatePass::getSEH3RegistrationType() { + if (SEH3RegistrationTy) + return SEH3RegistrationTy; + LLVMContext &Context = TheModule->getContext(); + Type *FieldTys[] = { + getEHRegistrationType(), // EHRegistrationNode SubRecord + Type::getInt8PtrTy(Context), // void *ScopeTable + Type::getInt32Ty(Context) // int32_t TryLevel + }; + SEH3RegistrationTy = StructType::create(FieldTys, "EH3ExceptionRegistration"); + return SEH3RegistrationTy; +} + +/// The _except_handler4 registration node: +/// struct EH4ExceptionRegistration { +/// void *SavedESP; +/// _EXCEPTION_POINTERS *ExceptionPointers; +/// EHRegistrationNode SubRecord; +/// int32_t EncodedScopeTable; +/// int32_t TryLevel; +/// }; +Type *WinEHStatePass::getSEH4RegistrationType() { + if (SEH4RegistrationTy) + return SEH4RegistrationTy; + LLVMContext &Context = TheModule->getContext(); + Type *FieldTys[] = { + Type::getInt8PtrTy(Context), // void *SavedESP + Type::getInt8PtrTy(Context), // void *ExceptionPointers + getEHRegistrationType(), // EHRegistrationNode SubRecord + Type::getInt32Ty(Context), // int32_t EncodedScopeTable + Type::getInt32Ty(Context) // int32_t TryLevel + }; + SEH4RegistrationTy = StructType::create(FieldTys, "EH4ExceptionRegistration"); + return SEH4RegistrationTy; +} + +// Emit an exception registration record. These are stack allocations with the +// common subobject of two pointers: the previous registration record (the old +// fs:00) and the personality function for the current frame. The data before +// and after that is personality function specific. +void WinEHStatePass::emitExceptionRegistrationRecord(Function *F) { + assert(Personality == EHPersonality::MSVC_CXX || + Personality == EHPersonality::MSVC_X86SEH); + + StringRef PersonalityName = PersonalityFn->getName(); + IRBuilder<> Builder(&F->getEntryBlock(), F->getEntryBlock().begin()); + Type *Int8PtrType = Builder.getInt8PtrTy(); + Value *SubRecord = nullptr; + if (PersonalityName == "__CxxFrameHandler3") { + Type *RegNodeTy = getCXXEH3RegistrationType(); + Value *RegNode = Builder.CreateAlloca(RegNodeTy); + // FIXME: We can skip this in -GS- mode, when we figure that out. + // SavedESP = llvm.stacksave() + Value *SP = Builder.CreateCall( + Intrinsic::getDeclaration(TheModule, Intrinsic::stacksave)); + Builder.CreateStore(SP, Builder.CreateStructGEP(RegNodeTy, RegNode, 0)); + // TryLevel = -1 + Builder.CreateStore(Builder.getInt32(-1), + Builder.CreateStructGEP(RegNodeTy, RegNode, 2)); + // FIXME: 'Personality' is incorrect here. We need to generate a trampoline + // that effectively gets the LSDA. + SubRecord = Builder.CreateStructGEP(RegNodeTy, RegNode, 1); + linkExceptionRegistration(Builder, SubRecord, PersonalityFn); + } else if (PersonalityName == "_except_handler3") { + Type *RegNodeTy = getSEH3RegistrationType(); + Value *RegNode = Builder.CreateAlloca(RegNodeTy); + // TryLevel = -1 + Builder.CreateStore(Builder.getInt32(-1), + Builder.CreateStructGEP(RegNodeTy, RegNode, 2)); + // FIXME: Generalize llvm.eh.sjljl.lsda for this. + // ScopeTable = nullptr + Builder.CreateStore(Constant::getNullValue(Int8PtrType), + Builder.CreateStructGEP(RegNodeTy, RegNode, 1)); + SubRecord = Builder.CreateStructGEP(RegNodeTy, RegNode, 0); + linkExceptionRegistration(Builder, SubRecord, PersonalityFn); + } else if (PersonalityName == "_except_handler4") { + Type *RegNodeTy = getSEH4RegistrationType(); + Value *RegNode = Builder.CreateAlloca(RegNodeTy); + // SavedESP = llvm.stacksave() + Value *SP = Builder.CreateCall( + Intrinsic::getDeclaration(TheModule, Intrinsic::stacksave)); + Builder.CreateStore(SP, Builder.CreateStructGEP(RegNodeTy, RegNode, 0)); + // TryLevel = -2 + Builder.CreateStore(Builder.getInt32(-2), + Builder.CreateStructGEP(RegNodeTy, RegNode, 4)); + // FIXME: Generalize llvm.eh.sjljl.lsda for this, and then do the stack + // cookie xor. + // ScopeTable = nullptr + Builder.CreateStore(Builder.getInt32(0), + Builder.CreateStructGEP(RegNodeTy, RegNode, 3)); + SubRecord = Builder.CreateStructGEP(RegNodeTy, RegNode, 2); + linkExceptionRegistration(Builder, SubRecord, PersonalityFn); + } else { + llvm_unreachable("unexpected personality function"); + } + + // FIXME: Insert an unlink before all returns. + for (BasicBlock &BB : *F) { + TerminatorInst *T = BB.getTerminator(); + if (!isa(T)) + continue; + Builder.SetInsertPoint(T); + unlinkExceptionRegistration(Builder, SubRecord); + } +} + +void WinEHStatePass::linkExceptionRegistration(IRBuilder<> &Builder, + Value *RegNode, Value *Handler) { + Type *RegNodeTy = getEHRegistrationType(); + // Handler = Handler + Handler = Builder.CreateBitCast(Handler, Builder.getInt8PtrTy()); + Builder.CreateStore(Handler, Builder.CreateStructGEP(RegNodeTy, RegNode, 1)); + // Next = [fs:00] + Constant *FSZero = + Constant::getNullValue(RegNodeTy->getPointerTo()->getPointerTo(257)); + Value *Next = Builder.CreateLoad(FSZero); + Builder.CreateStore(Next, Builder.CreateStructGEP(RegNodeTy, RegNode, 0)); + // [fs:00] = RegNode + Builder.CreateStore(RegNode, FSZero); +} + +void WinEHStatePass::unlinkExceptionRegistration(IRBuilder<> &Builder, + Value *RegNode) { + // Clone RegNode into the current BB for better address mode folding. + if (auto *GEP = dyn_cast(RegNode)) { + GEP = cast(GEP->clone()); + Builder.Insert(GEP); + RegNode = GEP; + } + Type *RegNodeTy = getEHRegistrationType(); + // [fs:00] = RegNode->Next + Value *Next = + Builder.CreateLoad(Builder.CreateStructGEP(RegNodeTy, RegNode, 0)); + Constant *FSZero = + Constant::getNullValue(RegNodeTy->getPointerTo()->getPointerTo(257)); + Builder.CreateStore(Next, FSZero); +} diff --git a/test/CodeGen/X86/inalloca-invoke.ll b/test/CodeGen/X86/inalloca-invoke.ll index d6fc76ee50b..cf5cbe142ec 100644 --- a/test/CodeGen/X86/inalloca-invoke.ll +++ b/test/CodeGen/X86/inalloca-invoke.ll @@ -4,6 +4,7 @@ %frame.reverse = type { %Iter, %Iter } +declare i32 @pers(...) declare void @llvm.stackrestore(i8*) declare i8* @llvm.stacksave() declare void @begin(%Iter* sret) @@ -22,8 +23,7 @@ blah: ; CHECK: calll __chkstk ; CHECK: movl %esp, %[[beg:[^ ]*]] -; CHECK: movl %esp, %[[end:[^ ]*]] -; CHECK: addl $12, %[[end]] +; CHECK: leal 12(%[[beg]]), %[[end:[^ ]*]] call void @begin(%Iter* sret %temp.lvalue) ; CHECK: calll _begin @@ -49,7 +49,7 @@ invoke.cont5: ; preds = %invoke.cont ret i32 0 lpad: ; preds = %invoke.cont, %entry - %lp = landingpad { i8*, i32 } personality i8* null + %lp = landingpad { i8*, i32 } personality i32 (...)* @pers cleanup unreachable } diff --git a/test/CodeGen/X86/win32-eh.ll b/test/CodeGen/X86/win32-eh.ll new file mode 100644 index 00000000000..875949740ec --- /dev/null +++ b/test/CodeGen/X86/win32-eh.ll @@ -0,0 +1,77 @@ +; RUN: llc -mtriple=i686-pc-windows-msvc < %s | FileCheck %s + +declare void @may_throw_or_crash() +declare i32 @_except_handler3(...) +declare i32 @_except_handler4(...) +declare i32 @__CxxFrameHandler3(...) +declare void @llvm.eh.begincatch(i8*, i8*) +declare void @llvm.eh.endcatch() + +define void @use_except_handler3() { + invoke void @may_throw_or_crash() + to label %cont unwind label %catchall +cont: + ret void +catchall: + landingpad { i8*, i32 } personality i32 (...)* @_except_handler3 + catch i8* null + br label %cont +} + +; CHECK-LABEL: _use_except_handler3: +; CHECK: subl ${{[0-9]+}}, %esp +; CHECK: movl %fs:0, %[[next:[^ ,]*]] +; CHECK: movl %[[next]], (%esp) +; CHECK: leal (%esp), %[[node:[^ ,]*]] +; CHECK: movl %[[node]], %fs:0 +; CHECK: calll _may_throw_or_crash +; CHECK: movl (%esp), %[[next:[^ ,]*]] +; CHECK: movl %[[next]], %fs:0 +; CHECK: retl + +define void @use_except_handler4() { + invoke void @may_throw_or_crash() + to label %cont unwind label %catchall +cont: + ret void +catchall: + landingpad { i8*, i32 } personality i32 (...)* @_except_handler4 + catch i8* null + br label %cont +} + +; CHECK-LABEL: _use_except_handler4: +; CHECK: subl ${{[0-9]+}}, %esp +; CHECK: leal 8(%esp), %[[node:[^ ,]*]] +; CHECK: movl %fs:0, %[[next:[^ ,]*]] +; CHECK: movl %[[next]], 8(%esp) +; CHECK: movl %[[node]], %fs:0 +; CHECK: calll _may_throw_or_crash +; CHECK: movl 8(%esp), %[[next:[^ ,]*]] +; CHECK: movl %[[next]], %fs:0 +; CHECK: retl + +define void @use_CxxFrameHandler3() { + invoke void @may_throw_or_crash() + to label %cont unwind label %catchall +cont: + ret void +catchall: + %ehvals = landingpad { i8*, i32 } personality i32 (...)* @__CxxFrameHandler3 + catch i8* null + %ehptr = extractvalue { i8*, i32 } %ehvals, 0 + call void @llvm.eh.begincatch(i8* %ehptr, i8* null) + call void @llvm.eh.endcatch() + br label %cont +} + +; CHECK-LABEL: _use_CxxFrameHandler3: +; CHECK: subl ${{[0-9]+}}, %esp +; CHECK: leal 4(%esp), %[[node:[^ ,]*]] +; CHECK: movl %fs:0, %[[next:[^ ,]*]] +; CHECK: movl %[[next]], 4(%esp) +; CHECK: movl %[[node]], %fs:0 +; CHECK: calll _may_throw_or_crash +; CHECK: movl 4(%esp), %[[next:[^ ,]*]] +; CHECK: movl %[[next]], %fs:0 +; CHECK: retl