0

The following C code is used to generate a lookup table at runtime to help implement the "ICSI" log algorithm (referenced from https://github.com/mgbellemare/SkipCTS/blob/master/src/icsilog.cpp):

    /*
This method fills a given array of floats with the information necessary to compute the icsi_log. This method has to be called before any call to icsi_log.
Parameters:
    n is the number of bits used from the mantissa (0<=n<=23). Higher n means higher accuracy but slower execution. We found that a good value for n is 14.
    lookup_table requires a float* pointing to a continuous (preallocated) memory array of 2^n*sizeof(float) bytes.
Return values: void
*/
void fill_icsi_log_table(const int n, float *lookup_table)
{
    float numlog;
    int incr,i,p;
    int *const exp_ptr = ((int*)&numlog);
    int x = *exp_ptr; /*x is the float treated as an integer*/
    x = 0x3F800000; /*set the exponent to 0 so numlog=1.0*/
        *exp_ptr = x;
    incr = 1 << (23-n); /*amount to increase the mantissa*/
    p = 1 << n;
    for(i=0;i<p;i++)
    {
        lookup_table[i] = (float) log2(numlog); /*save the log of the value*/
        x += incr;
        *exp_ptr = x; /*update the float value*/
    }
}


/* ICSIlog V 2.0 */
void fill_icsi_log_table2(const unsigned precision, float* const   pTable)
{
    /* step along table elements and x-axis positions
      (start with extra half increment, so the steps intersect at their midpoints.) */
    float oneToTwo = 1.0f + (1.0f / (float)( 1 <<(precision + 1) ));
    int i;
    for(i = 0;  i < (1 << precision);  ++i )
    {+
        // make y-axis value for table element
        pTable[i] = logf(oneToTwo) / 0.69314718055995f;

        oneToTwo += 1.0f / (float)( 1 << precision );
    }
}

Is there a way that either of these functions could be adapted to generate a lookup table at compile-time using templates and C++11-amenable single-line return constexpr functions similar to the following structure?

/** Range generation,
 * from http://stackoverflow.com/questions/13313980/populate-an-array-using-constexpr-at-compile-time **/
template<unsigned... Is> struct seq{};

template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};

template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

/** A table consisting of indexes and values,
 * which will all be computed at compile-time **/
template<unsigned N>
struct Table
{
    unsigned indexes[N];
    double  values[N];

    static constexpr unsigned length = N;
};


template< typename LambdaType, unsigned... Is>
constexpr Table< sizeof...(Is) > TableGenerator(seq<Is...>, LambdaType evalFunc)
{
    return {{ Is... }, { evalFunc(Is)... }};
}

template<unsigned N, typename LambdaType>
constexpr Table<N> TableGenerator( LambdaType evalFunc )
{
    return TableGenerator(gen_seq<N>(), evalFunc);
}



/** Function that computes a value for each index **/
constexpr double myFunc( unsigned idx )
{ 
    return sin(0.2 * idx) + cos(0.5*idx);
}
MattyZ
  • 1,541
  • 4
  • 24
  • 41
  • Can you explain what is the second piece of code used for? Surely it doesn't do what you want, so what's wrong with it? – user202729 May 17 '18 at 03:36
  • @user202729 this is a working example of the second implementation, it generates a lookup table of size N containing the values of `return sin(0.2 * idx) + cos(0.5*idx);` at compile-time. https://ideone.com/ce21lp I would also like a compile-time lookup table of some size given a template parameter N but one which is filled with elements generated from the somewhat more complex expression in the first snippet, wondering if this is possible to do. – MattyZ May 17 '18 at 03:42
  • There are no reason to reply in comment. You can [edit] the question. – user202729 May 17 '18 at 03:44
  • @user202729 The question was perfectly clear. Do you have anything of relevance to add? – MattyZ May 17 '18 at 03:45
  • Not everything needs to be done by the compiler. Sometimes it's okay for your makefile to just call a bash or python or whatever script, or even a just-compiled binary (but in the latter case you have to be aware of cross-compilation issues). – o11c May 17 '18 at 04:44
  • sorry but your second piece of code doen't works: `std::sin()` and `std::cos()` aren't `constexpr` – max66 May 17 '18 at 09:29
  • 2
    casting as `((int*)&numlog)`, `log2`, `logf` are not `constexpr`, so you have to find alternative. – Jarod42 May 17 '18 at 10:16
  • @max65 It seems to work OK under recent versions of GCC if using the math functions from the C header rather than the std:: namespace functions from . This may be a non-standard thing on the compiler's part but can't say off the top of my head what kind of catastrophe it might cause in practice. If during compile the compiler encounters an evaluation in the sequence which would otherwise cause a range exception or error code e.g. log(0) it generates an error. – MattyZ May 17 '18 at 15:02

