OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-gpu / opencog / persist / gpu / GpuAtomTable_ocl.cc

375 lines · cc ref:
/*
 * opencog/persist/gpu/GpuAtomTable_ocl.cc
 *
 * OpenCL implementation of the GpuAtomTable API.
 *
 * AoS for per-atom fixed fields: one GpuAtom struct per slot,
 * one enqueueWriteBuffer/enqueueReadBuffer per atom.
 * Variable-length data (names, outgoing sets) in side pools.
 *
 * Copyright (C) 2026 OpenCog Foundation
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#ifdef HAVE_OPENCL

#include "GpuAtomTable.h"
#include "opencl-headers.h"

#include <cstdio>
#include <cstring>
#include <vector>

/* ----------------------------------------------------------------
 * Internal state: OpenCL handles stored alongside the table.
 * ---------------------------------------------------------------- */
struct OclAtomTableState
{
    cl::Context       context;
    cl::CommandQueue  queue;

    cl::Buffer  b_atoms;       /* GpuAtom[CAPACITY] */
    cl::Buffer  b_name_pool;   /* char[NAME_POOL_BYTES] */
    cl::Buffer  b_out_pool;    /* uint32_t[OUT_POOL_SLOTS] */
};

static GpuAtomTable* s_active_table = nullptr;
static OclAtomTableState* s_state = nullptr;

static OclAtomTableState* get_state(const GpuAtomTable* t)
{
    if (s_active_table == t) return s_state;
    return nullptr;
}

static bool pick_device(cl::Platform& plat, cl::Device& dev)
{
    std::vector<cl::Platform> platforms;
    cl::Platform::get(&platforms);
    if (platforms.empty()) return false;

    for (auto& p : platforms)
    {
        std::vector<cl::Device> devs;
        p.getDevices(CL_DEVICE_TYPE_GPU, &devs);
        if (!devs.empty()) { plat = p; dev = devs[0]; return true; }
    }
    for (auto& p : platforms)
    {
        std::vector<cl::Device> devs;
        p.getDevices(CL_DEVICE_TYPE_ALL, &devs);
        if (!devs.empty()) { plat = p; dev = devs[0]; return true; }
    }
    return false;
}

/* ================================================================
 * gpu_table_alloc
 * ================================================================ */
