PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/vm/jit/relocation-x64.cpp

https://github.com/soitun/hiphop-php
C++ | 416 lines | 301 code | 38 blank | 77 comment | 94 complexity | dadbf7c31a6b26c87b3434df3728cd6f MD5 | raw file
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include "hphp/runtime/vm/jit/relocation.h"
  17. #include "hphp/runtime/base/runtime-option.h"
  18. #include "hphp/runtime/vm/jit/align-x64.h"
  19. #include "hphp/runtime/vm/jit/asm-info.h"
  20. #include "hphp/runtime/vm/jit/cg-meta.h"
  21. #include "hphp/runtime/vm/jit/containers.h"
  22. #include "hphp/runtime/vm/jit/fixup.h"
  23. #include "hphp/runtime/vm/jit/ir-opcode.h"
  24. #include "hphp/runtime/vm/jit/smashable-instr.h"
  25. namespace HPHP::jit::x64 {
  26. namespace {
  27. TRACE_SET_MOD(hhir);
  28. //////////////////////////////////////////////////////////////////////
  29. constexpr int kJmpLen = 5;
  30. using WideJmpSet = hphp_hash_set<void*>;
  31. struct JmpOutOfRange : std::exception {};
  32. // For align = 2^n for some n
  33. #define ALIGN(x, align) ((((align) - 1) | (x)) + 1)
  34. #define ALIGN_OFFSET(x, align) ((x) & ((align) - 1))
  35. size_t relocateImpl(RelocationInfo& rel,
  36. CodeBlock& destBlock,
  37. TCA start, TCA end,
  38. DataBlock& srcBlock,
  39. CGMeta& fixups,
  40. TCA* exitAddr,
  41. WideJmpSet& wideJmps,
  42. AreaIndex codeArea) {
  43. TCA src = start;
  44. size_t range = end - src;
  45. bool hasInternalRefs = false;
  46. bool internalRefsNeedUpdating = false;
  47. TCA destStart = destBlock.frontier();
  48. size_t asm_count{0};
  49. TCA jmpDest = nullptr;
  50. TCA keepNopLow = nullptr;
  51. TCA keepNopHigh = nullptr;
  52. try {
  53. while (src != end) {
  54. assertx(src < end);
  55. DecodedInstruction di(srcBlock.toDestAddress(src), src);
  56. asm_count++;
  57. int destRange = 0;
  58. auto willAlignTo64 = [&](TCA src, TCA dest) {
  59. bool align = false;
  60. auto af = fixups.alignments.equal_range(src);
  61. while (af.first != af.second) {
  62. auto const alignPair = af.first->second;
  63. auto const alignInfo = alignment_info(alignPair.first);
  64. if (alignPair.second == AlignContext::Live &&
  65. !is_aligned(dest, alignPair.first)) {
  66. align = align ||
  67. 0 == ALIGN_OFFSET(ALIGN((uint64_t)dest, alignInfo.align),
  68. x64::cache_line_size());
  69. }
  70. ++af.first;
  71. }
  72. return align;
  73. };
  74. // Make macro-fusion pairs not be split by the end of a cache line.
  75. // According to Intel 64 and IA-32 Architectures Optimization Refernce
  76. // Manual (page 2-18):
  77. // "Macro fusion does not happen if the first instruction ends on byte 63
  78. // of a cache line, and the second instruction is a conditional branch
  79. // that starts at byte 0 of the next cache line."
  80. auto nextSrc = src + di.size();
  81. auto const nextDest = destBlock.frontier() + di.size();
  82. if (RuntimeOption::EvalJitAlignMacroFusionPairs &&
  83. codeArea == AreaIndex::Main) {
  84. while (nextSrc != end) {
  85. DecodedInstruction next(srcBlock.toDestAddress(nextSrc), nextSrc);
  86. if (!next.isNop()) {
  87. if (di.isFuseable(next) &&
  88. (0 == ALIGN_OFFSET((uint64_t)nextDest,
  89. x64::cache_line_size()) ||
  90. willAlignTo64(nextSrc, nextDest))) {
  91. // Offset to 1 past end of cache line.
  92. size_t offset = ALIGN_OFFSET((~(uint64_t)nextDest) + 2,
  93. x64::cache_line_size());
  94. NEW_X64_ASM(a, destBlock);
  95. a.emitNop(offset);
  96. destRange += offset;
  97. internalRefsNeedUpdating = true;
  98. }
  99. break;
  100. }
  101. nextSrc += next.size();
  102. }
  103. }
  104. auto af = fixups.alignments.equal_range(src);
  105. while (af.first != af.second) {
  106. auto const alignPair = af.first->second;
  107. auto const alignInfo = alignment_info(alignPair.first);
  108. auto const low = src + alignInfo.offset;
  109. auto const hi = src + alignInfo.nbytes;
  110. assertx(low < hi);
  111. if (!keepNopLow || keepNopLow > low) keepNopLow = low;
  112. if (!keepNopHigh || keepNopHigh < hi) keepNopHigh = hi;
  113. TCA tmp = destBlock.frontier();
  114. align(destBlock, nullptr, alignPair.first, alignPair.second);
  115. if (destBlock.frontier() != tmp) {
  116. destRange += destBlock.frontier() - tmp;
  117. internalRefsNeedUpdating = true;
  118. }
  119. ++af.first;
  120. }
  121. bool preserveAlignment = keepNopLow && keepNopHigh &&
  122. keepNopLow <= src && keepNopHigh > src;
  123. TCA target = nullptr;
  124. TCA dest = destBlock.frontier();
  125. destBlock.bytes(di.size(), srcBlock.toDestAddress(src));
  126. DecodedInstruction d2(destBlock.toDestAddress(dest), dest);
  127. if (di.hasPicOffset()) {
  128. if (di.isBranch(DecodedInstruction::Unconditional)) {
  129. target = di.picAddress();
  130. }
  131. /*
  132. * Rip-relative offsets that point outside the range
  133. * being moved need to be adjusted so they continue
  134. * to point at the right thing
  135. */
  136. if (size_t(di.picAddress() - start) >= range) {
  137. bool DEBUG_ONLY success = d2.setPicAddress(di.picAddress());
  138. assertx(success);
  139. } else {
  140. if (!preserveAlignment && d2.isBranch()) {
  141. if (wideJmps.count(src)) {
  142. if (d2.size() < kJmpLen) {
  143. d2.widenBranch();
  144. internalRefsNeedUpdating = true;
  145. // widening a branch makes the dest instruction bigger
  146. destBlock.setFrontier(dest + d2.size());
  147. }
  148. } else if (d2.shrinkBranch()) {
  149. internalRefsNeedUpdating = true;
  150. }
  151. }
  152. hasInternalRefs = true;
  153. }
  154. }
  155. if (di.hasImmediate()) {
  156. if (fixups.addressImmediates.count(src)) {
  157. if (size_t(di.immediate() - (uint64_t)start) < range) {
  158. hasInternalRefs = internalRefsNeedUpdating = true;
  159. }
  160. } else {
  161. if (fixups.addressImmediates.count((TCA)~uintptr_t(src))) {
  162. // Handle weird, encoded offset, used by LdSmashable
  163. always_assert(di.immediate() == ((uintptr_t(src) << 1) | 1));
  164. bool DEBUG_ONLY success =
  165. d2.setImmediate(((uintptr_t)dest << 1) | 1);
  166. assertx(success);
  167. }
  168. /*
  169. * An immediate that points into the range being moved, but which
  170. * isn't tagged as an addressImmediate, is most likely a bug
  171. * and its instruction's address needs to be put into
  172. * fixups.addressImmediates. But it could just happen by bad
  173. * luck, so just log it.
  174. */
  175. if (size_t(di.immediate() - (uint64_t)start) < range) {
  176. FTRACE(3,
  177. "relocate: instruction at {} has immediate 0x{:x}"
  178. "which looks like an address that needs relocating\n",
  179. src, di.immediate());
  180. }
  181. }
  182. }
  183. if (src == start) {
  184. // for the start of the range, we only want to overwrite the "after"
  185. // address (since the "before" address could belong to the previous
  186. // tracelet, which could be being relocated to a completely different
  187. // address. recordRange will do that for us, so just make sure we
  188. // have the right address setup.
  189. destStart = dest;
  190. } else {
  191. rel.recordAddress(src, dest - destRange, destRange);
  192. }
  193. if (preserveAlignment && di.size() == kJmpLen &&
  194. di.isNop() && src + kJmpLen == end) {
  195. smashJmp(dest, src + kJmpLen);
  196. dest += kJmpLen;
  197. } else if (di.isNop() && !preserveAlignment) {
  198. internalRefsNeedUpdating = true;
  199. } else {
  200. dest += d2.size();
  201. }
  202. jmpDest = target;
  203. assertx(dest <= destBlock.frontier());
  204. destBlock.setFrontier(dest);
  205. src += di.size();
  206. if (keepNopHigh && src >= keepNopHigh) {
  207. keepNopLow = keepNopHigh = nullptr;
  208. }
  209. } // while (src != end)
  210. if (exitAddr) {
  211. *exitAddr = jmpDest;
  212. }
  213. rel.recordRange(start, end, destStart, destBlock.frontier());
  214. if (hasInternalRefs && internalRefsNeedUpdating) {
  215. src = start;
  216. bool ok = true;
  217. while (src != end) {
  218. DecodedInstruction di(srcBlock.toDestAddress(src), src);
  219. TCA newPicAddress = nullptr;
  220. int64_t newImmediate = 0;
  221. if (di.hasPicOffset() &&
  222. size_t(di.picAddress() - start) < range) {
  223. newPicAddress = rel.adjustedAddressAfter(di.picAddress());
  224. always_assert(newPicAddress);
  225. }
  226. if (di.hasImmediate() &&
  227. size_t((TCA)di.immediate() - start) < range &&
  228. fixups.addressImmediates.count(src)) {
  229. newImmediate =
  230. (int64_t)rel.adjustedAddressAfter((TCA)di.immediate());
  231. always_assert(newImmediate);
  232. }
  233. if (newImmediate || newPicAddress) {
  234. TCA dest = rel.adjustedAddressAfter(src);
  235. DecodedInstruction d2(destBlock.toDestAddress(dest), dest);
  236. if (newPicAddress) {
  237. if (!d2.setPicAddress(newPicAddress)) {
  238. always_assert(d2.isBranch() && d2.size() == 2);
  239. wideJmps.insert(src);
  240. ok = false;
  241. }
  242. }
  243. if (newImmediate) {
  244. if (!d2.setImmediate(newImmediate)) {
  245. always_assert(false);
  246. }
  247. }
  248. }
  249. src += di.size();
  250. }
  251. if (!ok) {
  252. throw JmpOutOfRange();
  253. }
  254. }
  255. rel.markAddressImmediates(fixups.addressImmediates);
  256. } catch (...) {
  257. rel.rewind(start, end);
  258. destBlock.setFrontier(destStart);
  259. throw;
  260. }
  261. return asm_count;
  262. }
  263. //////////////////////////////////////////////////////////////////////
  264. }
  265. /*
  266. * This should be called after calling relocate on all relevant ranges. It
  267. * will adjust all references into the original src ranges to point into the
  268. * corresponding relocated ranges.
  269. */
  270. void adjustForRelocation(RelocationInfo& rel) {
  271. for (const auto& range : rel.srcRanges()) {
  272. adjustForRelocation(rel, range.first, range.second);
  273. }
  274. }
  275. /*
  276. * This will update a single range that was not relocated, but that
  277. * might refer to relocated code (such as the cold code corresponding
  278. * to a tracelet). Unless its guaranteed to be all position independent,
  279. * its "fixups" should have been passed into a relocate call earlier.
  280. */
  281. void adjustForRelocation(RelocationInfo& rel, TCA srcStart, TCA srcEnd) {
  282. auto start = rel.adjustedAddressAfter(srcStart);
  283. auto end = rel.adjustedAddressBefore(srcEnd);
  284. if (!start) {
  285. start = srcStart;
  286. end = srcEnd;
  287. } else {
  288. always_assert(end);
  289. }
  290. while (start != end) {
  291. assertx(start < end);
  292. DecodedInstruction di(start);
  293. if (di.hasPicOffset()) {
  294. /*
  295. * A pointer into something that has been relocated needs to be
  296. * updated.
  297. */
  298. if (TCA adjusted = rel.adjustedAddressAfter(di.picAddress())) {
  299. di.setPicAddress(adjusted);
  300. }
  301. }
  302. if (di.hasImmediate()) {
  303. /*
  304. * Similarly for addressImmediates - and see comment above
  305. * for non-address immediates.
  306. */
  307. if (TCA adjusted = rel.adjustedAddressAfter((TCA)di.immediate())) {
  308. if (rel.isAddressImmediate(start)) {
  309. di.setImmediate((int64_t)adjusted);
  310. } else {
  311. FTRACE(3,
  312. "relocate: instruction at {} has immediate 0x{:x}"
  313. "which looks like an address that needs relocating\n",
  314. start, di.immediate());
  315. }
  316. }
  317. }
  318. start += di.size();
  319. if (start == end && di.isNop() &&
  320. di.size() == kJmpLen &&
  321. rel.adjustedAddressAfter(srcEnd)) {
  322. smashJmp(start - di.size(), rel.adjustedAddressAfter(end));
  323. }
  324. }
  325. }
  326. /*
  327. * Adjust potentially live references that point into the relocated
  328. * area.
  329. * Must not be called until its safe to run the relocated code.
  330. */
  331. void adjustCodeForRelocation(RelocationInfo& rel, CGMeta& fixups) {
  332. for (auto codePtr : fixups.codePointers) {
  333. if (TCA adjusted = rel.adjustedAddressAfter(*codePtr)) {
  334. *codePtr = adjusted;
  335. }
  336. }
  337. }
  338. void findFixups(TCA start, TCA end, CGMeta& meta) {
  339. while (start != end) {
  340. assertx(start < end);
  341. DecodedInstruction di(start);
  342. start += di.size();
  343. if (di.isCall()) {
  344. if (auto fixup = FixupMap::findFixup(start)) {
  345. meta.fixups.emplace_back(start, *fixup);
  346. }
  347. if (auto ct = getCatchTrace(start)) {
  348. meta.catches.emplace_back(start, *ct);
  349. }
  350. }
  351. }
  352. }
  353. /*
  354. * Relocate code in the range start, end into dest, and record
  355. * information about what was done to rel.
  356. * On exit, internal references (references into the source range)
  357. * will have been adjusted (ie they are still references into the
  358. * relocated code). External code references continue to point to
  359. * the same address as before relocation.
  360. */
  361. size_t relocate(RelocationInfo& rel,
  362. CodeBlock& destBlock,
  363. TCA start, TCA end,
  364. DataBlock& srcBlock,
  365. CGMeta& fixups,
  366. TCA* exitAddr,
  367. AreaIndex codeArea) {
  368. WideJmpSet wideJmps;
  369. while (true) {
  370. try {
  371. return relocateImpl(rel, destBlock, start, end, srcBlock,
  372. fixups, exitAddr, wideJmps, codeArea);
  373. } catch (JmpOutOfRange& j) {
  374. }
  375. }
  376. }
  377. //////////////////////////////////////////////////////////////////////
  378. }