DiffConsumer.cpp revision bd3c5ecd37e760f0430c9cbd1fda5740eb7c0e27
17d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin//===-- DiffConsumer.cpp - Difference Consumer ------------------*- C++ -*-===// 27d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// 37d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// The LLVM Compiler Infrastructure 47d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// 57d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// This file is distributed under the University of Illinois Open Source 67d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// License. See LICENSE.TXT for details. 77d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// 87d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin//===----------------------------------------------------------------------===// 97d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// 107d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// This files implements the the LLVM difference Consumer 117d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin// 127d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin//===----------------------------------------------------------------------===// 137d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 147d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin#include "DiffConsumer.h" 157d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 167d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin#include "llvm/Module.h" 177d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin#include "llvm/Instructions.h" 187d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin#include "llvm/Support/ErrorHandling.h" 197d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 207d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinusing namespace llvm; 217d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 227d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinstatic void ComputeNumbering(Function *F, DenseMap<Value*,unsigned> &Numbering){ 237d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin unsigned IN = 0; 247d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 257d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin // Arguments get the first numbers. 267d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin for (Function::arg_iterator 277d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin AI = F->arg_begin(), AE = F->arg_end(); AI != AE; ++AI) 287d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (!AI->hasName()) 297d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Numbering[&*AI] = IN++; 307d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 317d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin // Walk the basic blocks in order. 327d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin for (Function::iterator FI = F->begin(), FE = F->end(); FI != FE; ++FI) { 337d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (!FI->hasName()) 347d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Numbering[&*FI] = IN++; 357d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 367d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin // Walk the instructions in order. 377d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin for (BasicBlock::iterator BI = FI->begin(), BE = FI->end(); BI != BE; ++BI) 387d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin // void instructions don't get numbers. 397d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (!BI->hasName() && !BI->getType()->isVoidTy()) 407d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Numbering[&*BI] = IN++; 417d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 427d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 437d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin assert(!Numbering.empty() && "asked for numbering but numbering was no-op"); 447d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 457d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 467d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 477d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::printValue(Value *V, bool isL) { 487d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (V->hasName()) { 497d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << (isa<GlobalValue>(V) ? '@' : '%') << V->getName(); 507d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin return; 517d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 527d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (V->getType()->isVoidTy()) { 537d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (isa<StoreInst>(V)) { 547d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "store to "; 557d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(cast<StoreInst>(V)->getPointerOperand(), isL); 567d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } else if (isa<CallInst>(V)) { 577d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "call to "; 587d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(cast<CallInst>(V)->getCalledValue(), isL); 597d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } else if (isa<InvokeInst>(V)) { 607d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "invoke to "; 617d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(cast<InvokeInst>(V)->getCalledValue(), isL); 627d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } else { 637d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << *V; 647d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 657d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin return; 667d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 67bd3c5ecd37e760f0430c9cbd1fda5740eb7c0e27John McCall if (dyn_cast<Constant>(V)) { 68bd3c5ecd37e760f0430c9cbd1fda5740eb7c0e27John McCall out << *V; 69bd3c5ecd37e760f0430c9cbd1fda5740eb7c0e27John McCall return; 70bd3c5ecd37e760f0430c9cbd1fda5740eb7c0e27John McCall } 717d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 727d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin unsigned N = contexts.size(); 737d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin while (N > 0) { 747d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin --N; 757d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin DiffContext &ctxt = contexts[N]; 767d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (!ctxt.IsFunction) continue; 777d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (isL) { 787d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (ctxt.LNumbering.empty()) 797d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin ComputeNumbering(cast<Function>(ctxt.L), ctxt.LNumbering); 807d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << '%' << ctxt.LNumbering[V]; 817d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin return; 827d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } else { 837d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (ctxt.RNumbering.empty()) 847d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin ComputeNumbering(cast<Function>(ctxt.R), ctxt.RNumbering); 857d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << '%' << ctxt.RNumbering[V]; 867d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin return; 877d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 887d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 897d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 907d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "<anonymous>"; 917d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 927d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 937d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::header() { 947d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (contexts.empty()) return; 957d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin for (SmallVectorImpl<DiffContext>::iterator 967d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin I = contexts.begin(), E = contexts.end(); I != E; ++I) { 977d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (I->Differences) continue; 987d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (isa<Function>(I->L)) { 997d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin // Extra newline between functions. 1007d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (Differences) out << "\n"; 1017d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1027d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Function *L = cast<Function>(I->L); 1037d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Function *R = cast<Function>(I->R); 1047d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (L->getName() != R->getName()) 1057d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "in function " << L->getName() 1067d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin << " / " << R->getName() << ":\n"; 1077d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin else 1087d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "in function " << L->getName() << ":\n"; 1097d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } else if (isa<BasicBlock>(I->L)) { 1107d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin BasicBlock *L = cast<BasicBlock>(I->L); 1117d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin BasicBlock *R = cast<BasicBlock>(I->R); 1127d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (L->hasName() && R->hasName() && L->getName() == R->getName()) 1137d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << " in block %" << L->getName() << ":\n"; 1147d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin else { 1157d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << " in block "; 1167d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(L, true); 1177d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << " / "; 1187d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(R, false); 1197d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << ":\n"; 1207d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 1217d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } else if (isa<Instruction>(I->L)) { 1227d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << " in instruction "; 1237d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(I->L, true); 1247d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << " / "; 1257d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin printValue(I->R, false); 1267d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << ":\n"; 1277d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 1287d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1297d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin I->Differences = true; 1307d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 1317d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1327d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1337d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::indent() { 1347d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin unsigned N = Indent; 1357d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin while (N--) out << ' '; 1367d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1377d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1387d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinbool DiffConsumer::hadDifferences() const { 1397d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin return Differences; 1407d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1417d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1427d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::enterContext(Value *L, Value *R) { 1437d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin contexts.push_back(DiffContext(L, R)); 1447d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Indent += 2; 1457d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1467d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1477d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::exitContext() { 1487d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Differences |= contexts.back().Differences; 1497d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin contexts.pop_back(); 1507d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Indent -= 2; 1517d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1527d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1537d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::log(StringRef text) { 1547d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin header(); 1557d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin indent(); 1567d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << text << '\n'; 1577d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1587d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1597d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::logf(const LogBuilder &Log) { 1607d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin header(); 1617d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin indent(); 1627d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1637d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin unsigned arg = 0; 1647d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1657d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin StringRef format = Log.getFormat(); 1667d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin while (true) { 1677d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin size_t percent = format.find('%'); 1687d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (percent == StringRef::npos) { 1697d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << format; 1707d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin break; 1717d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 1727d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin assert(format[percent] == '%'); 1737d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1747d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin if (percent > 0) out << format.substr(0, percent); 1757d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1767d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin switch (format[percent+1]) { 1777d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin case '%': out << '%'; break; 1787d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin case 'l': printValue(Log.getArgument(arg++), true); break; 1797d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin case 'r': printValue(Log.getArgument(arg++), false); break; 1807d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin default: llvm_unreachable("unknown format character"); 1817d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 1827d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1837d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin format = format.substr(percent+2); 1847d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 1857d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1867d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << '\n'; 1877d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 1887d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1897d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golinvoid DiffConsumer::logd(const DiffLogBuilder &Log) { 1907d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin header(); 1917d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin 1927d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin for (unsigned I = 0, E = Log.getNumLines(); I != E; ++I) { 1937d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin indent(); 1947d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin switch (Log.getLineKind(I)) { 1957d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin case DC_match: 1967d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << " "; 1977d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Log.getLeft(I)->dump(); 1987d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin //printValue(Log.getLeft(I), true); 1997d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin break; 2007d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin case DC_left: 2017d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "< "; 2027d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Log.getLeft(I)->dump(); 2037d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin //printValue(Log.getLeft(I), true); 2047d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin break; 2057d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin case DC_right: 2067d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin out << "> "; 2077d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin Log.getRight(I)->dump(); 2087d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin //printValue(Log.getRight(I), false); 2097d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin break; 2107d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 2117d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin //out << "\n"; 2127d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin } 2137d4fc4fb345ee8a1de15c718a854b5f38c1e6e46Renato Golin} 214