1 /* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow/c/tf_tensor.h"
17
18 #include <memory>
19 #include <vector>
20
21 #include "tensorflow/c/tf_status.h"
22 #include "tensorflow/c/tf_status_helper.h"
23 #include "tensorflow/c/tf_tensor_internal.h"
24 #include "tensorflow/core/framework/allocation_description.pb.h"
25 #include "tensorflow/core/framework/log_memory.h"
26 #include "tensorflow/core/framework/tensor.h"
27 #include "tensorflow/core/framework/tensor_shape.pb.h"
28 #include "tensorflow/core/framework/types.pb.h"
29 #include "tensorflow/core/lib/core/coding.h"
30 #include "tensorflow/core/platform/casts.h"
31
32 using tensorflow::Status;
33 using tensorflow::Tensor;
34 using tensorflow::TensorBuffer;
35 using tensorflow::errors::FailedPrecondition;
36 using tensorflow::errors::InvalidArgument;
37
38 namespace tensorflow {
allocate_tensor(const char * operation,size_t len,Allocator * allocator)39 void* allocate_tensor(const char* operation, size_t len, Allocator* allocator) {
40 void* data = allocator->AllocateRaw(EIGEN_MAX_ALIGN_BYTES, len);
41 if (LogMemory::IsEnabled() && data != nullptr) {
42 LogMemory::RecordRawAllocation(
43 operation, LogMemory::EXTERNAL_TENSOR_ALLOCATION_STEP_ID, len, data,
44 allocator);
45 }
46 return data;
47 }
48
allocate_tensor(const char * operation,size_t len)49 void* allocate_tensor(const char* operation, size_t len) {
50 return allocate_tensor(operation, len, cpu_allocator());
51 }
52
deallocate_buffer(void * data,size_t len,void * arg)53 void deallocate_buffer(void* data, size_t len, void* arg) {
54 Allocator* allocator = nullptr;
55 if (arg == nullptr) {
56 allocator = cpu_allocator();
57 } else {
58 allocator = reinterpret_cast<Allocator*>(arg);
59 }
60 if (LogMemory::IsEnabled() && data != nullptr) {
61 LogMemory::RecordRawDeallocation(
62 "TensorFlow C Api", LogMemory::EXTERNAL_TENSOR_ALLOCATION_STEP_ID, data,
63 allocator, false);
64 }
65 allocator->DeallocateRaw(data);
66 }
67 } // namespace tensorflow
68
69 namespace {
CreateTensor(TF_ManagedBuffer * buf,TF_DataType dtype,const int64_t * dims,int num_dims,size_t len)70 TF_Tensor* CreateTensor(TF_ManagedBuffer* buf, TF_DataType dtype,
71 const int64_t* dims, int num_dims, size_t len) {
72 std::vector<int64_t> dimvec(num_dims);
73 for (int i = 0; i < num_dims; ++i) {
74 dimvec[i] = static_cast<int64_t>(dims[i]);
75 }
76
77 // TODO(gjn): Make the choice of interface a compile-time configuration.
78 tensorflow::TensorInterface ret(
79 Tensor(static_cast<tensorflow::DataType>(dtype),
80 tensorflow::TensorShape(dimvec), buf));
81 buf->Unref();
82 size_t elem_size = TF_DataTypeSize(dtype);
83 if (elem_size > 0 && len < (elem_size * ret.NumElements())) {
84 return nullptr;
85 }
86 return new TF_Tensor{new tensorflow::TensorInterface(ret)};
87 }
88 } // namespace
89
TF_AllocateTensor(TF_DataType dtype,const int64_t * dims,int num_dims,size_t len)90 TF_Tensor* TF_AllocateTensor(TF_DataType dtype, const int64_t* dims,
91 int num_dims, size_t len) {
92 void* data = tensorflow::allocate_tensor("TF_AllocateTensor", len,
93 tensorflow::cpu_allocator());
94 TF_ManagedBuffer* buf =
95 new TF_ManagedBuffer(data, len, tensorflow::deallocate_buffer,
96 tensorflow::cpu_allocator(), /*owns_memory=*/true);
97 return CreateTensor(buf, dtype, dims, num_dims, len);
98 }
99
TF_NewTensor(TF_DataType dtype,const int64_t * dims,int num_dims,void * data,size_t len,void (* deallocator)(void * data,size_t len,void * arg),void * deallocator_arg)100 TF_Tensor* TF_NewTensor(TF_DataType dtype, const int64_t* dims, int num_dims,
101 void* data, size_t len,
102 void (*deallocator)(void* data, size_t len, void* arg),
103 void* deallocator_arg) {
104 TF_ManagedBuffer* buf = nullptr;
105 if (dtype != TF_STRING && dtype != TF_RESOURCE &&
106 tensorflow::DataTypeCanUseMemcpy(
107 static_cast<tensorflow::DataType>(dtype)) &&
108 reinterpret_cast<intptr_t>(data) % std::max(1, EIGEN_MAX_ALIGN_BYTES) !=
109 0) {
110 // TF_STRING and TF_RESOURCE tensors have a different representation in
111 // TF_Tensor than they do in tensorflow::Tensor. So a copy here is a waste
112 // (any alignment requirements will be taken care of by TF_TensorToTensor
113 // and TF_TensorFromTensor).
114 //
115 // Other types have the same representation, so copy only if it is safe to
116 // do so.
117 buf = new TF_ManagedBuffer(tensorflow::allocate_tensor("TF_NewTensor", len),
118 len, tensorflow::deallocate_buffer, nullptr,
119 /*owns_memory=*/true);
120 std::memcpy(buf->data(), data, len);
121 // Free the original buffer.
122 deallocator(data, len, deallocator_arg);
123 } else {
124 buf = new TF_ManagedBuffer(data, len, deallocator, deallocator_arg,
125 /*owns_memory=*/false);
126 }
127
128 return CreateTensor(buf, dtype, dims, num_dims, len);
129 }
130
TF_TensorMaybeMove(TF_Tensor * t)131 TF_Tensor* TF_TensorMaybeMove(TF_Tensor* t) {
132 return t->tensor->CanMove() ? t : nullptr;
133 }
134
TF_DeleteTensor(TF_Tensor * t)135 void TF_DeleteTensor(TF_Tensor* t) {
136 if (t == nullptr) {
137 return;
138 }
139
140 if (t->tensor) {
141 t->tensor->Release();
142 }
143
144 delete t;
145 }
146
TF_TensorType(const TF_Tensor * t)147 TF_DataType TF_TensorType(const TF_Tensor* t) {
148 return static_cast<TF_DataType>(t->tensor->Type());
149 }
150
TF_SetShape(TF_Tensor * t,const int64_t * dims,int num_dims)151 void TF_SetShape(TF_Tensor* t, const int64_t* dims, int num_dims) {
152 tensorflow::down_cast<tensorflow::TensorInterface*>(t->tensor)->SetShape(
153 dims, num_dims);
154 }
155
TF_NumDims(const TF_Tensor * t)156 int TF_NumDims(const TF_Tensor* t) { return t->tensor->NumDims(); }
157
TF_Dim(const TF_Tensor * t,int dim_index)158 int64_t TF_Dim(const TF_Tensor* t, int dim_index) {
159 return t->tensor->Dim(dim_index);
160 }
161
TF_TensorByteSize(const TF_Tensor * t)162 size_t TF_TensorByteSize(const TF_Tensor* t) { return t->tensor->ByteSize(); }
163
TF_TensorData(const TF_Tensor * t)164 void* TF_TensorData(const TF_Tensor* t) { return t->tensor->Data(); }
165
TF_TensorElementCount(const TF_Tensor * t)166 int64_t TF_TensorElementCount(const TF_Tensor* t) {
167 int64_t result = 1;
168 int rank = TF_NumDims(t);
169 for (int dim = 0; dim < rank; ++dim) {
170 result *= TF_Dim(t, dim);
171 }
172 return result;
173 }
174
TF_TensorBitcastFrom(const TF_Tensor * from,TF_DataType type,TF_Tensor * to,const int64_t * new_dims,int num_new_dims,TF_Status * status)175 void TF_TensorBitcastFrom(const TF_Tensor* from, TF_DataType type,
176 TF_Tensor* to, const int64_t* new_dims,
177 int num_new_dims, TF_Status* status) {
178 TF_SetStatus(status, TF_OK, "");
179 Status cc_status(
180 tensorflow::down_cast<tensorflow::TensorInterface*>(to->tensor)
181 ->BitcastFrom(
182 *tensorflow::down_cast<const tensorflow::TensorInterface*>(
183 from->tensor),
184 static_cast<tensorflow::DataType>(type), new_dims, num_new_dims));
185 Set_TF_Status_from_Status(status, cc_status);
186 }
187
188 namespace tensorflow {
189
Release()190 void TensorInterface::Release() {
191 if (Type() == DT_STRING && NumElements() > 0) {
192 TF_TString* data = static_cast<TF_TString*>(Data());
193 if (CanMove() && data != nullptr) {
194 for (int64_t i = 0; i < NumElements(); ++i) {
195 TF_TString_Dealloc(&data[i]);
196 }
197 }
198 }
199 delete this;
200 }
201
CanMove() const202 bool TensorInterface::CanMove() const {
203 // It is safe to move the Tensor if and only if we own the unique reference to
204 // it. In that case, we might as well not delete and reallocate, but a future
205 // implementation might need to do so.
206 TensorBuffer* buf = tensorflow::TensorCApi::Buffer(tensor_);
207 if (buf->RefCountIsOne() && buf->root_buffer()->RefCountIsOne() &&
208 buf->OwnsMemory()) {
209 return true;
210 }
211 return false;
212 }
213
SummarizeValue() const214 std::string TensorInterface::SummarizeValue() const {
215 return tensor_.SummarizeValue(/*max_entries=*/3, /*print_v2=*/true);
216 }
217
Type() const218 DataType TensorInterface::Type() const { return tensor_.dtype(); }
219
NumDims() const220 int TensorInterface::NumDims() const { return tensor_.dims(); }
221
Dim(int dim_index) const222 int64_t TensorInterface::Dim(int dim_index) const {
223 return static_cast<int64_t>(tensor_.dim_size(dim_index));
224 }
225
NumElements() const226 int64_t TensorInterface::NumElements() const {
227 return static_cast<int64_t>(tensor_.NumElements());
228 }
229
ByteSize() const230 size_t TensorInterface::ByteSize() const {
231 return tensorflow::TensorCApi::Buffer(tensor_)->size();
232 }
233
Data() const234 void* TensorInterface::Data() const {
235 return tensorflow::TensorCApi::Buffer(tensor_)->data();
236 }
237
SetShape(const int64_t * dims,int num_dims)238 void TensorInterface::SetShape(const int64_t* dims, int num_dims) {
239 tensorflow::TensorShape s;
240 for (int i = 0; i < num_dims; ++i) {
241 s.AddDim(dims[i]);
242 }
243 tensor_.set_shape(s);
244 }
245
BitcastFrom(const TensorInterface & from,DataType type,const int64_t * new_dims,int num_new_dims)246 Status TensorInterface::BitcastFrom(const TensorInterface& from, DataType type,
247 const int64_t* new_dims, int num_new_dims) {
248 tensorflow::TensorShape s;
249 for (int i = 0; i < num_new_dims; ++i) {
250 s.AddDim(new_dims[i]);
251 }
252 return tensor_.BitcastFrom(from.tensor_, type, s);
253 }
254
FromProto(const tensorflow::TensorProto & from)255 Status TensorInterface::FromProto(const tensorflow::TensorProto& from) {
256 bool success = tensor_.FromProto(from);
257 if (success) return OkStatus();
258 return errors::InvalidArgument("Unparseable tensor proto");
259 }
260
261 } // namespace tensorflow
262
263 // --------------------------------------------------------------------------
264
DeleteArray(void * data,size_t size,void * arg)265 static void DeleteArray(void* data, size_t size, void* arg) {
266 DCHECK_EQ(data, arg);
267 delete[] reinterpret_cast<char*>(arg);
268 }
269
270 // Create an empty tensor of type 'dtype'. 'shape' can be arbitrary, but has to
271 // result in a zero-sized tensor.
EmptyTensor(TF_DataType dtype,const tensorflow::TensorShape & shape)272 static TF_Tensor* EmptyTensor(TF_DataType dtype,
273 const tensorflow::TensorShape& shape) {
274 static char empty;
275 int64_t nelems = 1;
276 std::vector<int64_t> dims;
277 auto shape_dims = shape.dims();
278 dims.reserve(shape_dims);
279 for (int i = 0; i < shape_dims; ++i) {
280 dims.push_back(shape.dim_size(i));
281 nelems *= shape.dim_size(i);
282 }
283 CHECK_EQ(nelems, 0);
284 return TF_NewTensor(
285 dtype, reinterpret_cast<const int64_t*>(dims.data()), shape.dims(),
286 reinterpret_cast<void*>(&empty), 0, [](void*, size_t, void*) {}, nullptr);
287 }
288
289 namespace tensorflow {
290
291 // Non-static for testing.
TF_TensorFromTensor(const tensorflow::Tensor & src,Status * status)292 TF_Tensor* TF_TensorFromTensor(const tensorflow::Tensor& src, Status* status) {
293 *status = OkStatus();
294 if (!src.IsInitialized()) {
295 *status = FailedPrecondition(
296 "attempt to use a tensor with an uninitialized value");
297 return nullptr;
298 }
299 if (src.NumElements() == 0) {
300 return EmptyTensor(static_cast<TF_DataType>(src.dtype()), src.shape());
301 }
302
303 Tensor tensor;
304 if (!tensor.CopyFrom(src, src.shape())) {
305 return nullptr;
306 }
307 return new TF_Tensor{new tensorflow::TensorInterface(std::move(tensor))};
308 }
309
TF_TensorFromTensorShallow(const tensorflow::Tensor & src,Status * status)310 TF_Tensor* TF_TensorFromTensorShallow(const tensorflow::Tensor& src,
311 Status* status) {
312 *status = OkStatus();
313 if (!src.IsInitialized()) {
314 *status = FailedPrecondition(
315 "attempt to use a tensor with an uninitialized value");
316 return nullptr;
317 }
318 if (src.NumElements() == 0) {
319 return EmptyTensor(static_cast<TF_DataType>(src.dtype()), src.shape());
320 }
321 return new TF_Tensor{new tensorflow::TensorInterface(src)};
322 }
323
TF_TensorToTensor(const TF_Tensor * src,Tensor * dst)324 Status TF_TensorToTensor(const TF_Tensor* src, Tensor* dst) {
325 return tensorflow::down_cast<const tensorflow::TensorInterface*>(src->tensor)
326 ->ToTensor(dst);
327 }
328
ToTensor(tensorflow::Tensor * dst) const329 Status TensorInterface::ToTensor(tensorflow::Tensor* dst) const {
330 *dst = tensor_;
331 return OkStatus();
332 }
333
IsAligned() const334 bool TensorInterface::IsAligned() const { return tensor_.IsAligned(); }
335
336 } // namespace tensorflow
337
TF_TensorIsAligned(const TF_Tensor * t)338 bool TF_TensorIsAligned(const TF_Tensor* t) { return t->tensor->IsAligned(); }
339