C++程序  |  1242行  |  33.3 KB

/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can
 * be found in the LICENSE file.
 *
 */

//
//
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

//
//
//

#include "common/cl/assert_cl.h"
#include "common/macros.h"
#include "common/util.h"

//
//
//

#include "hs_cl.h"

//
//
//

struct hs_cl
{
  struct hs_cl_target_config config;

  uint32_t                   key_val_size;
  uint32_t                   slab_keys;
  uint32_t                   bs_slabs_log2_ru;
  uint32_t                   bc_slabs_log2_max;

  struct {
    uint32_t                 count;
    cl_kernel              * bs;
    cl_kernel              * bc;
    cl_kernel              * fm[3];
    cl_kernel              * hm[3];
    cl_kernel              * transpose;
    cl_kernel                all[];
  } kernels;
};

//
//
//

struct hs_state
{
#ifndef NDEBUG
  cl_ulong         t_total; // 0
#endif

  cl_command_queue cq;

  // key buffers
  cl_mem           vin;
  cl_mem           vout; // can be vin

  // enforces ordering on out-of-order queue
  cl_event         wait_list[3]; // worst case
  uint32_t         wait_list_size;

  // bx_ru is number of rounded up warps in vin
  uint32_t         bx_ru;
};

//
//
//

static
void
hs_state_wait_list_release(struct hs_state * const state)
{
  for (uint32_t ii=0; ii<state->wait_list_size; ii++)
    cl(ReleaseEvent(state->wait_list[ii]));

  state->wait_list_size = 0;
}

static
void
hs_state_wait_list_update(struct hs_state * const state,
                          uint32_t          const wait_list_size,
                          cl_event  const * const wait_list)
{
  uint32_t const new_size = state->wait_list_size + wait_list_size;

  for (uint32_t ii=state->wait_list_size; ii<new_size; ii++)
    state->wait_list[ii] = wait_list[ii];

  state->wait_list_size = new_size;
}

//
//
//

#ifdef NDEBUG

#define HS_STATE_WAIT_LIST_PROFILE(state)
#define HS_STATE_WAIT_LIST_PROFILE_EX(state,wait_list_size,wait_list)

#else

#include <stdio.h>

#define HS_STATE_WAIT_LIST_PROFILE(state)               \
  hs_state_wait_list_profile(state,                     \
                             state->wait_list_size,     \
                             state->wait_list)

#define HS_STATE_WAIT_LIST_PROFILE_EX(state,wait_list_size,wait_list)   \
  hs_state_wait_list_profile(state,                                     \
                             wait_list_size,                            \
                             wait_list)

static
void
hs_state_wait_list_profile(struct hs_state  * const state,
                           uint32_t           const wait_list_size,
                           cl_event   const * const wait_list)
{
  cl(Finish(state->cq));

  cl_command_queue_properties props;

  cl(GetCommandQueueInfo(state->cq,
                         CL_QUEUE_PROPERTIES,
                         sizeof(props),
                         &props,
                         NULL));

  for (uint32_t ii=0; ii<wait_list_size; ii++)
    {
      cl_event event = wait_list[ii];

      //
      // profiling
      //
      cl_ulong t_start=0, t_end=0;

      if (props & CL_QUEUE_PROFILING_ENABLE)
        {
          // start
          cl(GetEventProfilingInfo(event,
                                   CL_PROFILING_COMMAND_START,
                                   sizeof(cl_ulong),
                                   &t_start,
                                   NULL));
          // end
          cl(GetEventProfilingInfo(event,
                                   CL_PROFILING_COMMAND_END,
                                   sizeof(cl_ulong),
                                   &t_end,
                                   NULL));

          state->t_total += t_end - t_start;
        }

      //
      // status
      //
      cl_int          status;
      cl_command_type type;

      cl_get_event_info(event,&status,&type);

      fprintf(stdout,"%-13s, %-28s, %20"PRIu64", %20"PRIu64", %20"PRIu64", %20"PRIu64"\n",
              cl_get_event_command_status_string(status),
              cl_get_event_command_type_string(type),
              t_start,t_end,t_end-t_start,state->t_total);
    }
}

#endif

//
//
//

