blob: 95b05724b11c90730f701f4820caf951cc2bba5e [file] [log] [blame]
/*
* 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);
}
//
//
//