• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

krakjoe / ort / 18867650330

28 Oct 2025 07:43AM UTC coverage: 91.307% (-1.0%) from 92.264%
18867650330

push

github

krakjoe
workflow updates

4212 of 4613 relevant lines covered (91.31%)

30951.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.91
/src/tensor.c
1
/*
2
  +----------------------------------------------------------------------+
3
  | ort                                                                  |
4
  +----------------------------------------------------------------------+
5
  | Copyright (c) Joe Watkins 2025                                       |
6
  +----------------------------------------------------------------------+
7
  | This source file is subject to version 3.01 of the PHP license,      |
8
  | that is bundled with this package in the file LICENSE, and is        |
9
  | available through the world-wide-web at the following url:           |
10
  | http://www.php.net/license/3_01.txt                                  |
11
  | If you did not receive a copy of the PHP license and are unable to   |
12
  | obtain it through the world-wide-web, please send a note to          |
13
  | license@php.net so we can mail you a copy immediately.               |
14
  +----------------------------------------------------------------------+
15
  | Author: krakjoe                                                      |
16
  +----------------------------------------------------------------------+
17
 */
18

19
#include "ort.h"
20

21
#include "alloc.h"
22
#include "tensor.h"
23
#include "generators.h"
24
#include "status.h"
25

26
#ifdef ZTS
27
static MUTEX_T php_ort_tensor_mutex;
28
#endif
29

30
static HashTable php_ort_tensors;
31

32
zend_class_entry *php_ort_tensor_interface_ce;
33
zend_class_entry *php_ort_tensor_persistent_ce;
34
zend_class_entry *php_ort_tensor_transient_ce;
35
zend_object_handlers php_ort_tensor_handlers;
36

37
// Recursive shape/data validator
38
static inline zend_bool php_ort_tensor_validate_next(ONNXTensorElementDataType type, zend_long rank, zend_long *dimensions, zval *node, zend_long depth) {
1,996,340✔
39
    if (Z_TYPE_P(node) == IS_OBJECT &&
1,996,340✔
40
            instanceof_function(
1,996,340✔
41
                Z_OBJCE_P(node), php_ort_generator_ce)) {
×
42
        return 1;
×
43
    }
44

45
    if (depth == rank) {
1,996,340✔
46
        // Leaf node — must match scalar type
47
        switch (type) {
1,973,208✔
48
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
49
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
50
                php_ort_status_flow(
469,548✔
51
                    !(Z_TYPE_P(node) == IS_DOUBLE || Z_TYPE_P(node) == IS_LONG),
52
                    return 0,
53
                    php_ort_status_tensor_invaliddata_ce,
54
                    "tensor leaf at depth %zd: expected float/double, got %s",
55
                    depth, zend_zval_type_name(node)
56
                );
57
                break;
58
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
59
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
60
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
61
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
62
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
63
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
64
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
65
                php_ort_status_flow(
1,497,764✔
66
                    (Z_TYPE_P(node) != IS_LONG),
67
                    return 0,
68
                    php_ort_status_tensor_invaliddata_ce,
69
                    "tensor leaf at depth %zd: expected integer, got %s",
70
                    depth, zend_zval_type_name(node)
71
                );
72
                break;
73
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
74
                php_ort_status_flow(!SUCCESS,
×
75
                    return 0,
76
                    php_ort_status_tensor_invalidtype_ce,
77
                    "UINT64 tensor type is not supported (values exceed PHP integer range)");
78
                break;
79
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
80
                php_ort_status_flow(
5,884✔
81
                    !(Z_TYPE_P(node) == IS_TRUE || Z_TYPE_P(node) == IS_FALSE),
82
                    return 0,
83
                    php_ort_status_tensor_invaliddata_ce,
84
                    "tensor leaf at depth %zd: expected bool, got %s",
85
                    depth, zend_zval_type_name(node)
86
                );
87
                break;
88
            default:
89
                php_ort_status_flow(!SUCCESS,
12✔
90
                    return 0,
91
                    php_ort_status_tensor_invalidtype_ce,
92
                    "unknown data type (%zd) provided",
93
                    (zend_long) type);
94
        }
95
        return 1;
96
    }
97

98
    php_ort_status_flow(
23,132✔
99
        (Z_TYPE_P(node) != IS_ARRAY),
100
        return 0,
101
        php_ort_status_tensor_invaliddata_ce,
102
        "tensor node at depth %zd: expected array, got %s",
103
        depth, zend_zval_type_name(node)
104
    );
105

106
    HashTable *ht = Z_ARRVAL_P(node);
23,132✔
107
    php_ort_status_flow(
23,132✔
108
        (depth >= rank),
109
        return 0,
110
        php_ort_status_tensor_invalidshape_ce,
111
        "validator exceeded tensor rank: depth %zd, rank %zd",
112
        depth, rank
113
    );
114
    php_ort_status_flow(
23,132✔
115
        (zend_hash_num_elements(ht) != dimensions[depth]),
116
        return 0,
117
        php_ort_status_tensor_invaliddata_ce,
118
        "ragged array: sub-array at dimension %zd has length %zd, expected %zd",
119
        depth, zend_hash_num_elements(ht), dimensions[depth]
120
    );
121

122
    for (zend_long i = 0; i < dimensions[depth]; i++) {
2,011,164✔
123
        zval *child = zend_hash_index_find(ht, i);
1,988,104✔
124
        php_ort_status_flow(
1,988,104✔
125
            (!child),
126
            return 0,
127
            php_ort_status_tensor_invalidshape_ce,
128
            "tensor node at depth %zd: missing element at index %zd",
129
            depth, i
130
        );
131
        if (!php_ort_tensor_validate_next(
1,988,104✔
132
                type, rank, dimensions, child, depth + 1)) {
133
            // The recursive call will emit its own exception with details
134
            return 0;
135
        }
136
    }
137

138
    return 1;
139
}
140

141
static zend_always_inline zend_bool php_ort_tensor_validate(zval *shape, zend_string *name, zval *data, ONNXTensorElementDataType type) {
8,440✔
142
    // Skip generator validation
143
    if (Z_TYPE_P(data) == IS_OBJECT && 
8,440✔
144
            instanceof_function(
8,560✔
145
                Z_OBJCE_P(data), php_ort_generator_ce)) {
120✔
146
        return 1;
120✔
147
    }
148

149
    zend_long dimensions[16];
8,320✔
150
    zend_long rank = 0;
8,320✔
151
    
152
    rank = zend_hash_num_elements(Z_ARRVAL_P(shape));
8,320✔
153
    
154
    // Handle scalar tensor case (rank 0, empty shape array)
155
    if (rank == 0) {
8,320✔
156
        // For scalar tensors, data must be a single-element array
157
        if (Z_TYPE_P(data) != IS_ARRAY) {
56✔
158
            php_ort_status_flow(!SUCCESS,
×
159
            {
160
                return 0;
161
            },
162
            php_ort_status_tensor_invaliddata_ce,
163
            "scalar tensor data must be provided as a single-element array");
164
        }
165
        
166
        // Check that the data array has exactly one element
167
        if (zend_hash_num_elements(Z_ARRVAL_P(data)) != 1) {
56✔
168
            php_ort_status_flow(!SUCCESS,
8✔
169
            {
170
                return 0;
171
            },
172
            php_ort_status_tensor_invaliddata_ce,
173
            "scalar tensor must have exactly one data element");
174
        }
175
        
176
        // Get the first (and only) element
177
        zval *scalar = zend_hash_index_find(Z_ARRVAL_P(data), 0);
48✔
178
        if (!scalar) {
48✔
179
            return 0;
180
        }
181
        
182
        // Validate the scalar element type
183
        switch (type) {
48✔
184
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
185
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
186
                return Z_TYPE_P(scalar) == IS_DOUBLE || Z_TYPE_P(scalar) == IS_LONG;
28✔
187
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
188
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
189
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
190
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
191
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
192
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
193
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
194
                return Z_TYPE_P(scalar) == IS_LONG;
12✔
195
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
196
                // UINT64 is not supported due to PHP's signed 64-bit integer limitation
197
                php_ort_status_flow(!SUCCESS,
×
198
                {
199
                    return 0;
200
                },
201
                php_ort_status_tensor_invalidtype_ce,
202
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
203
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
204
                return Z_TYPE_P(scalar) == IS_TRUE || Z_TYPE_P(scalar) == IS_FALSE;
8✔
205
            
206
            default: php_ort_status_flow(!SUCCESS,
×
207
            {
208
                return 0;
209
            },
210
            php_ort_status_tensor_invalidtype_ce,
211
            "unknown data type (%zd) provided",
212
            (zend_long) type);
213
        }
214
    }
215

216
    php_ort_status_flow(
8,264✔
217
        (rank > 16),
218
        return 0,
219
        php_ort_status_tensor_invalidshape_ce,
220
        "invalid shape information (must not exceed 16 dimensions)");
221

222
    // Extract and validate dimensions
223
    zend_long index = 0;
8,264✔
224
    zval *next;
8,264✔
225
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), next) {
18,564✔
226
        php_ort_status_flow(
10,324✔
227
            (Z_TYPE_P(next) != IS_LONG || Z_LVAL_P(next) <= 0),
228
            return 0,
229
            php_ort_status_tensor_invalidshape_ce,
230
            "shape information must be an array of positive integers");
231

232
        php_ort_status_flow(
10,300✔
233
            (!zend_hash_index_exists(Z_ARRVAL_P(shape), index)),
234
            return 0,
235
            php_ort_status_tensor_invalidshape_ce,
236
            "shape must be a packed array, index %zd is missing",
237
            index);
238

239
        dimensions[index++] = Z_LVAL_P(next);
10,296✔
240
    } ZEND_HASH_FOREACH_END();
241

242
    return php_ort_tensor_validate_next(type, rank, dimensions, data, 0);
8,236✔
243
}
244