extern "C"
int ocl_table_alloc(GpuAtomTable* t)
{
    memset(t, 0, sizeof(GpuAtomTable));

    cl::Platform plat;
    cl::Device dev;
    if (!pick_device(plat, dev))
    {
        fprintf(stderr, "OpenCL: no device found\n");
        return -1;
    }

    auto* st = new OclAtomTableState();

    try {
        st->context = cl::Context(dev);
        st->queue   = cl::CommandQueue(st->context, dev);

        cl_int err;

        #define MAKE_BUF(field, bytes) \
            st->field = cl::Buffer(st->context, CL_MEM_READ_WRITE, (bytes), nullptr, &err); \
            if (err != CL_SUCCESS) { fprintf(stderr, "OpenCL alloc " #field " failed\n"); delete st; return -1; }

        MAKE_BUF(b_atoms,     (size_t)GPU_ATOM_CAPACITY * sizeof(GpuAtom))
        MAKE_BUF(b_name_pool, GPU_NAME_POOL_BYTES)
        MAKE_BUF(b_out_pool,  (size_t)GPU_OUT_POOL_SLOTS * sizeof(uint32_t))

        #undef MAKE_BUF

        /* Zero all buffers */
        uint8_t zero = 0;
        st->queue.enqueueFillBuffer(st->b_atoms,     zero, 0,
                                     (size_t)GPU_ATOM_CAPACITY * sizeof(GpuAtom));
        st->queue.enqueueFillBuffer(st->b_name_pool,  zero, 0, GPU_NAME_POOL_BYTES);
        st->queue.enqueueFillBuffer(st->b_out_pool,   (uint32_t)0, 0,
                                     (size_t)GPU_OUT_POOL_SLOTS * sizeof(uint32_t));
        st->queue.finish();

    } catch (cl::Error& e) {
        fprintf(stderr, "OpenCL exception in alloc: %s (%d)\n",
                e.what(), e.err());
        delete st;
        return -1;
    }

    /* Opaque non-null markers */
    t->atoms     = (GpuAtom*)(uintptr_t)1;
    t->name_pool = (char*)(uintptr_t)1;
    t->out_pool  = (uint32_t*)(uintptr_t)1;

    t->atom_count     = 0;
    t->name_pool_used = 0;
    t->out_pool_used  = 0;

    s_active_table = t;
    s_state = st;

    return 0;
}

/* ================================================================
 * gpu_table_free
 * ================================================================ */
extern "C"
void ocl_table_free(GpuAtomTable* t)
{
    if (s_active_table == t && s_state)
    {
        delete s_state;
        s_state = nullptr;
        s_active_table = nullptr;
    }
    memset(t, 0, sizeof(GpuAtomTable));
}

/* ================================================================
 * gpu_table_clear
 * ================================================================ */
extern "C"
void ocl_table_clear(GpuAtomTable* t)
{
    auto* st = get_state(t);
    if (!st) return;

    try {
        uint8_t zero = 0;
        st->queue.enqueueFillBuffer(st->b_atoms,     zero, 0,
                                     (size_t)GPU_ATOM_CAPACITY * sizeof(GpuAtom));
        st->queue.enqueueFillBuffer(st->b_name_pool,  zero, 0, GPU_NAME_POOL_BYTES);
        st->queue.enqueueFillBuffer(st->b_out_pool,   (uint32_t)0, 0,
                                     (size_t)GPU_OUT_POOL_SLOTS * sizeof(uint32_t));
        st->queue.finish();
    } catch (cl::Error& e) {
        fprintf(stderr, "OpenCL exception in clear: %s (%d)\n",
                e.what(), e.err());
    }

    t->atom_count     = 0;
    t->name_pool_used = 0;
    t->out_pool_used  = 0;
}

/* ================================================================
 * gpu_store_node
 * ================================================================ */
extern "C"
int ocl_store_node(GpuAtomTable* t, uint32_t slot,
                   uint16_t type, const char* name, uint16_t name_len)
{
    if (slot >= GPU_ATOM_CAPACITY) return -1;
    if (t->name_pool_used + name_len > GPU_NAME_POOL_BYTES) return -1;

    auto* st = get_state(t);
    if (!st) return -1;

    uint32_t off = t->name_pool_used;

    GpuAtom a;
    memset(&a, 0, sizeof(a));
    a.type        = type;
    a.is_node     = 1;
    a.data_offset = off;
    a.data_len    = name_len;

    try {
        st->queue.enqueueWriteBuffer(st->b_atoms, CL_FALSE,
            slot * sizeof(GpuAtom), sizeof(GpuAtom), &a);

        if (name_len > 0)
            st->queue.enqueueWriteBuffer(st->b_name_pool, CL_FALSE,
                off, name_len, name);

        st->queue.finish();
    } catch (cl::Error& e) {
        fprintf(stderr, "OpenCL exception in store_node: %s (%d)\n",
                e.what(), e.err());
        return -1;
    }

    t->name_pool_used += name_len;
    if (slot >= t->atom_count) t->atom_count = slot + 1;
    return 0;
}

/* ================================================================
 * gpu_store_link
 * ================================================================ */
extern "C"
int ocl_store_link(GpuAtomTable* t, uint32_t slot,
                   uint16_t type,
                   const uint32_t* outgoing, uint16_t arity)
{
    if (slot >= GPU_ATOM_CAPACITY) return -1;
    if (t->out_pool_used + arity > GPU_OUT_POOL_SLOTS) return -1;

    auto* st = get_state(t);
    if (!st) return -1;

    uint32_t off = t->out_pool_used;

    GpuAtom a;
    memset(&a, 0, sizeof(a));
    a.type        = type;
    a.is_node     = 0;
    a.data_offset = off;
    a.data_len    = arity;

    try {
        st->queue.enqueueWriteBuffer(st->b_atoms, CL_FALSE,
            slot * sizeof(GpuAtom), sizeof(GpuAtom), &a);

        if (arity > 0)
            st->queue.enqueueWriteBuffer(st->b_out_pool, CL_FALSE,
                off * sizeof(uint32_t), arity * sizeof(uint32_t), outgoing);

        st->queue.finish();
    } catch (cl::Error& e) {
        fprintf(stderr, "OpenCL exception in store_link: %s (%d)\n",
                e.what(), e.err());
        return -1;
    }

    t->out_pool_used += arity;
    if (slot >= t->atom_count) t->atom_count = slot + 1;
    return 0;
}

/* ================================================================
 * gpu_fetch_node
 * ================================================================ */
extern "C"
int ocl_fetch_node(const GpuAtomTable* t, uint32_t slot,
                   uint16_t* type, char* name_buf, uint16_t* name_len)
{
    if (slot >= GPU_ATOM_CAPACITY) return -1;

    auto* st = get_state(t);
    if (!st) return -1;

    try {
        GpuAtom a;
        st->queue.enqueueReadBuffer(st->b_atoms, CL_TRUE,
            slot * sizeof(GpuAtom), sizeof(GpuAtom), &a);

        *type     = a.type;
        *name_len = a.data_len;

        if (a.data_len > 0 && name_buf)
        {
            st->queue.enqueueReadBuffer(st->b_name_pool, CL_TRUE,
                a.data_offset, a.data_len, name_buf);
        }
    } catch (cl::Error& e) {
        fprintf(stderr, "OpenCL exception in fetch_node: %s (%d)\n",
                e.what(), e.err());
        return -1;
    }
    return 0;
}

/* ================================================================
 * gpu_fetch_link
 * ================================================================ */
extern "C"
int ocl_fetch_link(const GpuAtomTable* t, uint32_t slot,
                   uint16_t* type, uint32_t* out_buf, uint16_t* arity)
{
    if (slot >= GPU_ATOM_CAPACITY) return -1;

    auto* st = get_state(t);
    if (!st) return -1;

    try {
        GpuAtom a;
        st->queue.enqueueReadBuffer(st->b_atoms, CL_TRUE,
            slot * sizeof(GpuAtom), sizeof(GpuAtom), &a);

        *type  = a.type;
        *arity = a.data_len;

        if (a.data_len > 0 && out_buf)
        {
            st->queue.enqueueReadBuffer(st->b_out_pool, CL_TRUE,
                a.data_offset * sizeof(uint32_t),
                a.data_len * sizeof(uint32_t), out_buf);
        }
    } catch (cl::Error& e) {
        fprintf(stderr, "OpenCL exception in fetch_link: %s (%d)\n",
                e.what(), e.err());
        return -1;
    }
    return 0;
}

/* ================================================================
 * gpu_table_barrier
 * ================================================================ */
extern "C"
void ocl_table_barrier(GpuAtomTable* t)
{
    auto* st = get_state(t);
    if (st)
    {
        try { st->queue.finish(); }
        catch (cl::Error& e) {
            fprintf(stderr, "OpenCL exception in barrier: %s (%d)\n",
                    e.what(), e.err());
        }
    }
}

/* ================================================================
 * Incoming-set index (Phase 2a) — not yet implemented for OpenCL.
 * The CUDA backend is preferred; these stubs keep the dispatch
 * layer linkable. Returns -1 (caller treats as "no incoming").
 * ================================================================ */
extern "C"
int ocl_table_build_incoming(GpuAtomTable* t)
{
    (void)t;
    fprintf(stderr, "OpenCL: build_incoming not implemented (use CUDA)\n");
    return -1;
}

extern "C"
int ocl_fetch_incoming(const GpuAtomTable* t, uint32_t slot,
                       uint32_t* buf, uint32_t max, uint32_t* count)
{
    (void)t; (void)slot; (void)buf; (void)max;
    if (count) *count = 0;
    return -1;
}

extern "C"
int ocl_intersect_incoming(const GpuAtomTable* t, uint32_t a, uint32_t b,
                           uint32_t* out, uint32_t max, uint32_t* count)
{
    (void)t; (void)a; (void)b; (void)out; (void)max;
    if (count) *count = 0;
    return -1;
}

#endif /* HAVE_OPENCL */