diff --git a/CMakeLists.txt b/CMakeLists.txt index fc8cf73d..1457496d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,6 @@ blt_list_append( TO lvarray_dependencies ELEMENTS cuda IF ENABLE_CUDA ) blt_list_append( TO lvarray_dependencies ELEMENTS caliper IF ENABLE_CALIPER ) - add_subdirectory( src ) if( ENABLE_TESTS AND NOT DISABLE_UNIT_TESTS ) @@ -108,5 +107,3 @@ endif() if( ENABLE_DOCS ) add_subdirectory( docs ) endif() - - diff --git a/cmake/SetupTPL.cmake b/cmake/SetupTPL.cmake index bff94834..ae11e1ca 100644 --- a/cmake/SetupTPL.cmake +++ b/cmake/SetupTPL.cmake @@ -30,7 +30,6 @@ set(ENABLE_RAJA ON CACHE BOOL "") set(thirdPartyLibs ${thirdPartyLibs} RAJA) - ############################### # UMPIRE ############################### @@ -43,7 +42,12 @@ if(ENABLE_UMPIRE) find_package(umpire REQUIRED PATHS ${UMPIRE_DIR}) - + + blt_register_library(NAME umpire + INCLUDES ${UMPIRE_INCLUDE_DIRS} + LIBRARIES umpire + TREAT_INCLUDES_AS_SYSTEM ON) + set(thirdPartyLibs ${thirdPartyLibs} umpire) else() message(STATUS "Not using Umpire.") @@ -92,6 +96,8 @@ if(ENABLE_CALIPER) message(STATUS "Using caliper from ${CALIPER_DIR}") + set(FIND_LIBRARY_USE_LIB64_PATHS TRUE) + find_package(caliper REQUIRED PATHS ${CALIPER_DIR}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03f627c2..9ec30ad6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,3 +79,5 @@ lvarray_add_code_checks( PREFIX lvarray if( ENABLE_PYLVARRAY ) add_subdirectory( python ) endif() + +add_subdirectory( jitti ) diff --git a/src/jitti/CMakeLists.txt b/src/jitti/CMakeLists.txt new file mode 100644 index 00000000..c1436c87 --- /dev/null +++ b/src/jitti/CMakeLists.txt @@ -0,0 +1,19 @@ +set( jitti_headers + types.hpp + CompilationInfo.hpp + utils.hpp + Function.hpp + Cache.hpp + ) + +set( jitti_sources + CompilationInfo.cpp + Function.cpp + utils.cpp + ) + +blt_add_library( NAME jitti + SOURCES ${jitti_sources} + HEADERS ${jitti_headers} + DEPENDS_ON lvarray dl + ) diff --git a/src/jitti/Cache.hpp b/src/jitti/Cache.hpp new file mode 100644 index 00000000..54bd5be3 --- /dev/null +++ b/src/jitti/Cache.hpp @@ -0,0 +1,237 @@ +#pragma once + +#include "Function.hpp" +#include "utils.hpp" +#include "CompilationInfo.hpp" +#include "../Macros.hpp" +#include "../typeManipulation.hpp" + +#include +#include +#include + +namespace jitti +{ + +// template < const char * TYPE, const char * HEADER > +// struct constexpr_jitti_info +// { +// constexpr static const char * typeName = TYPE; +// constexpr static const char * headerFile = HEADER; +// }; + +// +// NOTE: The TYPE value in the following macros needs to be *FULLY SCOPED*! +// +#if JITTI == 1 + // We can't programmatically get a constexpr expression with the name of the type without fully + // specifying the type e.g. specifying all the template parameters + // Since JIITTI is based around delaying that specification until we JIT a function, we can't + // use templates to derive this info, it can only happen via preprocessing + // + // C++20 will allow using string literals as template parameters, until then we have to + // declare/define a constexpr character array and use a pointer to that instead + // but when it is available we can obsolete the JITTI_DECL macro, and jitti::Name struct entirely + #define JITTI_DECL( VARNAME, TYPE, HEADER ) \ +constexpr const char VARNAME##Name[] = STRINGIZE( TYPE ); \ +constexpr const char VARNAME##Header[] = HEADER; + #define JITTI_TPARAM( VARNAME, TYPE ) VARNAME##Name, VARNAME##Header +#else + #define JITTI_DECL( VARNAME, TYPE, HEADER ) + #define JITTI_TPARAM( VARNAME, TYPE ) TYPE +#endif + +template< typename ARG0, typename ... ARGS > +std::string getInstantiationName( std::string const & templateName, + ARG0 const & firstTemplateParam, + ARGS const & ... remainingTemplateParams ) +{ + std::string output = templateName; + output += "< " + firstTemplateParam; + LvArray::typeManipulation::forEachArg( [&output] ( auto const & param ) + { + output += ", " + param; + }, remainingTemplateParams... ); + + output += " >"; + return output; +} + +/** + * + */ +template< typename FUNC_POINTER > +class Cache +{ +public: + + /** + * + */ + Cache( time_t const compilationTime, + std::string const & libraryOutputDir, + std::vector< std::string > const & libraryDirs={} ): + m_compilationTime( compilationTime ), + m_libraryOutputDir( libraryOutputDir ), + m_libraryDirs( libraryDirs ) + { + utils::makeDirsForPath( libraryOutputDir ); + m_libraryDirs.push_back( libraryOutputDir ); + refresh(); + } + + Cache( Cache const & ) = delete; + Cache( Cache && ) = delete; + + /** + * + */ + void refresh() const + { + for( std::string const & libraryDir : m_libraryDirs ) + { + utils::readDirectoryFiles( m_compilationTime, libraryDir, m_libraries ); + } + } + + /** + * + */ + Function< FUNC_POINTER > const * tryGet( std::string const & instantiationName ) const + { + auto const iter = m_cache.find( instantiationName ); + if( iter == m_cache.end() ) + { return nullptr; } + + return &iter->second; + } + + /** + * + */ + Function< FUNC_POINTER > const * tryGet( CompilationInfo const & info ) const + { + return tryGet( getInstantiationName( info.templateFunction, info.templateParams ) ); + } + + /** + * + */ + Function< FUNC_POINTER > const & get( std::string const & instantiationName ) const + { + Function< FUNC_POINTER > const * const function = tryGet( instantiationName ); + LVARRAY_ERROR_IF( function == nullptr, + "Could not find an instantiation called '" << instantiationName << "' in the cache. " << + "Maybe you need to compile it (use getOrLoadOrCompile)." ); + + return *function; + } + + /** + * + */ + Function< FUNC_POINTER > const & get( CompilationInfo const & info ) const + { + return get( getInstantiationName( info.templateFunction, info.templateParams ) ); + } + + /** + * + */ + Function< FUNC_POINTER > const * tryGetOrLoad( std::string const & instantiationName ) + { + Function< FUNC_POINTER > const * function = tryGet( instantiationName ); + if( function != nullptr ) + { + return function; + } + + // Then look among those already compiled. + std::string const libraryName = getLibraryName( instantiationName ); + auto const iter = m_libraries.find( libraryName ); + if( iter != m_libraries.end() ) + { + auto const result = m_cache.emplace( std::make_pair( instantiationName, Function< FUNC_POINTER >( iter->second ) ) ); + LVARRAY_ERROR_IF( !result.second, "This shouldn't be possible." ); + + function = &result.first->second; + LVARRAY_ERROR_IF_NE_MSG( function->getName(), instantiationName, + "Expected a different function at '" << iter->second << "'." ); + + return function; + } + + return nullptr; + } + + /** + * + */ + Function< FUNC_POINTER > const * tryGetOrLoad( CompilationInfo const & info ) + { + return tryGetOrLoad( getInstantiationName( info.templateFunction, info.templateParams ) ); + } + + /** + * + */ + Function< FUNC_POINTER > const & getOrLoad( std::string const & instantiationName ) + { + Function< FUNC_POINTER > const * function = tryGetOrLoad( instantiationName ); + LVARRAY_ERROR_IF( function == nullptr, + "Could not find an instantiation called '" << instantiationName << "' in the cache. " << + "Maybe you need to compile it (use getOrLoadOrCompile)." ); + + return *function; + } + + /** + * + */ + Function< FUNC_POINTER > const & getOrLoad( CompilationInfo & info ) + { + return getOrLoad( getInstantiationName( info.templateFunction, info.templateParams ) ); + } + + /** + * + */ + Function< FUNC_POINTER > const & getOrLoadOrCompile( CompilationInfo & info ) + { + std::string const instantiationName = getInstantiationName( info.templateFunction, info.templateParams ); + + Function< FUNC_POINTER > const * function = tryGetOrLoad( instantiationName ); + if( function != nullptr ) + { + return *function; + } + + // Otherwise we have to compile it. + info.outputLibrary = m_libraryOutputDir + "/" + getLibraryName( instantiationName ); + info.outputObject = info.outputLibrary.substr( 0, info.outputLibrary.size() - 3 ) + ".o"; + auto const result = m_cache.emplace( std::make_pair( instantiationName, Function< FUNC_POINTER >( info ) ) ); + LVARRAY_ERROR_IF( !result.second, "This shouldn't be possible." ); + return result.first->second; + } + +private: + + std::string getLibraryName( std::string const & instantiationName ) const + { return "lib" + std::to_string( std::hash< std::string >{}( instantiationName ) ) + ".so"; } + + time_t const m_compilationTime; + + /// The directory in which to create new libraries. + std::string const m_libraryOutputDir; + + /// A vector of library directories to search for libraries. + std::vector< std::string > m_libraryDirs; + + /// A map from library names to library paths. + std::unordered_map< std::string, std::string > mutable m_libraries; + + /// A map containing all the compiled libraries that have been loaded. + std::unordered_map< std::string, Function< FUNC_POINTER > > m_cache; +}; + +} // namespace jitti diff --git a/src/jitti/CompilationInfo.cpp b/src/jitti/CompilationInfo.cpp new file mode 100644 index 00000000..313b0630 --- /dev/null +++ b/src/jitti/CompilationInfo.cpp @@ -0,0 +1,63 @@ +#include "CompilationInfo.hpp" +#include "../Macros.hpp" + +#include +#include + +static int monthStringToInt( char const * const month ) +{ + if ( strncmp( month, "Jan", 3 ) == 0 ) + { return 0; } + if ( strncmp( month, "Feb", 3 ) == 0 ) + { return 1; } + if ( strncmp( month, "Mar", 3 ) == 0 ) + { return 2; } + if ( strncmp( month, "Apr", 3 ) == 0 ) + { return 3; } + if ( strncmp( month, "May", 3 ) == 0 ) + { return 4; } + if ( strncmp( month, "Jun", 3 ) == 0 ) + { return 5; } + if ( strncmp( month, "Jul", 3 ) == 0 ) + { return 6; } + if ( strncmp( month, "Aug", 3 ) == 0 ) + { return 7; } + if ( strncmp( month, "Sep", 3 ) == 0 ) + { return 8; } + if ( strncmp( month, "Oct", 3 ) == 0 ) + { return 9; } + if ( strncmp( month, "Nov", 3 ) == 0 ) + { return 10; } + if ( strncmp( month, "Dec", 3 ) == 0 ) + { return 11; } + + LVARRAY_ERROR( "Uncrecognized month: " << month ); + return -1; +} + +namespace jitti +{ + +namespace internal +{ + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +time_t getCompileTime( char const * const date, char const * const time ) +{ + struct tm dateTime {}; + dateTime.tm_mon = monthStringToInt( date ); + dateTime.tm_mday = std::atoi( date + 4 ); + dateTime.tm_year = std::atoi( date + 7 ) - 1900; + + dateTime.tm_hour = std::atoi( time ); + dateTime.tm_min = std::atoi( time + 3 ); + dateTime.tm_sec = std::atoi( time + 6 ); + + dateTime.tm_isdst = -1; + + return mktime( &dateTime ); +} + +} // namespace internal + +} // namespace jitti diff --git a/src/jitti/CompilationInfo.hpp b/src/jitti/CompilationInfo.hpp new file mode 100644 index 00000000..d3a3fc92 --- /dev/null +++ b/src/jitti/CompilationInfo.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace jitti +{ + +namespace internal +{ + +time_t getCompileTime( char const * const data, char const * const time ); + +} // namespace internal + +/** + * + */ +struct CompilationInfo +{ + time_t const compilationTime = internal::getCompileTime( __DATE__, __TIME__ ); + std::string compileCommand; + bool compilerIsNVCC; + std::string linker; + std::string linkArgs; + std::string templateFunction; + std::string templateParams; + std::string headerFile; + std::string outputObject; + std::string outputLibrary; +}; + +} // namespace jitti diff --git a/src/jitti/Function.cpp b/src/jitti/Function.cpp new file mode 100644 index 00000000..f8619983 --- /dev/null +++ b/src/jitti/Function.cpp @@ -0,0 +1,92 @@ +#include "Function.hpp" + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static std::ostream & operator<<( std::ostream & os, jitti::SymbolTable const & table ) +{ + for ( auto const & kvPair : table ) + { + os << kvPair.first << ": " << "{ \"" << kvPair.second.first << "\", " << + LvArray::system::demangle( kvPair.second.second.name() ) << " }\n"; + } + + return os; +} + +namespace jitti +{ +namespace internal +{ + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TypedDynamicLibrary::TypedDynamicLibrary( std::string const & path ): + m_handle( dlopen( path.c_str(), RTLD_LAZY ) ) +{ + if ( m_handle == nullptr ) + { + char const * const error = dlerror(); + if ( error != nullptr ) + { + LVARRAY_ERROR( "dlopen( " << path << " ) failed.\n" << error ); + } + + LVARRAY_ERROR( "dlopen( " << path << " ) failed." ); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TypedDynamicLibrary::TypedDynamicLibrary( TypedDynamicLibrary && src ): + m_handle( src.m_handle ) +{ + src.m_handle = nullptr; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TypedDynamicLibrary::~TypedDynamicLibrary() +{ + if ( m_handle != nullptr ) + { LVARRAY_ERROR_IF( dlclose( m_handle ), dlerror() ); } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +SymbolTable const & TypedDynamicLibrary::getSymbolTable() const +{ + LVARRAY_ERROR_IF( m_handle == nullptr, "Error" ); + + void * const symbolAddress = dlsym( m_handle, "getExportedSymbols" ); + + char const * const error = dlerror(); + + LVARRAY_ERROR_IF( error != nullptr, + "Expected a function named 'getExportedSymbols' but none exists\n" << + "The error from dlerror() is: " << error ); + + LVARRAY_ERROR_IF( symbolAddress == nullptr, + "Expected a function named 'getExportedSymbols' but none exists." ); + + SymbolTable const * (*symbols)() = + reinterpret_cast< SymbolTable const * (*)() >( symbolAddress ); + + return *symbols(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void * TypedDynamicLibrary::getSymbolCheckType( std::string const & name, std::type_index const expectedType ) const +{ + SymbolTable const & symbols = getSymbolTable(); + auto const iter = symbols.find( name ); + LVARRAY_ERROR_IF( iter == symbols.end(), + "Symbol \"" << name << "\" not found in the exported symbol table.\n" << + "table contains:\n" << symbols ); + + std::pair< void *, std::type_index > const & value = iter->second; + + LVARRAY_ERROR_IF( value.second != expectedType, + "Symbol \"" << name << "\" found but it has type " << + LvArray::system::demangle( value.second.name() ) << " not " << + LvArray::system::demangle( expectedType.name() ) ); + + return value.first; +} + +} // namespace internal +} // namespace jitti diff --git a/src/jitti/Function.hpp b/src/jitti/Function.hpp new file mode 100644 index 00000000..97ce8e4b --- /dev/null +++ b/src/jitti/Function.hpp @@ -0,0 +1,100 @@ +#pragma once + +// Source includes +#include "types.hpp" +#include "CompilationInfo.hpp" +#include "utils.hpp" +#include "../Macros.hpp" + +// System includes +#include +#include +#include +#include + +namespace jitti +{ +namespace internal +{ + +/** + * + */ +class TypedDynamicLibrary +{ +public: + TypedDynamicLibrary( std::string const & path ); + + TypedDynamicLibrary( TypedDynamicLibrary const & ) = delete; + + TypedDynamicLibrary( TypedDynamicLibrary && src ); + + ~TypedDynamicLibrary(); + + template< typename T > + T getSymbol( std::string const & name ) const + { return reinterpret_cast< T >( getSymbolCheckType( name, std::type_index( typeid( T ) ) ) ); } + + SymbolTable const & getSymbolTable() const; + +private: + + void * getSymbolCheckType( std::string const & name, std::type_index const expectedType ) const; + + void * m_handle; +}; + +} // namespace internal + +/** + * + */ +template< typename FUNC_POINTER > +class Function +{ +public: + Function( std::string const & path ): + m_library( path ) + { + SymbolTable const & symbols = m_library.getSymbolTable(); + LVARRAY_ERROR_IF_NE( symbols.size(), 1 ); + + m_functionName = symbols.begin()->first; + m_function = m_library.getSymbol< FUNC_POINTER >( m_functionName ); + } + + Function( CompilationInfo const & info ): + Function( utils::compileTemplate( info.compileCommand, + info.compilerIsNVCC, + info.linker, + info.linkArgs, + info.templateFunction, + info.templateParams, + info.headerFile, + info.outputObject, + info.outputLibrary ) ) + {} + + Function( Function && ) = default; + + Function( Function const & ) = delete; + Function & operator=( Function const & ) = delete; + Function & operator=( Function && ) = delete; + + template< typename ... ARGS > + decltype( auto ) operator()( ARGS && ... args ) const + { + LVARRAY_ERROR_IF( m_function == nullptr, "Function not set." ); + return m_function( std::forward< ARGS >( args )... ); + } + + std::string const & getName() const + { return m_functionName; } + +private: + internal::TypedDynamicLibrary m_library; + std::string m_functionName = ""; + FUNC_POINTER m_function = nullptr; +}; + +} // namespace jitti diff --git a/src/jitti/generateCompileCommandsHeader.py b/src/jitti/generateCompileCommandsHeader.py new file mode 100644 index 00000000..70295692 --- /dev/null +++ b/src/jitti/generateCompileCommandsHeader.py @@ -0,0 +1,108 @@ +import sys +import os +import json +import argparse + + +def getCompileCommand( compileCommandsPath, cppFilePath ): + command = None + with open( compileCommandsPath, "r" ) as compileCommands: + translationUnits = json.load( compileCommands ) + + for tu in translationUnits: + if tu[ "file" ] == cppFilePath: + command = tu[ "command" ] + + if command is None: + raise Exception( "Could not find {} in {}".format( cppFilePath, compileCommandsPath ) ) + + # Remove everything after "-c or -o" + pos = min( command.find( " -c " ), command.find( " -o " ) ) + if pos < 0: + raise Exception( "Could not reformat the compilation command: {}".format( command ) ) + + return command[ 0 : pos ] + + +def getLinkArgs( linkDirectories, linkLibraries ): + print( linkDirectories ) + print( linkLibraries ) + + command = [] + for linkDir in linkDirectories: + command.append( "-L" + linkDir ) + + for arg in linkLibraries: + libs = arg.split( " " ) + for lib in libs: + if lib.endswith( ".a" ) or lib.endswith( ".so" ): + command.append( lib ) + elif lib.startswith("-"): + command.append( lib ) + else: + command.append( "-l" + lib ) + + print(command) + return " ".join( command ) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( "compileCommandsPath", type=str, + help="The path to the compile_commands.json file." ) + parser.add_argument( "--cpp", dest="cppFilePath", required=True, type=str, + help="The translation unit to get the compilation arguments of." ) + parser.add_argument( "--hpp", dest="headerFilePath", required=True, type=str, + help="The path of the header file to generate." ) + parser.add_argument( "--linker", dest="linker", required=True, type=str, + help="The program to use for linking" ) + parser.add_argument( "--includeDirectories", dest="includeDirectories", required=False, type=str, nargs="*", + default=[], help="Extra directories to include." ) + parser.add_argument( "--linkDirectories", dest="linkDirectories", required=False, type=str, nargs="*", + default=[], help="The directories to add to the library search path." ) + parser.add_argument( "--linkLibraries", dest="linkLibraries", required=False, type=str, nargs="*", + default=[], help="The libraries to link against") + args = parser.parse_args() + + compileCommandsPath = os.path.abspath( args.compileCommandsPath ) + if not os.path.isfile( compileCommandsPath ): + raise Exception( "{} could not be found".format( compileCommandsPath ) ) + + cppFilePath = os.path.abspath( args.cppFilePath ) + if not os.path.isfile( cppFilePath ): + raise Exception( "{} could not be found".format( cppFilePath ) ) + + cppFileName = os.path.splitext( os.path.basename( cppFilePath ) )[ 0 ] + + headerFilePath = os.path.abspath( args.headerFilePath ) + + compileCommand = getCompileCommand( compileCommandsPath, cppFilePath ) + for includeDir in args.includeDirectories: + compileCommand += " -I" + includeDir + + linkCommand = getLinkArgs( args.linkDirectories, args.linkLibraries ) + + includeGuard = "GUARD_" + str( abs( hash( compileCommand ) ) ) + + newHeaderContents = ( "#ifndef {}\n".format( includeGuard ) + + "#define {}\n\n".format( includeGuard ) + + "#define {}_COMPILE_COMMAND \"{}\"\n\n".format( cppFileName, compileCommand ) + + "#define {}_LINKER \"{}\"\n\n".format( cppFileName, args.linker ) + + "#define {}_LINK_ARGS \"{}\"\n\n".format( cppFileName, linkCommand ) + + "#endif\n" ) + + # If the header file exists check to see if it needs to be modified. + # If it is modified then all dependent targets get rebuild so it should + # only be done if necessary. + # if os.path.isfile( headerFilePath ): + # with open( headerFilePath, "r" ) as existingHeader: + # if existingHeader.read() == newHeaderContents: + # return 0 + + with open( headerFilePath, "w" ) as output: + output.write( newHeaderContents ) + + return 0 + +if __name__ == "__main__": + sys.exit( main() ) diff --git a/src/jitti/templateSource.cpp b/src/jitti/templateSource.cpp new file mode 100644 index 00000000..10083db5 --- /dev/null +++ b/src/jitti/templateSource.cpp @@ -0,0 +1,44 @@ +#if !defined(JITTI_TEMPLATE_HEADER_FILE) + #error JITTI_TEMPLATE_HEADER_FILE must be defined! +#endif + +#if !defined(JITTI_TEMPLATE_FUNCTION) + #error JITTI_TEMPLATE_FUNCTION must be defined! +#endif + +#if !defined(JITTI_TEMPLATE_PARAMS) + #error JITTI_TEMPLATE_PARAMS must be defined! +#endif + +#if !defined(JITTI_TEMPLATE_PARAMS_STRING) + #error JITTI_TEMPLATE_PARAMS_STRING must be defined! +#endif + +// Include the header the template is defined in. +#include JITTI_TEMPLATE_HEADER_FILE + +#include "types.hpp" + +// Done so we don't have to #include ../Macros.hpp +#if !defined( STRINGIZE ) + #define STRINGIZE_NX( A ) #A + #define STRINGIZE( A ) STRINGIZE_NX( A ) +#endif + +auto instantiation = JITTI_TEMPLATE_FUNCTION< JITTI_TEMPLATE_PARAMS >; + +jitti::SymbolTable exportedSymbols = { + { std::string( STRINGIZE( JITTI_TEMPLATE_FUNCTION ) "< " JITTI_TEMPLATE_PARAMS_STRING " >" ), + { reinterpret_cast< void * >( instantiation ), std::type_index( typeid( instantiation ) ) } } +}; + +extern "C" +{ + +jitti::SymbolTable const * getExportedSymbols() +{ + return &exportedSymbols; +} + +} // extern "C" + diff --git a/src/jitti/types.hpp b/src/jitti/types.hpp new file mode 100644 index 00000000..2c27a4ca --- /dev/null +++ b/src/jitti/types.hpp @@ -0,0 +1,14 @@ +#pragma once + +// System includes +#include +#include +#include +#include + +namespace jitti +{ + +using SymbolTable = std::unordered_map< std::string, std::pair< void *, std::type_index > >; + +} // namespace jitti diff --git a/src/jitti/utils.cpp b/src/jitti/utils.cpp new file mode 100644 index 00000000..a89dea09 --- /dev/null +++ b/src/jitti/utils.cpp @@ -0,0 +1,123 @@ +#include "utils.hpp" +#include "../Macros.hpp" + +#include +#include + +#include +#include +#include + +namespace jitti +{ +namespace utils +{ + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +std::string compileTemplate( std::string const & compileCommand, + bool const compilerIsNVCC, + std::string const & linker, + std::string const & linkArgs, + std::string const & templateFunction, + std::string const & templateParams, + std::string const & headerFile, + std::string const & outputObject, + std::string const & outputLibrary ) +{ + std::string const currentFile = __FILE__; + std::string const sourceFile = currentFile.substr( 0, currentFile.size() - ( sizeof( "jitti.cpp" ) - 1 ) ) + + "templateSource.cpp"; + + std::ostringstream fullCompileCommand; + fullCompileCommand << compileCommand; + if( compilerIsNVCC ) + { + fullCompileCommand << " -Xcompiler"; + } + + fullCompileCommand << " -fPIC -c " + sourceFile + " -o " + outputObject; + + std::ostringstream joinedDefines; + joinedDefines << " -D JITTI_TEMPLATE_HEADER_FILE='\"" << headerFile << "\"'" + << " -D JITTI_TEMPLATE_FUNCTION=" << templateFunction + << " -D JITTI_TEMPLATE_PARAMS=\"" << templateParams << "\"" + << " -D JITTI_TEMPLATE_PARAMS_STRING='\"" << templateParams << "\"'"; + + std::string const joinedDefinesString = joinedDefines.str(); + + // You need to escape commas for NVCC. + if( compilerIsNVCC ) + { + fullCompileCommand << std::regex_replace( joinedDefinesString, std::regex( "," ), "\\," ); + } + else + { + fullCompileCommand << joinedDefinesString; + } + + std::string const fullCompileCommandString = fullCompileCommand.str(); + + LVARRAY_LOG( "\nCompiling " << sourceFile ); + LVARRAY_LOG_VAR( fullCompileCommandString ); + LVARRAY_ERROR_IF( std::system( fullCompileCommandString.c_str() ) != 0, fullCompileCommandString ); + + std::string linkCommand = linker + " -shared " + outputObject + " " + linkArgs + " -o " + outputLibrary; + + LVARRAY_LOG( "\nLinking " << outputObject ); + LVARRAY_LOG_VAR( linkCommand ); + LVARRAY_ERROR_IF( std::system( linkCommand.c_str() ) != 0, linkCommand ); + + return outputLibrary; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void makeDirsForPath( std::string const & path ) +{ + constexpr mode_t mode = 0770; // user and group rwx permissions + + std::string::size_type pos = 0; + do + { + pos = path.find( '/', pos + 1 ); + std::string dir_name = path.substr( 0, pos ); + int const err = mkdir( dir_name.c_str(), mode ); + LVARRAY_THROW_IF( err && ( errno != EEXIST ), "Failed to create a directories for " << path, std::runtime_error ); + } + while (pos != std::string::npos); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void readDirectoryFiles( time_t const time, + std::string const & directory, + std::unordered_map< std::string, std::string > & libraries ) +{ + DIR * const dirp = opendir( directory.c_str() ); + LVARRAY_ERROR_IF( dirp == nullptr, "Could not open '" << directory << "' as a directory." ); + + struct dirent * dp; + while( ( dp = readdir( dirp ) ) != nullptr ) + { + std::string const fileName = dp->d_name; + if( fileName.size() > 3 && fileName.substr( fileName.size() - 3 ) == ".so" ) + { + std::string const filePath = directory + "/" + fileName; + struct stat fileInfo; + LVARRAY_ERROR_IF( stat( filePath.c_str(), &fileInfo ) != 0, "Error stat'ing '" << filePath << "'" ); + + if( fileInfo.st_mtime >= time ) + { + auto const result = libraries.emplace( std::make_pair( std::move( fileName ), std::move( filePath ) ) ); + if( result.second ) + { + LVARRAY_ERROR_IF_NE_MSG( result.first->second, filePath, + "Two libraries with the same name exist in different directories." ); + } + } + } + } + + closedir( dirp ); +} + +} // namespace utils +} // namespace jitti diff --git a/src/jitti/utils.hpp b/src/jitti/utils.hpp new file mode 100644 index 00000000..1eb3aaa7 --- /dev/null +++ b/src/jitti/utils.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +namespace jitti +{ +namespace utils +{ + +/** + * + */ +std::string compileTemplate( std::string const & compileCommand, + bool const compilerIsNVCC, + std::string const & linker, + std::string const & linkArgs, + std::string const & templateFunction, + std::string const & templateParams, + std::string const & headerFile, + std::string const & outputObject, + std::string const & outputLibrary ); + +void makeDirsForPath( std::string const & path ); + +/** + * + */ +void readDirectoryFiles( time_t const time, + std::string const & directory, + std::unordered_map< std::string, std::string > & libraries ); + +} // namespace utils +} // namespace jitti diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index 4d91681e..4592bae4 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -149,3 +149,5 @@ install(TARGETS testTensorOps if( ENABLE_PYLVARRAY ) add_subdirectory( python ) endif() + +add_subdirectory( jitti ) diff --git a/unitTests/jitti/CMakeLists.txt b/unitTests/jitti/CMakeLists.txt new file mode 100644 index 00000000..113a9c84 --- /dev/null +++ b/unitTests/jitti/CMakeLists.txt @@ -0,0 +1,75 @@ + +target_include_directories( jitti PUBLIC ${CMAKE_BINARY_DIR}/include ) + +set( JITTI_TEST_OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY}/lib ) +set( JITTI_CXX_COMPILER ${CMAKE_CXX_COMPILER} ) +set( JITTI_LINKER ${CMAKE_CXX_COMPILER} ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/jittiUnitTestConfig.hpp.in + ${CMAKE_BINARY_DIR}/include/jittiUnitTestConfig.hpp ) + +set(testSources + testUtils.cpp + testCompilationInfo.cpp + ) + +foreach(test ${testSources}) + get_filename_component( test_name ${test} NAME_WE ) + blt_add_executable( NAME ${test_name} + SOURCES ${test} + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON gtest jitti ) + + target_include_directories( ${test_name} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/../../src + ${CMAKE_BINARY_DIR}/include ) + + blt_add_test( NAME ${test_name} + COMMAND ${test_name} ) + + install( TARGETS ${test_name} + DESTINATION bin ) + +endforeach() + +# Logic for generating the header containing the command used to build testFunction. +set( linkDirectories "" ) +set( linkLibraries "" ) + +if( ENABLE_CUDA ) + list( APPEND linkDirectories ${CUDA_TOOLKIT_ROOT_DIR}/lib64 ) + list( APPEND linkLibraries cudart_static cudadevrt ) +endif() + +add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/include/testFunctionCompileCommands.hpp + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + DEPENDS ${CMAKE_BINARY_DIR}/compile_commands.json + COMMAND python ${CMAKE_CURRENT_LIST_DIR}/../../src/jitti/generateCompileCommandsHeader.py + ${CMAKE_BINARY_DIR}/compile_commands.json + --cpp ${CMAKE_CURRENT_LIST_DIR}/testFunction.cpp + --hpp ${CMAKE_BINARY_DIR}/include/testFunctionCompileCommands.hpp + --linker ${CMAKE_CXX_COMPILER} + --linkDirectories ${linkDirectories} + --linkLibraries ${linkLibraries} + --includeDirectories ${CMAKE_BINARY_DIR}/include ) + +set(testSourcesDependentOnCompileCommands + testFunction.cpp + testCache.cpp + ) + +foreach(test ${testSourcesDependentOnCompileCommands}) + get_filename_component( test_name ${test} NAME_WE ) + blt_add_executable( NAME ${test_name} + SOURCES ${test} ${CMAKE_BINARY_DIR}/include/testFunctionCompileCommands.hpp + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON gtest jitti cuda ) + + target_include_directories( ${test_name} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/../../src + ${CMAKE_BINARY_DIR}/include ) + + blt_add_test( NAME ${test_name} + COMMAND ${test_name} ) + + install( TARGETS ${test_name} + DESTINATION bin ) + +endforeach() diff --git a/unitTests/jitti/fullOnTemplates.hpp b/unitTests/jitti/fullOnTemplates.hpp new file mode 100644 index 00000000..c58a3885 --- /dev/null +++ b/unitTests/jitti/fullOnTemplates.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "ArrayView.hpp" +#include "ChaiBuffer.hpp" + +#include + +constexpr auto fullOnTemplatesPath = __FILE__; + +template< typename T > +std::string getUmpireAllocatorName( T * const ptr ) +{ + return umpire::ResourceManager::getInstance().getAllocator( ptr ).getName(); +} + +template< typename POLICY > +void cubeAll( LvArray::ArrayView< int, 1, 0, int, LvArray::ChaiBuffer > const & dst, + LvArray::ArrayView< int const, 1, 0, int, LvArray::ChaiBuffer > const & src ) +{ + RAJA::forall< POLICY >( RAJA::TypedRangeSegment< int >( 0, dst.size() ), + [dst, src] LVARRAY_HOST_DEVICE ( int const i ) + { + dst[ i ] = src[ i ] * src[ i ] * src[ i ]; + } + ); +} diff --git a/unitTests/jitti/jittiUnitTestConfig.hpp.in b/unitTests/jitti/jittiUnitTestConfig.hpp.in new file mode 100644 index 00000000..fa29658e --- /dev/null +++ b/unitTests/jitti/jittiUnitTestConfig.hpp.in @@ -0,0 +1,7 @@ +#pragma once + +#cmakedefine JITTI_TEST_OUTPUT_DIR "@JITTI_TEST_OUTPUT_DIR@" + +#cmakedefine JITTI_CXX_COMPILER "@JITTI_CXX_COMPILER@" + +#cmakedefine JITTI_LINKER "@JITTI_LINKER@" diff --git a/unitTests/jitti/moreComplicatedTemplates.hpp b/unitTests/jitti/moreComplicatedTemplates.hpp new file mode 100644 index 00000000..2064f1aa --- /dev/null +++ b/unitTests/jitti/moreComplicatedTemplates.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + + +constexpr auto moreComplicatedTemplatesPath = __FILE__; + +template< int N > +std::string addToString( std::string const & m ) +{ return m + std::to_string( N ); } + +template< typename KEY, typename VALUE > +VALUE & staticMapAccess( KEY const & key, VALUE const & value ) +{ + static std::unordered_map< KEY, VALUE > s_map; + auto const iter = s_map.find( key ); + if ( iter == s_map.end() ) + { + return s_map.insert( std::make_pair( key, value ) ).first->second; + } + + return iter->second; +} + +struct Base +{ + virtual ~Base() = default; + virtual std::string getValueString() const = 0; +}; + +template< typename T > +struct Derived : public Base +{ + Derived( T const & value ): + m_value( value ) + {} + + virtual std::string getValueString() const override + { + std::ostringstream oss; + oss << m_value; + return oss.str(); + } + + T const m_value; +}; + +template< typename T > +std::unique_ptr< Base > factory( T const & value ) +{ + return std::make_unique< Derived< T > >( value ); +} diff --git a/unitTests/jitti/simpleTemplates.hpp b/unitTests/jitti/simpleTemplates.hpp new file mode 100644 index 00000000..6326d173 --- /dev/null +++ b/unitTests/jitti/simpleTemplates.hpp @@ -0,0 +1,16 @@ +#pragma once + +constexpr auto simpleTemplatesPath = __FILE__; + +template< int N > +int add( int const m ) +{ return m + N; } + +template< typename T > +void squareAll( T * const output, T const * const input, int const numValues ) +{ + for( int i = 0; i < numValues; ++i ) + { + output[ i ] = input[ i ] * input[ i ]; + } +} diff --git a/unitTests/jitti/testCache.cpp b/unitTests/jitti/testCache.cpp new file mode 100644 index 00000000..9777d2cb --- /dev/null +++ b/unitTests/jitti/testCache.cpp @@ -0,0 +1,55 @@ +// Source includes +#include "jittiUnitTestConfig.hpp" +#include "jitti/Cache.hpp" + +#include "simpleTemplates.hpp" +#include "testFunctionCompileCommands.hpp" + +// TPL includes +#include + +namespace jitti +{ +namespace testing +{ + +#if defined(LVARRAY_USE_CUDA) + constexpr bool compilerIsNVCC = true; +#else + constexpr bool compilerIsNVCC = false; +#endif + +TEST( simpleTemplates, add ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = compilerIsNVCC; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "add"; + info.templateParams = "5"; + info.headerFile = simpleTemplatesPath; + + { + jitti::Cache< int (*)( int ) > cache( 0, JITTI_TEST_OUTPUT_DIR ); + EXPECT_EQ( cache.getOrLoadOrCompile( info )( 7 ), 12 ); + EXPECT_EQ( cache.get( "add< 5 >" )( 8 ), 13 ); + + info.templateParams = "5"; + EXPECT_FALSE( cache.tryGetOrLoad( "add< 8 >" ) ); + EXPECT_EQ( cache.getOrLoadOrCompile( info )( 8 ), 13 ); + } + +} + +} // namespace testing +} // namespace jitti + +// This is the default gtest main method. It is included for ease of debugging. +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + int const result = RUN_ALL_TESTS(); + return result; +} diff --git a/unitTests/jitti/testCompilationInfo.cpp b/unitTests/jitti/testCompilationInfo.cpp new file mode 100644 index 00000000..fd5ae90e --- /dev/null +++ b/unitTests/jitti/testCompilationInfo.cpp @@ -0,0 +1,42 @@ +#include "jittiUnitTestConfig.hpp" +#include "../../src/jitti/CompilationInfo.hpp" +#include "../../src/Macros.hpp" + +// TPL includes +#include + +// System includes +#include + +namespace jitti +{ +namespace testing +{ + +/** + * Test that the @c CompilationInfo constructor initializes the @c compilationTime field + * to the time that the translation unit it is constructed in was compiled. + */ +TEST( CompilationInfo, Constructor ) +{ + CompilationInfo info; + EXPECT_EQ( info.compilationTime, internal::getCompileTime( __DATE__, __TIME__ ) ); + EXPECT_GE( time( nullptr ), info.compilationTime ); + + char const * const timeString = ctime( &info.compilationTime ); + EXPECT_EQ( std::string( timeString + 4, 6 ), std::string( __DATE__, 6 ) ); + EXPECT_EQ( std::string( timeString + 11, 8 ), std::string( __TIME__ ) ); + EXPECT_EQ( std::string( timeString + 20, 4 ), std::string( &__DATE__[ 7 ] ) ); +} + +} // namespace testing +} // namespace jitti + +// This is the default gtest main method. It is included for ease of debugging. +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + int const result = RUN_ALL_TESTS(); + return result; +} + diff --git a/unitTests/jitti/testFunction.cpp b/unitTests/jitti/testFunction.cpp new file mode 100644 index 00000000..58bfba0e --- /dev/null +++ b/unitTests/jitti/testFunction.cpp @@ -0,0 +1,368 @@ +// Source includes +#include "jittiUnitTestConfig.hpp" +#include "jitti/Function.hpp" + +#include "Array.hpp" +#include "ChaiBuffer.hpp" + +#include "simpleTemplates.hpp" +#include "moreComplicatedTemplates.hpp" +#include "fullOnTemplates.hpp" +#include "testFunctionCompileCommands.hpp" + +// TPL includes +#include + +namespace jitti +{ +namespace testing +{ + +#if defined(LVARRAY_USE_CUDA) + constexpr bool compilerIsNVCC = true; +#else + constexpr bool compilerIsNVCC = false; +#endif + +TEST( simpleTemplates, add ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = false; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "add"; + info.templateParams = "5"; + info.headerFile = simpleTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_add_5.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_add_5.so"; + + // Compile, load, test and then unload add5. + { + jitti::Function< int (*)( int ) > const add5( info ); + EXPECT_EQ( add5( 0 ), add< 5 >( 0 ) ); + EXPECT_EQ( add5( 5 ), add< 5 >( 5 ) ); + } + + // Load add5 again. + jitti::Function< int (*)( int ) > const add5( info.outputLibrary ); + EXPECT_EQ( add5( 10 ), add< 5 >( 10 ) ); + EXPECT_EQ( add5( 15 ), add< 5 >( 15 ) ); + + info.templateParams = "1024"; + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_add_1024.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_add_1024.so"; + + // Compile, load, test and then unload add1024. + { + jitti::Function< int (*)( int ) > const add1024( info ); + EXPECT_EQ( add1024( 0 ), add< 1024 >( 0 ) ); + EXPECT_EQ( add1024( 1024 ), add< 1024 >( 1024 ) ); + } + + // Load add1024 again. + jitti::Function< int (*)( int ) > const add1024( info.outputLibrary ); + EXPECT_EQ( add1024( 2048 ), add< 1024 >( 2048 ) ); + EXPECT_EQ( add1024( 3072 ), add< 1024 >( 3072 ) ); + + // Make sure add5 still works. + EXPECT_EQ( add5( 20 ), add< 5 >( 20 ) ); + EXPECT_EQ( add5( 25 ), add< 5 >( 25 ) ); +} + +TEST( simpleTemplates, squareAll ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = false; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "squareAll"; + info.headerFile = simpleTemplatesPath; + + // Prepare to compile squareAll< int > + info.templateParams = "int"; + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_squareAll_int.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_squareAll_int.so"; + + std::vector< int > const intInput { 0, 1, 2, 3 ,4, 5, 6, 7, 8, 9, 10 }; + std::vector< int > intOutput; + + std::vector< int > intExpectedOutput( intInput.size() ); + squareAll( intExpectedOutput.data(), intInput.data(), intExpectedOutput.size() ); + + // Compile, load, test and then unload squareAll< int >. + { + jitti::Function< void (*)( int *, int const *, int ) > const squareAllInt( info ); + intOutput = intInput; + squareAllInt( intOutput.data(), intInput.data(), intOutput.size() ); + EXPECT_EQ( intOutput, intExpectedOutput ); + } + + // Load squareAll< int > again. + jitti::Function< void (*)( int *, int const *, int ) > const squareAllInt( info.outputLibrary ); + intOutput = intInput; + squareAllInt( intOutput.data(), intInput.data(), intOutput.size() ); + EXPECT_EQ( intOutput, intExpectedOutput ); + + // Prepare to compile squareAll< float > + info.templateParams = "float"; + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_squareAll_float.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_squareAll_float.so"; + + std::vector< float > const floatInput { 0, 1, 2, 3 ,4, 5, 6, 7, 8, 9, 10 }; + std::vector< float > floatOutput; + + std::vector< float > floatExpectedOutput( floatInput.size() ); + squareAll( floatExpectedOutput.data(), floatInput.data(), floatExpectedOutput.size() ); + + // Compile, load, test and then unload squareAllFloat. + { + jitti::Function< void (*)( float *, float const *, int ) > const squareAllFloat( info ); + floatOutput = floatInput; + squareAllFloat( floatOutput.data(), floatInput.data(), floatOutput.size() ); + EXPECT_EQ( floatOutput, floatExpectedOutput ); + } + + // Load squareAll< float > again. + jitti::Function< void (*)( float *, float const *, int ) > const squareAllFloat( info.outputLibrary ); + floatOutput = floatInput; + squareAllFloat( floatOutput.data(), floatInput.data(), floatOutput.size() ); + EXPECT_EQ( floatOutput, floatExpectedOutput ); + + // Make sure squareAll< int > still works. + intOutput = intInput; + squareAllInt( intOutput.data(), intInput.data(), intOutput.size() ); + EXPECT_EQ( intOutput, intExpectedOutput ); +} + +TEST( moreComplicatedTemplates, addToString ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = false; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "addToString"; + info.templateParams = "5"; + info.headerFile = moreComplicatedTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_addToString_5.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_addToString_5.so"; + + // Compile, load, and test addToString5. + jitti::Function< std::string (*)( std::string const & ) > const addToString5( info ); + EXPECT_EQ( addToString5( "foo" ), addToString< 5 >( "foo" ) ); + EXPECT_EQ( addToString5( "bar" ), addToString< 5 >( "bar" ) ); +} + +TEST( moreComplicatedTemplates, staticMapAccess ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = false; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "staticMapAccess"; + info.templateParams = "std::string, int"; + info.headerFile = moreComplicatedTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_staticMapAccess_std::string_int.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_staticMapAccess_std::string_int.so"; + + // Populate the map as { "five": 5, "size": 6 } + staticMapAccess< std::string, int >( "five", 5 ); + staticMapAccess< std::string, int >( "six", 6 ); + + // Verify that the JIT'ed function sees the same map. + jitti::Function< int& (*)( std::string const &, int const & ) > const staticMapAccessStringInt( info ); + EXPECT_EQ( staticMapAccessStringInt( "five", 0 ), 5 ); + EXPECT_EQ( staticMapAccessStringInt( "six", 0 ), 6 ); + + // Update the map via the JIT'ed function to { "five": -1, "six": 6, "seven", 7 } + staticMapAccessStringInt( "five", 0 ) = -1; + staticMapAccessStringInt( "seven", 7 ); + + EXPECT_EQ( ( staticMapAccess< std::string, int >( "five", 0 ) ), -1 ); + EXPECT_EQ( ( staticMapAccess< std::string, int >( "six", 0 ) ), 6 ); + EXPECT_EQ( ( staticMapAccess< std::string, int >( "seven", 0 ) ), 7 ); +} + +TEST( moreComplicatedTemplates, factory ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = false; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "factory"; + info.templateParams = "int"; + info.headerFile = moreComplicatedTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_factory_int.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_factory_int.so"; + + // Compile, load, and test factoryInt. + jitti::Function< std::unique_ptr< Base > (*)( int const & ) > const factoryInt( info ); + EXPECT_EQ( factoryInt( 5 )->getValueString(), "5" ); + EXPECT_EQ( LvArray::system::demangleType( *factoryInt( 5 ) ), "Derived" ); + + info.templateParams = "double"; + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_factory_double.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_factory_double.so"; + + // Compile, load, and test factoryDouble. + jitti::Function< std::unique_ptr< Base > (*)( double const & ) > const factoryDouble( info ); + EXPECT_EQ( factoryDouble( 3.14 )->getValueString(), "3.14" ); + EXPECT_EQ( LvArray::system::demangleType( *factoryDouble( 3.14 ) ), "Derived" ); +} + +TEST( generatedCommands, add ) +{ + jitti::CompilationInfo info; + + info.compileCommand = testFunction_COMPILE_COMMAND; + info.compilerIsNVCC = compilerIsNVCC; + info.linker = testFunction_LINKER; + info.linkArgs = testFunction_LINK_ARGS; + info.templateFunction = "add"; + info.templateParams = "7"; + info.headerFile = simpleTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_generated_add7.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_generated_add7.so"; + + // Compile, load, and test add< 7 >. + jitti::Function< int (*)( int ) > const add7( info ); + EXPECT_EQ( add7( 5 ), add< 7 >( 5 ) ); +} + +TEST( fullOnTemplates, getUmpireAllocatorName ) +{ + jitti::CompilationInfo info; + + info.compileCommand = testFunction_COMPILE_COMMAND; + info.compilerIsNVCC = compilerIsNVCC; + info.linker = testFunction_LINKER; + info.linkArgs = testFunction_LINK_ARGS; + info.templateFunction = "getUmpireAllocatorName"; + info.templateParams = "int"; + info.headerFile = fullOnTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_getUmpireAllocatorName_int.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_getUmpireAllocatorName_int.so"; + + // Compile, load, and test add< 7 >. + jitti::Function< std::string (*)( int * ) > const getUmpireAllocatorNameInt( info ); + LvArray::Array< int, 1, RAJA::PERM_I, int, LvArray::ChaiBuffer > array( 10 ); + EXPECT_EQ( getUmpireAllocatorNameInt( array.data() ), "HOST" ); + +#if defined(LVARRAY_USE_CUDA) + array.move( LvArray::MemorySpace::GPU ); + EXPECT_EQ( getUmpireAllocatorNameInt( array.data() ), "DEVICE" ); +#endif +} + +TEST( fullOnTemplates, cubeAll ) +{ + using FuncType = void (*)( LvArray::ArrayView< int, 1, 0, int, LvArray::ChaiBuffer > const &, + LvArray::ArrayView< int const, 1, 0, int, LvArray::ChaiBuffer > const & ); + + jitti::CompilationInfo info; + + info.compileCommand = testFunction_COMPILE_COMMAND; + info.compilerIsNVCC = compilerIsNVCC; + info.linker = testFunction_LINKER; + info.linkArgs = testFunction_LINK_ARGS; + info.templateFunction = "cubeAll"; + info.templateParams = "RAJA::loop_exec"; + info.headerFile = fullOnTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_fullOnTemplates_cubeAll_serial.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_fullOnTemplates_cubeAll_serial.so"; + + LvArray::Array< int, 1, RAJA::PERM_I, int, LvArray::ChaiBuffer > src( 10 ); + LvArray::Array< int, 1, RAJA::PERM_I, int, LvArray::ChaiBuffer > dst( src ); + + for ( int i = 0; i < src.size(); ++i ) + { + src[ i ] = i; + } + + jitti::Function< FuncType > const cubeAll_loop_exec( info ); + cubeAll_loop_exec( src, dst ); + + for( int i = 0; i < src.size(); ++i ) + { + EXPECT_EQ( dst[ i ], src[ i ] * src[ i ] * src[ i ] ); + } + + dst.setValues< RAJA::loop_exec >( 0 ); + +#if defined( RAJA_ENABLE_OPENMP ) + info.templateParams = "RAJA::omp_parallel_for_exec"; + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_fullOnTemplates_cubeAll_omp.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_fullOnTemplates_cubeAll_omp.so"; + jitti::Function< FuncType > const cubeAll_omp( info ); + cubeAll_omp( dst, src ); + + for( int i = 0; i < src.size(); ++i ) + { + EXPECT_EQ( dst[ i ], src[ i ] * src[ i ] * src[ i ] ); + } + + dst.setValues< RAJA::loop_exec >( 0 ); +#endif + +#if defined( LVARRAY_USE_CUDA) + info.templateParams = "RAJA::cuda_exec< 32 >"; + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_fullOnTemplates_cubeAll_cuda.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_fullOnTemplates_cubeAll_cuda.so"; + jitti::Function< FuncType > const cubeAll_cuda( info ); + cubeAll_cuda( dst, src ); + + dst.move( LvArray::MemorySpace::CPU ); + for( int i = 0; i < src.size(); ++i ) + { + EXPECT_EQ( dst[ i ], src[ i ] * src[ i ] * src[ i ] ); + } +#endif +} + +TEST( Function, deathTest ) +{ + jitti::CompilationInfo info; + + info.compileCommand = JITTI_CXX_COMPILER " -std=c++14"; + info.compilerIsNVCC = false; + info.linker = JITTI_LINKER; + info.linkArgs = ""; + info.templateFunction = "add"; + info.templateParams = "5"; + info.headerFile = simpleTemplatesPath; + + info.outputObject = JITTI_TEST_OUTPUT_DIR "/testFunction_add_5_deathTest.o"; + info.outputLibrary = JITTI_TEST_OUTPUT_DIR "/libtestFunction_add_5_deathTest.so"; + + jitti::Function< int (*)( int ) > const add5( info ); + + EXPECT_DEATH_IF_SUPPORTED( jitti::Function< double (*)( int ) >( info.outputLibrary ), "" ); + EXPECT_DEATH_IF_SUPPORTED( jitti::Function< int (*)( int, int ) >( info.outputLibrary ), "" ); +} + +} // namespace testing +} // namespace jitti + +// This is the default gtest main method. It is included for ease of debugging. +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + int const result = RUN_ALL_TESTS(); + return result; +} diff --git a/unitTests/jitti/testUtils.cpp b/unitTests/jitti/testUtils.cpp new file mode 100644 index 00000000..6edb12c4 --- /dev/null +++ b/unitTests/jitti/testUtils.cpp @@ -0,0 +1,145 @@ +#include "jittiUnitTestConfig.hpp" +#include "../../src/jitti/utils.hpp" +#include "../../src/Macros.hpp" + +// TPL includes +#include + +#include // for remvoe +#include // for access + +namespace jitti +{ +namespace testing +{ + +/** + * @brief Return the current working directory. + * @return The current working directory. + */ +static std::string getCurrentDirectory() +{ + char * const currentDirectory = get_current_dir_name(); + std::string const ret( currentDirectory ); + free( currentDirectory ); + return ret; +} + +/** + * @brief Create a file at the given path. + * @param path The path of the file to create. + */ +static void createFile( std::string const & path ) +{ + FILE * const fp = fopen( path.c_str(), "w" ); + EXPECT_NE( fp, nullptr ) << "Could not fopen " << path.c_str(); + + if( fp != nullptr ) + { + EXPECT_EQ( fclose( fp ), 0 ); + } +} + +static void checkReadDirectoryFiles( time_t const t, + std::string const & dirName, + std::unordered_map< std::string, std::string > librariesInDir, + std::vector< std::string > const & expectedLibraries ) +{ + for( int i : { 0, 1 } ) + { + LVARRAY_UNUSED_VARIABLE( i ); + + utils::readDirectoryFiles( t, dirName, librariesInDir ); + EXPECT_EQ( librariesInDir.size(), expectedLibraries.size() ); + + for( std::string const & fileName : expectedLibraries ) + { + std::string const filePath = dirName + "/" + fileName; + bool const fileFound = librariesInDir.count( fileName ) == 1; + EXPECT_TRUE( fileFound ) << filePath; + + if( fileFound ) + { + EXPECT_EQ( librariesInDir.at( fileName ), filePath ); + } + } + } +} + +/** + * Tests that utils::compileTemplate produces an executable file. + */ +TEST( utils, compileTemplate ) +{ + std::string const currentFile = __FILE__; + std::string const headerFile = currentFile.substr( 0, currentFile.size() - ( sizeof( "testUtils.cpp" ) - 1 ) ) + + "simpleTemplates.hpp"; + + std::string const outputObject = JITTI_TEST_OUTPUT_DIR "/testUtilsAdd.o"; + std::string const outputLib = JITTI_TEST_OUTPUT_DIR "/libtestUtilsAdd.so"; + remove( outputLib.c_str() ); + EXPECT_NE( access( outputLib.c_str(), R_OK | X_OK ), 0 ); + + std::string const ret = utils::compileTemplate( JITTI_CXX_COMPILER " -std=c++14", + false, + JITTI_LINKER, + "", + "add", + "5", + headerFile, + outputObject, + outputLib ); + + EXPECT_EQ( outputLib, ret ); + EXPECT_EQ( access( outputLib.c_str(), R_OK | X_OK ), 0 ); +} + +/** + * Check that utils::readDirectoryFiles works. + */ +TEST( utils, readDirectoryFiles ) +{ + std::string const dirName = getCurrentDirectory() + "/testUtils_readDirectoryFilesTemp"; + std::string const deleteDirCommand = "rm -rf " + dirName; + EXPECT_EQ( std::system( deleteDirCommand.c_str() ), 0 ); + + EXPECT_EQ( mkdir( dirName.c_str(), 0700 ), 0 ); + + time_t const t0 = time( nullptr ); + + for( std::string const fileName : { "foo.so", "foo.o", "bar.so" } ) + { + createFile( dirName + "/" + fileName ); + } + + std::unordered_map< std::string, std::string > librariesInDir; + + checkReadDirectoryFiles( t0, dirName, librariesInDir, { "foo.so", "bar.so" } ); + + // Sleep one second to ensure the new files have a different time stamp. + sleep( 1 ); + + time_t const t1 = time( nullptr ); + + for( std::string const fileName : { "fooNewer.so", "fooNewer.o", "barNewer.so" } ) + { + createFile( dirName + "/" + fileName ); + } + + checkReadDirectoryFiles( t0, dirName, librariesInDir, { "foo.so", "bar.so", "fooNewer.so", "barNewer.so" } ); + + librariesInDir.clear(); + + checkReadDirectoryFiles( t1, dirName, librariesInDir, { "fooNewer.so", "barNewer.so" } ); +} + +} // namespace testing +} // namespace jitti + +// This is the default gtest main method. It is included for ease of debugging. +int main( int argc, char * * argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + int const result = RUN_ALL_TESTS(); + return result; +}