245
void php_ort_tensor_store(ONNXTensorElementDataType type, void* target, zval* node) {
1,983,036✔
246
    // Enter into generator objects
247
    if (Z_TYPE_P(node) == IS_OBJECT && 
1,983,036✔
248
            instanceof_function(
1,987,960✔
249
                Z_OBJCE_P(node), php_ort_generator_ce)) {
4,924✔
250
        php_ort_generator_invoke(
4,924✔
251
            node, type, target);
252
        return;
4,924✔
253
    }
254

255
    switch (type) {
1,978,112✔
256
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
257
            *(float *)target = Z_TYPE_P(node) == IS_DOUBLE
252,472✔
258
                ? (float)Z_DVAL_P(node)
34,696✔
259
                : (float)Z_LVAL_P(node);
252,472✔
260
            break;
252,472✔
261

262
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
263
            *(double *)target = Z_TYPE_P(node) == IS_DOUBLE
218,992✔
264
                ? Z_DVAL_P(node)
265
                : (double)Z_LVAL_P(node);
218,992✔
266
            break;
218,992✔
267

268
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
220,884✔
269
            *(int8_t *)target = (int8_t)Z_LVAL_P(node);
220,884✔
270
            break;
220,884✔
271

272
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
214,696✔
273
            *(int16_t *)target = (int16_t)Z_LVAL_P(node);
214,696✔
274
            break;
214,696✔
275

276
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
215,132✔
277
            *(int32_t *)target = (int32_t)Z_LVAL_P(node);
215,132✔
278
            break;
215,132✔
279

280
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
214,908✔
281
            *(int64_t *)target = (int64_t)Z_LVAL_P(node);
214,908✔
282
            break;
214,908✔
283

284
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
211,748✔
285
            *(uint8_t *)target = (uint8_t)Z_LVAL_P(node);
211,748✔
286
            break;
211,748✔
287

288
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
211,688✔
289
            *(uint16_t *)target = (uint16_t)Z_LVAL_P(node);
211,688✔
290
            break;
211,688✔
291

292
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
211,704✔
293
            *(uint32_t *)target = (uint32_t)Z_LVAL_P(node);
211,704✔
294
            break;
211,704✔
295

296
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
×
297
            // UINT64 is not supported - this should not be reached due to validation
298
            php_ort_status_throw(
×
299
                php_ort_status_tensor_invalidtype_ce,
300
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
301
            return;
×
302

303
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
304
            *(uint8_t *)target = (Z_TYPE_P(node) == IS_TRUE) ? 1 : 0;
5,888✔
305
            break;
5,888✔
306

307
        default: 
×
308
            php_ort_status_throw(
×
309
                php_ort_status_tensor_invalidtype_ce,
310
                "unknown data type (%zd) provided",
311
                (zend_long) type);
312
    }
313
}
314

315
static zend_always_inline zval* php_ort_tensor_flatten_next(ort_tensor_t* tensor, zval* node, zend_long idx) {
1,993,672✔
316
    // Special case for generators
317
    if (Z_TYPE_P(node) == IS_OBJECT &&
1,993,672✔
318
            instanceof_function(
3,987,400✔
319
                Z_OBJCE_P(node), php_ort_generator_ce)) {
5,632✔
320
        return node;
321
    }
322

323
    // Special case for scalar tensor (0 dimensions)
324
    if (tensor->dimensions == 0) {
1,988,040✔
325
        // For scalar tensors, we expect the data to be a single-element array
326
        if (Z_TYPE_P(node) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(node)) != 1) {
48✔
327
            return NULL;
328
        }
329

330
        // Get the scalar value (first element of the array)
331
        return zend_hash_index_find(Z_ARRVAL_P(node), idx);
48✔
332
    }
333

334
    if (Z_TYPE_P(node) != IS_ARRAY) {
1,987,992✔
335
        return NULL;
336
    }
337

338
    return zend_hash_index_find(Z_ARRVAL_P(node), idx);
1,987,992✔
339
}
340

341
static inline zend_bool php_ort_tensor_flatten(ort_tensor_t* tensor, size_t *offset, size_t size, zval *node, size_t depth) {
2,001,980✔
342
    // Scalar tensors have 0 dimensions
343
    if (tensor->dimensions == 0) {        
2,001,980✔
344
        // Get the scalar value (first element of the array)
345
        zval *scalar = php_ort_tensor_flatten_next(tensor, node, 0);
56✔
346
        if (!scalar) {
56✔
347
            return 0;
×
348
        }
349

350
        void *target = (char *)tensor->data + ((*offset) * size);
56✔
351

352
        php_ort_tensor_store(
56✔
353
            tensor->type, target, scalar);
354
        
355
        (*offset)++;
56✔
356
        return 1;
56✔
357
    }
358

359
    if (depth == tensor->dimensions) {
2,001,924✔
360
        void *target = (char *)tensor->data + ((*offset) * size);
1,978,056✔
361

362
        php_ort_tensor_store(
1,978,056✔
363
            tensor->type, target, node);
364
        
365
        (*offset)++;
1,978,056✔
366
        return 1;
1,978,056✔
367
    }
368

369
    for (int64_t i = 0; i < tensor->shape[depth]; i++) {
2,017,484✔
370
        zval *child = php_ort_tensor_flatten_next(tensor, node, i);
1,993,616✔
371
        if (!php_ort_tensor_flatten(tensor, offset, size, child, depth + 1)) {
1,993,616✔
372
            return 0;
373
        }
374
    }
375

376
    return 1;
377
}
378

379
static zend_always_inline zend_bool php_ort_tensor_allocate_persistent(ort_tensor_t *tensor, zend_string *name, zval *shape, zval *data, ONNXTensorElementDataType type) {
348✔
380
    size_t i = 0, offset = 0;
348✔
381

382
    tensor->name       = php_ort_string_copy(name);
696✔
383
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
348✔
384
    tensor->type       = type;
348✔
385
    tensor->owner      = PHP_ORT_OWN_HEAP;
348✔
386

387
    // Handle scalar tensor case (empty shape array)
388
    if (tensor->dimensions == 0) {
348✔
389
        tensor->shape    = NULL;  // No shape array needed for scalars
8✔
390
        tensor->elements = 1;     // A scalar has exactly one element
8✔
391
        tensor->data     = ort_alloc(php_ort_tensor_sizeof(tensor), 1);
16✔
392
        
393
        return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
16✔
394
    }
395
    
396
    // Normal case for non-scalar tensors
397
    tensor->shape    = pemalloc(tensor->dimensions * sizeof(int64_t), 1);
340✔
398
    tensor->elements = 1;
340✔
399

400
    // Copy shape and compute total number of elements
401
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), zval *dim) {
972✔
402
        int64_t size = (int64_t)Z_LVAL_P(dim);
632✔
403
        tensor->shape[i++] = size;
632✔
404
        tensor->elements *= size;
632✔
405
    } ZEND_HASH_FOREACH_END();
406

407
    tensor->data = ort_alloc(
680✔
408
        php_ort_tensor_sizeof(tensor), tensor->elements);
409

410
    return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
680✔
411
}
412

413
static zend_always_inline zend_bool php_ort_tensor_allocate_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type) {
8,016✔
414
    size_t i = 0, offset = 0;
8,016✔
415

416
    tensor->name       = NULL;
8,016✔
417
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
8,016✔
418
    tensor->type       = type;
8,016✔
419
    tensor->owner      = PHP_ORT_OWN_ZEND;
8,016✔
420

421
    // Handle scalar tensor case (empty shape array)
422
    if (tensor->dimensions == 0) {
8,016✔
423
        tensor->shape    = NULL;  // No shape array needed for scalars
48✔
424
        tensor->elements = 1;     // A scalar has exactly one element
48✔
425
        tensor->data     = ort_alloc(1, php_ort_tensor_sizeof(tensor));
96✔
426
        
427
        return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
96✔
428
    }
429
    
430
    // Normal case for non-scalar tensors
431
    tensor->shape    = ecalloc(tensor->dimensions, sizeof(int64_t));
7,968✔
432
    tensor->elements = 1;
7,968✔
433

434
    // Copy shape and compute total number of elements
435
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), zval *dim) {
17,768✔
436
        int64_t size = (int64_t)Z_LVAL_P(dim);
9,800✔
437
        tensor->shape[i++] = size;
9,800✔
438
        tensor->elements *= size;
9,800✔
439
    } ZEND_HASH_FOREACH_END();
440

441
    tensor->data = ort_alloc(
15,936✔
442
        php_ort_tensor_sizeof(tensor), tensor->elements);
443

444
    return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
15,936✔
445
}
446

447
static void ort_tensor_free(ort_tensor_t *tensor) {
14,772✔
448
    zend_bool persistent = 
14,772✔
449
        (tensor->owner == PHP_ORT_OWN_HEAP) ? 1 : 0;
14,772✔
450

451
    if (tensor->shape) {
14,772✔
452
        pefree(tensor->shape, persistent);
14,496✔
453
    }
454

455
#ifdef HAVE_ONNXRUNTIME
456
    if (!tensor->parent && tensor->data && !tensor->value) {
14,772✔
457
        ort_free(tensor->data);
14,748✔
458
    }
459

460
    if (tensor->value) {
14,772✔
461
        api->ReleaseValue(tensor->value);
4✔
462
    }
463
#else
464
    if (!tensor->parent && tensor->data) {
465
        ort_free(tensor->data);
466
    }
467
#endif
468

469
    if (tensor->name && !persistent) {
14,772✔
470
        zend_string_free(tensor->name);
6,368✔
471
    }
472

473
    if (tensor->parent) {
14,772✔
474
        ort_tensor_release(tensor->parent);
20✔
475
    }
476

477
    pefree(tensor, persistent);
14,772✔
478
}
14,772✔
479

480
void ort_tensor_release(ort_tensor_t *tensor) {
15,352✔
481
    if (!tensor) {
15,352✔
482
        return;
483
    }
484

485
    if (php_ort_atomic_delref(&tensor->refcount) == 0){
15,148✔
486
        ort_tensor_free(tensor);
14,772✔
487
    }
488
}
489

490
static void php_ort_tensor_del(zval *zv) {
348✔
491
    ort_tensor_release(
348✔
492
        ((ort_tensor_t*)
493
            Z_PTR_P(zv)));
348✔
494
}
348✔
495

496
static zend_bool php_ort_tensor_construct_persistent(ort_tensor_t *tensor, zend_string *name, zval *shape, zval *data, ONNXTensorElementDataType type){
388✔
497
    if (!php_ort_tensor_validate(shape, name, data, type)) {
776✔
498
        return 0;
40✔
499
    }
500

501
    if (!php_ort_tensor_allocate_persistent(tensor, name, shape, data, type)) {
696✔
502
        return 0;
503
    }
504

505
    return 1;
506
}
507

508
static zend_bool php_ort_tensor_construct_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type){
8,052✔
509
    if (!php_ort_tensor_validate(shape, NULL, data, type)) {
16,104✔
510
        return 0;
36✔
511
    }
512

513
    if (!php_ort_tensor_allocate_transient(tensor, shape, data, type)) {
16,032✔
514
        return 0;
515
    }
516

517
    return 1;
518
}
519

520
#ifdef HAVE_ONNXRUNTIME
521
OrtValue* php_ort_tensor_value(php_ort_tensor_t* ort) {
8✔
522
    OrtMemoryInfo* mi;
8✔
523
    OrtValue* value = NULL;
8✔
524
    OrtStatus* status;
8✔
525

526
    php_ort_status_flow(
8✔
527
        (status = api->CreateCpuMemoryInfo(
528
            OrtArenaAllocator, OrtMemTypeDefault, &mi)),
529
        {
530
            api->ReleaseStatus(status);
531

532
            return NULL;
533
        },
534
        php_ort_status_tensor_invalidmemory_ce,
535
        "failed to allocate MemoryInfo* for Tensor conversion: %s",
536
        api->GetErrorMessage(status));
537

538
    php_ort_status_flow(
16✔
539
        (status = api->CreateTensorWithDataAsOrtValue(
540
            mi,
541
            ort->object->data,
542
            ort->object->elements * php_ort_tensor_sizeof(ort->object),
543
            ort->object->shape,
544
            ort->object->dimensions,
545
            ort->object->type,
546
            &value)),
547
        {
548
            api->ReleaseStatus(status);
549

550
            return NULL;
551
        },
552
        php_ort_status_tensor_invalidmemory_ce,
553
        "failed to allocate OrtValue* for Tensor conversion: %s",
554
        api->GetErrorMessage(status));
555

556
    api->ReleaseMemoryInfo(mi);
8✔
557

558
    return value;
8✔
559
}
560