1 Answers1

0

Working from this example as a starting point and the "v2.0" variant of the table generation code:

  /* ICSIlog V 2.0 */
void fill_icsi_log_table2(const unsigned precision, float* const   pTable)
{
    /* step along table elements and x-axis positions
      (start with extra half increment, so the steps intersect at their midpoints.) */
    float oneToTwo = 1.0f + (1.0f / (float)( 1 <<(precision + 1) ));
    int i;
    for(i = 0;  i < (1 << precision);  ++i )
    {
        // make y-axis value for table element
        pTable[i] = logf(oneToTwo) / 0.69314718055995f;

        oneToTwo += 1.0f / (float)( 1 << precision );
    }
}

This recursive template structure:

#include <math.h>

#define PRECISION (4)

constexpr float table_log(float oneToTwo)
{
    return logf(oneToTwo) / 0.69314718055995f;
}

template<size_t c, size_t precision,  float* const* pTable>
struct ForLoop {
    template<template <size_t, size_t,  float* const*> class Func>
    static void iterate(float oneToTwo) {
        ForLoop<c - 1, precision, pTable>::template 
        iterate<Func>(Func<c - 1, precision, pTable>()(oneToTwo));
    }
};

template<size_t precision,  float* const* pTable>
struct ForLoop<0, precision, pTable> {
    template<template <size_t, size_t,  float* const*> class Func>
    static void iterate(float oneToTwo) {
        Func<0, precision, pTable>()(oneToTwo);
    }
};

template <size_t index, size_t precision,  float* const *pTable>
struct LogTabe {
    float operator()(float oneToTwo) {
        float a = table_log(oneToTwo);
        (*pTable)[(1 << precision) - index] = a;
        return oneToTwo + 1.0f / (float)(1 << precision);
    }
};

static float *const table = new float[1 << PRECISION];
extern float *const table;

int main() {
    ForLoop<(1 << PRECISION) + 1, PRECISION, &table>::iterate<LogTabe>(1.0f + (1.0f / (float)( 1 << (PRECISION + 1))));
}

Compiled with gcc x86-64 8.1, -std=c++11 -O1, generates an output table consistent with the original code and the asm output:

  mov rax, QWORD PTR table[rip]
  mov DWORD PTR [rax], 0x3d35d69b
  mov DWORD PTR [rax+4], 0x3e0462c4
  mov DWORD PTR [rax+8], 0x3e567af2
  mov DWORD PTR [rax+12], 0x3e92203d
  mov DWORD PTR [rax+16], 0x3eb7110e
  mov DWORD PTR [rax+20], 0x3eda3f60
  mov DWORD PTR [rax+24], 0x3efbd42b
  mov DWORD PTR [rax+28], 0x3f0df989
  mov DWORD PTR [rax+32], 0x3f1d5da0
  mov DWORD PTR [rax+36], 0x3f2c2411
  mov DWORD PTR [rax+40], 0x3f3a58fe
  mov DWORD PTR [rax+44], 0x3f480731
  mov DWORD PTR [rax+48], 0x3f553848
  mov DWORD PTR [rax+52], 0x3f61f4e6
  mov DWORD PTR [rax+56], 0x3f6e44cd
  mov DWORD PTR [rax+60], 0x3f7a2f04
  mov DWORD PTR [rax+64], 0x3f88759c
  mov eax, 0
  ret
_GLOBAL__sub_I_main:
  sub rsp, 8
  mov edi, 64
  call operator new[](unsigned long)
  mov QWORD PTR table[rip], rax
  add rsp, 8
  ret

Showing that the table values have been successfully pre-computed at compile-time. However recent versions of Clang refuse to compile the code on the objection given by max66 in the comments that the "cmath" and "math.h" library functions are not strictly constexpr (but since it's being evaluated at compile-time anyway, a Taylor series expansion to arbitrary precision itself implemented as a constexpr function would likely work fine as a substitute.)

MattyZ
  • 1,541
  • 4
  • 24
  • 41