Index: trunk/src/VBox/Devices/VirtIO/VirtioCore.cpp
===================================================================
--- trunk/src/VBox/Devices/VirtIO/VirtioCore.cpp	(revision 94287)
+++ trunk/src/VBox/Devices/VirtIO/VirtioCore.cpp	(revision 94969)
@@ -620,5 +620,10 @@
     uint16_t uUsedIdxShadow  = pVirtq->uUsedIdxShadow;
 
+#ifdef VIRTIO_VBUF_ON_STACK
+    VIRTQBUF_T VirtqBuf;
+    PVIRTQBUF pVirtqBuf = &VirtqBuf;
+#else /* !VIRTIO_VBUF_ON_STACK */
     PVIRTQBUF pVirtqBuf = NULL;
+#endif /* !VIRTIO_VBUF_ON_STACK */
 
     bool fEmpty = IS_VIRTQ_EMPTY(pDevIns, pVirtio, pVirtq);
@@ -629,5 +634,9 @@
     if (!fEmpty)
     {
+#ifdef VIRTIO_VBUF_ON_STACK
+        virtioCoreR3VirtqAvailBufPeek(pDevIns,  pVirtio, uVirtq, pVirtqBuf);
+#else /* !VIRTIO_VBUF_ON_STACK */
         virtioCoreR3VirtqAvailBufPeek(pDevIns,  pVirtio, uVirtq, &pVirtqBuf);
+#endif /* !VIRTIO_VBUF_ON_STACK */
         cSendSegs   = pVirtqBuf->pSgPhysSend ? pVirtqBuf->pSgPhysSend->cSegs : 0;
         cReturnSegs = pVirtqBuf->pSgPhysReturn ? pVirtqBuf->pSgPhysReturn->cSegs : 0;
@@ -680,4 +689,16 @@
 }
 
+#ifdef VIRTIO_VBUF_ON_STACK
+/** API Function: See header file */
+PVIRTQBUF virtioCoreR3VirtqBufAlloc(void)
+{
+    PVIRTQBUF pVirtqBuf = (PVIRTQBUF)RTMemAllocZ(sizeof(VIRTQBUF_T));
+    AssertReturn(pVirtqBuf, NULL);
+    pVirtqBuf->u32Magic  = VIRTQBUF_MAGIC;
+    pVirtqBuf->cRefs     = 1;
+    return pVirtqBuf;
+}
+#endif /* VIRTIO_VBUF_ON_STACK */
+
 /** API Function: See header file */
 uint32_t virtioCoreR3VirtqBufRetain(PVIRTQBUF pVirtqBuf)
@@ -752,4 +773,10 @@
 
 /** API function: See Header file  */
+#ifdef VIRTIO_VBUF_ON_STACK
+int virtioCoreR3VirtqAvailBufPeek(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtq, PVIRTQBUF pVirtqBuf)
+{
+    return virtioCoreR3VirtqAvailBufGet(pDevIns, pVirtio, uVirtq, pVirtqBuf, false);
+}
+#else /* !VIRTIO_VBUF_ON_STACK */
 int virtioCoreR3VirtqAvailBufPeek(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtq,
                          PPVIRTQBUF ppVirtqBuf)
@@ -757,4 +784,5 @@
     return virtioCoreR3VirtqAvailBufGet(pDevIns, pVirtio, uVirtq, ppVirtqBuf, false);
 }
+#endif /* !VIRTIO_VBUF_ON_STACK */
 
 /** API function: See Header file  */
@@ -778,9 +806,16 @@
 
 /** API Function: See header file */
+#ifdef VIRTIO_VBUF_ON_STACK
+int virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtq,
+                                 uint16_t uHeadIdx, PVIRTQBUF pVirtqBuf)
+#else /* !VIRTIO_VBUF_ON_STACK */
 int virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtq,
                              uint16_t uHeadIdx, PPVIRTQBUF ppVirtqBuf)