561
ort_tensor_t* php_ort_tensor_object(OrtValue* value) {
4✔
562
    ort_tensor_t  *tensor = pecalloc(1, sizeof(ort_tensor_t), 0);
4✔
563

564
    tensor->refcount = 1;
4✔
565
    tensor->owner    = PHP_ORT_OWN_ZEND;
4✔
566
    tensor->value    = value;
4✔
567

568
    OrtTensorTypeAndShapeInfo* otsi;
4✔
569

570
    php_ort_status_flow(
4✔
571
        api->GetTensorTypeAndShape(value, &otsi), {
572
            ort_tensor_free(tensor);
573

574
            return NULL;
575
        },
576
        php_ort_status_tensor_invalidshape_ce,
577
        "failed to determine shape for OrtValue* conversion");
578

579
    php_ort_status_flow(
4✔
580
        api->GetTensorElementType(otsi, &tensor->type),
581
        {
582
            ort_tensor_free(tensor);
583

584
            return NULL;
585
        },
586
        php_ort_status_tensor_invalidshape_ce,
587
        "failed to determine type for OrtValue* conversion");
588

589
    php_ort_status_flow(
4✔
590
        api->GetDimensionsCount(otsi, &tensor->dimensions),
591
        {
592
            ort_tensor_free(tensor);
593

594
            return NULL;
595
        },
596
        php_ort_status_tensor_invalidshape_ce,
597
        "failed to determine dimension count for OrtValue* conversion");
598

599
    // For non-scalar tensors, allocate shape array and populate it
600
    if (tensor->dimensions > 0) {
4✔
601
        tensor->shape = pecalloc(tensor->dimensions, sizeof(int64_t), 0);
4✔
602

603
        php_ort_status_flow(
4✔
604
            api->GetDimensions(otsi, tensor->shape, tensor->dimensions),
605
            {
606
                ort_tensor_free(tensor);
607

608
                return NULL;
609
            },
610
            php_ort_status_tensor_invalidshape_ce,
611
            "failed to fetch dimensions for OrtValue* conversion");
612
    } else {
613
        // For scalar tensors, shape remains NULL
614
        tensor->shape = NULL;
×
615
    }
616

617
    php_ort_status_flow(
4✔
618
        api->GetTensorShapeElementCount(otsi, &tensor->elements),
619
        {
620
            ort_tensor_free(tensor);
621

622
            return NULL;
623
        },
624
        php_ort_status_tensor_invalidshape_ce,
625
        "failed to determine element count for OrtValue* conversion");
626

627
    php_ort_status_flow(
4✔
628
        api->GetTensorMutableData(value, &tensor->data),
629
        {
630
            ort_tensor_free(tensor);
631

632
            return NULL;
633
        },
634
        php_ort_status_tensor_invaliddata_ce,
635
        "failed to fetch data for OrtValue* conversion");
636

637
    api->ReleaseTensorTypeAndShapeInfo(otsi);
4✔
638

639
    return tensor;
4✔
640
}
641
#endif
642

643
static zend_always_inline size_t php_ort_tensor_indexof(ort_tensor_t *tensor, int64_t *coords) {
12✔
644
    // For scalar tensors, always return index 0
645
    if (tensor->dimensions == 0) {
12✔
646
        return 0;
647
    }
648
    
649
    size_t index = 0;
12✔
650
    size_t stride = 1;
12✔
651

652
    for (int64_t i = tensor->dimensions - 1; i >= 0; i--) {
36✔
653
        index += 
24✔
654
            coords[i] * stride;
24✔
655
        stride *= tensor->shape[i];
24✔
656
    }
657

658
    return index;
659
}
660

661
ZEND_BEGIN_ARG_INFO_EX(php_ort_tensor_persistent_construct_arginfo, 0, 0, 1)
662
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
663
    ZEND_ARG_TYPE_INFO(0, shape, IS_ARRAY, 0)
664
    ZEND_ARG_TYPE_MASK(0, data, MAY_BE_ARRAY | MAY_BE_OBJECT, NULL)
665
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
666
ZEND_END_ARG_INFO()
667

668
PHP_METHOD(ONNX_Tensor_Persistent, __construct)
404✔
669
{
670
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
404✔
671

672
    zend_string *name;
404✔
673
    zval        *shape = NULL;
404✔
674
    zval        *data  = NULL;
404✔
675
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
404✔
676

677
    ZEND_PARSE_PARAMETERS_START(1, 4);
404✔
678
        Z_PARAM_STR(name)
404✔
679
        Z_PARAM_OPTIONAL
404✔
680
        Z_PARAM_ARRAY(shape)
404✔
681
        Z_PARAM_ZVAL(data)
388✔
682
        Z_PARAM_LONG(type)
388✔
683
    ZEND_PARSE_PARAMETERS_END();
×
684

685
#ifdef ZTS
686
    php_ort_status_flow(
404✔
687
        tsrm_mutex_lock(php_ort_tensor_mutex) != SUCCESS,
688
        return,
689
        php_ort_status_safetyerror_ce,
690
        "it was not possible to acquire the tensor mutex, something is terribly wrong");
691
#endif
692

693
    if (!(ort->object = zend_hash_find_ptr(&php_ort_tensors, name))) {
808✔
694
#ifdef ZTS
695
        php_ort_status_flow(
396✔
696
            (!shape || !data),
697
            {
698
                php_ort_status_flow(
699
                    tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
700
                    return,
701
                    php_ort_status_safetyerror_ce,
702
                    "it was not possible to release the tensor mutex, something is terribly wrong");
703
                return;
704
            },
705
            php_ort_status_tensor_notfound_ce, 
706
            "Could not find the Tensor named \"%s\"",
707
                ZSTR_VAL(name));
708
#else
709
        php_ort_status_flow(
710
            (!shape || !data),
711
            {
712
                return;
713
            },
714
            php_ort_status_tensor_notfound_ce, 
715
            "Could not find the Tensor named \"%s\"",
716
                ZSTR_VAL(name));
717
#endif
718

719
        ort_tensor_t *tensor = 
388✔
720
            pecalloc(1, sizeof(ort_tensor_t), 1);
388✔
721

722
        tensor->refcount = 1;
388✔
723

724
        if (!php_ort_tensor_construct_persistent(tensor, name, shape, data, type)) {
388✔
725
#ifdef ZTS
726
            php_ort_status_flow(
40✔
727
                tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
728
                return,
729
                php_ort_status_safetyerror_ce,
730
                "it was not possible to release the tensor mutex, something is terribly wrong");
731
#endif
732
            return;
733
        }
734

735
        ort->object = zend_hash_add_ptr(
348✔
736
            &php_ort_tensors,
737
            tensor->name,
738
            tensor);
739

740
        ort->object->refcount++;
348✔
741
    } else {
742
        php_ort_atomic_addref(&ort->object->refcount);
8✔
743
    }
744

745
#ifdef ZTS
746
    php_ort_status_flow(
356✔
747
        tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
748
        return,
749
        php_ort_status_safetyerror_ce,
750
        "it was not possible to release the tensor mutex, something is terribly wrong");
751
#endif
752
}
753

754
ZEND_BEGIN_ARG_INFO_EX(php_ort_tensor_transient_construct_arginfo, 0, 0, 2)
755
    ZEND_ARG_TYPE_INFO(0, shape, IS_ARRAY, 0)
756
    ZEND_ARG_TYPE_MASK(0, data, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL)
757
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
758
ZEND_END_ARG_INFO()
759

760
PHP_METHOD(ONNX_Tensor_Transient, __construct)
8,052✔
761
{
762
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
8,052✔
763

764
    zval        *shape = NULL;
8,052✔
765
    zval        *data  = NULL;
8,052✔
766
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
8,052✔
767

768
    ZEND_PARSE_PARAMETERS_START(2, 3);
8,052✔
769
        Z_PARAM_ARRAY(shape)
8,052✔
770
        Z_PARAM_ZVAL(data)
8,052✔
771
        Z_PARAM_OPTIONAL
8,052✔
772
        Z_PARAM_LONG(type)
8,052✔
773
    ZEND_PARSE_PARAMETERS_END();
×
774

775
    ort_tensor_t *tensor = ecalloc(1, sizeof(ort_tensor_t));
8,052✔
776
    tensor->refcount = 1;
8,052✔
777

778
    if (!php_ort_tensor_construct_transient(tensor, shape, data, type)) {
8,052✔
779
        efree(tensor);
36✔
780
        return;
36✔
781
    }
782

783
    ort->object = tensor;
8,016✔
784
}
785

786
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_isPersistent_arginfo, 0, 0, _IS_BOOL, 0)
787
ZEND_END_ARG_INFO()
788

789
PHP_METHOD(ONNX_Tensor, isPersistent)
4✔
790
{
791
    php_ort_tensor_t* ort =
4✔
792
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
4✔
793

794
    ZEND_PARSE_PARAMETERS_NONE();
4✔
795

796
    RETURN_BOOL(ort->object->owner == PHP_ORT_OWN_HEAP);
4✔
797
}
798

799
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getName_arginfo, 0, 0, IS_STRING, 1)
800
ZEND_END_ARG_INFO()
801

802
PHP_METHOD(ONNX_Tensor, getName)
12✔
803
{
804
    php_ort_tensor_t* ort =
12✔
805
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
12✔
806
    
807
    ZEND_PARSE_PARAMETERS_NONE();
12✔
808

809
    if (!ort->object->name) {
12✔
810
        return;
811
    }
812

813
    RETURN_STR_COPY(ort->object->name);
12✔
814
}
815

816
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getType_arginfo, 0, 0, IS_LONG, 0)
817
ZEND_END_ARG_INFO()
818

819
PHP_METHOD(ONNX_Tensor, getType)
220✔
820
{
821
    php_ort_tensor_t* ort =
220✔
822
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
220✔
823

824
    ZEND_PARSE_PARAMETERS_NONE();
220✔
825

826
    RETURN_LONG(ort->object->type);
220✔
827
}
828

829
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getTypeName_arginfo, 0, 0, IS_STRING, 0)
830
ZEND_END_ARG_INFO()
831

832
PHP_METHOD(ONNX_Tensor, getTypeName)
4,436✔
833
{
834
    php_ort_tensor_t* ort =
4,436✔
835
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
4,436✔
836

837
    ZEND_PARSE_PARAMETERS_NONE();
4,436✔
838

839
    /* @todo(krakjoe) this performs terribly ... */
840
    RETURN_STRING(php_ort_type_name(ort->object->type));
7,692✔
841
}
842