#ifdef NDEBUG

#define HS_TRACE(k,g,l)

#else

#define HS_KERNEL_NAME_MAX 20

static
void
hs_trace(cl_kernel            kernel,
                uint32_t       const dim,
                size_t const * const global_work_size)
{
  if (kernel == NULL)
    return;

  char name[HS_KERNEL_NAME_MAX];

  cl(GetKernelInfo(kernel,CL_KERNEL_FUNCTION_NAME,HS_KERNEL_NAME_MAX,name,NULL));

  fprintf(stderr,"%-19s ( %6zu, %6zu, %6zu )\n",
          name,
          global_work_size[0],
          dim < 2 ? 0 : global_work_size[1],
          dim < 3 ? 0 : global_work_size[2]);
}

#define HS_TRACE(k,d,g)  hs_trace(k,d,g)

#endif

//
//
//

static
void
hs_transpose(struct hs_cl const * const hs,
             struct hs_state    * const state)
{
  size_t const size[1] = { state->bx_ru << hs->config.slab.threads_log2 };
  cl_kernel    kernel  = hs->kernels.transpose[0];

  HS_TRACE(kernel,1,size);

  //
  // The transpose kernel operates on a single slab.  For now, let's
  // rely on the driver to choose a workgroup size.
  //
  // size_t local_work_size[1] = { HS_SLAB_THREADS };
  //
  cl(SetKernelArg(kernel,0,sizeof(state->vout),&state->vout));

  cl_event wait_list_out[1];

  cl(EnqueueNDRangeKernel(state->cq,
                          kernel,
                          1,
                          NULL,
                          size,
                          NULL,
                          state->wait_list_size,
                          state->wait_list,
                          wait_list_out));

  hs_state_wait_list_release(state);
  hs_state_wait_list_update(state,1,wait_list_out);

  HS_STATE_WAIT_LIST_PROFILE(state);
}

//
//
//

static
void
hs_hm_enqueue(struct hs_cl const * const hs,
              struct hs_state    * const state,
              uint32_t             const scale_log2,
              uint32_t             const spans,
              uint32_t             const span_threads)
{
  //
  // Note that some platforms might need to use .z on large grids
  //
  size_t const size[3] = { span_threads, spans, 1 };
  cl_kernel    kernel  = hs->kernels.hm[scale_log2][0];

  HS_TRACE(kernel,3,size);

  cl(SetKernelArg(kernel,0,sizeof(state->vout),&state->vout));

  cl_event wait_list_out[1];

  cl(EnqueueNDRangeKernel(state->cq,
                          kernel,
                          3,
                          NULL,
                          size,
                          NULL,
                          state->wait_list_size,
                          state->wait_list,
                          wait_list_out));

  hs_state_wait_list_release(state);
  hs_state_wait_list_update(state,1,wait_list_out);

  HS_STATE_WAIT_LIST_PROFILE(state);
}

//
//
//

static
uint32_t
hs_hm(struct hs_cl const * const hs,
      struct hs_state    * const state,
      uint32_t             const down_slabs,
      uint32_t             const clean_slabs_log2)
{
  // how many scaled half-merge spans are there?
  uint32_t const frac_ru    = (1 << clean_slabs_log2) - 1;
  uint32_t const spans      = (down_slabs + frac_ru) >> clean_slabs_log2;

  // for now, just clamp to the max
  uint32_t const log2_rem   = clean_slabs_log2 - hs->bc_slabs_log2_max;
  uint32_t const scale_log2 = MIN_MACRO(hs->config.merge.hm.scale_max,log2_rem);
  uint32_t const log2_out   = log2_rem - scale_log2;

  // size the grid
  uint32_t const span_threads = hs->slab_keys << log2_out;

  // launch the hm kernel
  hs_hm_enqueue(hs,
                state,
                scale_log2,
                spans,
                span_threads);

  return log2_out;
}

//
//
//

