MCJIT.cpp revision c0ceedb6f885b1cbd3d3cea02f695afe393dfd2c
1bb498ca5c2acb7567d8d4d84b00229bed6f501b1Eric Christopher//===-- MCJIT.cpp - MC-based Just-in-Time Compiler ------------------------===// 26aec29848676494867e26307698155bc2c5a4033Daniel Dunbar// 36aec29848676494867e26307698155bc2c5a4033Daniel Dunbar// The LLVM Compiler Infrastructure 46aec29848676494867e26307698155bc2c5a4033Daniel Dunbar// 56aec29848676494867e26307698155bc2c5a4033Daniel Dunbar// This file is distributed under the University of Illinois Open Source 66aec29848676494867e26307698155bc2c5a4033Daniel Dunbar// License. See LICENSE.TXT for details. 76aec29848676494867e26307698155bc2c5a4033Daniel Dunbar// 86aec29848676494867e26307698155bc2c5a4033Daniel Dunbar//===----------------------------------------------------------------------===// 96aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 106aec29848676494867e26307698155bc2c5a4033Daniel Dunbar#include "MCJIT.h" 11fcbe5b71936b820647dffff0e4f9c60ece3988a5Jim Grosbach#include "MCJITMemoryManager.h" 1234714a06096f854c76371295d8c20b017fbba50bJim Grosbach#include "llvm/DerivedTypes.h" 1331649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach#include "llvm/Function.h" 146aec29848676494867e26307698155bc2c5a4033Daniel Dunbar#include "llvm/ExecutionEngine/GenericValue.h" 156aec29848676494867e26307698155bc2c5a4033Daniel Dunbar#include "llvm/ExecutionEngine/MCJIT.h" 16f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach#include "llvm/ExecutionEngine/JITMemoryManager.h" 17f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach#include "llvm/MC/MCAsmInfo.h" 186aec29848676494867e26307698155bc2c5a4033Daniel Dunbar#include "llvm/Support/ErrorHandling.h" 191f6efa3996dd1929fbc129203ce5009b620e6969Michael J. Spencer#include "llvm/Support/DynamicLibrary.h" 20f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach#include "llvm/Support/MemoryBuffer.h" 2131649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach#include "llvm/Target/TargetData.h" 226aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 236aec29848676494867e26307698155bc2c5a4033Daniel Dunbarusing namespace llvm; 246aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 256aec29848676494867e26307698155bc2c5a4033Daniel Dunbarnamespace { 266aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 276aec29848676494867e26307698155bc2c5a4033Daniel Dunbarstatic struct RegisterJIT { 286aec29848676494867e26307698155bc2c5a4033Daniel Dunbar RegisterJIT() { MCJIT::Register(); } 296aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} JITRegistrator; 306aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 316aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 326aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 336aec29848676494867e26307698155bc2c5a4033Daniel Dunbarextern "C" void LLVMLinkInMCJIT() { 346aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 356aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 366aec29848676494867e26307698155bc2c5a4033Daniel DunbarExecutionEngine *MCJIT::createJIT(Module *M, 376aec29848676494867e26307698155bc2c5a4033Daniel Dunbar std::string *ErrorStr, 386aec29848676494867e26307698155bc2c5a4033Daniel Dunbar JITMemoryManager *JMM, 396aec29848676494867e26307698155bc2c5a4033Daniel Dunbar CodeGenOpt::Level OptLevel, 406aec29848676494867e26307698155bc2c5a4033Daniel Dunbar bool GVsWithCode, 41c5b28580a94e247300e5d3ccf532e153f2ae6f12Dylan Noblesmith TargetMachine *TM) { 426aec29848676494867e26307698155bc2c5a4033Daniel Dunbar // Try to register the program as a source of symbols to resolve against. 436aec29848676494867e26307698155bc2c5a4033Daniel Dunbar // 446aec29848676494867e26307698155bc2c5a4033Daniel Dunbar // FIXME: Don't do this here. 456aec29848676494867e26307698155bc2c5a4033Daniel Dunbar sys::DynamicLibrary::LoadLibraryPermanently(0, NULL); 466aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 476aec29848676494867e26307698155bc2c5a4033Daniel Dunbar // If the target supports JIT code generation, create the JIT. 486aec29848676494867e26307698155bc2c5a4033Daniel Dunbar if (TargetJITInfo *TJ = TM->getJITInfo()) 49c154514e2daf5497141980544f6b0b03a8e6c37cJim Grosbach return new MCJIT(M, TM, *TJ, new MCJITMemoryManager(JMM, M), OptLevel, 50fcbe5b71936b820647dffff0e4f9c60ece3988a5Jim Grosbach GVsWithCode); 516aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 526aec29848676494867e26307698155bc2c5a4033Daniel Dunbar if (ErrorStr) 536aec29848676494867e26307698155bc2c5a4033Daniel Dunbar *ErrorStr = "target does not support JIT code generation"; 546aec29848676494867e26307698155bc2c5a4033Daniel Dunbar return 0; 556aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 566aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 5731649e61bcead26a63c7cd452da90fff5e000b91Jim GrosbachMCJIT::MCJIT(Module *m, TargetMachine *tm, TargetJITInfo &tji, 58fcbe5b71936b820647dffff0e4f9c60ece3988a5Jim Grosbach RTDyldMemoryManager *MM, CodeGenOpt::Level OptLevel, 596aec29848676494867e26307698155bc2c5a4033Daniel Dunbar bool AllocateGVsWithCode) 60fcbe5b71936b820647dffff0e4f9c60ece3988a5Jim Grosbach : ExecutionEngine(m), TM(tm), MemMgr(MM), M(m), OS(Buffer), Dyld(MM) { 6131649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach 6231649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach PM.add(new TargetData(*TM->getTargetData())); 6331649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach 6431649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // Turn the machine code intermediate representation into bytes in memory 6531649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // that may be executed. 6631649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach if (TM->addPassesToEmitMC(PM, Ctx, OS, CodeGenOpt::Default, false)) { 6731649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach report_fatal_error("Target does not support MC emission!"); 6831649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach } 6931649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach 7031649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // Initialize passes. 7131649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // FIXME: When we support multiple modules, we'll want to move the code 7231649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // gen and finalization out of the constructor here and do it more 7331649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // on-demand as part of getPointerToFunction(). 7431649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach PM.run(*M); 7531649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach // Flush the output buffer so the SmallVector gets its data. 7631649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach OS.flush(); 77f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach 78f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach // Load the object into the dynamic linker. 79f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach // FIXME: It would be nice to avoid making yet another copy. 80f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach MemoryBuffer *MB = MemoryBuffer::getMemBufferCopy(StringRef(Buffer.data(), 81f922910494377909b4cf2a0b73f509b2b1925799Jim Grosbach Buffer.size())); 828086f3b49429e02603270c8e09e2aabac9215a21Jim Grosbach if (Dyld.loadObject(MB)) 838086f3b49429e02603270c8e09e2aabac9215a21Jim Grosbach report_fatal_error(Dyld.getErrorString()); 8469e813282d4aa078102ce058f8269d0c13260061Jim Grosbach // Resolve any relocations. 8569e813282d4aa078102ce058f8269d0c13260061Jim Grosbach Dyld.resolveRelocations(); 866aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 876aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 886aec29848676494867e26307698155bc2c5a4033Daniel DunbarMCJIT::~MCJIT() { 89fcbe5b71936b820647dffff0e4f9c60ece3988a5Jim Grosbach delete MemMgr; 906aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 916aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 926aec29848676494867e26307698155bc2c5a4033Daniel Dunbarvoid *MCJIT::getPointerToBasicBlock(BasicBlock *BB) { 936aec29848676494867e26307698155bc2c5a4033Daniel Dunbar report_fatal_error("not yet implemented"); 946aec29848676494867e26307698155bc2c5a4033Daniel Dunbar return 0; 956aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 966aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 976aec29848676494867e26307698155bc2c5a4033Daniel Dunbarvoid *MCJIT::getPointerToFunction(Function *F) { 9834714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (F->isDeclaration() || F->hasAvailableExternallyLinkage()) { 9934714a06096f854c76371295d8c20b017fbba50bJim Grosbach bool AbortOnFailure = !F->hasExternalWeakLinkage(); 10034714a06096f854c76371295d8c20b017fbba50bJim Grosbach void *Addr = getPointerToNamedFunction(F->getName(), AbortOnFailure); 10134714a06096f854c76371295d8c20b017fbba50bJim Grosbach addGlobalMapping(F, Addr); 10234714a06096f854c76371295d8c20b017fbba50bJim Grosbach return Addr; 10334714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 10434714a06096f854c76371295d8c20b017fbba50bJim Grosbach 1053ec2c7c3e48f1fbab749870c51a74920f91c82c1Jim Grosbach // FIXME: Should we be using the mangler for this? Probably. 1063ec2c7c3e48f1fbab749870c51a74920f91c82c1Jim Grosbach StringRef BaseName = F->getName(); 1073ec2c7c3e48f1fbab749870c51a74920f91c82c1Jim Grosbach if (BaseName[0] == '\1') 108c0ceedb6f885b1cbd3d3cea02f695afe393dfd2cJim Grosbach return (void*)Dyld.getSymbolAddress(BaseName.substr(1)); 109c0ceedb6f885b1cbd3d3cea02f695afe393dfd2cJim Grosbach return (void*)Dyld.getSymbolAddress((TM->getMCAsmInfo()->getGlobalPrefix() 110c0ceedb6f885b1cbd3d3cea02f695afe393dfd2cJim Grosbach + BaseName).str()); 1116aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 1126aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 1136aec29848676494867e26307698155bc2c5a4033Daniel Dunbarvoid *MCJIT::recompileAndRelinkFunction(Function *F) { 1146aec29848676494867e26307698155bc2c5a4033Daniel Dunbar report_fatal_error("not yet implemented"); 1156aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 1166aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 1176aec29848676494867e26307698155bc2c5a4033Daniel Dunbarvoid MCJIT::freeMachineCodeForFunction(Function *F) { 1186aec29848676494867e26307698155bc2c5a4033Daniel Dunbar report_fatal_error("not yet implemented"); 1196aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 1206aec29848676494867e26307698155bc2c5a4033Daniel Dunbar 1216aec29848676494867e26307698155bc2c5a4033Daniel DunbarGenericValue MCJIT::runFunction(Function *F, 1226aec29848676494867e26307698155bc2c5a4033Daniel Dunbar const std::vector<GenericValue> &ArgValues) { 12334714a06096f854c76371295d8c20b017fbba50bJim Grosbach assert(F && "Function *F was null at entry to run()"); 12434714a06096f854c76371295d8c20b017fbba50bJim Grosbach 12531649e61bcead26a63c7cd452da90fff5e000b91Jim Grosbach void *FPtr = getPointerToFunction(F); 12634714a06096f854c76371295d8c20b017fbba50bJim Grosbach assert(FPtr && "Pointer to fn's code was null after getPointerToFunction"); 12734714a06096f854c76371295d8c20b017fbba50bJim Grosbach const FunctionType *FTy = F->getFunctionType(); 12834714a06096f854c76371295d8c20b017fbba50bJim Grosbach const Type *RetTy = FTy->getReturnType(); 12934714a06096f854c76371295d8c20b017fbba50bJim Grosbach 13034714a06096f854c76371295d8c20b017fbba50bJim Grosbach assert((FTy->getNumParams() == ArgValues.size() || 13134714a06096f854c76371295d8c20b017fbba50bJim Grosbach (FTy->isVarArg() && FTy->getNumParams() <= ArgValues.size())) && 13234714a06096f854c76371295d8c20b017fbba50bJim Grosbach "Wrong number of arguments passed into function!"); 13334714a06096f854c76371295d8c20b017fbba50bJim Grosbach assert(FTy->getNumParams() == ArgValues.size() && 13434714a06096f854c76371295d8c20b017fbba50bJim Grosbach "This doesn't support passing arguments through varargs (yet)!"); 13534714a06096f854c76371295d8c20b017fbba50bJim Grosbach 13634714a06096f854c76371295d8c20b017fbba50bJim Grosbach // Handle some common cases first. These cases correspond to common `main' 13734714a06096f854c76371295d8c20b017fbba50bJim Grosbach // prototypes. 13834714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (RetTy->isIntegerTy(32) || RetTy->isVoidTy()) { 13934714a06096f854c76371295d8c20b017fbba50bJim Grosbach switch (ArgValues.size()) { 14034714a06096f854c76371295d8c20b017fbba50bJim Grosbach case 3: 14134714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (FTy->getParamType(0)->isIntegerTy(32) && 14234714a06096f854c76371295d8c20b017fbba50bJim Grosbach FTy->getParamType(1)->isPointerTy() && 14334714a06096f854c76371295d8c20b017fbba50bJim Grosbach FTy->getParamType(2)->isPointerTy()) { 14434714a06096f854c76371295d8c20b017fbba50bJim Grosbach int (*PF)(int, char **, const char **) = 14534714a06096f854c76371295d8c20b017fbba50bJim Grosbach (int(*)(int, char **, const char **))(intptr_t)FPtr; 14634714a06096f854c76371295d8c20b017fbba50bJim Grosbach 14734714a06096f854c76371295d8c20b017fbba50bJim Grosbach // Call the function. 14834714a06096f854c76371295d8c20b017fbba50bJim Grosbach GenericValue rv; 14934714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(32, PF(ArgValues[0].IntVal.getZExtValue(), 15034714a06096f854c76371295d8c20b017fbba50bJim Grosbach (char **)GVTOP(ArgValues[1]), 15134714a06096f854c76371295d8c20b017fbba50bJim Grosbach (const char **)GVTOP(ArgValues[2]))); 15234714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 15334714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 15434714a06096f854c76371295d8c20b017fbba50bJim Grosbach break; 15534714a06096f854c76371295d8c20b017fbba50bJim Grosbach case 2: 15634714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (FTy->getParamType(0)->isIntegerTy(32) && 15734714a06096f854c76371295d8c20b017fbba50bJim Grosbach FTy->getParamType(1)->isPointerTy()) { 15834714a06096f854c76371295d8c20b017fbba50bJim Grosbach int (*PF)(int, char **) = (int(*)(int, char **))(intptr_t)FPtr; 15934714a06096f854c76371295d8c20b017fbba50bJim Grosbach 16034714a06096f854c76371295d8c20b017fbba50bJim Grosbach // Call the function. 16134714a06096f854c76371295d8c20b017fbba50bJim Grosbach GenericValue rv; 16234714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(32, PF(ArgValues[0].IntVal.getZExtValue(), 16334714a06096f854c76371295d8c20b017fbba50bJim Grosbach (char **)GVTOP(ArgValues[1]))); 16434714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 16534714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 16634714a06096f854c76371295d8c20b017fbba50bJim Grosbach break; 16734714a06096f854c76371295d8c20b017fbba50bJim Grosbach case 1: 16834714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (FTy->getNumParams() == 1 && 16934714a06096f854c76371295d8c20b017fbba50bJim Grosbach FTy->getParamType(0)->isIntegerTy(32)) { 17034714a06096f854c76371295d8c20b017fbba50bJim Grosbach GenericValue rv; 17134714a06096f854c76371295d8c20b017fbba50bJim Grosbach int (*PF)(int) = (int(*)(int))(intptr_t)FPtr; 17234714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(32, PF(ArgValues[0].IntVal.getZExtValue())); 17334714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 17434714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 17534714a06096f854c76371295d8c20b017fbba50bJim Grosbach break; 17634714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 17734714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 17834714a06096f854c76371295d8c20b017fbba50bJim Grosbach 17934714a06096f854c76371295d8c20b017fbba50bJim Grosbach // Handle cases where no arguments are passed first. 18034714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (ArgValues.empty()) { 18134714a06096f854c76371295d8c20b017fbba50bJim Grosbach GenericValue rv; 18234714a06096f854c76371295d8c20b017fbba50bJim Grosbach switch (RetTy->getTypeID()) { 18334714a06096f854c76371295d8c20b017fbba50bJim Grosbach default: llvm_unreachable("Unknown return type for function call!"); 18434714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::IntegerTyID: { 18534714a06096f854c76371295d8c20b017fbba50bJim Grosbach unsigned BitWidth = cast<IntegerType>(RetTy)->getBitWidth(); 18634714a06096f854c76371295d8c20b017fbba50bJim Grosbach if (BitWidth == 1) 18734714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(BitWidth, ((bool(*)())(intptr_t)FPtr)()); 18834714a06096f854c76371295d8c20b017fbba50bJim Grosbach else if (BitWidth <= 8) 18934714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(BitWidth, ((char(*)())(intptr_t)FPtr)()); 19034714a06096f854c76371295d8c20b017fbba50bJim Grosbach else if (BitWidth <= 16) 19134714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(BitWidth, ((short(*)())(intptr_t)FPtr)()); 19234714a06096f854c76371295d8c20b017fbba50bJim Grosbach else if (BitWidth <= 32) 19334714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(BitWidth, ((int(*)())(intptr_t)FPtr)()); 19434714a06096f854c76371295d8c20b017fbba50bJim Grosbach else if (BitWidth <= 64) 19534714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(BitWidth, ((int64_t(*)())(intptr_t)FPtr)()); 19634714a06096f854c76371295d8c20b017fbba50bJim Grosbach else 19734714a06096f854c76371295d8c20b017fbba50bJim Grosbach llvm_unreachable("Integer types > 64 bits not supported"); 19834714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 19934714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 20034714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::VoidTyID: 20134714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.IntVal = APInt(32, ((int(*)())(intptr_t)FPtr)()); 20234714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 20334714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::FloatTyID: 20434714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.FloatVal = ((float(*)())(intptr_t)FPtr)(); 20534714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 20634714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::DoubleTyID: 20734714a06096f854c76371295d8c20b017fbba50bJim Grosbach rv.DoubleVal = ((double(*)())(intptr_t)FPtr)(); 20834714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 20934714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::X86_FP80TyID: 21034714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::FP128TyID: 21134714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::PPC_FP128TyID: 21234714a06096f854c76371295d8c20b017fbba50bJim Grosbach llvm_unreachable("long double not supported yet"); 21334714a06096f854c76371295d8c20b017fbba50bJim Grosbach return rv; 21434714a06096f854c76371295d8c20b017fbba50bJim Grosbach case Type::PointerTyID: 21534714a06096f854c76371295d8c20b017fbba50bJim Grosbach return PTOGV(((void*(*)())(intptr_t)FPtr)()); 21634714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 21734714a06096f854c76371295d8c20b017fbba50bJim Grosbach } 21834714a06096f854c76371295d8c20b017fbba50bJim Grosbach 21934714a06096f854c76371295d8c20b017fbba50bJim Grosbach assert("Full-featured argument passing not supported yet!"); 2206aec29848676494867e26307698155bc2c5a4033Daniel Dunbar return GenericValue(); 2216aec29848676494867e26307698155bc2c5a4033Daniel Dunbar} 222