843
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getShape_arginfo, 0, 0, IS_ARRAY, 0)
844
ZEND_END_ARG_INFO()
845

846
PHP_METHOD(ONNX_Tensor, getShape)
4,588✔
847
{
848
    php_ort_tensor_t* ort =
4,588✔
849
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
4,588✔
850

851
    ZEND_PARSE_PARAMETERS_NONE();
4,588✔
852

853
    array_init(return_value);
4,588✔
854
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
9,496✔
855
        add_next_index_long(
4,908✔
856
            return_value, ort->object->shape[dimension]);
4,908✔
857
    }
858
}
859

860
static zend_always_inline ort_tensor_t* php_ort_tensor_transpose(ort_tensor_t* input, zval *axis, zval* return_value) {
16✔
861
    int64_t ndim = input->dimensions;
16✔
862
    int64_t* perm = NULL;
16✔
863
    int64_t* inv_perm = NULL;
16✔
864
    int64_t* out_shape = NULL;
16✔
865
    int64_t* in_strides = NULL;
16✔
866
    int64_t* out_strides = NULL;
16✔
867
    size_t type_size = php_ort_tensor_sizeof(input);
32✔
868
    size_t numel = input->elements;
16✔
869
    ort_tensor_t* result = NULL;
16✔
870

871
    // Argument validation and axes parsing
872
    if (ndim == 0) {
16✔
873
        // Scalar: transpose is a no-op, just return a copy
874
        result = pecalloc(1, sizeof(ort_tensor_t), 0);
×
875
        result->refcount = 1;
×
876
        result->owner = PHP_ORT_OWN_ZEND;
×
877
        result->type = input->type;
×
878
        result->dimensions = 0;
×
879
        result->elements = 1;
×
880
        result->shape = NULL;
×
881
        result->data = ort_alloc(type_size, 1);
×
882
        ort_memcpy(result->data, input->data, type_size);
×
883
        goto __php_ort_tensor_transpose_done;
×
884
    }
885

886
    perm = ecalloc(ndim, sizeof(int64_t));
16✔
887
    out_shape = ecalloc(ndim, sizeof(int64_t));
16✔
888
    in_strides = ecalloc(ndim, sizeof(int64_t));
16✔
889
    out_strides = ecalloc(ndim, sizeof(int64_t));
16✔
890
    inv_perm = ecalloc(ndim, sizeof(int64_t));
16✔
891

892
    // Parse axes argument
893
    if (axis) {
16✔
894
        php_ort_status_flow(
12✔
895
            (zend_hash_num_elements(Z_ARRVAL_P(axis)) != ndim),
896
            {
897
                goto __php_ort_tensor_transpose_failed;
898
            },
899
            php_ort_status_tensor_invalidshape_ce,
900
            "axes array must be an array of length %zd", ndim);
901

902
        // Fill perm from user axes, handle negatives, check for duplicates
903
        zend_bool* seen = ecalloc(ndim, sizeof(zend_bool));
12✔
904
        int64_t i = 0;
12✔
905
        zval* zv;
12✔
906
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(axis), zv) {
36✔
907
            php_ort_status_flow(
24✔
908
                (Z_TYPE_P(zv) != IS_LONG),
909
                {
910
                    efree(seen);
911
                    goto __php_ort_tensor_transpose_failed;
912
                },
913
                php_ort_status_tensor_invalidshape_ce,
914
                "axes array must contain only integers");
915
            
916
            int64_t ax = Z_LVAL_P(zv);
24✔
917
            if (ax < 0)
24✔
918
                ax += ndim;
8✔
919
            
920
            php_ort_status_flow(
24✔
921
                (ax < 0 || ax >= ndim),
922
                {
923
                    efree(seen);
924
                    goto __php_ort_tensor_transpose_failed;
925
                },
926
                php_ort_status_tensor_invalidshape_ce,
927
                "axis value %zd out of range [0, %zd)", ax, ndim);
928

929
            php_ort_status_flow(
24✔
930
                (seen[ax]),
931
                {
932
                    efree(seen);
933
                    goto __php_ort_tensor_transpose_failed;
934
                },
935
                php_ort_status_tensor_invalidshape_ce,
936
                "duplicate axis value %zd in axes", ax);
937

938
            seen[ax] = 1;
24✔
939
            perm[i++] = ax;
24✔
940
        } ZEND_HASH_FOREACH_END();
941
        efree(seen);
12✔
942
    } else {
943
        // Default: reverse axes
944
        for (int64_t i = 0; i < ndim; i++) {
12✔
945
            perm[i] = ndim - 1 - i;
8✔
946
        }
947
    }
948

949
    // Compute output shape and strides
950
    for (int64_t i = 0; i < ndim; i++) {
48✔
951
        out_shape[i] = input->shape[perm[i]];
32✔
952
    }
953
    // Input strides
954
    in_strides[ndim-1] = 1;
16✔
955
    for (int64_t i = ndim-2; i >= 0; i--) {
32✔
956
        in_strides[i] = in_strides[i+1] * input->shape[i+1];
16✔
957
    }
958
    // Output strides
959
    out_strides[ndim-1] = 1;
16✔
960
    for (int64_t i = ndim-2; i >= 0; i--) {
32✔
961
        out_strides[i] = out_strides[i+1] * out_shape[i+1];
16✔
962
    }
963
    // Inverse permutation: inv_perm[perm[i]] = i
964
    for (int64_t i = 0; i < ndim; i++) {
48✔
965
        inv_perm[perm[i]] = i;
32✔
966
    }
967

968
    // Allocate result tensor
969
    result = pecalloc(1, sizeof(ort_tensor_t), 0);
16✔
970
    result->refcount = 1;
16✔
971
    result->owner = PHP_ORT_OWN_ZEND;
16✔
972
    result->type = input->type;
16✔
973
    result->dimensions = ndim;
16✔
974
    result->elements = numel;
16✔
975
    result->shape = pecalloc(ndim, sizeof(int64_t), 0);
16✔
976
    memcpy(result->shape,
16✔
977
        out_shape, ndim * sizeof(int64_t));
978
    result->data = ort_alloc(type_size, numel);
16✔
979

980
    // Main permutation loop
981
    int64_t* in_coords =
16✔
982
        ecalloc(ndim, sizeof(int64_t));
16✔
983
    int64_t* out_coords =
16✔
984
        ecalloc(ndim, sizeof(int64_t));
16✔
985
    for (size_t idx = 0; idx < numel; idx++) {
160✔
986
        // Compute input coordinates from flat idx
987
        size_t rem = idx;
988
        for (int64_t i = 0; i < ndim; i++) {
432✔
989
            in_coords[i] = rem / in_strides[i];
288✔
990
            rem = rem % in_strides[i];
288✔
991
        }
992
        // Permute coordinates
993
        for (int64_t i = 0; i < ndim; i++) {
432✔
994
            out_coords[i] = in_coords[perm[i]];
288✔
995
        }
996
        // Compute output flat index
997
        size_t out_idx = 0;
998
        for (int64_t i = 0; i < ndim; i++) {
432✔
999
            out_idx += out_coords[i] * out_strides[i];
288✔
1000
        }
1001
        // Copy element
1002
        ort_memcpy(
144✔
1003
            (char*)result->data + out_idx * type_size,
144✔
1004
            (char*)input->data + idx * type_size,
144✔
1005
            type_size);
1006
    }
1007
    efree(in_coords);
16✔
1008
    efree(out_coords);
16✔
1009

1010
__php_ort_tensor_transpose_done:
16✔
1011
    object_init_ex(return_value,
16✔
1012
        php_ort_tensor_transient_ce);
1013
    php_ort_tensor_t* rv =
16✔
1014
        php_ort_tensor_fetch(
16✔
1015
            Z_OBJ_P(return_value));
1016
    rv->object = result;
16✔
1017

1018
    if (perm)
16✔
1019
        efree(perm);
16✔
1020
    if (out_shape)
16✔
1021
        efree(out_shape);
16✔
1022
    if (in_strides)
16✔
1023
        efree(in_strides);
16✔
1024
    if (out_strides)
16✔
1025
        efree(out_strides);
16✔
1026
    if (inv_perm)
16✔
1027
        efree(inv_perm);
16✔
1028
    return result;
1029

1030
__php_ort_tensor_transpose_failed:
×
1031
    if (perm)
×
1032
        efree(perm);
×
1033
    if (out_shape)
×
1034
        efree(out_shape);
×
1035
    if (in_strides)
×
1036
        efree(in_strides);
×
1037
    if (out_strides)
×
1038
        efree(out_strides);
×
1039
    if (inv_perm)
×
1040
        efree(inv_perm);
×
1041
    return NULL;
1042
}
1043

1044
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_transpose_arginfo, 0, 0, ORT\\Tensor, 0)
1045
    ZEND_ARG_TYPE_INFO(0, axis, IS_ARRAY, 1)
1046
ZEND_END_ARG_INFO()
1047

1048
PHP_METHOD(ONNX_Tensor, transpose)
16✔
1049
{
1050
    php_ort_tensor_t* ort =
16✔
1051
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
16✔
1052
    zval* axis = NULL;
16✔
1053

1054
    ZEND_PARSE_PARAMETERS_START(0, 1);
16✔
1055
        Z_PARAM_OPTIONAL
16✔
1056
        Z_PARAM_ZVAL(axis)
16✔
1057
    ZEND_PARSE_PARAMETERS_END();
12✔
1058

1059
    php_ort_tensor_transpose(ort->object, axis, return_value);
16✔
1060
}
1061