static
void
hs_bc_enqueue(struct hs_cl const * const hs,
              struct hs_state    * const state,
              uint32_t             const full,
              uint32_t             const clean_slabs_log2)
{
  size_t const size[1] = { full << hs->config.slab.threads_log2 };
  cl_kernel    kernel  = hs->kernels.bc[clean_slabs_log2];

  HS_TRACE(kernel,1,size);

  cl(SetKernelArg(kernel,0,sizeof(state->vout),&state->vout));

  cl_event wait_list_out[1];

  cl(EnqueueNDRangeKernel(state->cq,
                          kernel,
                          1,
                          NULL,
                          size,
                          NULL,
                          state->wait_list_size,
                          state->wait_list,
                          wait_list_out));

  hs_state_wait_list_release(state);
  hs_state_wait_list_update(state,1,wait_list_out);

  HS_STATE_WAIT_LIST_PROFILE(state);
}

//
//
//

static
void
hs_bc(struct hs_cl const * const hs,
      struct hs_state    * const state,
      uint32_t             const down_slabs,
      uint32_t             const clean_slabs_log2)
{
  // block clean the minimal number of down_slabs_log2 spans
  uint32_t const frac_ru = (1u << clean_slabs_log2) - 1;
  uint32_t const full    = (down_slabs + frac_ru) & ~frac_ru;

  // we better be capable of cleaning at least two warps !!!
  hs_bc_enqueue(hs,state,full,clean_slabs_log2);
}

//
// FIXME -- some of this logic can be skipped if BS is a power-of-two
//

static
void
hs_fm_enqueue(struct hs_cl const * const hs,
              struct hs_state    * const state,
              uint32_t             const scale_log2,
              uint32_t             const fm_full,
              uint32_t             const fm_frac,
              uint32_t             const span_threads)
{
  //
  // Note that some platforms might need to use .z on large grids
  //
  uint32_t wait_list_out_size = 0;
  cl_event wait_list_out[2];

  if (fm_full > 0)
    {
      size_t const size_full[3] = { span_threads, fm_full, 1 };
      cl_kernel    kernel_full  = hs->kernels.fm[scale_log2][hs->bs_slabs_log2_ru-1+scale_log2];

      HS_TRACE(kernel_full,3,size_full);

      cl(SetKernelArg(kernel_full,0,sizeof(state->vout),&state->vout));

      cl(EnqueueNDRangeKernel(state->cq,
                              kernel_full,
                              3,
                              NULL,
                              size_full,
                              NULL,
                              state->wait_list_size,
                              state->wait_list,
                              wait_list_out+wait_list_out_size++));
    }

  if (fm_frac > 0)
    {
      size_t const offset_frac[3] = { 0,            fm_full, 0 };
      size_t const size_frac  [3] = { span_threads, 1,       1 };
      cl_kernel    kernel_frac    = hs->kernels.fm[scale_log2][msb_idx_u32(fm_frac)];

      HS_TRACE(kernel_frac,3,size_frac);

      cl(SetKernelArg(kernel_frac,0,sizeof(state->vout),&state->vout));

      cl(EnqueueNDRangeKernel(state->cq,
                              kernel_frac,
                              3,
                              offset_frac,
                              size_frac,
                              NULL,
                              state->wait_list_size,
                              state->wait_list,
                              wait_list_out+wait_list_out_size++));
    }

  hs_state_wait_list_release(state);
  hs_state_wait_list_update(state,wait_list_out_size,wait_list_out);

  HS_STATE_WAIT_LIST_PROFILE(state);
}

//
//
//