-{
+#endif /* !VIRTIO_VBUF_ON_STACK */
+{
+#ifndef VIRTIO_VBUF_ON_STACK
     AssertReturn(ppVirtqBuf, VERR_INVALID_POINTER);
     *ppVirtqBuf = NULL;
+#endif /* !VIRTIO_VBUF_ON_STACK */
 
     AssertMsgReturn(uVirtq < RT_ELEMENTS(pVirtio->aVirtqueues),
@@ -800,11 +835,15 @@
      * Allocate and initialize the descriptor chain structure.
      */
+#ifndef VIRTIO_VBUF_ON_STACK
     PVIRTQBUF pVirtqBuf = (PVIRTQBUF)RTMemAllocZ(sizeof(VIRTQBUF_T));
     AssertReturn(pVirtqBuf, VERR_NO_MEMORY);
     pVirtqBuf->u32Magic  = VIRTQBUF_MAGIC;
     pVirtqBuf->cRefs     = 1;
+#endif /* !VIRTIO_VBUF_ON_STACK */
     pVirtqBuf->uHeadIdx  = uHeadIdx;
     pVirtqBuf->uVirtq    = uVirtq;
+#ifndef VIRTIO_VBUF_ON_STACK
     *ppVirtqBuf          = pVirtqBuf;
+#endif /* !VIRTIO_VBUF_ON_STACK */
 
     /*
@@ -904,6 +943,11 @@
 
 /** API function: See Header file  */
+#ifdef VIRTIO_VBUF_ON_STACK
+int virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtq,
+                                 PVIRTQBUF pVirtqBuf, bool fRemove)
+#else /* !VIRTIO_VBUF_ON_STACK */
 int virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtq,
                          PPVIRTQBUF ppVirtqBuf, bool fRemove)
+#endif /* !VIRTIO_VBUF_ON_STACK */
 {
     Assert(uVirtq < RT_ELEMENTS(pVirtio->aVirtqueues));
@@ -921,5 +965,9 @@
         pVirtq->uAvailIdxShadow++;
 
+#ifdef VIRTIO_VBUF_ON_STACK
+    int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, pVirtio, uVirtq, uHeadIdx, pVirtqBuf);
+#else /* !VIRTIO_VBUF_ON_STACK */
     int rc = virtioCoreR3VirtqAvailBufGet(pDevIns, pVirtio, uVirtq, uHeadIdx, ppVirtqBuf);
+#endif /* !VIRTIO_VBUF_ON_STACK */
     return rc;
 }
Index: trunk/src/VBox/Devices/VirtIO/VirtioCore.h
===================================================================
--- trunk/src/VBox/Devices/VirtIO/VirtioCore.h	(revision 94287)
+++ trunk/src/VBox/Devices/VirtIO/VirtioCore.h	(revision 94969)
@@ -22,4 +22,7 @@
 # pragma once
 #endif
+
+/* Do not allocate VIRTQBUF from the heap when possible */
+#define VIRTIO_VBUF_ON_STACK 1
 
 #include <iprt/ctype.h>
@@ -676,4 +679,74 @@
 uint16_t virtioCoreVirtqAvailBufCount(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr);
 
