/* ****************************************************************************** * * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See * the License for the specific language governing permissions and limitations * under the License. * * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ #include #include #include #include #include #include "execution/Threads.h" #include "helpers/OpTracker.h" #if defined(SD_GCC_FUNCTRACE) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #else #include #include #endif #include #include #include #include extern bool experimentalSupport; // Defined in NativeOpsHelpers_Arrays.cpp // OpaqueNDArray allocation tracking static std::atomic g_opaqueArrayCount{0}; static std::atomic g_opaqueArrayBytes{0}; static std::mutex g_opaqueArrayMutex; // InteropDataBuffer/OpaqueDataBuffer allocation tracking static std::atomic g_dataBufferCount{0}; static std::atomic g_dataBufferBytes{0}; static std::mutex g_dataBufferMutex; // TadPack lifetime registry - keeps shared_ptr alive for TadPacks returned to Java // Without this, when ConstantTadHelper::tadForDimensions() returns shared_ptr, // but tadOnlyShapeInfo() returns raw TadPack*, the local shared_ptr goes out of scope // and TadPack can be deleted while Java still holds the raw pointer → SIGSEGV std::unordered_map> g_tadPackRegistry; std::mutex g_tadPackMutex; #include #include #include #include #include #include #include #include #ifdef CPU_FEATURES #include #endif #include #include /* * TypeDef: * void convertTypes(Pointer *extras, DataType srcType, Pointer hX, long N, DataType dstType, Pointer hZ); */ void* mapFromNpzFile(std::string path) { cnpy::npz_t* mapPtr = new cnpy::npz_t(); cnpy::npz_t map = cnpy::npzLoad(path); mapPtr->insert(map.begin(), map.end()); return reinterpret_cast(mapPtr); } int getNumNpyArraysInMap(void* map) { cnpy::npz_t* arrays = reinterpret_cast(map); int n = arrays->size(); return n; } const char* getNpyArrayNameFromMap(void* map, int index, char* nameBuffer) { cnpy::npz_t* arrays = reinterpret_cast(map); cnpy::npz_t::iterator it = arrays->begin(); cnpy::npz_t::iterator end = arrays->end(); int cnt = 0; for (; it != end; ++it, ++cnt) { if (cnt == index) { size_t len_of_str = strlen(it->first.c_str()); memcpy(nameBuffer, it->first.c_str(), len_of_str); } } return ""; } void* getNpyArrayFromMap(void* map, int index) { cnpy::npz_t* arrays = reinterpret_cast(map); cnpy::npz_t::iterator it = arrays->begin(); cnpy::npz_t::iterator end = arrays->end(); cnpy::NpyArray* arr = new cnpy::NpyArray(); int cnt = 0; for (; it != end; ++it, ++cnt) { if (cnt == index) { *arr = it->second; return arr; } } return nullptr; } void* getNpyArrayData(void* npArray) { cnpy::NpyArray* npyArray2 = reinterpret_cast(npArray); return reinterpret_cast(npyArray2->data); } int getNpyArrayRank(void* npArray) { cnpy::NpyArray* arr = reinterpret_cast(npArray); int rank = arr->shape.size(); return rank; } sd::LongType* getNpyArrayShape(void* npArray) { cnpy::NpyArray* arr = reinterpret_cast(npArray); int ndim = arr->shape.size(); sd::LongType* shape = new sd::LongType[ndim]; for (int i = 0; i < ndim; i++) { shape[i] = arr->shape.at(i); } return shape; } char getNpyArrayOrder(void* npArray) { cnpy::NpyArray* arr = reinterpret_cast(npArray); return (arr->fortranOrder) ? 'f' : 'c'; } int getNpyArrayElemSize(void* npArray) { cnpy::NpyArray* arr = reinterpret_cast(npArray); return arr->wordSize; } void deleteNPArrayStruct(void* npArray) { cnpy::NpyArray* arr = reinterpret_cast(npArray); delete arr; } void deleteNPArrayMap(void* map) { cnpy::npz_t* arrays = reinterpret_cast(map); delete arrays; } ////// /** * Get the element size for a numpy array * @param npyArray the numpy array's address * to get the length for * @return */ int elementSizeForNpyArray(sd::Pointer npyArray) { cnpy::NpyArray arr = cnpy::loadNpyFromPointer(reinterpret_cast(npyArray)); cnpy::NpyArray* arrPointer = &arr; int size = arrPointer->wordSize; // arrPointer->destruct(); return size; } /** * Get the element size for a numpy array * @param npyArray the numpy array's address * to get the length for * @return */ int elementSizeForNpyArrayHeader(sd::Pointer npyArray) { cnpy::NpyArray arr = cnpy::loadNpyFromHeader(reinterpret_cast(npyArray)); cnpy::NpyArray* arrPointer = &arr; int size = arrPointer->wordSize; return size; } void releaseNumpy(sd::Pointer npyArray) { free(reinterpret_cast(npyArray)); } #if defined(SD_GCC_FUNCTRACE) // this is mainly a c based function. extern "C" { //note this is a c++ 17 feature #ifndef INSTRUMENT_FILE_DEF #define INSTRUMENT_FILE_DEF 1 FILE* instrumentFile = nullptr; #endif } #endif void ctxAllowHelpers(OpaqueContext *ptr, bool reallyAllow) { ptr->allowHelpers(reallyAllow); } void ctxSetExecutionMode(OpaqueContext *ptr, int execMode) { if (execMode < 0 || execMode > 2) execMode = 0; ptr->setExecutionMode((samediff::ExecutionMode)execMode); } sd::LongType getCachedMemory(int deviceId) { return sd::ConstantHelper::getInstance().getCachedAmount(deviceId); } void ctxShapeFunctionOverride(OpaqueContext *ptr, bool reallyOverride) { ptr->setShapeFunctionOverride(reallyOverride); } void ctxPurge(OpaqueContext *ptr) { ptr->clearFastPath(); } int lastErrorCode() { return sd::LaunchContext::defaultContext()->errorReference()->errorCode(); } const char *lastErrorMessage() { return sd::LaunchContext::defaultContext()->errorReference()->errorMessage(); } sd::LaunchContext *defaultLaunchContext() { return sd::LaunchContext::defaultContext(); } void setIntermediateResult(OpaqueContext *contextPointer, int index, OpaqueDataBuffer *buffer, OpaqueDataBuffer *shapeInfo, sd::LongType dataOffset) { if(shapeInfo == nullptr) { THROW_EXCEPTION("Set Intermediate Result: shapeInfo is null"); } auto casted = reinterpret_cast(shapeInfo->primary()); auto desc = new sd::ShapeDescriptor(casted, false); auto arr = new sd::NDArray(buffer->dataBuffer(), desc, sd::LaunchContext::defaultContext(), dataOffset); contextPointer->setIntermediateResult(index, arr); } std::vector intermediateResultsShapeInfo(OpaqueContext *contextPointer) { std::vector intermediates; for (auto v: contextPointer->intermediateResults()) { const sd::LongType *buff = v->shapeInfo(); intermediates.push_back(buff); } return intermediates; } std::vector intermediateResults(OpaqueContext *contextPointer) { std::vector intermediates; for (auto v: contextPointer->intermediateResults()) { OpaqueDataBuffer *buff = new OpaqueDataBuffer (v->dataBuffer()); intermediates.push_back(buff); } return intermediates; } int numIntermediateResults(OpaqueContext *contextPointer) { return contextPointer->numIntermediates(); } void pushIntermediateResult(OpaqueContext *contextPointer, OpaqueDataBuffer *buffer, OpaqueDataBuffer *shapeInfo, sd::LongType offset) { auto shapeInfoCast = reinterpret_cast(shapeInfo->primary()); auto desc = new sd::ShapeDescriptor(shapeInfoCast, false); auto arr = new sd::NDArray(buffer->dataBuffer(), desc, sd::LaunchContext::defaultContext(), offset); contextPointer->pushIntermediateResult(arr); } OpaqueDataBuffer * intermediateResultDataAt(int index, OpaqueContext *contextPointer) { auto arr = contextPointer->intermediateResult(index); return new OpaqueDataBuffer(arr->dataBuffer()); } const sd::LongType * intermediateResultShapeInfoAt(int index, OpaqueContext *contextPointer) { auto context = reinterpret_cast(contextPointer); auto arr = context->intermediateResult(index); return arr->shapeInfo(); } sd::LongType const *getPrimaryShapeInfo(sd::TadPack *pack) { return const_cast(pack->primaryShapeInfo()); } sd::LongType const *getPrimaryOffsets(sd::TadPack *pack) { if(pack->primaryOffsets() == nullptr) THROW_EXCEPTION("getPrimaryOffsets: primaryOffsets is nullptr!"); return const_cast(pack->primaryOffsets()); } sd::LongType const *getSpecialShapeInfo(sd::TadPack *pack) { return const_cast(pack->specialShapeInfo()); } sd::LongType const *getSpecialOffsets(sd::TadPack *pack) { return const_cast(pack->specialOffsets()); } sd::LongType getNumberOfTads(sd::TadPack *pack) { return pack->numberOfTads(); } int getShapeInfoLength(sd::TadPack *pack) { return pack->shapeInfoLength(); } const char* getTadPackStackTrace(OpaqueTadPack *pack) { if (pack == nullptr) { return "TadPack is null"; } // // ROOT CAUSE: thread_local uses R_X86_64_GOTPC32_TLSDESC relocations which have ±2GB limit // When SD_GCC_FUNCTRACE is enabled, binary size exceeds 2GB → TLS relocations fail // // SOLUTION: Use regular static instead of thread_local // - Eliminates all TLS relocations from this function // - Trade-off: Not thread-safe (acceptable for debugging function) // - If called concurrently by multiple threads, traces may interleave (rare edge case) // // This is fundamentally different from Sessions #159-164 which tried linker workarounds // Those approaches CAN'T work - TLS relocations are architectural limitation static std::string cachedTrace; cachedTrace = pack->getStackTraceAsString(); return cachedTrace.c_str(); } sd::TadPack *tadOnlyShapeInfo(OpaqueDataBuffer *hXShapeInfo, sd::LongType *dimension, sd::LongType dimensionLength) { #ifdef __cpp_exceptions try { if(hXShapeInfo->primary() == nullptr) { THROW_EXCEPTION("tadOnlyShapeInfo: hXShapeInfo->primary() is nullptr!"); } auto buffPrim = reinterpret_cast(hXShapeInfo->primary()); auto shapeFromCache = sd::ConstantShapeHelper::getInstance().bufferForShapeInfo(buffPrim)->primary(); auto rankVal = shapeFromCache[0]; if(rankVal == 0) { //detect when the shape buffer values are unset. auto len = shape::shapeInfoLength(rankVal); //min number of values in a shape info buffer bool allZero = true; for(int i = 0; i < len; i++) { if(buffPrim[i] != 0) { allZero = false; break; } } if(allZero) { THROW_EXCEPTION("Found shape buffer with all zero values. Values likely unset."); } } // If we just return pack.get(), the local shared_ptr goes out of scope and TadPack can be deleted // when cache evicts it, leaving Java with a dangling pointer → SIGSEGV // // Solution: Store the shared_ptr in a global registry to keep the TadPack alive. // The shared_ptr is removed from registry when Java explicitly releases it, or when // the cache is explicitly cleared. auto pack = sd::ConstantTadHelper::getInstance().tadForDimensions( shapeFromCache, dimension, dimensionLength); if (!pack) { THROW_EXCEPTION("tadOnlyShapeInfo: Failed to create TadPack!"); } // Get raw pointer BEFORE storing in registry sd::TadPack* rawPtr = pack.get(); // Store shared_ptr in registry to keep TadPack alive { std::lock_guard lock(g_tadPackMutex); g_tadPackRegistry[rawPtr] = pack; } return rawPtr; } catch (std::exception &e) { safeSetErrorContext(1, e.what()); THROW_EXCEPTION(e.what()); } #else if(hXShapeInfo->primary() == nullptr) { safeSetErrorContext(1, "tadOnlyShapeInfo: hXShapeInfo->primary() is nullptr!"); return nullptr; } auto buffPrim = reinterpret_cast(hXShapeInfo->primary()); auto shapeFromCache = sd::ConstantShapeHelper::getInstance().bufferForShapeInfo(buffPrim)->primary(); auto rankVal = shapeFromCache[0]; if(rankVal == 0) { //detect when the shape buffer values are unset. auto len = shape::shapeInfoLength(rankVal); //min number of values in a shape info buffer bool allZero = true; for(int i = 0; i < len; i++) { if(buffPrim[i] != 0) { allZero = false; break; } } if(allZero) { safeSetErrorContext(1, "Found shape buffer with all zero values. Values likely unset."); return nullptr; } } // If we just return pack.get(), the local shared_ptr goes out of scope and TadPack can be deleted // when cache evicts it, leaving Java with a dangling pointer → SIGSEGV // // Solution: Store the shared_ptr in a global registry to keep the TadPack alive. // The shared_ptr is removed from registry when Java explicitly releases it, or when // the cache is explicitly cleared. auto pack = sd::ConstantTadHelper::getInstance().tadForDimensions( shapeFromCache, dimension, dimensionLength); if (!pack) { safeSetErrorContext(1, "tadOnlyShapeInfo: Failed to create TadPack!"); return nullptr; } // Get raw pointer BEFORE storing in registry sd::TadPack* rawPtr = pack.get(); // Store shared_ptr in registry to keep TadPack alive { std::lock_guard lock(g_tadPackMutex); g_tadPackRegistry[rawPtr] = pack; } return rawPtr; #endif return nullptr; } // Helper function to clear the TadPack registry // This should be called when explicitly clearing caches to prevent memory leaks void clearTadPackRegistry() { std::lock_guard lock(g_tadPackMutex); g_tadPackRegistry.clear(); } OpaqueConstantShapeBuffer shapeBuffer(int rank, sd::LongType *shape, sd::LongType *strides, sd::DataType dtype, char order, sd::LongType ews, bool empty) { return shapeBufferEx(rank, shape, strides, dtype, order, ews, empty ? ARRAY_EMPTY : 0); } void dbPrintAllocationTrace(OpaqueDataBuffer *db) { db->dataBuffer()->printAllocationTrace(); } sd::LongType dbBufferLength(OpaqueDataBuffer *dataBuffer) { return dataBuffer->dataBuffer()->getNumElements(); } OpaqueDataBuffer *dbAllocateDataBuffer(sd::LongType elements, int dataType, bool allocateBoth) { return allocateDataBuffer(elements, dataType, allocateBoth); } OpaqueDataBuffer *allocateDataBuffer(sd::LongType elements, int dataType, bool allocateBoth) { #ifdef __cpp_exceptions try { auto dtype = sd::DataTypeUtils::fromInt(dataType); sd::LongType totalElementSize = elements == 0 ? sd::DataTypeUtils::sizeOf(dtype) : elements * sd::DataTypeUtils::sizeOf(dtype); auto buffer = new sd::InteropDataBuffer(totalElementSize, dtype, allocateBoth); // Track allocation if (buffer != nullptr) { size_t bytes = totalElementSize; g_dataBufferCount.fetch_add(1, std::memory_order_relaxed); g_dataBufferBytes.fetch_add(bytes, std::memory_order_relaxed); if(sd::Environment::getInstance().isVerbose()) { sd_printf("allocateDataBuffer: allocated buffer at %p, count=%zu, total_bytes=%zu, this_bytes=%zu\n", buffer, g_dataBufferCount.load(), g_dataBufferBytes.load(), bytes); } } return buffer; } catch (std::exception &e) { safeSetErrorContext(1, e.what()); return nullptr; } #else auto dtype = sd::DataTypeUtils::fromInt(dataType); sd::LongType totalElementSize = elements == 0 ? sd::DataTypeUtils::sizeOf(dtype) : elements * sd::DataTypeUtils::sizeOf(dtype); auto buffer = new sd::InteropDataBuffer(totalElementSize, dtype, allocateBoth); // Track allocation if (buffer != nullptr) { size_t bytes = totalElementSize; g_dataBufferCount.fetch_add(1, std::memory_order_relaxed); g_dataBufferBytes.fetch_add(bytes, std::memory_order_relaxed); if(sd::Environment::getInstance().isVerbose()) { sd_printf("allocateDataBuffer: allocated buffer at %p, count=%zu, total_bytes=%zu, this_bytes=%zu\n", buffer, g_dataBufferCount.load(), g_dataBufferBytes.load(), bytes); } } return buffer; #endif } OpaqueDataBuffer *dbCreateExternalDataBuffer(sd::LongType elements, int dataType, sd::Pointer primary, sd::Pointer special) { auto buffer = dbAllocateDataBuffer(0, dataType, false); buffer->markOwner(false); if (primary != nullptr) buffer->setPrimary(primary, elements); if (special != nullptr) buffer->setSpecial(special, elements); return buffer; } sd::Pointer dbPrimaryBuffer(OpaqueDataBuffer *dataBuffer) { if (dataBuffer == nullptr) THROW_EXCEPTION("dbPrimaryBuffer: dataBuffer is null"); return dataBuffer->primary(); } sd::Pointer dbSpecialBuffer(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbSpecialBuffer: dataBuffer is null"); return dataBuffer->special(); } void deleteDataBuffer(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("deleteDataBuffer: dataBuffer is null"); // Close the buffer first to ensure proper cleanup of underlying DataBuffer // This updates tracking counters and frees the actual data dbClose(dataBuffer); // Now delete the wrapper delete dataBuffer; } void dbSetPrimaryBuffer(OpaqueDataBuffer *dataBuffer, sd::Pointer primaryBuffer, sd::LongType numBytes) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbSetPrimaryBuffer: dataBuffer is null"); dataBuffer->setPrimary(primaryBuffer, numBytes); } void dbSetSpecialBuffer(OpaqueDataBuffer *dataBuffer, sd::Pointer specialBuffer, sd::LongType numBytes) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbSetSpecialBuffer: dataBuffer is null"); dataBuffer->setSpecial(specialBuffer, numBytes); } void dbAllocatePrimaryBuffer(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbAllocatePrimaryBuffer: dataBuffer is null"); dataBuffer->dataBuffer()->allocatePrimary(); } void dbAllocateSpecialBuffer(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbAllocateSpecialBuffer: dataBuffer is null"); dataBuffer->dataBuffer()->allocateSpecial(); } void dbExpandBuffer(OpaqueDataBuffer *dataBuffer, sd::LongType elements) { #ifdef __cpp_exceptions try { if(dataBuffer == nullptr) THROW_EXCEPTION("dbExpandBuffer: dataBuffer is null"); dataBuffer->dataBuffer()->expand(elements * sd::DataTypeUtils::sizeOf(dataBuffer->dataBuffer()->getDataType())); } catch (std::exception &e) { safeSetErrorContext(1, e.what()); } #else if(dataBuffer == nullptr) { safeSetErrorContext(1, "dbExpandBuffer: dataBuffer is null"); return; } dataBuffer->dataBuffer()->expand(elements * sd::DataTypeUtils::sizeOf(dataBuffer->dataBuffer()->getDataType())); #endif } OpaqueDataBuffer *dbCreateView(OpaqueDataBuffer *dataBuffer, sd::LongType length) { return new OpaqueDataBuffer(dataBuffer, length); } int dbUseCount(OpaqueDataBuffer* dataBuffer) { if(dataBuffer) return dataBuffer->useCount(); return 0; } void dbSyncToSpecial(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbSyncToSpecial: dataBuffer is null"); if(dataBuffer->dataBuffer() != nullptr && dataBuffer->dataBuffer()->getNumElements() > 0) dataBuffer->dataBuffer()->syncToSpecial(); } void dbSyncToPrimary(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbSyncToPrimary: dataBuffer is null"); if(dataBuffer->dataBuffer() != nullptr && dataBuffer->dataBuffer()->getNumElements() > 0) dataBuffer->dataBuffer()->syncToPrimary(sd::LaunchContext::defaultContext(),false); } void dbTickHostRead(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbTickHostRead: dataBuffer is null"); dataBuffer->dataBuffer()->readPrimary(); } void dbTickHostWrite(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbTickHostWrite: dataBuffer is null"); dataBuffer->dataBuffer()->writePrimary(); } void dbTickDeviceRead(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbTickDeviceRead: dataBuffer is null"); dataBuffer->dataBuffer()->readSpecial(); } void dbTickDeviceWrite(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbTickDeviceWrite: dataBuffer is null"); dataBuffer->dataBuffer()->writeSpecial(); } void dbExpand(OpaqueDataBuffer *dataBuffer, sd::LongType elements) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbExpand: dataBuffer is null"); dataBuffer->expand(elements); } void dbClose(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbClose: dataBuffer is null"); // Check if already closed - this flag is in InteropDataBuffer, not the freed DataBuffer if(dataBuffer->_closed) { return; } // Check constant flag (public field, safe to access) if(dataBuffer->isConstant) { return; } // Check if we even have a DataBuffer pointer if(!dataBuffer->hasValidDataBuffer()) { dataBuffer->_closed = true; return; } // If we don't own it, don't close it if(!dataBuffer->isOwner()) { return; } // Track deallocation using cached size - DO NOT touch the DataBuffer as it may be freed // Use the cached size from InteropDataBuffer instead of accessing potentially freed memory size_t bytes = dataBuffer->_cachedLenInBytes; g_dataBufferCount.fetch_sub(1, std::memory_order_relaxed); g_dataBufferBytes.fetch_sub(bytes, std::memory_order_relaxed); if(sd::Environment::getInstance().isVerbose()) { sd_printf("dbClose: deallocating buffer at %p, count=%zu, total_bytes=%zu, freed_bytes=%zu\n", dataBuffer, g_dataBufferCount.load(), g_dataBufferBytes.load(), bytes); } #if defined(SD_GCC_FUNCTRACE) // Record deallocation using cached pointers (safe even if DataBuffer is freed) if(dataBuffer->_cachedPrimaryPtr != nullptr) { sd::array::DataBufferLifecycleTracker::getInstance().recordDeallocation( dataBuffer->_cachedPrimaryPtr, sd::array::BufferType::PRIMARY); } if(dataBuffer->_cachedSpecialPtr != nullptr) { sd::array::DataBufferLifecycleTracker::getInstance().recordDeallocation( dataBuffer->_cachedSpecialPtr, sd::array::BufferType::SPECIAL); } #endif // Get the DataBuffer before marking closed sd::DataBuffer* db = dataBuffer->getDataBufferDirect(); // Mark as closed and invalidate pointer BEFORE deleting to prevent concurrent access dataBuffer->_closed = true; dataBuffer->invalidateDataBuffer(); // Delete the DataBuffer if we have one and we own it // This is safe because we passed the isOwner() check above if(db != nullptr) { delete db; } } int dbDeviceId(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbDeviceId: dataBuffer is null"); return dataBuffer->deviceId(); } void dbSetDeviceId(OpaqueDataBuffer *dataBuffer, int deviceId) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbSetDeviceId: dataBuffer is null"); dataBuffer->setDeviceId(deviceId); } int dbLocality(OpaqueDataBuffer *dataBuffer) { if(dataBuffer == nullptr) THROW_EXCEPTION("dbLocality: dataBuffer is null"); auto p = dataBuffer->dataBuffer()->isPrimaryActual(); auto d = dataBuffer->dataBuffer()->isSpecialActual(); if (p && d) return 0; else if (p) return -1; else return 1; }