1062
static zend_always_inline ort_tensor_t* php_ort_tensor_slice(ort_tensor_t* input, zval* start, zval* end, zval* axis, zval* return_value) {
24✔
1063
    // Handle axis parameter - if provided, validate it
1064
    zend_bool has_axis = (axis != NULL);
24✔
1065
    HashTable *axis_ht = has_axis ? Z_ARRVAL_P(axis) : NULL;
48✔
1066

1067
    if (has_axis) {
24✔
1068
        // When axis is provided, start and end should match axis array length
1069
        php_ort_status_flow(
4✔
1070
            (zend_hash_num_elements(Z_ARRVAL_P(start)) != zend_hash_num_elements(axis_ht) ||
1071
             zend_hash_num_elements(Z_ARRVAL_P(end))   != zend_hash_num_elements(axis_ht)), 
1072
            return NULL,
1073
            php_ort_status_tensor_invalidshape_ce,
1074
            "when axis is provided, start and end arrays must have same length as axis array (%zd)",
1075
            zend_hash_num_elements(axis_ht));
1076
    } else {
1077
        // When no axis provided, start and end must match tensor dimensions
1078
        php_ort_status_flow(
20✔
1079
            (zend_hash_num_elements(Z_ARRVAL_P(start)) != input->dimensions ||
1080
             zend_hash_num_elements(Z_ARRVAL_P(end))   != input->dimensions), 
1081
            return NULL,
1082
            php_ort_status_tensor_invalidshape_ce,
1083
            "start and end arrays must have same length as tensor dimensions (%zd)",
1084
            input->dimensions);
1085
    }
1086

1087
    object_init_ex(return_value, php_ort_tensor_transient_ce);
20✔
1088

1089
    php_ort_tensor_t* ort = php_ort_tensor_fetch(Z_OBJ_P(return_value));
20✔
1090
    
1091
    // Allocate new tensor structure
1092
    ort->object = pecalloc(1, sizeof(ort_tensor_t), 0);
20✔
1093
    
1094
    // Set up parent relationship
1095
    ort->object->parent = input;
20✔
1096
    php_ort_atomic_addref(
20✔
1097
        &ort->object->parent->refcount);
1098

1099
    ort->object->refcount = 1;
20✔
1100
    ort->object->owner = PHP_ORT_OWN_ZEND;
20✔
1101
    ort->object->type = input->type;
20✔
1102

1103
    int64_t *starting = pecalloc(input->dimensions, sizeof(int64_t), 0);
20✔
1104
    int64_t *ending = pecalloc(input->dimensions, sizeof(int64_t), 0);
20✔
1105
    int64_t *slicing = pecalloc(input->dimensions, sizeof(int64_t), 0);
20✔
1106

1107
    // Initialize with full range for all dimensions
1108
    for (int64_t idim = 0; idim < input->dimensions; idim++) {
60✔
1109
        starting[idim] = 0;
40✔
1110
        ending[idim]   = input->shape[idim];
40✔
1111
        slicing[idim]  = input->shape[idim];
40✔
1112
    }
1113

1114
    // Process based on whether axis is provided
1115
    if (has_axis) {
20✔
1116
        // Process each axis specification
1117
        zval *zaxis, *zstart, *zend;
4✔
1118
        zend_ulong iaxis = 0;
4✔
1119
        
1120
        ZEND_HASH_FOREACH_VAL(axis_ht, zaxis) {
8✔
1121
            php_ort_status_flow(
4✔
1122
                (Z_TYPE_P(zaxis) != IS_LONG),
1123
                {
1124
                    pefree(starting, 0);
1125
                    pefree(ending, 0);
1126
                    pefree(slicing, 0);
1127
                    return NULL;
1128
                },
1129
                php_ort_status_tensor_invalidshape_ce,
1130
                "axis array must contain integers");
1131
            
1132
            int64_t idim = Z_LVAL_P(zaxis);
4✔
1133

1134
            php_ort_status_flow(
4✔
1135
                (idim < 0 || idim >= input->dimensions),
1136
                {
1137
                    pefree(starting, 0);
1138
                    pefree(ending, 0);
1139
                    pefree(slicing, 0);
1140

1141
                    return NULL;
1142
                },
1143
                php_ort_status_tensor_invalidshape_ce,
1144
                "axis value %zd out of range [0, %zd)", idim, input->dimensions);
1145
            
1146
            // Get corresponding start and end values using the same index
1147
            zstart = zend_hash_index_find(Z_ARRVAL_P(start), iaxis);
4✔
1148
            zend   = zend_hash_index_find(Z_ARRVAL_P(end),   iaxis);
4✔
1149
            
1150
            php_ort_status_flow(
4✔
1151
                (!zstart || !zend || 
1152
                  Z_TYPE_P(zstart) != IS_LONG || 
1153
                  Z_TYPE_P(zend)   != IS_LONG),
1154
                {
1155
                    pefree(starting, 0);
1156
                    pefree(ending, 0);
1157
                    pefree(slicing, 0);
1158

1159
                    return NULL;
1160
                },
1161
                php_ort_status_tensor_invalidshape_ce,
1162
                "start and end values must be integers");
1163

1164
            int64_t istart = Z_LVAL_P(zstart);
4✔
1165
            int64_t iend = Z_LVAL_P(zend);
4✔
1166
            
1167
            // Handle negative indices
1168
            if (istart < 0) istart += input->shape[idim];
4✔
1169
            if (iend < 0)   iend += input->shape[idim];
4✔
1170

1171
            // Validate bounds
1172
            php_ort_status_flow(
4✔
1173
                (istart < 0 || istart >= input->shape[idim] ||
1174
                 iend < 0   || iend    > input->shape[idim] ||
1175
                 istart >= iend),
1176
                {
1177
                    pefree(starting, 0);
1178
                    pefree(ending, 0);
1179
                    pefree(slicing, 0);
1180
                    return NULL;
1181
                },
1182
                php_ort_status_tensor_invalidshape_ce,
1183
                "invalid slice bounds for axis %zd: start=%zd, end=%zd, shape=%zd",
1184
                idim, istart, iend, input->shape[idim]);
1185

1186
            starting[idim] = istart;
4✔
1187
            ending[idim]   = iend;
4✔
1188
            slicing[idim]  = iend - istart;
4✔
1189

1190
            iaxis++;
4✔
1191
        } ZEND_HASH_FOREACH_END();
1192
    } else {
1193
        // No axis specified, use start and end arrays directly for all dimensions
1194
        for (int64_t idim = 0; idim < input->dimensions; idim++) {
32✔
1195
            zval *zstart = zend_hash_index_find(Z_ARRVAL_P(start), idim);
24✔
1196
            zval *zend   = zend_hash_index_find(Z_ARRVAL_P(end),   idim);
24✔
1197

1198
            php_ort_status_flow(
24✔
1199
                (!zstart || !zend ||
1200
                 Z_TYPE_P(zstart) != IS_LONG ||
1201
                 Z_TYPE_P(zend)   != IS_LONG),
1202
                {
1203
                    pefree(starting, 0);
1204
                    pefree(ending, 0);
1205
                    pefree(slicing, 0);
1206
                    return NULL;
1207
                },
1208
                php_ort_status_tensor_invalidshape_ce,
1209
                "start and end values must be integers");
1210

1211
            int64_t istart = Z_LVAL_P(zstart);
24✔
1212
            int64_t iend = Z_LVAL_P(zend);
24✔
1213

1214
            // Handle negative indices
1215
            if (istart < 0) istart += input->shape[idim];
24✔
1216
            if (iend < 0)   iend   += input->shape[idim];
24✔
1217

1218
            php_ort_status_flow(
24✔
1219
                (istart < 0 || istart >= input->shape[idim] ||
1220
                 iend < 0   || iend   >  input->shape[idim] ||
1221
                 istart     >= iend),
1222
                {
1223
                    pefree(starting, 0);
1224
                    pefree(ending, 0);
1225
                    pefree(slicing, 0);
1226

1227
                    return NULL;
1228
                },
1229
                php_ort_status_tensor_invalidshape_ce,
1230
                "invalid slice bounds for dimension %zd: start=%zd, end=%zd, shape=%zd",
1231
                idim, istart, iend, input->shape[idim]);
1232

1233
            starting[idim] = istart;
16✔
1234
            ending[idim]   = iend;
16✔
1235
            slicing[idim]  = iend - istart;
16✔
1236
        }
1237
    }
1238

1239
    // Set up slice tensor metadata
1240
    ort->object->dimensions = input->dimensions;
12✔
1241
    ort->object->shape      = slicing; // Transfer ownership
12✔
1242

1243
    // Calculate number of elements in slice
1244
    ort->object->elements = 1;
12✔
1245
    for (int64_t idim = 0; idim < ort->object->dimensions; idim++) {
36✔
1246
        ort->object->elements *= ort->object->shape[idim];
24✔
1247
    }
1248

1249
    // Set data pointer to offset into parent's data (readonly view)
1250
    ort->object->data = (char*)input->data + 
12✔
1251
        (php_ort_tensor_indexof(input, starting) * 
12✔
1252
            php_ort_tensor_sizeof(input));
12✔
1253

1254
    pefree(starting, 0);
12✔
1255
    pefree(ending, 0);
12✔
1256

1257
    return ort->object;
12✔
1258
}
1259

1260
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_getSlice_arginfo, 0, 2, ORT\\Tensor, 0)
1261
    ZEND_ARG_TYPE_INFO(0, start, IS_ARRAY, 0)
1262
    ZEND_ARG_TYPE_INFO(0, end,   IS_ARRAY, 0)
1263
    ZEND_ARG_TYPE_INFO(0, axis,  IS_ARRAY, 0)
1264
ZEND_END_ARG_INFO()
1265

1266
PHP_METHOD(ONNX_Tensor, getSlice)
24✔
1267
{
1268
    php_ort_tensor_t* ort =
24✔
1269
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
24✔
1270
    zval *start, *end, *axis = NULL;
24✔
1271

1272
    ZEND_PARSE_PARAMETERS_START(2, 3)
24✔
1273
        Z_PARAM_ARRAY(start)
24✔
1274
        Z_PARAM_ARRAY(end)
24✔
1275
        Z_PARAM_OPTIONAL
24✔
1276
        Z_PARAM_ARRAY(axis)
24✔
1277
    ZEND_PARSE_PARAMETERS_END();
×
1278

1279
    php_ort_tensor_slice(ort->object, start, end, axis, return_value);
24✔
1280
}
1281