static
uint32_t
hs_fm(struct hs_cl const * const hs,
      struct hs_state    * const state,
      uint32_t           * const down_slabs,
      uint32_t             const up_scale_log2)
{
  //
  // FIXME OPTIMIZATION: in previous HotSort launchers it's sometimes
  // a performance win to bias toward launching the smaller flip merge
  // kernel in order to get more warps in flight (increased
  // occupancy).  This is useful when merging small numbers of slabs.
  //
  // Note that HS_FM_SCALE_MIN will always be 0 or 1.
  //
  // So, for now, just clamp to the max until there is a reason to
  // restore the fancier and probably low-impact approach.
  //
  uint32_t const scale_log2 = MIN_MACRO(hs->config.merge.fm.scale_max,up_scale_log2);
  uint32_t const clean_log2 = up_scale_log2 - scale_log2;

  // number of slabs in a full-sized scaled flip-merge span
  uint32_t const full_span_slabs = hs->config.block.slabs << up_scale_log2;

  // how many full-sized scaled flip-merge spans are there?
  uint32_t fm_full = state->bx_ru / full_span_slabs;
  uint32_t fm_frac = 0;

  // initialize down_slabs
  *down_slabs = fm_full * full_span_slabs;

  // how many half-size scaled + fractional scaled spans are there?
  uint32_t const span_rem        = state->bx_ru - *down_slabs;
  uint32_t const half_span_slabs = full_span_slabs >> 1;

  // if we have over a half-span then fractionally merge it
  if (span_rem > half_span_slabs)
    {
      // the remaining slabs will be cleaned
      *down_slabs += span_rem;

      uint32_t const frac_rem      = span_rem - half_span_slabs;
      uint32_t const frac_rem_pow2 = pow2_ru_u32(frac_rem);

      if (frac_rem_pow2 >= half_span_slabs)
        {
          // bump it up to a full span
          fm_full += 1;
        }
      else
        {
          // otherwise, add fractional
          fm_frac  = MAX_MACRO(1,frac_rem_pow2 >> clean_log2);
        }
    }

  // size the grid
  uint32_t const span_threads = hs->slab_keys << clean_log2;

  //
  // launch the fm kernel
  //
  hs_fm_enqueue(hs,
                state,
                scale_log2,
                fm_full,
                fm_frac,
                span_threads);

  return clean_log2;
}

//
//
//

static
void
hs_bs_enqueue(struct hs_cl const * const hs,
              struct hs_state    * const state,
              uint32_t             const full,
              uint32_t             const frac,
              uint32_t             const wait_list_size,
              cl_event           *       wait_list)
{
  uint32_t wait_list_out_size = 0;
  cl_event wait_list_out[2];

  if (full > 0)
    {
      size_t const size_full[1] = { full << hs->config.slab.threads_log2 };
      cl_kernel    kernel_full  = hs->kernels.bs[hs->bs_slabs_log2_ru];

      HS_TRACE(kernel_full,1,size_full);

      cl(SetKernelArg(kernel_full,0,sizeof(state->vin), &state->vin));
      cl(SetKernelArg(kernel_full,1,sizeof(state->vout),&state->vout));

      cl(EnqueueNDRangeKernel(state->cq,
                              kernel_full,
                              1,
                              NULL,
                              size_full,
                              NULL,
                              wait_list_size,
                              wait_list,
                              wait_list_out+wait_list_out_size++));
    }

  if (frac > 0)
    {
      size_t const offset_frac[1] = { full << hs->config.slab.threads_log2 };
      size_t const size_frac  [1] = { frac << hs->config.slab.threads_log2 };
      cl_kernel    kernel_frac    = hs->kernels.bs[msb_idx_u32(frac)];

      HS_TRACE(kernel_frac,1,size_frac);

      cl(SetKernelArg(kernel_frac,0,sizeof(state->vin), &state->vin));
      cl(SetKernelArg(kernel_frac,1,sizeof(state->vout),&state->vout));

      cl(EnqueueNDRangeKernel(state->cq,
                              kernel_frac,
                              1,
                              offset_frac,
                              size_frac,
                              NULL,
                              wait_list_size,
                              wait_list,
                              wait_list_out+wait_list_out_size++));
    }

  hs_state_wait_list_release(state);
  hs_state_wait_list_update(state,wait_list_out_size,wait_list_out);

  HS_STATE_WAIT_LIST_PROFILE(state);
}

//
//
//

static
void
hs_bs(struct hs_cl const * const hs,
      struct hs_state    * const state,
      uint32_t             const count_padded_in,
      uint32_t             const wait_list_size,
      cl_event           *       wait_list)
{
  uint32_t const slabs_in = count_padded_in / hs->slab_keys;
  uint32_t const full     = (slabs_in / hs->config.block.slabs) * hs->config.block.slabs;
  uint32_t const frac     = slabs_in - full;

  hs_bs_enqueue(hs,state,
                full,frac,
                wait_list_size,wait_list);
}

//
//
//

