From 2f2bd55e9d1ae17b193a4a2e95762f3c83b91d9f Mon Sep 17 00:00:00 2001
From: Dan McDonald <danmcd@mnx.io>
Date: Fri, 2 May 2025 17:16:35 -0400
Subject: [PATCH 1/2] illumos uses full VA48 virtual address range for process
 pointers

V8 wrongly assumed pointers were always < 0x800000000000. Two problems with
that:
  - Usable pointer space may exceed 0x800000000000.
  - In some virtual address schemes, like VA48 or VA57 in amd64/x64,
    usable pointers may come from the bottom-up AND from the top-down.

The recent IBM fix for AIX addresses the first problem, but not the
second.

This patch, designed for illumos (the open-source successor to OpenSolaris),
addresses the second one for a VA48 amd64/x64 process, where pointers can be
between 0x0(ish) and 0x7fffffffffff, OR between 0xffff800000000000 and
0xffffffffffffffff(ish). We could make this work for VA57 if need be as
well, but would need a different dispatch-table free-list marker.
---
 src/codegen/code-stub-assembler.cc  |  9 +++++++++
 src/sandbox/js-dispatch-table-inl.h | 15 +++++++++++++++
 src/sandbox/js-dispatch-table.h     | 17 +++++++++++++++++
 3 files changed, 41 insertions(+)

diff --git a/src/codegen/code-stub-assembler.cc b/src/codegen/code-stub-assembler.cc
index 5f1ceda4788..40cb08401b0 100644
--- a/src/codegen/code-stub-assembler.cc
+++ b/src/codegen/code-stub-assembler.cc
@@ -1988,7 +1988,16 @@ TNode<Code> CodeStubAssembler::LoadCodeObjectFromJSDispatchTable(
   TNode<UintPtrT> shifted_value;
   if (JSDispatchEntry::kObjectPointerOffset == 0) {
     shifted_value =
+#if defined(__illumos__) && defined(V8_HOST_ARCH_64_BIT)
+    // amd64/x64 pointers in illumos span both the low 2^47 range and the high
+    // 2^47 range as well (VA48). The high bit in the compressed pointer being
+    // set in illumos means ALL higher bits need to be set to 1 after shifting
+    // right. Use WordSar() (with casting tricks) to make this happen.
+        UncheckedCast<UintPtrT>(WordSar(UncheckedCast<IntPtrT>(value),
+          IntPtrConstant(JSDispatchEntry::kObjectPointerShift)));
+#else
         WordShr(value, UintPtrConstant(JSDispatchEntry::kObjectPointerShift));
+#endif /* __illumos__ */
   } else {
     shifted_value = UintPtrAdd(
         WordShr(value, UintPtrConstant(JSDispatchEntry::kObjectPointerShift)),
diff --git a/src/sandbox/js-dispatch-table-inl.h b/src/sandbox/js-dispatch-table-inl.h
index 319b4069c44..8c200412b0a 100644
--- a/src/sandbox/js-dispatch-table-inl.h
+++ b/src/sandbox/js-dispatch-table-inl.h
@@ -23,6 +23,8 @@ void JSDispatchEntry::MakeJSDispatchEntry(Address object, Address entrypoint,
                                           uint16_t parameter_count,
                                           bool mark_as_alive) {
   DCHECK_EQ(object & kHeapObjectTag, 0);
+#if !defined(__illumos__) || !defined(V8_TARGET_ARCH_64_BIT)
+  // These checks all fail on illumos's amd64/x64 VA48 available address space.
   DCHECK_EQ((((object - kObjectPointerOffset) << kObjectPointerShift) >>
              kObjectPointerShift) +
                 kObjectPointerOffset,
@@ -30,6 +32,7 @@ void JSDispatchEntry::MakeJSDispatchEntry(Address object, Address entrypoint,
   DCHECK_EQ((object - kObjectPointerOffset) + kObjectPointerOffset, object);
   DCHECK_LT((object - kObjectPointerOffset),
             1ULL << ((sizeof(encoded_word_) * 8) - kObjectPointerShift));
+#endif /* __illumos__ */
 
   Address payload = ((object - kObjectPointerOffset) << kObjectPointerShift) |
                     (parameter_count & kParameterCountMask);
@@ -55,8 +58,15 @@ Address JSDispatchEntry::GetCodePointer() const {
   // and so may be 0 or 1 here. As the return value is a tagged pointer, the
   // bit must be 1 when returned, so we need to set it here.
   Address payload = encoded_word_.load(std::memory_order_relaxed);
+#if defined(__illumos__) && defined(V8_TARGET_ARCH_64_BIT)
+  // Unsigned types won't sign-extend on shift-right, but we need to sign-
+  // extend because of how illumos VA48 addressing works.
+  return (Address)((intptr_t)payload >> (int)kObjectPointerShift) |
+    kHeapObjectTag;
+#else
   return ((payload >> kObjectPointerShift) + kObjectPointerOffset) |
          kHeapObjectTag;
+#endif /* __illumos__ */
 }
 
 Tagged<Code> JSDispatchEntry::GetCode() const {
@@ -214,7 +224,12 @@ void JSDispatchEntry::MakeFreelistEntry(uint32_t next_entry_index) {
 bool JSDispatchEntry::IsFreelistEntry() const {
 #ifdef V8_TARGET_ARCH_64_BIT
   auto entrypoint = entrypoint_.load(std::memory_order_relaxed);
+#ifdef __illumos__
+  // See the illumos definition of kFreeEntryTag for why we have to do this.
+  return (entrypoint & 0xffff000000000000ull) == kFreeEntryTag;
+#else
   return (entrypoint & kFreeEntryTag) == kFreeEntryTag;
+#endif /* __illumos__ */
 #else
   return next_free_entry_.load(std::memory_order_relaxed) != 0;
 #endif
diff --git a/src/sandbox/js-dispatch-table.h b/src/sandbox/js-dispatch-table.h
index c29b0987170..8baf06a5ad9 100644
--- a/src/sandbox/js-dispatch-table.h
+++ b/src/sandbox/js-dispatch-table.h
@@ -90,7 +90,24 @@ struct JSDispatchEntry {
 #if defined(V8_TARGET_ARCH_64_BIT)
   // Freelist entries contain the index of the next free entry in their lower 32
   // bits and are tagged with this tag.
+#ifdef __illumos__
+  // In illumos 64-bit apps, pointers are allocated both the bottom 2^47 range
+  // AND the top 2^47 range in the 64-bit space. Instead of 47 bits of VA
+  // space we have 48 bits. This means, however, the top 16-bits may be
+  // 0xffff. We therefore pick a different value for the kFreeEntryTag.
+  // If/when we go to VA57, aka 5-level paging, we'll need to revisit this
+  // again, as will V8 by default, since the fixed-bits on the high end will
+  // shrink from top 16-bits to top 7-bits (or 8-bits... it'll be either 0xff
+  // or 0x00, nothing between 0x01 -> 0xfe).
+  //
+  // Unless illumos ships an Oracle-Solaris-like VA47 link-time option to
+  // restrict pointers from allocating from above the VA48 Virtual Address
+  // hole, (i.e. >= 0x800000000000) we need to be mindful of this.
+
+  static constexpr Address kFreeEntryTag = 0xfeed000000000000ull;
+#else
   static constexpr Address kFreeEntryTag = 0xffff000000000000ull;
+#endif /* __illumos__ */
 #ifdef V8_TARGET_BIG_ENDIAN
   // 2-byte parameter count is on the least significant side of encoded_word_.
   static constexpr int kBigEndianParamCountOffset =
-- 
2.48.1