1282
static inline zend_bool php_ort_tensor_data(ort_tensor_t *tensor, size_t *offset, size_t size, zval *node, size_t depth) {
16,648✔
1283
    // Special case for scalar tensors (0 dimensions)
1284
    if (tensor->dimensions == 0) {
16,648✔
1285
        // This should not be called for scalar tensors
1286
        // The getData method directly handles scalar case
1287
        php_ort_status_flow(
×
1288
            !SUCCESS,
1289
            return 0,
1290
            php_ort_status_tensor_invalidshape_ce,
1291
            "php_ort_tensor_data should not be called for scalar tensors");
1292
    }
1293

1294
    // Validate depth bounds
1295
    php_ort_status_flow(
16,648✔
1296
        (depth >= tensor->dimensions),
1297
        return 0,
1298
        php_ort_status_tensor_invalidshape_ce,
1299
        "depth %zd exceeds tensor dimensions %zd", depth, tensor->dimensions);
1300

1301
    // Validate shape at current depth
1302
    php_ort_status_flow(
16,648✔
1303
        (tensor->shape[depth] <= 0),
1304
        return 0,
1305
        php_ort_status_tensor_invalidshape_ce,
1306
        "invalid shape at dimension %zd: %zd", depth, tensor->shape[depth]);
1307

1308
    // Calculate remaining elements from current offset
1309
    size_t remaining_elements = (*offset < tensor->elements) ? (tensor->elements - *offset) : 0;
16,648✔
1310
    
1311
    // Calculate how many elements we can actually extract at this depth
1312
    int64_t elements_to_extract = tensor->shape[depth];
16,648✔
1313
    if (depth == tensor->dimensions - 1) {
16,648✔
1314
        // At leaf level, limit to remaining elements
1315
        elements_to_extract = (remaining_elements < (size_t)tensor->shape[depth]) ? 
16,024✔
1316
                             (int64_t)remaining_elements : tensor->shape[depth];
16,024✔
1317
    }
1318

1319
    // Initialize array with actual size we'll extract
1320
    array_init_size(node, elements_to_extract);
16,648✔
1321

1322
    if (depth == tensor->dimensions - 1) {
16,648✔
1323
        // Leaf level - extract scalar values
1324
        for (int64_t i = 0; i < elements_to_extract; i++) {
3,272,804✔
1325
            zval val;
3,256,780✔
1326
            
1327
            // Validate offset bounds before accessing data
1328
            php_ort_status_flow(
3,256,780✔
1329
                (*offset >= tensor->elements),
1330
                return 0,
1331
                php_ort_status_tensor_invaliddata_ce,
1332
                "data offset %zd exceeds tensor element count %zd", *offset, tensor->elements);
1333

1334
            void *source = (char *)tensor->data + ((*offset) * size);
3,256,780✔
1335

1336
            switch (tensor->type) {
3,256,780✔
1337
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
392,836✔
1338
                    ZVAL_DOUBLE(&val, *(float *)source);
392,836✔
1339
                    break;
392,836✔
1340

1341
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
495,232✔
1342
                    ZVAL_DOUBLE(&val, *(double *)source);
495,232✔
1343
                    break;
495,232✔
1344

1345
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
353,968✔
1346
                    ZVAL_LONG(&val, *(int8_t *)source);
353,968✔
1347
                    break;
353,968✔
1348

1349
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
335,608✔
1350
                    ZVAL_LONG(&val, *(int16_t *)source);
335,608✔
1351
                    break;
335,608✔
1352

1353
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
335,772✔
1354
                    ZVAL_LONG(&val, *(int32_t *)source);
335,772✔
1355
                    break;
335,772✔
1356

1357
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
336,184✔
1358
                    ZVAL_LONG(&val, *(int64_t *)source);
336,184✔
1359
                    break;
336,184✔
1360

1361
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
341,128✔
1362
                    ZVAL_LONG(&val, *(uint8_t *)source);
341,128✔
1363
                    break;
341,128✔
1364

1365
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
332,952✔
1366
                    ZVAL_LONG(&val, *(uint16_t *)source);
332,952✔
1367
                    break;
332,952✔
1368

1369
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
332,952✔
1370
                    ZVAL_LONG(&val, *(uint32_t *)source);
332,952✔
1371
                    break;
332,952✔
1372

1373
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
1374
                    // UINT64 is not supported - this should not be reached
1375
                    php_ort_status_flow(
×
1376
                        !SUCCESS,
1377
                        {
1378
                            return 0;
1379
                        },
1380
                        php_ort_status_tensor_invalidtype_ce,
1381
                        "UINT64 tensor type is not supported (values exceed PHP integer range)");
1382

1383
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
1384
                    ZVAL_BOOL(&val, *(uint8_t *)source ? 1 : 0);
148✔
1385
                    break;
148✔
1386

1387
                default:
1388
                    php_ort_status_flow(
×
1389
                        !SUCCESS,
1390
                        {
1391
                            return 0;
1392
                        },
1393
                        php_ort_status_tensor_invaliddata_ce,
1394
                        "unsupported tensor data type: %d", tensor->type);
1395
            }
1396

1397
            php_ort_status_flow(
3,256,780✔
1398
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &val) == NULL),
1399
                return 0,
1400
                php_ort_status_tensor_invaliddata_ce,
1401
                "failed to update array at index %zd", i);
1402

1403
            (*offset)++;
3,256,780✔
1404
        }
1405
    } else {
1406
        // Recursive case - process sub-dimensions
1407
        // For non-leaf levels, we need to check if we have enough remaining elements
1408
        // to fill the expected sub-structures
1409
        for (int64_t i = 0; i < elements_to_extract; i++) {
12,372✔
1410
            // Check if we still have elements remaining before processing
1411
            if (*offset >= tensor->elements) {
11,756✔
1412
                break; // No more elements available
1413
            }
1414
            
1415
            zval child;
11,748✔
1416
            
1417
            php_ort_status_flow(
11,748✔
1418
                (!php_ort_tensor_data(tensor, offset, size, &child, depth + 1)),
1419
                return 0,
1420
                php_ort_status_tensor_invaliddata_ce,
1421
                "failed to extract data at dimension %zd, index %zd", depth, i);
1422

1423
            php_ort_status_flow(
11,748✔
1424
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &child) == NULL),
1425
                return 0,
1426
                php_ort_status_tensor_invaliddata_ce,
1427
                "failed to update array at dimension %zd, index %zd", depth, i);
1428
        }
1429
    }
1430

1431
    return 1;
1432
}
1433

1434
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getData_arginfo, 0, 0, IS_ARRAY, 0)
1435
    ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
1436
    ZEND_ARG_TYPE_INFO(0, depth,  IS_LONG, 0)
1437
ZEND_END_ARG_INFO()
1438

1439
PHP_METHOD(ONNX_Tensor, getData)
5,192✔
1440
{
1441
    php_ort_tensor_t* ort =
5,192✔
1442
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
5,192✔
1443
    zend_long offset = 0, depth = 0;
5,192✔
1444

1445
    ZEND_PARSE_PARAMETERS_START(0, 2)
5,192✔
1446
        Z_PARAM_OPTIONAL
5,192✔
1447
        Z_PARAM_LONG(offset)
5,192✔
1448
        Z_PARAM_LONG(depth)
88✔
1449
    ZEND_PARSE_PARAMETERS_END();
×
1450

1451
    php_ort_status_flow(
5,192✔
1452
        (offset < 0 || offset > ort->object->elements),
1453
        return,
1454
        php_ort_status_tensor_invaliddata_ce,
1455
        "offset %zd out of range [0, %zd]",
1456
        offset, ort->object->elements);
1457

1458
    // Special case for scalar tensors (0 dimensions)
1459
    if (ort->object->dimensions == 0) {
5,180✔
1460
        // For scalar tensors, check if depth parameter was provided
1461
        php_ort_status_flow(
260✔
1462
            (ZEND_NUM_ARGS() > 1 && depth != 0),
1463
            return,
1464
            php_ort_status_tensor_invalidshape_ce,
1465
            "depth parameter cannot be used with scalar tensors");
1466

1467
        // Handle offset parameter - for scalar tensors, offset 0 returns the value, any other offset returns empty array
1468
        if (offset > 0) {
256✔
1469
            array_init(return_value);
4✔
1470
            return;
4✔
1471
        }
1472
        
1473
        // For scalar tensors, return a single-element array with the scalar value
1474
        array_init_size(return_value, 1);
252✔
1475
        zval val;
252✔
1476
        
1477
        void *source = ort->object->data;
252✔
1478
        
1479
        switch (ort->object->type) {
252✔
1480
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
52✔
1481
                ZVAL_DOUBLE(&val, *(float *)source);
52✔
1482
                break;
52✔
1483

1484
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
52✔
1485
                ZVAL_DOUBLE(&val, *(double *)source);
52✔
1486
                break;
52✔
1487

1488
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
8✔
1489
                ZVAL_LONG(&val, *(int8_t *)source);
8✔
1490
                break;
8✔
1491

1492
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
8✔
1493
                ZVAL_LONG(&val, *(int16_t *)source);
8✔
1494
                break;
8✔
1495

1496
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
8✔
1497
                ZVAL_LONG(&val, *(int32_t *)source);
8✔
1498
                break;
8✔
1499

1500
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
84✔
1501
                ZVAL_LONG(&val, *(int64_t *)source);
84✔
1502
                break;
84✔
1503

1504
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
8✔
1505
                ZVAL_LONG(&val, *(uint8_t *)source);
8✔
1506
                break;
8✔
1507

1508
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
8✔
1509
                ZVAL_LONG(&val, *(uint16_t *)source);
8✔
1510
                break;
8✔
1511

1512
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
8✔
1513
                ZVAL_LONG(&val, *(uint32_t *)source);
8✔
1514
                break;
8✔
1515

1516
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
1517
                // UINT64 is not supported - this should not be reached
1518
                php_ort_status_flow(
×
1519
                    !SUCCESS,
1520
                    return,
1521
                    php_ort_status_tensor_invalidtype_ce,
1522
                    "UINT64 tensor type is not supported (values exceed PHP integer range)");
1523

1524
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
1525
                ZVAL_BOOL(&val, *(uint8_t *)source ? 1 : 0);
16✔
1526
                break;
16✔
1527

1528
            default:
1529
                php_ort_status_flow(
×
1530
                    !SUCCESS,
1531
                    return,
1532
                    php_ort_status_tensor_invaliddata_ce,
1533
                    "unsupported tensor data type: %d", ort->object->type);
1534
        }
1535
        
1536
        add_index_zval(return_value, 0, &val);
252✔
1537
        return;
252✔
1538
    }
1539

1540
    // Normal case for non-scalar tensors
1541
    php_ort_status_flow(
4,920✔
1542
        (depth < 0 || depth >= ort->object->dimensions),
1543
        return,
1544
        php_ort_status_tensor_invalidshape_ce,
1545
        "depth %zd out of range [0, %zd)",
1546
        depth, ort->object->dimensions);
1547

1548
    size_t offset_size = (size_t)offset;
4,900✔
1549
    php_ort_status_flow(
9,800✔
1550
        (!php_ort_tensor_data(
1551
            ort->object, 
1552
            &offset_size,
1553
            php_ort_tensor_sizeof(ort->object), 
1554
            return_value, 
1555
            (size_t)depth)),
1556
        return,
1557
        php_ort_status_tensor_invaliddata_ce,
1558
        "failed to extract tensor data starting at offset %zd, depth %zd", offset_size, depth);
1559
}
1560