static
void
hs_keyset_pre_sort(struct hs_cl const * const hs,
                   struct hs_state    * const state,
                   uint32_t             const count,
                   uint32_t             const count_hi,
                   uint32_t             const wait_list_size,
                   cl_event           *       wait_list,
                   cl_event           *       event)
{
  uint32_t const vin_span = count_hi - count;
  uint32_t const pattern  = UINT32_MAX;

  cl(EnqueueFillBuffer(state->cq,
                       state->vin,
                       &pattern,
                       sizeof(pattern),
                       count    * hs->key_val_size,
                       vin_span * hs->key_val_size,
                       wait_list_size,
                       wait_list,
                       event));

  HS_STATE_WAIT_LIST_PROFILE_EX(state,1,event);
}

//
//
//

static
void
hs_keyset_pre_merge(struct hs_cl const * const hs,
                    struct hs_state    * const state,
                    uint32_t             const count_lo,
                    uint32_t             const count_hi,
                    uint32_t             const wait_list_size,
                    cl_event           *       wait_list)
{
  uint32_t const vout_span = count_hi - count_lo;
  uint32_t const pattern   = UINT32_MAX;

  // appends event to incoming wait list
  cl(EnqueueFillBuffer(state->cq,
                       state->vout,
                       &pattern,
                       sizeof(pattern),
                       count_lo  * hs->key_val_size,
                       vout_span * hs->key_val_size,
                       wait_list_size,
                       wait_list,
                       state->wait_list+state->wait_list_size++));

  HS_STATE_WAIT_LIST_PROFILE(state);
}

//
// We want concurrent kernel execution to occur in a few places.
//
// The summary is:
//
//   1) If necessary, some max valued keys are written to the end of
//      the vin/vout buffers.
//
//   2) Blocks of slabs of keys are sorted.
//
//   3) If necesary, the blocks of slabs are merged until complete.
//
//   4) If requested, the slabs will be converted from slab ordering
//      to linear ordering.
//
// Below is the general "happens-before" relationship between HotSort
// compute kernels.
//
// Note the diagram assumes vin and vout are different buffers.  If
// they're not, then the first merge doesn't include the pad_vout
// event in the wait list.
//
//                    +----------+            +---------+
//                    | pad_vout |            | pad_vin |
//                    +----+-----+            +----+----+
//                         |                       |
//                         |                WAITFOR(pad_vin)
//                         |                       |
//                         |                 +-----v-----+
//                         |                 |           |
//                         |            +----v----+ +----v----+
//                         |            | bs_full | | bs_frac |
//                         |            +----+----+ +----+----+
//                         |                 |           |
//                         |                 +-----v-----+
//                         |                       |
//                         |  +------NO------JUST ONE BLOCK?
//                         | /                     |
//                         |/                     YES
//                         +                       |
//                         |                       v
//                         |         END_WITH_EVENTS(bs_full,bs_frac)
//                         |
//                         |
//        WAITFOR(pad_vout,bs_full,bs_frac) >>> first iteration of loop <<<
//                         |
//                         |
//                         +-----------<------------+
//                         |                        |
//                   +-----v-----+                  |
//                   |           |                  |
//              +----v----+ +----v----+             |
//              | fm_full | | fm_frac |             |
//              +----+----+ +----+----+             |
//                   |           |                  ^
//                   +-----v-----+                  |
//                         |                        |
//              WAITFOR(fm_full,fm_frac)            |
//                         |                        |
//                         v                        |
//                      +--v--+                WAITFOR(bc)
//                      | hm  |                     |
//                      +-----+                     |
//                         |                        |
//                    WAITFOR(hm)                   |
//                         |                        ^
//                      +--v--+                     |
//                      | bc  |                     |
//                      +-----+                     |
//                         |                        |
//                         v                        |
//                  MERGING COMPLETE?-------NO------+
//                         |
//                        YES
//                         |
//                         v
//                END_WITH_EVENTS(bc)
//

