| --- |
| layout: doc |
| title: "Starboard Module Reference: memory.h" |
| --- |
| |
| This module defines functions for memory allocation, alignment, copying, |
| and comparing. |
| |
| ## Enums |
| |
| ### SbMemoryMapFlags |
| |
| The bitwise OR of these flags should be passed to SbMemoryMap to indicate |
| how the mapped memory can be used. |
| |
| **Values** |
| |
| * `kSbMemoryMapProtectRead` - Mapped memory can be read. |
| * `kSbMemoryMapProtectWrite` - Mapped memory can be written to. |
| * `kSbMemoryMapProtectExec` - Mapped memory can be executed. |
| * `kSbMemoryMapProtectRead` |
| |
| ## Macros |
| |
| <div id="macro-documentation-section"> |
| |
| <h3 id="sb_memory_map_failed" class="small-h3">SB_MEMORY_MAP_FAILED</h3> |
| |
| </div> |
| |
| ## Functions |
| |
| ### SbAbortIfAllocationFailed |
| |
| **Definition** |
| |
| ``` |
| static SB_C_FORCE_INLINE void SbAbortIfAllocationFailed(size_t requested_bytes, |
| void* address) { |
| if (SB_UNLIKELY(requested_bytes > 0 && address == NULL)) { |
| // Will abort the program if no debugger is attached. |
| SbSystemBreakIntoDebugger(); |
| } |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code></td> |
| <td><code>requested_bytes</code></td> |
| </tr> |
| <tr> |
| <td><code>void*</code></td> |
| <td><code>address</code></td> |
| </tr> |
| </table> |
| |
| ### SbMemoryAlignToPageSize |
| |
| **Description** |
| |
| Rounds `size` up to SB_MEMORY_PAGE_SIZE. |
| |
| **Definition** |
| |
| ``` |
| static SB_C_FORCE_INLINE size_t SbMemoryAlignToPageSize(size_t size) { |
| return (size + SB_MEMORY_PAGE_SIZE - 1) & ~(SB_MEMORY_PAGE_SIZE - 1); |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code></td> |
| <td><code>size</code></td> |
| </tr> |
| </table> |
| |
| ### SbMemoryAllocateAlignedChecked |
| |
| **Description** |
| |
| This function is identical to <code><a href="#sbmemoryallocatealignedunchecked">SbMemoryAllocateAlignedUnchecked</a></code>, but it |
| aborts (`abort()`) in the case of an allocation failure. |
| |
| **Definition** |
| |
| ``` |
| static SB_C_FORCE_INLINE void* SbMemoryAllocateAlignedChecked(size_t alignment, |
| size_t size) { |
| void* address = SbMemoryAllocateAlignedUnchecked(alignment, size); |
| SbAbortIfAllocationFailed(size, address); |
| return address; |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code><br> <code>alignment</code></td> |
| <td>The way that data is arranged and accessed in memory. The value |
| must be a power of two.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td>The size of the memory to be allocated. If <code>size</code> is <code>0</code>, the |
| function may return <code>NULL</code> or it may return a unique aligned pointer value |
| that can be passed to <code><a href="#sbmemoryfree">SbMemoryFree</a></code>Aligned.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryAllocateAlignedUnchecked |
| |
| **Description** |
| |
| Allocates and returns a chunk of memory of at least `size` bytes, aligned |
| to `alignment`. This function is meant to be a drop-in replacement for |
| `memalign`.<br><br> |
| |
| |
| The function returns `NULL` if it cannot allocate the memory. In addition, |
| the function's behavior is undefined if `alignment` is not a power of two. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemoryAllocateAlignedUnchecked(size_t alignment, size_t size); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code><br> <code>alignment</code></td> |
| <td>The way that data is arranged and accessed in memory. The value |
| must be a power of two.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td>The size of the memory to be allocated. If <code>size</code> is <code>0</code>, the |
| function may return <code>NULL</code> or it may return a unique aligned pointer value |
| that can be passed to <code><a href="#sbmemoryfree">SbMemoryFree</a></code>Aligned.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryAllocateChecked |
| |
| **Description** |
| |
| This function is identical to <code><a href="#sbmemoryallocateunchecked">SbMemoryAllocateUnchecked</a></code>, but it aborts |
| (`abort()`) in the case of an allocation failure. |
| |
| **Definition** |
| |
| ``` |
| static SB_C_FORCE_INLINE void* SbMemoryAllocateChecked(size_t size) { |
| void* address = SbMemoryAllocateUnchecked(size); |
| SbAbortIfAllocationFailed(size, address); |
| return address; |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td>The amount of memory to be allocated. If <code>size</code> is 0, the function |
| may return <code>NULL</code> or it may return a unique pointer value that can be |
| passed to <code><a href="#sbmemoryfree">SbMemoryFree</a></code>.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryAllocateUnchecked |
| |
| **Description** |
| |
| Allocates and returns a chunk of memory of at least `size` bytes. This |
| function is intended to be a drop-in replacement for `malloc`.<br><br> |
| |
| |
| Note that this function returns `NULL` if it is unable to allocate the |
| memory. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemoryAllocateUnchecked(size_t size); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td>The amount of memory to be allocated. If <code>size</code> is 0, the function |
| may return <code>NULL</code> or it may return a unique pointer value that can be |
| passed to <code><a href="#sbmemoryfree">SbMemoryFree</a></code>.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryCalloc |
| |
| **Description** |
| |
| A wrapper that implements a drop-in replacement for `calloc`, which is used |
| in some packages. |
| |
| **Definition** |
| |
| ``` |
| static SB_C_INLINE void* SbMemoryCalloc(size_t count, size_t size) { |
| size_t total = count * size; |
| void* result = SbMemoryAllocate(total); |
| if (result) { |
| SbMemorySet(result, 0, total); |
| } |
| return result; |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>size_t</code><br> <code>count</code></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td></td> |
| </tr> |
| </table> |
| |
| ### SbMemoryCompare |
| |
| **Description** |
| |
| Compares the contents of the first `count` bytes of `buffer1` and `buffer2`. |
| This function returns: |
| <ul><li>`-1` if `buffer1` is "less-than" `buffer2` |
| </li><li>`0` if `buffer1` and `buffer2` are equal |
| </li><li>`1` if `buffer1` is "greater-than" `buffer2`. |
| This function is meant to be a drop-in replacement for `memcmp`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT int SbMemoryCompare(const void* buffer1, |
| const void* buffer2, |
| size_t count); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>const void*</code><br> <code>buffer1</code></td> |
| <td>The first buffer to be compared.</td> |
| </tr> |
| <tr> |
| <td><code>const void*</code><br> <code>buffer2</code></td> |
| <td>The second buffer to be compared.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>count</code></td> |
| <td>The number of bytes to be compared.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryCopy |
| |
| **Description** |
| |
| Copies `count` sequential bytes from `source` to `destination`, without |
| support for the `source` and `destination` regions overlapping. This |
| function is meant to be a drop-in replacement for `memcpy`.<br><br> |
| |
| |
| The function's behavior is undefined if `destination` or `source` are NULL, |
| and the function is a no-op if `count` is 0. The return value is |
| `destination`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemoryCopy(void* destination, |
| const void* source, |
| size_t count); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>destination</code></td> |
| <td>The destination of the copied memory.</td> |
| </tr> |
| <tr> |
| <td><code>const void*</code><br> <code>source</code></td> |
| <td>The source of the copied memory.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>count</code></td> |
| <td>The number of sequential bytes to be copied.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryFindByte |
| |
| **Description** |
| |
| Finds the lower 8-bits of `value` in the first `count` bytes of `buffer` |
| and returns either a pointer to the first found occurrence or `NULL` if |
| the value is not found. This function is meant to be a drop-in replacement |
| for `memchr`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT const void* SbMemoryFindByte(const void* buffer, |
| int value, |
| size_t count); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>const void*</code><br> <code>buffer</code></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td><code>int</code><br> <code>value</code></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>count</code></td> |
| <td></td> |
| </tr> |
| </table> |
| |
| ### SbMemoryFlush |
| |
| **Description** |
| |
| Flushes any data in the given virtual address range that is cached locally in |
| the current processor core to physical memory, ensuring that data and |
| instruction caches are cleared. This is required to be called on executable |
| memory that has been written to and might be executed in the future. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void SbMemoryFlush(void* virtual_address, int64_t size_bytes); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code></td> |
| <td><code>virtual_address</code></td> |
| </tr> |
| <tr> |
| <td><code>int64_t</code></td> |
| <td><code>size_bytes</code></td> |
| </tr> |
| </table> |
| |
| ### SbMemoryFree |
| |
| **Description** |
| |
| Frees a previously allocated chunk of memory. If `memory` is NULL, then the |
| operation is a no-op. This function is meant to be a drop-in replacement for |
| `free`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void SbMemoryFree(void* memory); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>memory</code></td> |
| <td>The chunk of memory to be freed.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryFreeAligned |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void SbMemoryFreeAligned(void* memory); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>memory</code></td> |
| <td>The chunk of memory to be freed. If <code>memory</code> is NULL, then the |
| function is a no-op.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryGetStackBounds |
| |
| **Description** |
| |
| Gets the stack bounds for the current thread. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void SbMemoryGetStackBounds(void** out_high, void** out_low); |
| ``` |
| |
| <div class="mdl-tabs mdl-js-tabs mdl-js-ripple-effect"> |
| <div class="mdl-tabs__tab-bar"> |
| <a href="#SbMemoryGetStackBounds-stub" class="mdl-tabs__tab is-active">stub</a> |
| <a href="#SbMemoryGetStackBounds-bsd" class="mdl-tabs__tab">bsd</a> |
| <a href="#SbMemoryGetStackBounds-linux" class="mdl-tabs__tab">linux</a> |
| </div> |
| <div class="mdl-tabs__panel is-active" id="SbMemoryGetStackBounds-stub"> |
| <pre> |
| #include "starboard/memory.h" |
| |
| void SbMemoryGetStackBounds(void** /*out_high*/, void** /*out_low*/) { |
| } |
| </pre> |
| |
| </div> |
| <div class="mdl-tabs__panel " id="SbMemoryGetStackBounds-bsd"> |
| |
| <pre> |
| #include <pthread_np.h> |
| |
| void SbMemoryGetStackBounds(void** out_high, void** out_low) { |
| pthread_attr_t attr; |
| pthread_attr_init(&attr); |
| pthread_attr_get_np(pthread_self(), &attr); |
| |
| void* stack_address; |
| size_t stack_size; |
| pthread_attr_getstack(&attr, &stack_address, &stack_size); |
| *out_high = static_cast<uint8_t*>(stack_address) + stack_size; |
| *out_low = stack_address; |
| |
| pthread_attr_destroy(&attr); |
| } |
| </pre> |
| |
| </div> |
| <div class="mdl-tabs__panel " id="SbMemoryGetStackBounds-linux"> |
| <pre> |
| #include "starboard/log.h" |
| |
| void SbMemoryGetStackBounds(void** out_high, void** out_low) { |
| void* stackBase = 0; |
| size_t stackSize = 0; |
| |
| pthread_t thread = pthread_self(); |
| pthread_attr_t sattr; |
| pthread_attr_init(&sattr); |
| pthread_getattr_np(thread, &sattr); |
| int rc = pthread_attr_getstack(&sattr, &stackBase, &stackSize); |
| SB_DCHECK(rc == 0); |
| SB_DCHECK(stackBase); |
| pthread_attr_destroy(&sattr); |
| *out_high = static_cast<char*>(stackBase) + stackSize; |
| *out_low = stackBase; |
| } |
| </pre> |
| </div> |
| </div> |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void**</code><br> <code>out_high</code></td> |
| <td>The highest addressable byte + 1 for the current thread.</td> |
| </tr> |
| <tr> |
| <td><code>void**</code><br> <code>out_low</code></td> |
| <td>The lowest addressable byte for the current thread.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryIsAligned |
| |
| **Description** |
| |
| Checks whether `memory` is aligned to `alignment` bytes. |
| |
| **Definition** |
| |
| ``` |
| static SB_C_FORCE_INLINE bool SbMemoryIsAligned(const void* memory, |
| size_t alignment) { |
| return ((uintptr_t)memory) % alignment == 0; |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>const void*</code></td> |
| <td><code>memory</code></td> |
| </tr> |
| <tr> |
| <td><code>size_t</code></td> |
| <td><code>alignment</code></td> |
| </tr> |
| </table> |
| |
| ### SbMemoryMap |
| |
| **Description** |
| |
| Allocates `size_bytes` worth of physical memory pages and maps them into an |
| available virtual region. This function returns `SB_MEMORY_MAP_FAILED` on |
| failure. `NULL` is a valid return value. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemoryMap(int64_t size_bytes, int flags, const char* name); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>int64_t</code><br> <code>size_bytes</code></td> |
| <td>The amount of physical memory pages to be allocated.</td> |
| </tr> |
| <tr> |
| <td><code>int</code><br> <code>flags</code></td> |
| <td>The bitwise OR of the protection flags for the mapped memory |
| as specified in <code>SbMemoryMapFlags</code>.</td> |
| </tr> |
| <tr> |
| <td><code>const char*</code><br> <code>name</code></td> |
| <td>A value that appears in the debugger on some platforms. The value |
| can be up to 32 bytes.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryMove |
| |
| **Description** |
| |
| Copies `count` sequential bytes from `source` to `destination`, with support |
| for the `source` and `destination` regions overlapping. This function is |
| meant to be a drop-in replacement for `memmove`.<br><br> |
| |
| |
| The function's behavior is undefined if `destination` or `source` are NULL, |
| and the function is a no-op if `count` is 0. The return value is |
| `destination`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemoryMove(void* destination, |
| const void* source, |
| size_t count); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>destination</code></td> |
| <td>The destination of the copied memory.</td> |
| </tr> |
| <tr> |
| <td><code>const void*</code><br> <code>source</code></td> |
| <td>The source of the copied memory.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>count</code></td> |
| <td>The number of sequential bytes to be copied.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryReallocateChecked |
| |
| **Description** |
| |
| This function is identical to <code><a href="#sbmemoryreallocateunchecked">SbMemoryReallocateUnchecked</a></code>, but it aborts |
| (`abort()`) in the case of an allocation failure. |
| |
| **Definition** |
| |
| ``` |
| static SB_C_FORCE_INLINE void* SbMemoryReallocateChecked(void* memory, |
| size_t size) { |
| void* address = SbMemoryReallocateUnchecked(memory, size); |
| SbAbortIfAllocationFailed(size, address); |
| return address; |
| } |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>memory</code></td> |
| <td>The chunk of memory to be resized. <code>memory</code> may be NULL, in which |
| case it behaves exactly like <code><a href="#sbmemoryallocateunchecked">SbMemoryAllocateUnchecked</a></code>.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td>The size to which <code>memory</code> will be resized. If <code>size</code> is <code>0</code>, |
| the function may return <code>NULL</code> or it may return a unique pointer value |
| that can be passed to <code><a href="#sbmemoryfree">SbMemoryFree</a></code>.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryReallocateUnchecked |
| |
| **Description** |
| |
| Attempts to resize `memory` to be at least `size` bytes, without touching |
| the contents of memory. |
| <ul><li>If the function cannot perform the fast resize, it allocates a new chunk |
| of memory, copies the contents over, and frees the previous chunk, |
| returning a pointer to the new chunk. |
| </li><li>If the function cannot perform the slow resize, it returns `NULL`, |
| leaving the given memory chunk unchanged. |
| This function is meant to be a drop-in replacement for `realloc`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemoryReallocateUnchecked(void* memory, size_t size); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>memory</code></td> |
| <td>The chunk of memory to be resized. <code>memory</code> may be NULL, in which |
| case it behaves exactly like <code><a href="#sbmemoryallocateunchecked">SbMemoryAllocateUnchecked</a></code>.</td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>size</code></td> |
| <td>The size to which <code>memory</code> will be resized. If <code>size</code> is <code>0</code>, |
| the function may return <code>NULL</code> or it may return a unique pointer value |
| that can be passed to <code><a href="#sbmemoryfree">SbMemoryFree</a></code>.</td> |
| </tr> |
| </table> |
| |
| ### SbMemorySet |
| |
| **Description** |
| |
| Fills `count` sequential bytes starting at `destination`, with the unsigned |
| char coercion of `byte_value`. This function is meant to be a drop-in |
| replacement for `memset`.<br><br> |
| |
| |
| The function's behavior is undefined if `destination` is NULL, and the |
| function is a no-op if `count` is 0. The return value is `destination`. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT void* SbMemorySet(void* destination, int byte_value, size_t count); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>destination</code></td> |
| <td>The destination of the copied memory.</td> |
| </tr> |
| <tr> |
| <td><code>int</code><br> <code>byte_value</code></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td><code>size_t</code><br> <code>count</code></td> |
| <td>The number of sequential bytes to be set.</td> |
| </tr> |
| </table> |
| |
| ### SbMemoryUnmap |
| |
| **Description** |
| |
| Unmap `size_bytes` of physical pages starting from `virtual_address`, |
| returning `true` on success. After this function completes, |
| [virtual_address, virtual_address + size_bytes) will not be read/writable. |
| This function can unmap multiple contiguous regions that were mapped with |
| separate calls to <code><a href="#sbmemorymap">SbMemoryMap()</a></code>. For example, if one call to |
| `SbMemoryMap(0x1000)` returns `(void*)0xA000`, and another call to |
| `SbMemoryMap(0x1000)` returns `(void*)0xB000`, |
| `SbMemoryUnmap(0xA000, 0x2000)` should free both regions. |
| |
| **Definition** |
| |
| ``` |
| SB_EXPORT bool SbMemoryUnmap(void* virtual_address, int64_t size_bytes); |
| ``` |
| |
| **Parameters** |
| |
| |
| |
| <table class="responsive"> |
| <tr><th colspan="2">Parameters</th></tr> |
| <tr> |
| <td><code>void*</code><br> <code>virtual_address</code></td> |
| <td></td> |
| </tr> |
| <tr> |
| <td><code>int64_t</code><br> <code>size_bytes</code></td> |
| <td></td> |
| </tr> |
| </table> |
| |