/* ****************************************************************************** * * * 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" #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 #include bool experimentalSupport = false; // External reference to TadPack registry (defined in NativeOpsHelpers_DataBuffers.cpp) extern std::unordered_map> g_tadPackRegistry; extern std::mutex g_tadPackMutex; // 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; #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 deleteNDArray(OpaqueNDArray array) { if (array == nullptr) { return; } // Track deallocation size_t bytes = array->lengthOf() * array->sizeOfT(); g_opaqueArrayCount.fetch_sub(1, std::memory_order_relaxed); g_opaqueArrayBytes.fetch_sub(bytes, std::memory_order_relaxed); if(sd::Environment::getInstance().isVerbose()) { sd_printf("deleteNDArray: deallocating array at %p, count=%zu, total_bytes=%zu, freed_bytes=%zu\n", array, g_opaqueArrayCount.load(), g_opaqueArrayBytes.load(), bytes); } delete array; } sd::LongType getOpaqueNDArrayOffset(OpaqueNDArray array) { return array->offset(); } const sd::LongType* getOpaqueNDArrayShapeInfo(OpaqueNDArray array) { return array->shapeInfo(); } void* getOpaqueNDArrayBuffer(OpaqueNDArray array) { if(array == nullptr || array->dataBuffer() == nullptr) { THROW_EXCEPTION("getOpaqueNDArrayBuffer: Array or data buffer was null!"); } return array->dataBuffer()->primary(); } void* getOpaqueNDArraySpecialBuffer(OpaqueNDArray array) { if(array == nullptr || array->dataBuffer() == nullptr) { THROW_EXCEPTION("getOpaqueNDArraySpecialBuffer: Array or data buffer was null!"); } return array->dataBuffer()->special(); } sd::LongType getShapeInfoLength(OpaqueNDArray array) { return shape::shapeInfoLength(array->rankOf()); } sd::LongType getOpaqueNDArrayLength(OpaqueNDArray array) { return array->dataBuffer()->getNumElements(); } OpaqueNDArray createOpaqueNDArray(OpaqueDataBuffer *shapeInfo, OpaqueDataBuffer *buffer, OpaqueDataBuffer *specialBuffer, sd::LongType offset) { if(shapeInfo == nullptr) { THROW_EXCEPTION("createOpaqueNDArray: Shape info was null!"); } sd::LongType* shapeInfoCast = reinterpret_cast(shapeInfo->primary()); // If primary() returns nullptr, the NDArray constructor will fail with undefined behavior // when it tries to call shape::length(nullptr) and other shape functions. // This check provides clear error message at the source rather than cryptic failures downstream. if (shapeInfoCast == nullptr) { THROW_EXCEPTION("createOpaqueNDArray: shapeInfo->primary() returned nullptr - shape buffer is invalid! " "This indicates the Java-side DataBuffer for shape information is corrupted or deallocated."); } if(shape::isEmpty(shapeInfoCast) && buffer != nullptr) { THROW_EXCEPTION("createOpaqueNDArray: Shape info was empty but buffer was not null!"); } else if(!shape::isEmpty(shapeInfoCast) && buffer == nullptr) { THROW_EXCEPTION("createOpaqueNDArray: Shape info was not empty but buffer was null!"); } sd::NDArray* ret = new sd::NDArray( buffer != nullptr ? buffer->getDataBuffer() : nullptr, shapeInfoCast, sd::LaunchContext::defaultContext(), offset ); // Track allocation if (ret != nullptr) { size_t bytes = ret->lengthOf() * ret->sizeOfT(); g_opaqueArrayCount.fetch_add(1, std::memory_order_relaxed); g_opaqueArrayBytes.fetch_add(bytes, std::memory_order_relaxed); if(sd::Environment::getInstance().isVerbose()) { sd_printf("createOpaqueNDArray: allocated array at %p, count=%zu, total_bytes=%zu, this_bytes=%zu\n", ret, g_opaqueArrayCount.load(), g_opaqueArrayBytes.load(), bytes); } } return ret; } void copyBuffer(OpaqueDataBuffer *target, long n, OpaqueDataBuffer *from, long fromOffset, long targetOffset) { sd::DataBuffer::memcpy(target->dataBuffer(), from->dataBuffer(), targetOffset, fromOffset); } int contextNumInputs(void *contextPointer) { Context *context = (Context *) contextPointer; return context->width(); } int contextNumOutputs(void *contextPointer) { Context *context = (Context *) contextPointer; return context->outputWidth(); } int numInputs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return trace->inputShapeBuffers->size(); } int numOutputs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return trace->outputShapeBuffers->size(); } std::vector * bArgs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return &trace->bArgs; } std::vector * sArgs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return (&trace->sArguments); } std::vector * tArgs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return (&trace->tArgs); } std::vector * dArgs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; std::vector *dArgs = new std::vector(); for (size_t e = 0; e < trace->dArgs.size(); e++) { dArgs->push_back(trace->dArgs[e]); } return dArgs; } std::vector * iArgs(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return &(trace->iArgs); } std::vector *inputShapeBuffers(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return trace->inputShapeBuffers; } std::vector *outputShapeBuffers(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return trace->outputShapeBuffers; } char *opName(void *execTrace) { ExecTrace *trace = (ExecTrace *) execTrace; return const_cast(trace->opName->c_str()); } void setElementThreshold(int num) { if (num > 0) sd::Environment::getInstance().setElementwiseThreshold(num); } void setTADThreshold(int num) { if (num > 0) sd::Environment::getInstance().setTadThreshold(num); } sd::Status registerGraph(sd::Pointer *extraPointers, sd::LongType graphId, sd::Pointer flatBufferPointer) { #ifdef __cpp_exceptions try { auto graph = sd::graph::GraphExecutioner::importFromFlatPointer(flatBufferPointer); GraphHolder::getInstance().registerGraph(graphId, graph); return sd::Status::OK; } catch (std::exception &e) { safeSetErrorContext(1, e.what()); return sd::Status::BAD_INPUT; } #else auto graph = sd::graph::GraphExecutioner::importFromFlatPointer(flatBufferPointer); GraphHolder::getInstance().registerGraph(graphId, graph); return sd::Status::OK; #endif } static VariablesSet *executeStoredGraphT(sd::Pointer *extraPointers, sd::LongType graphId, sd::Pointer *inputBuffers, sd::Pointer *inputShapes, int *inputIndices, int numInputs) { auto graph = sd::graph::GraphHolder::getInstance().cloneGraph(graphId); auto varSpace = graph->getVariableSpace(); std::vector handles; for (int e = 0; e < numInputs; e++) { auto idx = inputIndices[e]; // we'll delete this array later, together with cloned VariableSpace auto array = new sd::NDArray(inputBuffers[e], reinterpret_cast(inputShapes[e]), nullptr, 0, 0); handles.emplace_back(array); if (varSpace->hasVariable(idx)) { auto var = varSpace->getVariable(idx); if (var->hasNDArray()) delete var->getNDArray(); var->setNDArray(array); } else varSpace->putVariable(idx, array); } auto hZ = sd::graph::GraphExecutioner::execute(graph, varSpace); auto varSet = new sd::graph::VariablesSet(hZ); if (hZ == sd::Status::OK) { // pull back results, and provide them auto outputs = graph->fetchOutputs(); int size = static_cast(outputs->size()); for (int e = 0; e < size; e++) { // we're only getting variable ID/Index from original grap. values will be taken from cloned workspace std::pair varId(outputs->at(e)->id(), outputs->at(e)->index()); auto var = varSpace->getVariable(varId); varSet->push_back(var->clone()); } delete outputs; } delete graph; return varSet; } VariablesSet *executeStoredGraph(sd::Pointer *extraPointers, sd::LongType graphId, sd::Pointer *inputBuffers, sd::Pointer *inputShapes, int *inputIndices, int numInputs) { #ifdef __cpp_exceptions try { return executeStoredGraphT(extraPointers, graphId, inputBuffers, inputShapes, inputIndices, numInputs); } catch (std::exception &e) { safeSetErrorContext(1, e.what()); return nullptr; } #else return executeStoredGraphT(extraPointers, graphId, inputBuffers, inputShapes, inputIndices, numInputs); #endif } sd::LongType getVariablesSetSize(OpaqueVariablesSet *set) { return set->size(); } sd::Status getVariablesSetStatus(OpaqueVariablesSet *set) { return set->status(); } OpaqueVariable *getVariable(OpaqueVariablesSet *set, sd::LongType i) { return set->at(i); } int getVariableId(Variable *variable) { return variable->id(); } int getVariableIndex(Variable *variable) { return variable->index(); } const char *getVariableName(Variable *variable) { return variable->getName()->c_str(); } sd::LongType const *getVariableShape(Variable *variable) { return variable->getNDArray()->shapeInfo(); } void *getVariableBuffer(Variable *variable) { return variable->getNDArray()->buffer(); } sd::Status unregisterGraph(sd::Pointer *extraPointers, sd::LongType graphId) { #ifdef __cpp_exceptions try { GraphHolder::getInstance().dropGraphAny(graphId); return sd::Status::OK; } catch (std::exception &e) { safeSetErrorContext(1, e.what()); return sd::Status::BAD_INPUT; } #else GraphHolder::getInstance().dropGraphAny(graphId); return sd::Status::OK; #endif } void deletePointerArray(sd::Pointer pointer) { sd::Pointer *ptr = reinterpret_cast(pointer); delete[] ptr; } void deleteCharArray(sd::Pointer pointer) { auto ptr = reinterpret_cast(pointer); delete[] ptr; } void deleteIntArray(sd::Pointer pointer) { auto ptr = reinterpret_cast(pointer); delete[] ptr; } void deleteLongArray(sd::Pointer pointer) { auto ptr = reinterpret_cast(pointer); delete[] ptr; } void deleteVariablesSet(VariablesSet *pointer) { delete pointer; } void deleteShapeList(sd::Pointer shapeList) { sd::ShapeList *list = reinterpret_cast(shapeList); delete list; } const char *getAllOperations() { return sd::OpTracker::getInstance().exportOperations(); } sd::Pointer getGraphState(sd::LongType id) { return (sd::Pointer) new GraphState(id); } void deleteGraphState(sd::Pointer state) { auto stateP = reinterpret_cast(state); delete stateP; } sd::Status execCustomOpWithScope_(sd::Pointer *extraPointers, sd::graph::GraphState *state, sd::LongType opHash, sd::LongType *scopes, int numScopes, sd::Pointer *inputBuffers, sd::Pointer *inputShapes, int numInputs, sd::Pointer *outputBuffers, sd::Pointer *outputShapes, int numOutputs) { /** * That's basically exec, with VariableSpace provided in GraphState: * depending on operation (i.e. while of if), different logic executors could be used */ auto graph = state->graph(); auto varSpace = state->variableSpace(); // Node is dynamically created, and has nothing beyond it: only inputs and outputs // this node has id of 0, and inputs are Node node(::graph::OpType_LOGIC, opHash, 0); // mapping inputs for (int e = 0; e < numInputs; e++) { auto buffer = inputBuffers[e]; auto shapeInfo = reinterpret_cast(inputShapes[e]); auto array = new sd::NDArray(buffer, shapeInfo, varSpace->launchContext(), 0, 0); // now we just put array to VarSpace varSpace->putVariable(0, e, *array); node.pickInput(0, e); } // mapping scopes for (int e = 0; e < numScopes; e++) { // we should check scope existence in GraphState/Graph int scopeId = (int)scopes[e]; if (!state->hasScope(scopeId)) { return sd::Logger::logKernelFailureMsg(); } node.pickInput(scopeId, 0); } auto hZ = LogicExecutor::processNode(graph, &node); if (hZ != sd::Status::OK) return hZ; // mapping outputs for (int e = 0; e < numOutputs; e++) { auto buffer = outputBuffers[e]; auto shapeInfo = reinterpret_cast(outputShapes[e]); sd::NDArray array(buffer, shapeInfo, varSpace->launchContext(), 0, 0); // now we just put array to VarSpace to the same ID // varSpace->putVariable(0, e, array); auto t = varSpace->getVariable(0, e)->getNDArray(); array.assign(t); } // removing input variables for (int e = 0; e < numInputs; e++) { varSpace->dropVariable(0, e); } return sd::Status::OK; } void deleteResultWrapper(sd::Pointer ptr) { auto p = reinterpret_cast(ptr); delete p; } template SD_INLINE int estimateThresholdGeneric(sd::Pointer *extraPointers, sd::Pointer hX, int N, float threshold) { auto buffer = reinterpret_cast(hX); int span = (N / 6) + 8; // Cast the threshold to the appropriate type T T typedThreshold = static_cast(threshold); auto func = PRAGMA_REDUCE_LONG { int64_t cnt = 0; PRAGMA_OMP_SIMD for (auto e = start; e < stop; e++) { auto v = sd::math::sd_abs(buffer[e]); if (v >= typedThreshold) cnt++; } return cnt; }; return samediff::Threads::parallel_long( func, LAMBDA_AL { return _old + _new; }, 0, N); } int estimateThreshold(sd::Pointer *extraPointers, sd::Pointer hX, sd::LongType const *hXShapeInfo, int N, float threshold) { #ifdef __cpp_exceptions try { auto xType = sd::ArrayOptions::dataType(hXShapeInfo); BUILD_SINGLE_SELECTOR(xType, return estimateThresholdGeneric, (extraPointers, hX, N, threshold), SD_FLOAT_TYPES); } catch (std::exception &e) { safeSetErrorContext(1, e.what()); return 0; } #else auto xType = sd::ArrayOptions::dataType(hXShapeInfo); BUILD_SINGLE_SELECTOR(xType, return estimateThresholdGeneric, (extraPointers, hX, N, threshold), SD_FLOAT_TYPES); #endif return 0; } void deleteTadPack(sd::TadPack *ptr) { if (!ptr) return; // The registry holds a shared_ptr to keep TadPacks alive while Java uses them // When Java is done and calls deleteTadPack, we remove it from the registry // This decrements the shared_ptr refcount, and if it reaches 0, the TadPack is deleted { std::lock_guard lock(g_tadPackMutex); auto it = g_tadPackRegistry.find(ptr); if (it != g_tadPackRegistry.end()) { // Found in registry - erase it (this decrements refcount) g_tadPackRegistry.erase(it); // DON'T delete ptr manually - shared_ptr destructor will handle it when refcount reaches 0 } else { // Not in registry - this might be a TadPack created without going through tadOnlyShapeInfo // Or it's already been removed from registry. Safe to delete directly. delete ptr; } } } OpaqueConstantDataBuffer constantBufferLong(sd::DataType dtype, sd::LongType *data, int length) { return sd::ConstantHelper::getInstance().constantBuffer(sd::ConstantDescriptor(data, length), dtype); } OpaqueConstantDataBuffer constantBufferDouble(sd::DataType dtype, double *data, int length) { return sd::ConstantHelper::getInstance().constantBuffer(sd::ConstantDescriptor(data, length), dtype); } OpaqueConstantDataBuffer constantBuffer(sd::DataType dtype, sd::ConstantDescriptor *descriptor) { return sd::ConstantHelper::getInstance().constantBuffer(*descriptor, dtype); } sd::Pointer getConstantDataBufferPrimary(OpaqueConstantDataBuffer dbf) { return dbf->primary(); } sd::Pointer getConstantDataBufferSpecial(OpaqueConstantDataBuffer dbf) { return dbf->special(); } sd::LongType getConstantDataBufferLength(OpaqueConstantDataBuffer dbf) { return dbf->length(); } sd::LongType getConstantDataBufferSizeOf(OpaqueConstantDataBuffer dbf) { return dbf->sizeOf(); } sd::Pointer getConstantShapeBufferPrimary(OpaqueConstantShapeBuffer dbf) { return const_cast(dbf->primary()); } sd::Pointer getConstantShapeBufferSpecial(OpaqueConstantShapeBuffer dbf) { return const_cast(dbf->special()); } const char* getConstantShapeBufferStackTrace(OpaqueConstantShapeBuffer buffer) { if (buffer == nullptr) { return "ConstantShapeBuffer 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 = buffer->getStackTraceAsString(); return cachedTrace.c_str(); } Context *createGraphContext(int nodeId) { return new Context(nodeId); } OpaqueRandomGenerator getGraphContextRandomGenerator(Context *ptr) { return &ptr->randomGenerator(); } void markGraphContextInplace(Context *ptr, bool reallyInplace) { ptr->markInplace(reallyInplace); } // NOTE ABOUT SIGNATURE AND JAVACPP MAPPING // ---------------------------------------- // OpaqueNDArrayArr represents `NDArray**` (a pointer to an array of NDArray*). // // Earlier versions of this function used the signature: // void setGraphContextInputArraysArr(OpaqueContext* ptr, int numArrays, OpaqueNDArrayArr* arr) // which treated the argument as `NDArray***`. That required double‑dereferencing // (e.g. `(*arr)[i]`) and did not match how JavaCPP passes the native pointer. // // In the JavaCPP mapping, the Java side already passes an `NDArray**` directly for // this parameter. Using `OpaqueNDArrayArr*` added an extra level of indirection, // so the native code tried to dereference one level too many, leading to invalid // pointers and hard‑to‑debug crashes. // // The corrected signature below: // void setGraphContextInputArraysArr(OpaqueContext* ptr, int numArrays, OpaqueNDArrayArr arr) // matches the JavaCPP mapping exactly: `arr` is already an `NDArray**`, so // `arr[i]` yields the i‑th `NDArray*` without any extra dereference. void setGraphContextInputArraysArr(OpaqueContext* ptr, int numArrays, OpaqueNDArrayArr arr) { if (arr == nullptr) THROW_EXCEPTION("setGraphContextInputArraysArr: Input arrays were null!"); if (ptr == nullptr) THROW_EXCEPTION("setGraphContextInputArraysArr: Context was null!"); for (int i = 0; i < numArrays; i++) { if (arr[i] == nullptr) { std::string errorMessage; errorMessage += "setGraphContextInputArraysArr: Input array at index "; errorMessage += std::to_string(i); errorMessage += " was null!"; THROW_EXCEPTION(errorMessage.c_str()); } ptr->setInputArray(i, arr[i], false); } } void setGraphContextTArguments(Context *ptr, double *arguments, int numberOfArguments) { ptr->setTArguments(arguments, numberOfArguments); } void setGraphContextIArguments(Context *ptr, sd::LongType *arguments, int numberOfArguments) { ptr->setIArguments(arguments, numberOfArguments); } void setGraphContextBArguments(Context *ptr, bool *arguments, int numberOfArguments) { ptr->setBArguments(arguments, numberOfArguments); } void setGraphContextDArguments(OpaqueContext *ptr, int *arguments, int numberOfArguments) { std::vector dtypes(numberOfArguments); for (int e = 0; e < numberOfArguments; e++) dtypes[e] = sd::DataTypeUtils::fromInt(arguments[e]); ptr->setDArguments(dtypes); } void deleteGraphContext(Context *ptr) { delete ptr; } OpaqueRandomGenerator createRandomGenerator(sd::LongType rootSeed, sd::LongType nodeSeed) { #ifdef __cpp_exceptions try { return new RandomGenerator(rootSeed, nodeSeed); } catch (std::exception &e) { safeSetErrorContext(1, e.what()); return nullptr; } #else return new RandomGenerator(rootSeed, nodeSeed); #endif } sd::LongType getRandomGeneratorRootState(OpaqueRandomGenerator ptr) { return ptr->rootState(); } sd::LongType getRandomGeneratorNodeState(OpaqueRandomGenerator ptr) { return ptr->nodeState(); } void setRandomGeneratorStates(OpaqueRandomGenerator ptr, sd::LongType rootSeed, sd::LongType nodeSeed) { ptr->setStates(rootSeed, nodeSeed); } float getRandomGeneratorRelativeFloat(OpaqueRandomGenerator ptr, sd::LongType index) { return ptr->relativeT(index); } double getRandomGeneratorRelativeDouble(OpaqueRandomGenerator ptr, sd::LongType index) { return ptr->relativeT(index); } int getRandomGeneratorRelativeInt(OpaqueRandomGenerator ptr, sd::LongType index) { return ptr->relativeInt(index); } sd::LongType getRandomGeneratorRelativeLong(OpaqueRandomGenerator ptr, sd::LongType index) { return ptr->relativeLong(index); } int getRandomGeneratorNextInt(OpaqueRandomGenerator ptr) { // to nullify _nodeState._long ^= (steps ^ 0xdeadbeef); // we will use step = 0xdeadbeef auto result = ptr->relativeInt(1); ptr->rewindH(0xdeadbeef); return result; } sd::LongType getRandomGeneratorNextLong(OpaqueRandomGenerator ptr) { auto result = ptr->relativeLong(1); ptr->rewindH(0xdeadbeef); return result; } float getRandomGeneratorNextFloat(OpaqueRandomGenerator ptr) { auto result = ptr->relativeT(1); ptr->rewindH(0xdeadbeef); return result; } double getRandomGeneratorNextDouble(OpaqueRandomGenerator ptr) { auto result = ptr->relativeT(1); ptr->rewindH(0xdeadbeef); return result; } void deleteRandomGenerator(OpaqueRandomGenerator ptr) { delete ptr; } /** * Get the shape buffer from a * numpy array. * **Warning** this allocates memory * @param npyArray * @return */ sd::Pointer shapeBufferForNumpyHeader(sd::Pointer npyArray) { cnpy::NpyArray arr = cnpy::loadNpyFromHeader(reinterpret_cast(npyArray)); auto shape = new sd::LongType[arr.shape.size()]; for (unsigned int i = 0; i < arr.shape.size(); i++) { shape[i] = arr.shape[i]; } auto shapeBuffer = shape::shapeBufferOfNpy(arr.shape.size(), shape, arr.fortranOrder); delete[] shape; return reinterpret_cast(shapeBuffer); } /** * * @param npyArray * @return */ sd::Pointer dataPointForNumpyHeader(sd::Pointer npyArray) { cnpy::NpyArray arr = cnpy::loadNpyFromHeader(reinterpret_cast(npyArray)); unsigned char* dataToPrint = reinterpret_cast(arr.data); return dataToPrint; } /** * * @param npyArray * @return */ sd::Pointer dataPointForNumpyStruct(sd::Pointer npyArrayStruct) { cnpy::NpyArray* arrPointer = reinterpret_cast(npyArrayStruct); unsigned char* dataToPrint = reinterpret_cast(arrPointer->data); return reinterpret_cast(dataToPrint); } /** * * @param npyArray * @param fromFile * @return */ sd::Pointer dataPointForNumpy(sd::Pointer npyArray) { char* npyArrayBuffer = reinterpret_cast(npyArray); cnpy::NpyArray arr = cnpy::loadNpyFromPointer(npyArrayBuffer); return dataPointForNumpyStruct(reinterpret_cast(&arr)); } /** * Load a numpy array from a file * and return it as an sd::Pointer * @param path * @return */ sd::Pointer numpyFromFile(std::string path) { char* numpyBuffer = cnpy::loadFile(path.data()); return reinterpret_cast(numpyBuffer); } ////// NPZ //////