1//===- llvm/Support/Unix/Path.inc - Unix Path Implementation ----*- C++ -*-===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file implements the Unix specific implementation of the Path API. 11// 12//===----------------------------------------------------------------------===// 13 14//===----------------------------------------------------------------------===// 15//=== WARNING: Implementation here must contain only generic UNIX code that 16//=== is guaranteed to work on *all* UNIX variants. 17//===----------------------------------------------------------------------===// 18 19#include "Unix.h" 20#include <limits.h> 21#include <stdio.h> 22#if HAVE_SYS_STAT_H 23#include <sys/stat.h> 24#endif 25#if HAVE_FCNTL_H 26#include <fcntl.h> 27#endif 28#ifdef HAVE_SYS_MMAN_H 29#include <sys/mman.h> 30#endif 31#if HAVE_DIRENT_H 32# include <dirent.h> 33# define NAMLEN(dirent) strlen((dirent)->d_name) 34#else 35# define dirent direct 36# define NAMLEN(dirent) (dirent)->d_namlen 37# if HAVE_SYS_NDIR_H 38# include <sys/ndir.h> 39# endif 40# if HAVE_SYS_DIR_H 41# include <sys/dir.h> 42# endif 43# if HAVE_NDIR_H 44# include <ndir.h> 45# endif 46#endif 47 48#ifdef __APPLE__ 49#include <mach-o/dyld.h> 50#endif 51 52// Both stdio.h and cstdio are included via different pathes and 53// stdcxx's cstdio doesn't include stdio.h, so it doesn't #undef the macros 54// either. 55#undef ferror 56#undef feof 57 58// For GNU Hurd 59#if defined(__GNU__) && !defined(PATH_MAX) 60# define PATH_MAX 4096 61#endif 62 63using namespace llvm; 64 65namespace { 66 /// This class automatically closes the given file descriptor when it goes out 67 /// of scope. You can take back explicit ownership of the file descriptor by 68 /// calling take(). The destructor does not verify that close was successful. 69 /// Therefore, never allow this class to call close on a file descriptor that 70 /// has been read from or written to. 71 struct AutoFD { 72 int FileDescriptor; 73 74 AutoFD(int fd) : FileDescriptor(fd) {} 75 ~AutoFD() { 76 if (FileDescriptor >= 0) 77 ::close(FileDescriptor); 78 } 79 80 int take() { 81 int ret = FileDescriptor; 82 FileDescriptor = -1; 83 return ret; 84 } 85 86 operator int() const {return FileDescriptor;} 87 }; 88} 89 90namespace llvm { 91namespace sys { 92namespace fs { 93#if defined(__FreeBSD__) || defined (__NetBSD__) || defined(__Bitrig__) || \ 94 defined(__OpenBSD__) || defined(__minix) || defined(__FreeBSD_kernel__) || \ 95 defined(__linux__) || defined(__CYGWIN__) || defined(__DragonFly__) 96static int 97test_dir(char ret[PATH_MAX], const char *dir, const char *bin) 98{ 99 struct stat sb; 100 char fullpath[PATH_MAX]; 101 102 snprintf(fullpath, PATH_MAX, "%s/%s", dir, bin); 103 if (realpath(fullpath, ret) == NULL) 104 return (1); 105 if (stat(fullpath, &sb) != 0) 106 return (1); 107 108 return (0); 109} 110 111static char * 112getprogpath(char ret[PATH_MAX], const char *bin) 113{ 114 char *pv, *s, *t; 115 116 /* First approach: absolute path. */ 117 if (bin[0] == '/') { 118 if (test_dir(ret, "/", bin) == 0) 119 return (ret); 120 return (NULL); 121 } 122 123 /* Second approach: relative path. */ 124 if (strchr(bin, '/') != NULL) { 125 char cwd[PATH_MAX]; 126 if (getcwd(cwd, PATH_MAX) == NULL) 127 return (NULL); 128 if (test_dir(ret, cwd, bin) == 0) 129 return (ret); 130 return (NULL); 131 } 132 133 /* Third approach: $PATH */ 134 if ((pv = getenv("PATH")) == NULL) 135 return (NULL); 136 s = pv = strdup(pv); 137 if (pv == NULL) 138 return (NULL); 139 while ((t = strsep(&s, ":")) != NULL) { 140 if (test_dir(ret, t, bin) == 0) { 141 free(pv); 142 return (ret); 143 } 144 } 145 free(pv); 146 return (NULL); 147} 148#endif // __FreeBSD__ || __NetBSD__ || __FreeBSD_kernel__ 149 150/// GetMainExecutable - Return the path to the main executable, given the 151/// value of argv[0] from program startup. 152std::string getMainExecutable(const char *argv0, void *MainAddr) { 153#if defined(__APPLE__) 154 // On OS X the executable path is saved to the stack by dyld. Reading it 155 // from there is much faster than calling dladdr, especially for large 156 // binaries with symbols. 157 char exe_path[MAXPATHLEN]; 158 uint32_t size = sizeof(exe_path); 159 if (_NSGetExecutablePath(exe_path, &size) == 0) { 160 char link_path[MAXPATHLEN]; 161 if (realpath(exe_path, link_path)) 162 return link_path; 163 } 164#elif defined(__FreeBSD__) || defined (__NetBSD__) || defined(__Bitrig__) || \ 165 defined(__OpenBSD__) || defined(__minix) || defined(__DragonFly__) || \ 166 defined(__FreeBSD_kernel__) 167 char exe_path[PATH_MAX]; 168 169 if (getprogpath(exe_path, argv0) != NULL) 170 return exe_path; 171#elif defined(__linux__) || defined(__CYGWIN__) 172 char exe_path[MAXPATHLEN]; 173 StringRef aPath("/proc/self/exe"); 174 if (sys::fs::exists(aPath)) { 175 // /proc is not always mounted under Linux (chroot for example). 176 ssize_t len = readlink(aPath.str().c_str(), exe_path, sizeof(exe_path)); 177 if (len >= 0) 178 return StringRef(exe_path, len); 179 } else { 180 // Fall back to the classical detection. 181 if (getprogpath(exe_path, argv0) != NULL) 182 return exe_path; 183 } 184#elif defined(HAVE_DLFCN_H) 185 // Use dladdr to get executable path if available. 186 Dl_info DLInfo; 187 int err = dladdr(MainAddr, &DLInfo); 188 if (err == 0) 189 return ""; 190 191 // If the filename is a symlink, we need to resolve and return the location of 192 // the actual executable. 193 char link_path[MAXPATHLEN]; 194 if (realpath(DLInfo.dli_fname, link_path)) 195 return link_path; 196#else 197#error GetMainExecutable is not implemented on this host yet. 198#endif 199 return ""; 200} 201 202TimeValue file_status::getLastModificationTime() const { 203 TimeValue Ret; 204 Ret.fromEpochTime(fs_st_mtime); 205 return Ret; 206} 207 208UniqueID file_status::getUniqueID() const { 209 return UniqueID(fs_st_dev, fs_st_ino); 210} 211 212std::error_code current_path(SmallVectorImpl<char> &result) { 213 result.clear(); 214 215 const char *pwd = ::getenv("PWD"); 216 llvm::sys::fs::file_status PWDStatus, DotStatus; 217 if (pwd && llvm::sys::path::is_absolute(pwd) && 218 !llvm::sys::fs::status(pwd, PWDStatus) && 219 !llvm::sys::fs::status(".", DotStatus) && 220 PWDStatus.getUniqueID() == DotStatus.getUniqueID()) { 221 result.append(pwd, pwd + strlen(pwd)); 222 return std::error_code(); 223 } 224 225#ifdef MAXPATHLEN 226 result.reserve(MAXPATHLEN); 227#else 228// For GNU Hurd 229 result.reserve(1024); 230#endif 231 232 while (true) { 233 if (::getcwd(result.data(), result.capacity()) == nullptr) { 234 // See if there was a real error. 235 if (errno != ENOMEM) 236 return std::error_code(errno, std::generic_category()); 237 // Otherwise there just wasn't enough space. 238 result.reserve(result.capacity() * 2); 239 } else 240 break; 241 } 242 243 result.set_size(strlen(result.data())); 244 return std::error_code(); 245} 246 247std::error_code create_directory(const Twine &path, bool IgnoreExisting) { 248 SmallString<128> path_storage; 249 StringRef p = path.toNullTerminatedStringRef(path_storage); 250 251 if (::mkdir(p.begin(), S_IRWXU | S_IRWXG) == -1) { 252 if (errno != EEXIST || !IgnoreExisting) 253 return std::error_code(errno, std::generic_category()); 254 } 255 256 return std::error_code(); 257} 258 259// Note that we are using symbolic link because hard links are not supported by 260// all filesystems (SMB doesn't). 261std::error_code create_link(const Twine &to, const Twine &from) { 262 // Get arguments. 263 SmallString<128> from_storage; 264 SmallString<128> to_storage; 265 StringRef f = from.toNullTerminatedStringRef(from_storage); 266 StringRef t = to.toNullTerminatedStringRef(to_storage); 267 268 if (::symlink(t.begin(), f.begin()) == -1) 269 return std::error_code(errno, std::generic_category()); 270 271 return std::error_code(); 272} 273 274std::error_code remove(const Twine &path, bool IgnoreNonExisting) { 275 SmallString<128> path_storage; 276 StringRef p = path.toNullTerminatedStringRef(path_storage); 277 278 struct stat buf; 279 if (lstat(p.begin(), &buf) != 0) { 280 if (errno != ENOENT || !IgnoreNonExisting) 281 return std::error_code(errno, std::generic_category()); 282 return std::error_code(); 283 } 284 285 // Note: this check catches strange situations. In all cases, LLVM should 286 // only be involved in the creation and deletion of regular files. This 287 // check ensures that what we're trying to erase is a regular file. It 288 // effectively prevents LLVM from erasing things like /dev/null, any block 289 // special file, or other things that aren't "regular" files. 290 if (!S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode) && !S_ISLNK(buf.st_mode)) 291 return make_error_code(errc::operation_not_permitted); 292 293 if (::remove(p.begin()) == -1) { 294 if (errno != ENOENT || !IgnoreNonExisting) 295 return std::error_code(errno, std::generic_category()); 296 } 297 298 return std::error_code(); 299} 300 301std::error_code rename(const Twine &from, const Twine &to) { 302 // Get arguments. 303 SmallString<128> from_storage; 304 SmallString<128> to_storage; 305 StringRef f = from.toNullTerminatedStringRef(from_storage); 306 StringRef t = to.toNullTerminatedStringRef(to_storage); 307 308 if (::rename(f.begin(), t.begin()) == -1) 309 return std::error_code(errno, std::generic_category()); 310 311 return std::error_code(); 312} 313 314std::error_code resize_file(const Twine &path, uint64_t size) { 315 SmallString<128> path_storage; 316 StringRef p = path.toNullTerminatedStringRef(path_storage); 317 318 if (::truncate(p.begin(), size) == -1) 319 return std::error_code(errno, std::generic_category()); 320 321 return std::error_code(); 322} 323 324static int convertAccessMode(AccessMode Mode) { 325 switch (Mode) { 326 case AccessMode::Exist: 327 return F_OK; 328 case AccessMode::Write: 329 return W_OK; 330 case AccessMode::Execute: 331 return R_OK | X_OK; // scripts also need R_OK. 332 } 333 llvm_unreachable("invalid enum"); 334} 335 336std::error_code access(const Twine &Path, AccessMode Mode) { 337 SmallString<128> PathStorage; 338 StringRef P = Path.toNullTerminatedStringRef(PathStorage); 339 340 if (::access(P.begin(), convertAccessMode(Mode)) == -1) 341 return std::error_code(errno, std::generic_category()); 342 343 if (Mode == AccessMode::Execute) { 344 // Don't say that directories are executable. 345 struct stat buf; 346 if (0 != stat(P.begin(), &buf)) 347 return errc::permission_denied; 348 if (!S_ISREG(buf.st_mode)) 349 return errc::permission_denied; 350 } 351 352 return std::error_code(); 353} 354 355bool equivalent(file_status A, file_status B) { 356 assert(status_known(A) && status_known(B)); 357 return A.fs_st_dev == B.fs_st_dev && 358 A.fs_st_ino == B.fs_st_ino; 359} 360 361std::error_code equivalent(const Twine &A, const Twine &B, bool &result) { 362 file_status fsA, fsB; 363 if (std::error_code ec = status(A, fsA)) 364 return ec; 365 if (std::error_code ec = status(B, fsB)) 366 return ec; 367 result = equivalent(fsA, fsB); 368 return std::error_code(); 369} 370 371static std::error_code fillStatus(int StatRet, const struct stat &Status, 372 file_status &Result) { 373 if (StatRet != 0) { 374 std::error_code ec(errno, std::generic_category()); 375 if (ec == errc::no_such_file_or_directory) 376 Result = file_status(file_type::file_not_found); 377 else 378 Result = file_status(file_type::status_error); 379 return ec; 380 } 381 382 file_type Type = file_type::type_unknown; 383 384 if (S_ISDIR(Status.st_mode)) 385 Type = file_type::directory_file; 386 else if (S_ISREG(Status.st_mode)) 387 Type = file_type::regular_file; 388 else if (S_ISBLK(Status.st_mode)) 389 Type = file_type::block_file; 390 else if (S_ISCHR(Status.st_mode)) 391 Type = file_type::character_file; 392 else if (S_ISFIFO(Status.st_mode)) 393 Type = file_type::fifo_file; 394 else if (S_ISSOCK(Status.st_mode)) 395 Type = file_type::socket_file; 396 397 perms Perms = static_cast<perms>(Status.st_mode); 398 Result = 399 file_status(Type, Perms, Status.st_dev, Status.st_ino, Status.st_mtime, 400 Status.st_uid, Status.st_gid, Status.st_size); 401 402 return std::error_code(); 403} 404 405std::error_code status(const Twine &Path, file_status &Result) { 406 SmallString<128> PathStorage; 407 StringRef P = Path.toNullTerminatedStringRef(PathStorage); 408 409 struct stat Status; 410 int StatRet = ::stat(P.begin(), &Status); 411 return fillStatus(StatRet, Status, Result); 412} 413 414std::error_code status(int FD, file_status &Result) { 415 struct stat Status; 416 int StatRet = ::fstat(FD, &Status); 417 return fillStatus(StatRet, Status, Result); 418} 419 420std::error_code setLastModificationAndAccessTime(int FD, TimeValue Time) { 421#if defined(HAVE_FUTIMENS) 422 timespec Times[2]; 423 Times[0].tv_sec = Time.toEpochTime(); 424 Times[0].tv_nsec = 0; 425 Times[1] = Times[0]; 426 if (::futimens(FD, Times)) 427 return std::error_code(errno, std::generic_category()); 428 return std::error_code(); 429#elif defined(HAVE_FUTIMES) 430 timeval Times[2]; 431 Times[0].tv_sec = Time.toEpochTime(); 432 Times[0].tv_usec = 0; 433 Times[1] = Times[0]; 434 if (::futimes(FD, Times)) 435 return std::error_code(errno, std::generic_category()); 436 return std::error_code(); 437#else 438#warning Missing futimes() and futimens() 439 return make_error_code(errc::function_not_supported); 440#endif 441} 442 443std::error_code mapped_file_region::init(int FD, bool CloseFD, uint64_t Offset) { 444 AutoFD ScopedFD(FD); 445 if (!CloseFD) 446 ScopedFD.take(); 447 448 // Figure out how large the file is. 449 struct stat FileInfo; 450 if (fstat(FD, &FileInfo) == -1) 451 return std::error_code(errno, std::generic_category()); 452 uint64_t FileSize = FileInfo.st_size; 453 454 if (Size == 0) 455 Size = FileSize; 456 else if (FileSize < Size) { 457 // We need to grow the file. 458 if (ftruncate(FD, Size) == -1) 459 return std::error_code(errno, std::generic_category()); 460 } 461 462 int flags = (Mode == readwrite) ? MAP_SHARED : MAP_PRIVATE; 463 int prot = (Mode == readonly) ? PROT_READ : (PROT_READ | PROT_WRITE); 464#ifdef MAP_FILE 465 flags |= MAP_FILE; 466#endif 467 Mapping = ::mmap(nullptr, Size, prot, flags, FD, Offset); 468 if (Mapping == MAP_FAILED) 469 return std::error_code(errno, std::generic_category()); 470 return std::error_code(); 471} 472 473mapped_file_region::mapped_file_region(const Twine &path, 474 mapmode mode, 475 uint64_t length, 476 uint64_t offset, 477 std::error_code &ec) 478 : Mode(mode) 479 , Size(length) 480 , Mapping() { 481 // Make sure that the requested size fits within SIZE_T. 482 if (length > std::numeric_limits<size_t>::max()) { 483 ec = make_error_code(errc::invalid_argument); 484 return; 485 } 486 487 SmallString<128> path_storage; 488 StringRef name = path.toNullTerminatedStringRef(path_storage); 489 int oflags = (mode == readonly) ? O_RDONLY : O_RDWR; 490 int ofd = ::open(name.begin(), oflags); 491 if (ofd == -1) { 492 ec = std::error_code(errno, std::generic_category()); 493 return; 494 } 495 496 ec = init(ofd, true, offset); 497 if (ec) 498 Mapping = nullptr; 499} 500 501mapped_file_region::mapped_file_region(int fd, 502 bool closefd, 503 mapmode mode, 504 uint64_t length, 505 uint64_t offset, 506 std::error_code &ec) 507 : Mode(mode) 508 , Size(length) 509 , Mapping() { 510 // Make sure that the requested size fits within SIZE_T. 511 if (length > std::numeric_limits<size_t>::max()) { 512 ec = make_error_code(errc::invalid_argument); 513 return; 514 } 515 516 ec = init(fd, closefd, offset); 517 if (ec) 518 Mapping = nullptr; 519} 520 521mapped_file_region::~mapped_file_region() { 522 if (Mapping) 523 ::munmap(Mapping, Size); 524} 525 526mapped_file_region::mapped_file_region(mapped_file_region &&other) 527 : Mode(other.Mode), Size(other.Size), Mapping(other.Mapping) { 528 other.Mapping = nullptr; 529} 530 531mapped_file_region::mapmode mapped_file_region::flags() const { 532 assert(Mapping && "Mapping failed but used anyway!"); 533 return Mode; 534} 535 536uint64_t mapped_file_region::size() const { 537 assert(Mapping && "Mapping failed but used anyway!"); 538 return Size; 539} 540 541char *mapped_file_region::data() const { 542 assert(Mapping && "Mapping failed but used anyway!"); 543 assert(Mode != readonly && "Cannot get non-const data for readonly mapping!"); 544 return reinterpret_cast<char*>(Mapping); 545} 546 547const char *mapped_file_region::const_data() const { 548 assert(Mapping && "Mapping failed but used anyway!"); 549 return reinterpret_cast<const char*>(Mapping); 550} 551 552int mapped_file_region::alignment() { 553 return process::get_self()->page_size(); 554} 555 556std::error_code detail::directory_iterator_construct(detail::DirIterState &it, 557 StringRef path){ 558 SmallString<128> path_null(path); 559 DIR *directory = ::opendir(path_null.c_str()); 560 if (!directory) 561 return std::error_code(errno, std::generic_category()); 562 563 it.IterationHandle = reinterpret_cast<intptr_t>(directory); 564 // Add something for replace_filename to replace. 565 path::append(path_null, "."); 566 it.CurrentEntry = directory_entry(path_null.str()); 567 return directory_iterator_increment(it); 568} 569 570std::error_code detail::directory_iterator_destruct(detail::DirIterState &it) { 571 if (it.IterationHandle) 572 ::closedir(reinterpret_cast<DIR *>(it.IterationHandle)); 573 it.IterationHandle = 0; 574 it.CurrentEntry = directory_entry(); 575 return std::error_code(); 576} 577 578std::error_code detail::directory_iterator_increment(detail::DirIterState &it) { 579 errno = 0; 580 dirent *cur_dir = ::readdir(reinterpret_cast<DIR *>(it.IterationHandle)); 581 if (cur_dir == nullptr && errno != 0) { 582 return std::error_code(errno, std::generic_category()); 583 } else if (cur_dir != nullptr) { 584 StringRef name(cur_dir->d_name, NAMLEN(cur_dir)); 585 if ((name.size() == 1 && name[0] == '.') || 586 (name.size() == 2 && name[0] == '.' && name[1] == '.')) 587 return directory_iterator_increment(it); 588 it.CurrentEntry.replace_filename(name); 589 } else 590 return directory_iterator_destruct(it); 591 592 return std::error_code(); 593} 594 595std::error_code openFileForRead(const Twine &Name, int &ResultFD) { 596 SmallString<128> Storage; 597 StringRef P = Name.toNullTerminatedStringRef(Storage); 598 while ((ResultFD = open(P.begin(), O_RDONLY)) < 0) { 599 if (errno != EINTR) 600 return std::error_code(errno, std::generic_category()); 601 } 602 return std::error_code(); 603} 604 605std::error_code openFileForWrite(const Twine &Name, int &ResultFD, 606 sys::fs::OpenFlags Flags, unsigned Mode) { 607 // Verify that we don't have both "append" and "excl". 608 assert((!(Flags & sys::fs::F_Excl) || !(Flags & sys::fs::F_Append)) && 609 "Cannot specify both 'excl' and 'append' file creation flags!"); 610 611 int OpenFlags = O_CREAT; 612 613 if (Flags & F_RW) 614 OpenFlags |= O_RDWR; 615 else 616 OpenFlags |= O_WRONLY; 617 618 if (Flags & F_Append) 619 OpenFlags |= O_APPEND; 620 else 621 OpenFlags |= O_TRUNC; 622 623 if (Flags & F_Excl) 624 OpenFlags |= O_EXCL; 625 626 SmallString<128> Storage; 627 StringRef P = Name.toNullTerminatedStringRef(Storage); 628 while ((ResultFD = open(P.begin(), OpenFlags, Mode)) < 0) { 629 if (errno != EINTR) 630 return std::error_code(errno, std::generic_category()); 631 } 632 return std::error_code(); 633} 634 635} // end namespace fs 636 637namespace path { 638 639bool home_directory(SmallVectorImpl<char> &result) { 640 if (char *RequestedDir = getenv("HOME")) { 641 result.clear(); 642 result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); 643 return true; 644 } 645 646 return false; 647} 648 649static const char *getEnvTempDir() { 650 // Check whether the temporary directory is specified by an environment 651 // variable. 652 const char *EnvironmentVariables[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; 653 for (const char *Env : EnvironmentVariables) { 654 if (const char *Dir = std::getenv(Env)) 655 return Dir; 656 } 657 658 return nullptr; 659} 660 661static const char *getDefaultTempDir(bool ErasedOnReboot) { 662#ifdef P_tmpdir 663 if ((bool)P_tmpdir) 664 return P_tmpdir; 665#endif 666 667 if (ErasedOnReboot) 668 return "/tmp"; 669 return "/var/tmp"; 670} 671 672void system_temp_directory(bool ErasedOnReboot, SmallVectorImpl<char> &Result) { 673 Result.clear(); 674 675 if (ErasedOnReboot) { 676 // There is no env variable for the cache directory. 677 if (const char *RequestedDir = getEnvTempDir()) { 678 Result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); 679 return; 680 } 681 } 682 683#if defined(_CS_DARWIN_USER_TEMP_DIR) && defined(_CS_DARWIN_USER_CACHE_DIR) 684 // On Darwin, use DARWIN_USER_TEMP_DIR or DARWIN_USER_CACHE_DIR. 685 // macros defined in <unistd.h> on darwin >= 9 686 int ConfName = ErasedOnReboot? _CS_DARWIN_USER_TEMP_DIR 687 : _CS_DARWIN_USER_CACHE_DIR; 688 size_t ConfLen = confstr(ConfName, nullptr, 0); 689 if (ConfLen > 0) { 690 do { 691 Result.resize(ConfLen); 692 ConfLen = confstr(ConfName, Result.data(), Result.size()); 693 } while (ConfLen > 0 && ConfLen != Result.size()); 694 695 if (ConfLen > 0) { 696 assert(Result.back() == 0); 697 Result.pop_back(); 698 return; 699 } 700 701 Result.clear(); 702 } 703#endif 704 705 const char *RequestedDir = getDefaultTempDir(ErasedOnReboot); 706 Result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); 707} 708 709} // end namespace path 710 711} // end namespace sys 712} // end namespace llvm 713