lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


//////////////////////////////////////////////////////////////////////
//    filename:     luaHashTest.cpp
//    author:        James Poag for Rumbic Studios
//    
//    purpose:    This is to demonstrate a real-world scenario in which
//                'luai_hashnum'  FAILS to generate a DETERMINISTIC
//                hash value for identical keys when executed non-concurrently
//                on separate threads.
//
//                It is the fault of different precision controls being 
//                set on different threads, one of which is automatically
//                set to 24 bits when using DirectX without the D3DCREATE_FPU_PRESERVE
//                flag.  (But not strictly limited to DX, anyone wanting to
//                increase floating point performance with the sacrifice of 
//                precision).
//
//                Another scenario is where the LuaVM is created and setup
//                before DirectX (or anyone) resets the precision control and the
//                resulting LuaVM appears empty (because of hash misses)
/////////////////////////////////////////////////////////////////////

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>
#include <string.h>

#include <float.h>
#include <math.h>
#include <limits.h>

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#pragma fenv_access (on)


/*
================================================================================================

   Minimum Lua defined macros to reproduce error

================================================================================================
*/

/* type of numbers in Lua */
#define LUA_NUMBER    double
typedef LUA_NUMBER lua_Number;
#define cast(t, exp)    ((t)(exp))
#define cast_num(i)    cast(lua_Number, (i))

/*
** lua_number2int is a macro to convert lua_Number to int.
*/
#define lua_number2int(i,n)  __asm {__asm fld n   __asm fistp i}
#define l_mathop(x)        (x)

/*
** luai_hashnum is a macro to hash a lua_Number value into an integer.
** The hash must be deterministic and give reasonable values for
** both small and large values (outside the range of integers).
*/
#define luai_hashnum(i,n) { int e;  \
   n = l_mathop(frexp)(n, &e) * (lua_Number)(INT_MAX - DBL_MAX_EXP);  \
   lua_number2int(i, n); i += e; }





/*
================================================================================================

   Test Code to reproduce error

================================================================================================
*/

DWORD WINAPI workerThreadFunc (LPVOID lpThread); // forward decl
#define THREAD_COUNT 1    


/*
========================
Main Entry Point
========================
*/
int _tmain(int argc, _TCHAR* argv[])
{

   /*
    *  In this scenario, we setup both the Render System and the LuaVM
    *  on the [Main Thread].
    */


   //////////////////////////////////////////////////////////////////////////
   // DirectX sets precision to 24 bits on '[Main Thread]'
   unsigned int control_word_x87;
   control_word_x87 = __control87_2(_PC_24, MCW_PC,
       &control_word_x87, 0);
   //////////////////////////////////////////////////////////////////////////


   //////////////////////////////////////////////////////////////////////////
   // LuaVM is created and filled
   //////////////////////////////////////////////////////////////////////////


   //////////////////////////////////////////////////////////////////////////
   // Per Doom3 and many modern multithreaded renderers, the [Main Thread] is
   // used to:
   //        1. Process User/App input events (uses LuaVM on [Main Thread])
   //
   //        2. Spawn/Signal [Worker Thread] that will update game and generate
   //           render commands for next frame (uses LuaVM on [Worker Thread])
   //
   //        3. Execute render commands from previous frame on [Main Thread]
   //           (does not use LuaVM)
   //
   //        4. WaitFor/Join the threads together
   //
   //        Although the LuaVM is used on separate threads, it is never used
   //        concurrently.


   //////////////////////////////////////////////////////////////////////////
   // spawn [Worker Thread]
   HANDLE pThreads [THREAD_COUNT] = {0};
   for (int i = 0; i < THREAD_COUNT; ++i){

       DWORD nThreadId = 0;
       pThreads[i] = CreateThread (NULL,
           0,
           workerThreadFunc,
           0,
           0,
           &nThreadId);
   }
   //////////////////////////////////////////////////////////////////////////


   //////////////////////////////////////////////////////////////////////////
   // Run the hash function on the [Main Thread]
   lua_Number nk = cast_num(3);

   int hashVal = 0;
   luai_hashnum(hashVal, nk);

   printf("[Main Thread] reports: %i \n", hashVal); // 1610611970
   //////////////////////////////////////////////////////////////////////////



   //////////////////////////////////////////////////////////////////////////
   // Waitfor/Join
   WaitForMultipleObjects(THREAD_COUNT, pThreads, TRUE, INFINITE);
   //////////////////////////////////////////////////////////////////////////

   return 0;
}


/*
========================
[Worker Thread]
========================
*/
DWORD WINAPI workerThreadFunc (LPVOID lpThread)
{
   //////////////////////////////////////////////////////////////////////////
   // The precision is not set on the [Worker Thread]
   //////////////////////////////////////////////////////////////////////////


   Sleep(3); // avoid printf() collision & demo non-concurrent


   //////////////////////////////////////////////////////////////////////////
   // Run the hash function on the [Worker Thread]
   lua_Number nk = cast_num(3);

   int hashVal = 0;
   luai_hashnum(hashVal, nk);

   printf("[Worker Thread] reports: %i \n", hashVal); // 1610611969
   //////////////////////////////////////////////////////////////////////////
   
   return 0;
}