C

Memory allocation in Qt Quick Ultralite platform abstraction

All memory allocations from the Qt Quick Ultralite core libary use a dedicated set of functions, enabling you to handle dynamic memory allocation requests depending on the platform you are porting to.

Basic memory allocation

The Qt Quick Ultralite core library handles small memory allocations using the following functions:

You can forward these calls to the platform-specific functions as shown below:

void *qul_malloc(std::size_t size)
{
    return std::malloc(size);
}

void qul_free(void *ptr)
{
    std::free(ptr);
}

void *qul_realloc(void *ptr, size_t s)
{
    return std::realloc(ptr, s);
}

Memory allocator API

The Qt Quick Ultralite core library uses the memory allocator API for larger memory allocations. This API provides more control over how and where to allocate memory for different purposes. For example, it might make sense to store image data in VRAM on some platforms. The API also requires the Qt Quick Ultralite core library to explicitly acquire and release memory before and after writing to it, letting the platform do any necessary synchronization.

Example memory allocator

Here's what an example memory allocator might look like:

class ExampleMemoryAllocator : public PlatformInterface::MemoryAllocator
{
public:
    void *allocate(std::size_t size, UsagePattern) override
    {
        void *ptr = nullptr;
        // ptr = HW_VideoMemAlloc(size);
        return ptr;
    }

    void *reallocate(void *ptr, std::size_t size) override
    {
        void *new_ptr = nullptr;
        // new_ptr = HW_VideoMemRealloc(ptr, size);
        return new_ptr;
    }

    void free(void *ptr) override
    {
        // HW_VideoMemFree(ptr);
    }

    void acquire(void * /* ptr */, std::size_t /* offset */, std::size_t /* size */) override
    {
        // WaitForLcdUpdateComplete();
    }
};

In this example, it's assumed that there's a hardware API available to allocate video memory instead of normal memory. Although, a custom platform port might choose to use some other memory regions.

The Qul::PlatformInterface::MemoryAllocator::acquire function override is called before writing to a memory buffer allocated with the Qul::PlatformInterface::MemoryAllocator API. Some hardware might have limited memory bandwidth, for example so that SDRAM can't be written to while the framebuffer is being scanned by the LCD display controller. In that case the platform can choose to wait until the LCD update has finished before allowing any writes to SDRAM.

Next, here's the example implementation of the PlatformContext::memoryAllocator function:

PlatformInterface::MemoryAllocator *ExamplePlatform::memoryAllocator(
    PlatformInterface::MemoryAllocator::AllocationType type)
{
    static ExampleMemoryAllocator exampleMemoryAllocator;
    static ExampleReversePreloadAllocator<4> examplePreloadAllocator(preloadSdramEnd, preloadSdramStart);
    static PlatformInterface::MemoryAllocator defaultMemoryAllocator;

    switch (type) {
    case PlatformInterface::MemoryAllocator::Image:
        return &exampleMemoryAllocator;
    case PlatformInterface::MemoryAllocator::DefaultPreload:
        return &examplePreloadAllocator;
    default:
        return &defaultMemoryAllocator;
    }
}

Here we use our example memory allocator for image allocations, but fall back to the default implementation for other allocations. The default implementation forwards control to the Qul::Platform::qul_malloc, Qul::Platform::qul_realloc, and Qul::Platform::qul_free functions.

Example reverse preload allocator

Preloadable resources are loaded only once during the platform initialization so we might use a simpler allocator for this purpose. For instance memory reallocation and freeing are not needed so they don't need to be supported. By using reverse allocator the preload memory starts from the end of the section and grows towards the start of the section. This can be useful for example to avoid preload memory to grow in the same direction where the heap memory is located.

Here's what an example reverse preload allocator might look like:

template<std::size_t ALIGNMENT>
class ExampleReversePreloadAllocator : public PlatformInterface::MemoryAllocator
{
public:
    ExampleReversePreloadAllocator(void *endAddress, void *startAddress = nullptr)
        : m_currentAddress(endAddress)
        , m_startAddress(startAddress)
    {}

    void *allocate(std::size_t size, UsagePattern) override
    {
        m_currentAddress = static_cast<char *>(m_currentAddress) - size;
        m_currentAddress = static_cast<char *>(m_currentAddress)
                           - (reinterpret_cast<uintptr_t>(m_currentAddress) % ALIGNMENT);
        if (m_startAddress && m_currentAddress < m_startAddress)
            return nullptr;
        return m_currentAddress;
    }

    // Reallocating is not supported in preloading
    void *reallocate(void *, std::size_t) override
    {
        QUL_ASSERT(false, QulError_PreloadAllocator_UnsupportedOperation);
        return nullptr;
    };

    // Preallocated assets are not freed
    void free(void *) override { QUL_ASSERT(false, QulError_PreloadAllocator_UnsupportedOperation); };
    void acquire(void *, std::size_t, std::size_t) override
    {
        QUL_ASSERT(false, QulError_PreloadAllocator_UnsupportedOperation);
    };

    void release(void *, std::size_t, std::size_t) override
    {
        QUL_ASSERT(false, QulError_PreloadAllocator_UnsupportedOperation);
    };

private:
    void *m_currentAddress;
    const void *m_startAddress;
};

Memory statistics

The platform offers functions to get memory statistics like heap and stack sizes.

The Qul::Platform::printHeapStats and Qul::Platform::printStackStats functions must be implemented to print such information. You could use the mallinfo() function, if it is available on your platform.

void printHeapStats(void)
{
    struct mallinfo mi = mallinfo();
    PlatformInterface::log("Heap: %u/%u (in-use/total)\r\n", mi.uordblks, mi.arena);
}

void printStackStats(void)
{
    // Replace this with actual measuring for your platform
    uint32_t maxUsedStackSize = 0;
    PlatformInterface::log("Stack: %u (peak)\r\n", maxUsedStackSize);
}
void printHeapStats(void)
{
    struct mallinfo mi = __iar_dlmallinfo();
    PlatformInterface::log("Heap: %u/%u (in-use/total)\r\n", mi.uordblks, mi.arena);
}

void printStackStats(void)
{
    // Replace this with actual measuring for your platform
    uint32_t maxUsedStackSize = 0;
    PlatformInterface::log("Stack: %u (peak)\r\n", maxUsedStackSize);
}

Fast memory allocations

Use Qul::Platform::StackAllocator to allocate short-term memory faster than a regular allocation. An example usage can be found in its class documentation.

Preallocate static memory to prepare the allocator. This is done by reserving a static amount of memory and initializing the StackAllocator's static members.

static char qul_scratch_buffer[16 * 1024];

char *StackAllocator::m_buffer = qul_scratch_buffer;
char *StackAllocator::m_top = StackAllocator::m_buffer;
int32_t StackAllocator::m_size = sizeof(qul_scratch_buffer);

The code in the earlier example reserves 16kB of buffer, and sets its pointers and size to the StackAllocator.

Note: The stack allocator must be initialized so that the Qt Quick Ultralite core library can use it.

Available under certain Qt licenses.
Find out more.