void
hs_cl_sort(struct hs_cl const * const hs,
           cl_command_queue           cq,
           uint32_t             const wait_list_size,
           cl_event           *       wait_list,
           cl_event           *       event,
           cl_mem                     vin,
           cl_mem                     vout,
           uint32_t             const count,
           uint32_t             const count_padded_in,
           uint32_t             const count_padded_out,
           bool                 const linearize)
{
  // is this sort in place?
  bool const is_in_place = (vout == NULL);

  // cq, buffers, wait list and slab count
  struct hs_state state = {
#ifndef NDEBUG
    .t_total        = 0,
#endif
    .cq             = cq,
    .vin            = vin,
    .vout           = is_in_place ? vin : vout,
    .wait_list_size = 0,
    .bx_ru          = (count + hs->slab_keys - 1) / hs->slab_keys
  };

  // initialize vin
  uint32_t const count_hi                = is_in_place ? count_padded_out : count_padded_in;
  bool     const is_pre_sort_keyset_reqd = count_hi > count;
  cl_event       event_keyset_pre_sort[1];

  // initialize any trailing keys in vin before sorting
  if (is_pre_sort_keyset_reqd)
    {
      hs_keyset_pre_sort(hs,&state,
                         count,count_hi,
                         wait_list_size,wait_list,
                         event_keyset_pre_sort);
    }

  // initialize any trailing keys in vout before merging
  if (!is_in_place && (count_padded_out > count_padded_in))
    {
      hs_keyset_pre_merge(hs,&state,
                          count_padded_in,count_padded_out,
                          wait_list_size,wait_list);
    }

  //
  // sort blocks of slabs
  //
  hs_bs(hs,&state,
        count_padded_in,
        is_pre_sort_keyset_reqd ? 1                     : wait_list_size,
        is_pre_sort_keyset_reqd ? event_keyset_pre_sort : wait_list);

  // release the event
  if (is_pre_sort_keyset_reqd)
    cl(ReleaseEvent(event_keyset_pre_sort[0]));

  //
  // we're done if this was a single bs block...
  //
  // otherwise, merge sorted spans of slabs until done
  //
  if (state.bx_ru > hs->config.block.slabs)
    {
      int32_t up_scale_log2 = 1;

      while (true)
        {
          uint32_t down_slabs;

          // flip merge slabs -- return span of slabs that must be cleaned
          uint32_t clean_slabs_log2 = hs_fm(hs,&state,
                                            &down_slabs,
                                            up_scale_log2);

          // if span is gt largest slab block cleaner then half merge
          while (clean_slabs_log2 > hs->bc_slabs_log2_max)
            {
              clean_slabs_log2 = hs_hm(hs,&state,
                                       down_slabs,
                                       clean_slabs_log2);
            }

          // launch clean slab grid -- is it the final launch?
          hs_bc(hs,&state,down_slabs,clean_slabs_log2);

          // was this the final block clean?
          if (((uint32_t)hs->config.block.slabs << up_scale_log2) >= state.bx_ru)
            break;

          // otherwise, merge twice as many slabs
          up_scale_log2 += 1;
        }
    }

  // slabs or linear?
  if (linearize) {
    hs_transpose(hs,&state);
  }

  // does the caller want the final event?
  if (event != NULL) {
    *event = state.wait_list[0];
  } else {
    cl(ReleaseEvent(state.wait_list[0]));
  }
}

//
// all grids will be computed as a function of the minimum number of slabs
//

void
hs_cl_pad(struct hs_cl const * const hs,
          uint32_t             const count,
          uint32_t           * const count_padded_in,
          uint32_t           * const count_padded_out)
{
  //
  // round up the count to slabs
  //
  uint32_t const slabs_ru        = (count + hs->slab_keys - 1) / hs->slab_keys;
  uint32_t const blocks          = slabs_ru / hs->config.block.slabs;
  uint32_t const block_slabs     = blocks * hs->config.block.slabs;
  uint32_t const slabs_ru_rem    = slabs_ru - block_slabs;
  uint32_t const slabs_ru_rem_ru = MIN_MACRO(pow2_ru_u32(slabs_ru_rem),hs->config.block.slabs);

  *count_padded_in  = (block_slabs + slabs_ru_rem_ru) * hs->slab_keys;
  *count_padded_out = *count_padded_in;

  //
  // will merging be required?
  //
  if (slabs_ru > hs->config.block.slabs)
    {
      // more than one block
      uint32_t const blocks_lo       = pow2_rd_u32(blocks);
      uint32_t const block_slabs_lo  = blocks_lo * hs->config.block.slabs;
      uint32_t const block_slabs_rem = slabs_ru - block_slabs_lo;

      if (block_slabs_rem > 0)
        {
          uint32_t const block_slabs_rem_ru     = pow2_ru_u32(block_slabs_rem);

          uint32_t const block_slabs_hi         = MAX_MACRO(block_slabs_rem_ru,
                                                            blocks_lo << (1 - hs->config.merge.fm.scale_min));

          uint32_t const block_slabs_padded_out = MIN_MACRO(block_slabs_lo+block_slabs_hi,
                                                            block_slabs_lo*2); // clamp non-pow2 blocks

          *count_padded_out = block_slabs_padded_out * hs->slab_keys;
        }
    }
}