1561
// Robust recursive shape inference with raggedness and type checks
1562
static zend_bool php_ort_infer_shape(zval *data, size_t *shape, size_t max, size_t *dimensions) {
140✔
1563
    size_t dimension = 0;
140✔
1564
    zval *level = data;
140✔
1565
    while (Z_TYPE_P(level) == IS_ARRAY) {
208✔
1566
        php_ort_status_flow(
208✔
1567
            (dimension >= max),
1568
            return 0,
1569
            php_ort_status_tensor_invaliddata_ce,
1570
            "shape exceeds maximum allowed dimensions (%zd)", max);
1571

1572
        size_t len = zend_hash_num_elements(Z_ARRVAL_P(level));
208✔
1573

1574
        php_ort_status_flow(
208✔
1575
            len == 0,
1576
            return 0,
1577
            php_ort_status_tensor_invaliddata_ce,
1578
            "empty array encountered at dimension %zd (ragged or empty tensor)",
1579
            dimension);
1580

1581
        shape[dimension] = len;
200✔
1582

1583
        // Check all elements at this level are arrays or all are scalars
1584
        zend_bool found_array = 0, found_scalar = 0;
200✔
1585
        zval *first = NULL;
200✔
1586
        zend_bool first_is_array = 0;
200✔
1587
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(level), first) {
200✔
1588
            if (Z_TYPE_P(first) == IS_ARRAY) {
200✔
1589
                first_is_array = 1;
76✔
1590
            }
1591
            break;
1592
        } ZEND_HASH_FOREACH_END();
1593

1594
        if (first && first_is_array) {
200✔
1595
            found_array = 1;
1596
        } else {
1597
            found_scalar = 1;
124✔
1598
        }
1599

1600
        size_t expected_len =
400✔
1601
            (first && first_is_array) ?
1602
                zend_hash_num_elements(Z_ARRVAL_P(first)) : 0;
200✔
1603

1604
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(level), zval *sub) {
25,532✔
1605
            if (Z_TYPE_P(sub) == IS_ARRAY) {
25,340✔
1606
                found_array = 1;
324✔
1607
                php_ort_status_flow(
324✔
1608
                    (expected_len != 0 && zend_hash_num_elements(Z_ARRVAL_P(sub)) != expected_len),
1609
                    return 0,
1610
                    php_ort_status_tensor_invaliddata_ce,
1611
                    "ragged array: sub-array at dimension %zd has length %zd, expected %zd",
1612
                    dimension+1,
1613
                    zend_hash_num_elements(Z_ARRVAL_P(sub)),
1614
                    expected_len);
1615
            } else {
1616
                found_scalar = 1;
1617
            }
1618

1619
            php_ort_status_flow(
25,332✔
1620
                (Z_TYPE_P(sub) != IS_ARRAY &&
1621
                Z_TYPE_P(sub) != IS_LONG &&
1622
                Z_TYPE_P(sub) != IS_DOUBLE &&
1623
                !(Z_TYPE_P(sub) == IS_TRUE || Z_TYPE_P(sub) == IS_FALSE)),
1624
                return 0,
1625
                php_ort_status_tensor_invaliddata_ce,
1626
                "unsupported type at dimension %zd: %s",
1627
                dimension+1,
1628
                zend_zval_type_name(sub));
1629

1630
            php_ort_status_flow(
25,332✔
1631
                (found_array && found_scalar),
1632
                return 0,
1633
                php_ort_status_tensor_invaliddata_ce,
1634
                "mixed array/scalar types at dimension %zd (ragged tensor)",
1635
                dimension+1);
1636
        } ZEND_HASH_FOREACH_END();
1637
        if (found_array) {
192✔
1638
            // Go one level deeper
1639
            level = first;
68✔
1640
            dimension++;
68✔
1641
        } else {
1642
            // All scalars at this level, this is the last dimension
1643
            dimension++;
124✔
1644
            break;
124✔
1645
        }
1646
    }
1647
    *dimensions = dimension;
124✔
1648
    return 1;
124✔
1649
}
1650

1651
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_persistent_from_arginfo, 0, 3, ORT\\Tensor, 0)
1652
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
1653
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
1654
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
1655
ZEND_END_ARG_INFO()
1656

1657
PHP_METHOD(ONNX_Tensor_Persistent, from)
28✔
1658
{
1659
    zend_string* name;
28✔
1660
    zval *data;
28✔
1661
    zend_long type;
28✔
1662
    size_t shape[32];
28✔
1663
    size_t dimensions = 0;
28✔
1664

1665
    ZEND_PARSE_PARAMETERS_START(3, 3)
28✔
1666
        Z_PARAM_STR(name)
28✔
1667
        Z_PARAM_ARRAY(data)
28✔
1668
        Z_PARAM_LONG(type)
28✔
1669
    ZEND_PARSE_PARAMETERS_END();
×
1670

1671
    if (!php_ort_infer_shape(data, shape, 32, &dimensions)) {
28✔
1672
        return;
1673
    }
1674

1675
    php_ort_status_flow(
20✔
1676
        (dimensions == 0),
1677
        return,
1678
        php_ort_status_tensor_invaliddata_ce,
1679
        "empty tensor data provided (no dimensions inferred)");
1680

1681
    zval param;
20✔
1682
    array_init_size(&param, dimensions);
20✔
1683
    for (size_t i = 0; i < dimensions; i++) {
56✔
1684
        add_next_index_long(&param, shape[i]);
36✔
1685
    }
1686

1687
    zend_class_entry *scope =
20✔
1688
        zend_get_executed_scope();
20✔
1689
    object_init_ex(return_value, scope);
20✔
1690

1691
    zval params[4];
20✔
1692
    ZVAL_STR(&params[0], name);
20✔
1693
    ZVAL_ARR(&params[1], Z_ARRVAL(param));
20✔
1694
    ZVAL_ARR(&params[2], Z_ARRVAL_P(data));
20✔
1695
    ZVAL_LONG(&params[3], type);
20✔
1696

1697
    zval constructor;
20✔
1698
    ZVAL_STRING(&constructor, "__construct");
20✔
1699
    zval retval;
20✔
1700
    if (SUCCESS == call_user_function(
20✔
1701
            EG(function_table),
1702
            return_value,
1703
            &constructor,
1704
            &retval,
1705
            4,
1706
            params)) {
1707
        zval_ptr_dtor(&retval);
20✔
1708
    } else {
1709
        zval_ptr_dtor(return_value);
×
1710
    }
1711

1712
    zval_ptr_dtor(&constructor);
20✔
1713
    zval_ptr_dtor(&param);
20✔
1714
}
1715

1716
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_transient_from_arginfo, 0, 2, ORT\\Tensor, 0)
1717
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
1718
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
1719
ZEND_END_ARG_INFO()
1720

1721
PHP_METHOD(ONNX_Tensor_Transient, from)
112✔
1722
{
1723
    zval *data;
112✔
1724
    zend_long type;
112✔
1725
    size_t shape[32];
112✔
1726
    size_t dimensions = 0;
112✔
1727

1728
    ZEND_PARSE_PARAMETERS_START(2, 2)
112✔
1729
        Z_PARAM_ARRAY(data)
112✔
1730
        Z_PARAM_LONG(type)
112✔
1731
    ZEND_PARSE_PARAMETERS_END();
×
1732

1733
    if (!php_ort_infer_shape(data, shape, 32, &dimensions)) {
112✔
1734
        return;
1735
    }
1736

1737
    php_ort_status_flow(
104✔
1738
        (dimensions == 0),
1739
        return,
1740
        php_ort_status_tensor_invaliddata_ce,
1741
        "empty tensor data provided (no dimensions inferred)");
1742

1743
    zval param;
104✔
1744
    array_init_size(&param, dimensions);
104✔
1745
    for (size_t i = 0; i < dimensions; i++) {
260✔
1746
        add_next_index_long(&param, shape[i]);
156✔
1747
    }
1748

1749
    zend_class_entry *scope =
104✔
1750
        zend_get_executed_scope();
104✔
1751
    object_init_ex(return_value, scope);
104✔
1752

1753
    zval params[3];
104✔
1754
    ZVAL_ARR(&params[0], Z_ARRVAL(param));
104✔
1755
    ZVAL_ARR(&params[1], Z_ARRVAL_P(data));
104✔
1756
    ZVAL_LONG(&params[2], type);
104✔
1757

1758
    zval constructor;
104✔
1759
    ZVAL_STRING(&constructor, "__construct");
104✔
1760
    zval retval;
104✔
1761
    if (SUCCESS == call_user_function(
104✔
1762
            EG(function_table),
1763
            return_value,
1764
            &constructor,
1765
            &retval,
1766
            3,
1767
            params)) {
1768
        zval_ptr_dtor(&retval);
104✔
1769
    } else {
1770
        zval_ptr_dtor(return_value);
×
1771
    }
1772

1773
    zval_ptr_dtor(&constructor);
104✔
1774
    zval_ptr_dtor(&param);
104✔
1775
}
1776

1777
zend_function_entry php_ort_tensor_interface_methods[] = {
1778
    PHP_ABSTRACT_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo)
1779
    PHP_ABSTRACT_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo)
1780
    PHP_ABSTRACT_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo)
1781
    PHP_ABSTRACT_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo)
1782
    PHP_ABSTRACT_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo)
1783
    PHP_ABSTRACT_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo)
1784
    PHP_ABSTRACT_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo)
1785

1786
    PHP_ABSTRACT_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo)
1787
    PHP_FE_END
1788
};
1789

1790
zend_function_entry php_ort_tensor_persistent_methods[] = {
1791
    PHP_ME(ONNX_Tensor_Persistent, __construct,
1792
        php_ort_tensor_persistent_construct_arginfo, ZEND_ACC_PUBLIC)
1793
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1794
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1795
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1796
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1797
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1798
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1799
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1800
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1801

1802
    PHP_ME(ONNX_Tensor_Persistent, from,
1803
        php_ort_tensor_persistent_from_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)    PHP_FE_END
1804
};
1805

1806
zend_function_entry php_ort_tensor_transient_methods[] = {
1807
    PHP_ME(ONNX_Tensor_Transient, __construct,
1808
        php_ort_tensor_transient_construct_arginfo, ZEND_ACC_PUBLIC)
1809
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1810
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1811
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1812
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1813
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1814
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1815
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1816
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1817

1818
    PHP_ME(ONNX_Tensor_Transient, from,
1819
        php_ort_tensor_transient_from_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1820
    PHP_FE_END
1821
};
1822

1823
zend_object* php_ort_tensor_create(zend_class_entry *type) {
14,984✔
1824
    php_ort_tensor_t *ort = ecalloc(1,
14,984✔
1825
        sizeof(php_ort_tensor_t) + zend_object_properties_size(type));
1826

1827
    zend_object_std_init(&ort->std, type);
14,984✔
1828

1829
    ort->std.handlers = &php_ort_tensor_handlers;
14,984✔
1830
    ort->object       = NULL;
14,984✔
1831

1832
    return &ort->std;
14,984✔
1833
}
1834

1835
static HashTable* php_ort_tensor_debug(zend_object *zo, int *temp) {
52✔
1836
    php_ort_tensor_t *ort = php_ort_tensor_fetch(zo);
52✔
1837
    HashTable *debug;
52✔
1838

1839
    ALLOC_HASHTABLE(debug);
52✔
1840
    zend_hash_init(debug, 3, NULL, ZVAL_PTR_DTOR, 0);
52✔
1841

1842
    if (!ort->object) {
52✔
1843
        goto __php_ort_tensor_debug_return;
8✔
1844
    }
1845

1846
    zval persistent;
44✔
1847

1848
    ZVAL_BOOL(&persistent,
44✔
1849
        ort->object->owner == PHP_ORT_OWN_HEAP);
1850
    zend_hash_str_add(debug, 
44✔
1851
        "persistent", sizeof("persistent")-1, 
1852
        &persistent);
1853

1854
    zval type;
44✔
1855
    
1856
    ZVAL_LONG(&type, ort->object->type);
44✔
1857
    zend_hash_add(debug,
44✔
1858
        ZSTR_KNOWN(ZEND_STR_TYPE), &type);
44✔
1859

1860
    if (ort->object->name) {
44✔
1861
        zval name;
44✔
1862

1863
        ZVAL_STR_COPY(&name, ort->object->name);
44✔
1864
        zend_hash_add(debug,
44✔
1865
            ZSTR_KNOWN(ZEND_STR_NAME), &name);
44✔
1866
    }
1867

1868
    zval shape;
44✔
1869

1870
    array_init(&shape);
44✔
1871
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
120✔
1872
        add_next_index_long(
76✔
1873
            &shape, ort->object->shape[dimension]);
76✔
1874
    }
1875
    zend_hash_str_add(debug,
44✔
1876
        "shape", sizeof("shape")-1, &shape);
1877

1878
__php_ort_tensor_debug_return:
52✔
1879
    *temp = 1;
52✔
1880

1881
    return debug;
52✔
1882
}
1883