+#ifdef VIRTIO_VBUF_ON_STACK
+/**
+ * This function is identical to virtioCoreR3VirtqAvailBufGet(), *except* it doesn't consume
+ * peeked buffer from avail ring of the virtq. The function *becomes* identical to the
+ * virtioCoreR3VirtqAvailBufGet() only if virtioCoreR3VirtqAvailRingNext() is invoked to
+ * consume buf from the queue's avail ring, followed by invocation of virtioCoreR3VirtqUsedBufPut(),
+ * to hand host-processed buffer back to guest, which completes guest-initiated virtq buffer circuit.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pVirtio     Pointer to the shared virtio state.
+ * @param   uVirtqNbr   Virtq number
+ * @param   pVirtqBuf   Pointer to descriptor chain that contains the
+ *                      pre-processed transaction information pulled from the virtq.
+ *
+ * @returns VBox status code:
+ * @retval  VINF_SUCCESS         Success
+ * @retval  VERR_INVALID_STATE   VirtIO not in ready state (asserted).
+ * @retval  VERR_NOT_AVAILABLE   If the queue is empty.
+ */
+int  virtioCoreR3VirtqAvailBufPeek(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr,
+                                   PVIRTQBUF pVirtqBuf);
+
+/**
+ * This function fetches the next buffer (descriptor chain) from the VirtIO "avail" ring of
+ * indicated queue, separating the buf's s/g vectors into OUT (e.g. guest-to-host)
+ * components and and IN (host-to-guest) components.
+ *
+ * Caller is responsible for GCPhys to host virtual memory conversions. If the
+ * virtq buffer being peeked at is "consumed", virtioCoreR3VirtqAvailRingNext() must
+ * be called, and after that virtioCoreR3VirtqUsedBufPut() must be called to
+ * complete the buffer transfer cycle with the guest.
+ *
+ * @param   pDevIns     The device instance.
+ * @param   pVirtio     Pointer to the shared virtio state.
+ * @param   uVirtqNbr   Virtq number
+ * @param   pVirtqBuf   Pointer to descriptor chain that contains the
+ *                      pre-processed transaction information pulled from the virtq.
+ * @param   fRemove     flags whether to remove desc chain from queue (false = peek)
+ *
+ * @returns VBox status code:
+ * @retval  VINF_SUCCESS         Success
+ * @retval  VERR_INVALID_STATE   VirtIO not in ready state (asserted).
+ * @retval  VERR_NOT_AVAILABLE   If the queue is empty.
+ */
+int  virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr,
+                                  PVIRTQBUF pVirtqBuf, bool fRemove);
+
+/**
+ * Fetches a specific descriptor chain using avail ring of indicated queue and converts the
+ * descriptor chain into its OUT (to device) and IN (to guest) components.
+ *
+ * The caller is responsible for GCPhys to host virtual memory conversions and *must*
+ * return the virtq buffer using virtioCoreR3VirtqUsedBufPut() to complete the roundtrip
+ * virtq transaction.
+ * *
+ * @param   pDevIns     The device instance.
+ * @param   pVirtio     Pointer to the shared virtio state.
+ * @param   uVirtqNbr   Virtq number
+ * @param   pVirtqBuf   Pointer to descriptor chain that contains the
+ *                      pre-processed transaction information pulled from the virtq.
+ * @param   fRemove     flags whether to remove desc chain from queue (false = peek)
+ *
+ * @returns VBox status code:
+ * @retval  VINF_SUCCESS         Success
+ * @retval  VERR_INVALID_STATE   VirtIO not in ready state (asserted).
+ * @retval  VERR_NOT_AVAILABLE   If the queue is empty.
+ */
+int virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr,
+                                  uint16_t uHeadIdx, PVIRTQBUF pVirtqBuf);
+#else /* !VIRTIO_VBUF_ON_STACK */
 /**
  * This function is identical to virtioCoreR3VirtqAvailBufGet(), *except* it doesn't consume
@@ -748,4 +821,5 @@
 int virtioCoreR3VirtqAvailBufGet(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr,
                                   uint16_t uHeadIdx, PPVIRTQBUF ppVirtqBuf);
+#endif /* !VIRTIO_VBUF_ON_STACK */
 
 /**
@@ -1092,4 +1166,21 @@
 int  virtioCoreVirtqUsedRingSync(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, uint16_t uVirtqNbr);
 
+#ifdef VIRTIO_VBUF_ON_STACK
+/**
+ * Allocates a descriptor chain object with the reference count of one. Copying the reference
+ * to this object requires a call to virtioCoreR3VirtqBufRetain. All references must be later
+ * released with virtioCoreR3VirtqBufRelease. Just to be clear, one alloc plus one retain will
+ * require two releases.
+ *
+ * @returns A descriptor chain object.
+ *
+ * @retval  NULL if out of memory.
+ * 
+ * NOTE: VIRTQBUF_T objects allocated on the stack will have garbage in the u32Magic field,
+ * triggering an assertion if virtioCoreR3VirtqBufRelease is called on them.
+ */
+PVIRTQBUF virtioCoreR3VirtqBufAlloc(void);
+#endif /* VIRTIO_VBUF_ON_STACK */
+
 /**
  * Retains a reference to the given descriptor chain.