//
//
//

static
void
hs_create_kernel(cl_program         program,
                 cl_kernel  * const kernel,
                 char const * const name)
{
  cl_int err;

  *kernel = clCreateKernel(program,name,&err);

  cl_ok(err);
}

static
void
hs_create_kernels(cl_program     program,
                  cl_kernel    * kernels,
                  char           name_template[],
                  size_t   const name_template_size,
                  uint32_t const count)
{
  char const n_max = '0'+(char)count;

  for (char n = '0'; n<n_max; n++)
    {
      cl_int err;

      name_template[name_template_size-2] = n;

      *kernels++ = clCreateKernel(program,name_template,&err);

      cl_ok(err);
    }
}

//
//
//

struct hs_cl *
hs_cl_create(struct hs_cl_target const * const target,
             cl_context                        context,
             cl_device_id                      device_id)
{
  //
  // immediately try to build the OpenCL program
  //
  bool     const is_binary    = (target->program[0] == 0);
  uint32_t const program_size = NPBTOHL_MACRO(target->program+1);

  cl_program program;

  if (is_binary) // program is a binary
    {
      cl_int status, err;

      size_t        const   bins_sizeof[] = { program_size      };
      unsigned char const * bins[]        = { target->program+5 };

      program = clCreateProgramWithBinary(context,
                                          1,
                                          &device_id,
                                          bins_sizeof,
                                          bins,
                                          &status,
                                          &err);
      cl_ok(err);

      fprintf(stdout,"Building binary... ");

      fflush(stdout);

      cl(BuildProgram(program,
                      1,
                      &device_id,
                      NULL,
                      NULL,
                      NULL));
    }
  else // program is source code
    {
      cl_int err;

      size_t const   strings_sizeof[] = { program_size             };
      char   const * strings[]        = { (char*)target->program+5 };

      program = clCreateProgramWithSource(context,
                                          1,
                                          strings,
                                          strings_sizeof,
                                          &err);
      cl_ok(err);

      char const * const options =
        "-cl-std=CL1.2 -cl-fast-relaxed-math "
        "-cl-no-signed-zeros -cl-mad-enable "
        "-cl-denorms-are-zero "
        "-cl-kernel-arg-info";

      fprintf(stdout,"Building source... ");

      fflush(stdout);

      cl(BuildProgram(program,
                      1,
                      &device_id,
                      options,
                      NULL,
                      NULL));
    }

  //
  // we reference these values a lot
  //
  uint32_t const bs_slabs_log2_ru  = msb_idx_u32(pow2_ru_u32(target->config.block.slabs));
  uint32_t const bc_slabs_log2_max = msb_idx_u32(pow2_rd_u32(target->config.block.slabs));

  //
  // how many kernels will be created?
  //
  uint32_t const count_bs    = bs_slabs_log2_ru + 1;
  uint32_t const count_bc    = bc_slabs_log2_max + 1;
  uint32_t       count_fm[3] = { 0 };
  uint32_t       count_hm[3] = { 0 };

  // guaranteed to be in range [0,2]
  for (uint32_t scale = target->config.merge.fm.scale_min;
       scale <= target->config.merge.fm.scale_max;
       scale++)
    {
      uint32_t fm_left = (target->config.block.slabs / 2) << scale;

      count_fm[scale] = msb_idx_u32(pow2_ru_u32(fm_left)) + 1;
    }

  // guaranteed to be in range [0,2]
  for (uint32_t scale = target->config.merge.hm.scale_min;
       scale <= target->config.merge.hm.scale_max;
       scale++)
    {
      count_hm[scale] = 1;
    }

  uint32_t const count_all =
    1
    + count_bs
    + count_bc
    + count_fm[0] + count_fm[1] + count_fm[2]
    + count_hm[0] + count_hm[1] + count_hm[2];

  //
  // allocate hs_cl
  //
  struct hs_cl * hs = malloc(sizeof(*hs) + sizeof(cl_kernel) * count_all);

  memcpy(&hs->config,&target->config,sizeof(hs->config));

  // save some frequently used calculated values
  hs->key_val_size      = (target->config.words.key + target->config.words.val) * 4;
  hs->slab_keys         = target->config.slab.height << target->config.slab.width_log2;
  hs->bs_slabs_log2_ru  = bs_slabs_log2_ru;
  hs->bc_slabs_log2_max = bc_slabs_log2_max;

  // save kernel count
  hs->kernels.count     = count_all;

  //
  // create all the kernels and release the program
  //
  cl_kernel * kernel_next = hs->kernels.all;

  //
  // BS
  //
  {
    hs->kernels.bs = kernel_next;

    char bs_name[] = { "hs_kernel_bs_X" };

    hs_create_kernels(program,
                      kernel_next,
                      bs_name,sizeof(bs_name),
                      count_bs);

    kernel_next += count_bs;
  }

  //
  // BC
  //
  {
    hs->kernels.bc = kernel_next;

    char bc_name[] = { "hs_kernel_bc_X" };

    hs_create_kernels(program,
                      kernel_next,
                      bc_name,sizeof(bc_name),
                      count_bc);

    kernel_next += count_bc;
  }

  //
  // FM
  //
  if (count_fm[0] > 0)
    {
      hs->kernels.fm[0] = kernel_next;

      char fm_0_name[]  = { "hs_kernel_fm_0_X" };

      hs_create_kernels(program,
                        kernel_next,
                        fm_0_name,sizeof(fm_0_name),
                        count_fm[0]);

      kernel_next += count_fm[0];
    }

  if (count_fm[1] > 0)
    {
      hs->kernels.fm[1] = kernel_next;

      char fm_1_name[]  = { "hs_kernel_fm_1_X" };

      hs_create_kernels(program,
                        kernel_next,
                        fm_1_name,sizeof(fm_1_name),
                        count_fm[1]);

      kernel_next += count_fm[1];
    }

  if (count_fm[2] > 0)
    {
      hs->kernels.fm[2] = kernel_next;

      char fm_2_name[]  = { "hs_kernel_fm_2_X" };

      hs_create_kernels(program,
                        kernel_next,
                        fm_2_name,sizeof(fm_2_name),
                        count_fm[2]);

      kernel_next += count_fm[2];
    }

  //
  // HM
  //
  if (count_hm[0] > 0)
    {
      hs->kernels.hm[0] = kernel_next;

      hs_create_kernel(program,
                       kernel_next,
                       "hs_kernel_hm_0");

      kernel_next += count_hm[0];
    }

  if (count_hm[1] > 0)
    {
      hs->kernels.hm[1] = kernel_next;

      hs_create_kernel(program,
                       kernel_next,
                       "hs_kernel_hm_1");

      kernel_next += count_hm[1];
    }

  if (count_hm[2] > 0)
    {
      hs->kernels.hm[2] = kernel_next;

      hs_create_kernel(program,
                       kernel_next,
                       "hs_kernel_hm_2");

      kernel_next += count_hm[2]; // unnecessary
    }

  //
  // TRANSPOSE
  //
  {
    hs->kernels.transpose = kernel_next;

    hs_create_kernel(program,
                     kernel_next,
                     "hs_kernel_transpose");

    kernel_next += 1;
  }

  return hs;
}

//
//
//

void
hs_cl_release(struct hs_cl * const hs)
{
  for (uint32_t ii=0; ii<hs->kernels.count; ii++)
    cl(ReleaseKernel(hs->kernels.all[ii]));

  free(hs);
}

//
//
//