1884
static void php_ort_tensor_write(zend_object* object, zval* offset, zval* value) {
4✔
1885
    php_ort_status_flow(!SUCCESS, {
4✔
1886
        return;
1887
    },
1888
    php_ort_status_tensor_invalidaccess_ce,
1889
    "Tensors are immutable, illegal write operation cannot be performed");
1890
}
1891

1892
static zval* php_ort_tensor_read(zend_object* object, zval* offset, int type, zval* rv) {
16✔
1893
    php_ort_tensor_t *ort = php_ort_tensor_fetch(object);
16✔
1894
    zend_long index;
16✔
1895

1896
    if (Z_TYPE_P(offset) != IS_LONG) {
16✔
1897
        php_ort_status_flow(!SUCCESS, {
4✔
1898
                ZVAL_UNDEF(rv);
1899
                return rv;
1900
            },
1901
            php_ort_status_tensor_invalidoffset_ce,
1902
            "Tensor element must be an integer");
1903
        return NULL;
1904
    }
1905

1906
    index = Z_LVAL_P(offset);
12✔
1907

1908
    if (index < 0) {
12✔
1909
        // Negative index means offset from the end
1910

1911
        index += ort->object->elements;
4✔
1912
    }
1913

1914
    php_ort_status_flow(
12✔
1915
        (index >= ort->object->elements),
1916
        {
1917
            ZVAL_UNDEF(rv);
1918
            return rv;
1919
        },
1920
        php_ort_status_tensor_invalidoffset_ce,
1921
        "Tensor element %zd out of range [0, %zd]",
1922
        index, ort->object->elements);
1923

1924
    switch (ort->object->type) {
8✔
1925
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
×
1926
            ZVAL_DOUBLE(rv,
×
1927
                ((float*)ort->object->data)[index]);
1928
        break;
×
1929

1930
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
×
1931
            ZVAL_DOUBLE(rv,
×
1932
                ((double*)ort->object->data)[index]);
1933
            break;
×
1934

1935
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
×
1936
            ZVAL_LONG(rv,
×
1937
                ((int8_t*)ort->object->data)[index]);
1938
            break;
×
1939
        
1940
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
×
1941
            ZVAL_LONG(rv,
×
1942
                ((int16_t*)ort->object->data)[index]);
1943
            break;
×
1944

1945
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
8✔
1946
            ZVAL_LONG(rv,
8✔
1947
                ((int32_t*)ort->object->data)[index]);
1948
            break;
8✔
1949

1950
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
×
1951
            ZVAL_LONG(rv,
×
1952
                ((int64_t*)ort->object->data)[index]);
1953
            break;
×
1954

1955
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
×
1956
            ZVAL_LONG(rv,
×
1957
                ((uint8_t*)ort->object->data)[index]);
1958
            break;
×
1959
        
1960
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
×
1961
            ZVAL_LONG(rv,
×
1962
                ((uint16_t*)ort->object->data)[index]);
1963
            break;
×
1964
        
1965
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
×
1966
            ZVAL_LONG(rv,
×
1967
                ((uint32_t*)ort->object->data)[index]);
1968
            break;
×
1969

1970
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
×
1971
            ZVAL_BOOL(rv,
×
1972
                ((zend_bool*)ort->object->data)[index] ? 1 : 0);
1973
            break;
×
1974

1975
        default: {
1976
            /* unreachable */
1977
        }
1978
            
1979
    }
1980
    return rv;
1981
}
1982

1983
static zend_result php_ort_tensor_count(zend_object* object, zend_long *count) {
4✔
1984
    php_ort_tensor_t *ort =
4✔
1985
        php_ort_tensor_fetch(object);
4✔
1986
    *count =
4✔
1987
        ort->object->elements;
4✔
1988
    return SUCCESS;
4✔
1989
}
1990

1991
void php_ort_tensor_destroy(zend_object *o) {
14,984✔
1992
    php_ort_tensor_t *ort =
14,984✔
1993
        php_ort_tensor_fetch(o);
14,984✔
1994

1995
    ort_tensor_release(ort->object);
14,984✔
1996

1997
    zend_object_std_dtor(o);
14,984✔
1998
}
14,984✔
1999

2000
PHP_MINIT_FUNCTION(ORT_TENSOR)
840✔
2001
{
2002
    zend_class_entry ce;
840✔
2003

2004
#ifdef ZTS
2005
    php_ort_tensor_mutex = tsrm_mutex_alloc();
840✔
2006
#endif
2007

2008
    zend_hash_init(&php_ort_tensors, 16, NULL, php_ort_tensor_del, 1);
840✔
2009

2010
    // Setup shared handlers for all tensor types
2011
    memcpy(&php_ort_tensor_handlers,
840✔
2012
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
2013

2014
    php_ort_tensor_handlers.offset = XtOffsetOf(php_ort_tensor_t, std);
840✔
2015
    php_ort_tensor_handlers.get_debug_info = php_ort_tensor_debug;
840✔
2016
    php_ort_tensor_handlers.free_obj = php_ort_tensor_destroy;
840✔
2017
    php_ort_tensor_handlers.read_dimension = php_ort_tensor_read;
840✔
2018
    php_ort_tensor_handlers.write_dimension = php_ort_tensor_write;
840✔
2019
    php_ort_tensor_handlers.count_elements = php_ort_tensor_count;
840✔
2020
    php_ort_tensor_handlers.clone_obj = NULL;
840✔
2021

2022
    // Register the interface
2023
    INIT_NS_CLASS_ENTRY(ce, "ORT", "Tensor", php_ort_tensor_interface_methods);
840✔
2024
    php_ort_tensor_interface_ce = zend_register_internal_interface(&ce);
840✔
2025

2026
    // Register persistent tensor class
2027
    INIT_NS_CLASS_ENTRY(ce, "ORT\\Tensor", "Persistent", php_ort_tensor_persistent_methods);
840✔
2028
    php_ort_tensor_persistent_ce = zend_register_internal_class(&ce);
840✔
2029
    php_ort_tensor_persistent_ce->create_object = php_ort_tensor_create;
840✔
2030
    zend_class_implements(php_ort_tensor_persistent_ce, 1, php_ort_tensor_interface_ce);
840✔
2031

2032
    // Register transient tensor class
2033
    INIT_NS_CLASS_ENTRY(ce, "ORT\\Tensor", "Transient", php_ort_tensor_transient_methods);
840✔
2034
    php_ort_tensor_transient_ce = zend_register_internal_class(&ce);
840✔
2035
    php_ort_tensor_transient_ce->create_object = php_ort_tensor_create;
840✔
2036
    zend_class_implements(php_ort_tensor_transient_ce, 1, php_ort_tensor_interface_ce);
840✔
2037

2038
#ifdef ZEND_ACC_NOT_SERIALIZABLE
2039
    php_ort_tensor_persistent_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
840✔
2040
    php_ort_tensor_transient_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
840✔
2041
#else
2042
    php_ort_tensor_persistent_ce->serialize = zend_class_serialize_deny;
2043
    php_ort_tensor_persistent_ce->unserialize = zend_class_unserialize_deny;
2044
    php_ort_tensor_transient_ce->serialize = zend_class_serialize_deny;
2045
    php_ort_tensor_transient_ce->unserialize = zend_class_unserialize_deny;
2046
#endif
2047

2048
    // Register constants on the interface
2049
    zend_declare_class_constant_long(
840✔
2050
        php_ort_tensor_interface_ce,
2051
        "UNDEFINED", sizeof("UNDEFINED")-1,
2052
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED);
2053

2054
    zend_declare_class_constant_long(
840✔
2055
        php_ort_tensor_interface_ce,
2056
        "FLOAT", sizeof("FLOAT")-1,
2057
        ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
2058

2059
    zend_declare_class_constant_long(
840✔
2060
        php_ort_tensor_interface_ce,
2061
        "DOUBLE", sizeof("DOUBLE")-1,
2062
        ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE);
2063

2064
    zend_declare_class_constant_long(
840✔
2065
        php_ort_tensor_interface_ce,
2066
        "UINT8", sizeof("UINT8")-1,
2067
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8);
2068

2069
    zend_declare_class_constant_long(
840✔
2070
        php_ort_tensor_interface_ce,
2071
        "INT8", sizeof("INT8")-1,
2072
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8);
2073

2074
    zend_declare_class_constant_long(
840✔
2075
        php_ort_tensor_interface_ce,
2076
        "UINT16", sizeof("UINT16")-1,
2077
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16);
2078

2079
    zend_declare_class_constant_long(
840✔
2080
        php_ort_tensor_interface_ce,
2081
        "INT16", sizeof("INT16")-1,
2082
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16);
2083

2084
    zend_declare_class_constant_long(
840✔
2085
        php_ort_tensor_interface_ce,
2086
        "UINT32", sizeof("UINT32")-1,
2087
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32);
2088

2089
    zend_declare_class_constant_long(
840✔
2090
        php_ort_tensor_interface_ce,
2091
        "INT32", sizeof("INT32")-1,
2092
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32);
2093

2094
    zend_declare_class_constant_long(
840✔
2095
        php_ort_tensor_interface_ce,
2096
        "INT64", sizeof("INT64")-1,
2097
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64);
2098

2099
    zend_declare_class_constant_long(
840✔
2100
        php_ort_tensor_interface_ce,
2101
        "BOOL", sizeof("BOOL")-1,
2102
        ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL);
2103

2104
    return SUCCESS;
840✔
2105
}
2106

2107
PHP_MSHUTDOWN_FUNCTION(ORT_TENSOR)
840✔
2108
{
2109
    zend_hash_destroy(&php_ort_tensors);
840✔
2110

2111
#ifdef ZTS
2112
    tsrm_mutex_free(php_ort_tensor_mutex);
840✔
2113
#endif
2114

2115
    return SUCCESS;
840✔
2116
}
2117

2118
PHP_RINIT_FUNCTION(ORT_TENSOR)
840✔
2119
{
2120
    return SUCCESS;
840✔
2121
}
2122

2123
PHP_RSHUTDOWN_FUNCTION(ORT_TENSOR)
840✔
2124
{
2125
    return SUCCESS;
840✔
2126
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc