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

krakjoe / ort / 16541915164

26 Jul 2025 04:52PM UTC coverage: 91.549% (+0.02%) from 91.525%
16541915164

push

github

krakjoe
improve some errors during shape inference, zero mem from alloc()

22 of 22 new or added lines in 4 files covered. (100.0%)

1 existing line in 1 file now uncovered.

3683 of 4023 relevant lines covered (91.55%)

139349.42 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) {
7,984,848✔
39
    if (Z_TYPE_P(node) == IS_OBJECT &&
7,984,848✔
40
            instanceof_function(
7,984,848✔
41
                Z_OBJCE_P(node), php_ort_generator_ce)) {
×
42
        return 1;
×
43
    }
44

45
    if (depth == rank) {
7,984,848✔
46
        // Leaf node — must match scalar type
47
        switch (type) {
7,892,640✔
48
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
49
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
50
                php_ort_status_flow(
1,878,000✔
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(
5,991,056✔
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:
UNCOV
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(
23,536✔
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,
48✔
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(
92,208✔
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);
92,208✔
107
    php_ort_status_flow(
92,208✔
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(
92,208✔
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++) {
8,043,856✔
123
        zval *child = zend_hash_index_find(ht, i);
7,951,936✔
124
        php_ort_status_flow(
7,951,936✔
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(
7,951,936✔
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) {
33,728✔
142
    // Skip generator validation
143
    if (Z_TYPE_P(data) == IS_OBJECT && 
33,728✔
144
            instanceof_function(
34,208✔
145
                Z_OBJCE_P(data), php_ort_generator_ce)) {
480✔
146
        return 1;
480✔
147
    }
148

149
    zend_long dimensions[16];
33,248✔
150
    zend_long rank = 0;
33,248✔
151
    
152
    rank = zend_hash_num_elements(Z_ARRVAL_P(shape));
33,248✔
153
    
154
    // Handle scalar tensor case (rank 0, empty shape array)
155
    if (rank == 0) {
33,248✔
156
        // For scalar tensors, data must be a single-element array
157
        if (Z_TYPE_P(data) != IS_ARRAY) {
224✔
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) {
224✔
168
            php_ort_status_flow(!SUCCESS,
32✔
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);
192✔
178
        if (!scalar) {
192✔
179
            return 0;
180
        }
181
        
182
        // Validate the scalar element type
183
        switch (type) {
192✔
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;
112✔
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;
48✔
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;
32✔
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(
33,024✔
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;
33,024✔
224
    zval *next;
33,024✔
225
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), next) {
74,128✔
226
        php_ort_status_flow(
41,200✔
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(
41,104✔
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);
41,088✔
240
    } ZEND_HASH_FOREACH_END();
241

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

245
void php_ort_tensor_store(ONNXTensorElementDataType type, void* target, zval* node) {
7,931,952✔
246
    // Enter into generator objects
247
    if (Z_TYPE_P(node) == IS_OBJECT && 
7,931,952✔
248
            instanceof_function(
7,951,648✔
249
                Z_OBJCE_P(node), php_ort_generator_ce)) {
19,696✔
250
        php_ort_generator_invoke(
19,696✔
251
            node, type, target);
252
        return;
19,696✔
253
    }
254

255
    switch (type) {
7,912,256✔
256
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
257
            *(float *)target = Z_TYPE_P(node) == IS_DOUBLE
1,009,696✔
258
                ? (float)Z_DVAL_P(node)
138,592✔
259
                : (float)Z_LVAL_P(node);
1,009,696✔
260
            break;
1,009,696✔
261

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

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

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

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

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

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

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

292
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
846,816✔
293
            *(uint32_t *)target = (uint32_t)Z_LVAL_P(node);
846,816✔
294
            break;
846,816✔
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;
23,552✔
305
            break;
23,552✔
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) {
7,974,208✔
316
    // Special case for generators
317
    if (Z_TYPE_P(node) == IS_OBJECT &&
7,974,208✔
318
            instanceof_function(
15,948,640✔
319
                Z_OBJCE_P(node), php_ort_generator_ce)) {
22,528✔
320
        return node;
321
    }
322

323
    // Special case for scalar tensor (0 dimensions)
324
    if (tensor->dimensions == 0) {
7,951,680✔
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) {
192✔
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);
192✔
332
    }
333

334
    if (Z_TYPE_P(node) != IS_ARRAY) {
7,951,488✔
335
        return NULL;
336
    }
337

338
    return zend_hash_index_find(Z_ARRVAL_P(node), idx);
7,951,488✔
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) {
8,007,408✔
342
    // Scalar tensors have 0 dimensions
343
    if (tensor->dimensions == 0) {        
8,007,408✔
344
        // Get the scalar value (first element of the array)
345
        zval *scalar = php_ort_tensor_flatten_next(tensor, node, 0);
224✔
346
        if (!scalar) {
224✔
347
            return 0;
×
348
        }
349

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

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

359
    if (depth == tensor->dimensions) {
8,007,184✔
360
        void *target = (char *)tensor->data + ((*offset) * size);
7,912,032✔
361

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

369
    for (int64_t i = 0; i < tensor->shape[depth]; i++) {
8,069,136✔
370
        zval *child = php_ort_tensor_flatten_next(tensor, node, i);
7,973,984✔
371
        if (!php_ort_tensor_flatten(tensor, offset, size, child, depth + 1)) {
7,973,984✔
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) {
1,360✔
380
    size_t i = 0, offset = 0;
1,360✔
381

382
    tensor->name       = php_ort_string_copy(name);
2,720✔
383
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
1,360✔
384
    tensor->type       = type;
1,360✔
385
    tensor->owner      = PHP_ORT_OWN_HEAP;
1,360✔
386

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

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

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

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

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

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

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

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

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

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

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

451
    if (tensor->shape) {
59,040✔
452
        pefree(tensor->shape, persistent);
57,936✔
453
    }
454

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

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

469
    if (tensor->name && !persistent) {
59,040✔
470
        zend_string_free(tensor->name);
25,472✔
471
    }
472

473
    if (tensor->parent) {
59,040✔
474
        ort_tensor_release(tensor->parent);
80✔
475
    }
476

477
    pefree(tensor, persistent);
59,040✔
478
}
59,040✔
479

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

485
    if (php_ort_atomic_delref(&tensor->refcount) == 0){
60,512✔
486
        ort_tensor_free(tensor);
59,040✔
487
    }
488
}
489

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

496
static zend_bool php_ort_tensor_construct_persistent(ort_tensor_t *tensor, zend_string *name, zval *shape, zval *data, ONNXTensorElementDataType type){
1,520✔
497
    if (!php_ort_tensor_validate(shape, name, data, type)) {
3,040✔
498
        return 0;
160✔
499
    }
500

501
    if (!php_ort_tensor_allocate_persistent(tensor, name, shape, data, type)) {
2,720✔
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){
32,208✔
509
    if (!php_ort_tensor_validate(shape, NULL, data, type)) {
64,416✔
510
        return 0;
144✔
511
    }
512

513
    if (!php_ort_tensor_allocate_transient(tensor, shape, data, type)) {
64,128✔
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) {
522
    OrtMemoryInfo* mi;
523
    OrtValue* value = NULL;
524
    OrtStatus* status;
525

526
    php_ort_status_flow(
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(
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);
557

558
    return value;
559
}
560

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

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

568
    OrtTensorTypeAndShapeInfo* otsi;
569

570
    php_ort_status_flow(
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(
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(
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) {
601
        tensor->shape = pecalloc(tensor->dimensions, sizeof(int64_t), 0);
602

603
        php_ort_status_flow(
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(
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(
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);
638

639
    return tensor;
640
}
641
#endif
642

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

652
    for (int64_t i = tensor->dimensions - 1; i >= 0; i--) {
144✔
653
        index += 
96✔
654
            coords[i] * stride;
96✔
655
        stride *= tensor->shape[i];
96✔
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)
1,584✔
669
{
670
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
1,584✔
671

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

677
    ZEND_PARSE_PARAMETERS_START(1, 4);
1,584✔
678
        Z_PARAM_STR(name)
3,168✔
679
        Z_PARAM_OPTIONAL
1,584✔
680
        Z_PARAM_ARRAY(shape)
3,104✔
681
        Z_PARAM_ZVAL(data)
1,520✔
682
        Z_PARAM_LONG(type)
3,024✔
683
    ZEND_PARSE_PARAMETERS_END();
1,584✔
684

685
#ifdef ZTS
686
    php_ort_status_flow(
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))) {
3,168✔
694
#ifdef ZTS
695
        php_ort_status_flow(
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(
1,552✔
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 = 
1,520✔
720
            pecalloc(1, sizeof(ort_tensor_t), 1);
1,520✔
721

722
        tensor->refcount = 1;
1,520✔
723

724
        if (!php_ort_tensor_construct_persistent(tensor, name, shape, data, type)) {
1,520✔
725
#ifdef ZTS
726
            php_ort_status_flow(
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(
1,360✔
736
            &php_ort_tensors,
737
            tensor->name,
738
            tensor);
739

740
        ort->object->refcount++;
1,360✔
741
    } else {
742
        php_ort_atomic_addref(&ort->object->refcount);
32✔
743
    }
744

745
#ifdef ZTS
746
    php_ort_status_flow(
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)
32,208✔
761
{
762
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
32,208✔
763

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

768
    ZEND_PARSE_PARAMETERS_START(2, 3);
32,208✔
769
        Z_PARAM_ARRAY(shape)
64,416✔
770
        Z_PARAM_ZVAL(data)
32,208✔
771
        Z_PARAM_OPTIONAL
32,208✔
772
        Z_PARAM_LONG(type)
64,384✔
773
    ZEND_PARSE_PARAMETERS_END();
32,208✔
774

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

778
    if (!php_ort_tensor_construct_transient(tensor, shape, data, type)) {
32,208✔
779
        efree(tensor);
144✔
780
        return;
144✔
781
    }
782

783
    ort->object = tensor;
32,064✔
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)
16✔
790
{
791
    php_ort_tensor_t* ort =
16✔
792
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
16✔
793

794
    ZEND_PARSE_PARAMETERS_NONE();
16✔
795

796
    RETURN_BOOL(ort->object->owner == PHP_ORT_OWN_HEAP);
16✔
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)
48✔
803
{
804
    php_ort_tensor_t* ort =
48✔
805
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
48✔
806
    
807
    ZEND_PARSE_PARAMETERS_NONE();
48✔
808

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

813
    RETURN_STR_COPY(ort->object->name);
48✔
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)
880✔
820
{
821
    php_ort_tensor_t* ort =
880✔
822
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
880✔
823

824
    ZEND_PARSE_PARAMETERS_NONE();
880✔
825

826
    RETURN_LONG(ort->object->type);
880✔
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)
17,744✔
833
{
834
    php_ort_tensor_t* ort =
17,744✔
835
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
17,744✔
836

837
    ZEND_PARSE_PARAMETERS_NONE();
17,744✔
838

839
    /* @todo(krakjoe) this performs terribly ... */
840
    RETURN_STRING(php_ort_type_name(ort->object->type));
48,512✔
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)
18,352✔
847
{
848
    php_ort_tensor_t* ort =
18,352✔
849
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
18,352✔
850

851
    ZEND_PARSE_PARAMETERS_NONE();
18,352✔
852

853
    array_init(return_value);
18,352✔
854
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
37,984✔
855
        add_next_index_long(
19,632✔
856
            return_value, ort->object->shape[dimension]);
19,632✔
857
    }
858
}
859

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

871
    // Argument validation and axes parsing
872
    if (ndim == 0) {
64✔
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));
64✔
887
    out_shape = ecalloc(ndim, sizeof(int64_t));
64✔
888
    in_strides = ecalloc(ndim, sizeof(int64_t));
64✔
889
    out_strides = ecalloc(ndim, sizeof(int64_t));
64✔
890
    inv_perm = ecalloc(ndim, sizeof(int64_t));
64✔
891

892
    // Parse axes argument
893
    if (axis) {
64✔
894
        php_ort_status_flow(
48✔
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));
48✔
904
        int64_t i = 0;
48✔
905
        zval* zv;
48✔
906
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(axis), zv) {
144✔
907
            php_ort_status_flow(
96✔
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);
96✔
917
            if (ax < 0)
96✔
918
                ax += ndim;
32✔
919
            
920
            php_ort_status_flow(
96✔
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(
96✔
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;
96✔
939
            perm[i++] = ax;
96✔
940
        } ZEND_HASH_FOREACH_END();
941
        efree(seen);
48✔
942
    } else {
943
        // Default: reverse axes
944
        for (int64_t i = 0; i < ndim; i++) {
48✔
945
            perm[i] = ndim - 1 - i;
32✔
946
        }
947
    }
948

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

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

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

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

1018
    if (perm)
64✔
1019
        efree(perm);
64✔
1020
    if (out_shape)
64✔
1021
        efree(out_shape);
64✔
1022
    if (in_strides)
64✔
1023
        efree(in_strides);
64✔
1024
    if (out_strides)
64✔
1025
        efree(out_strides);
64✔
1026
    if (inv_perm)
64✔
1027
        efree(inv_perm);
64✔
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)
64✔
1049
{
1050
    php_ort_tensor_t* ort =
64✔
1051
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
64✔
1052
    zval* axis = NULL;
64✔
1053

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

1059
    php_ort_tensor_transpose(ort->object, axis, return_value);
64✔
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) {
96✔
1063
    // Handle axis parameter - if provided, validate it
1064
    zend_bool has_axis = (axis != NULL);
96✔
1065
    HashTable *axis_ht = has_axis ? Z_ARRVAL_P(axis) : NULL;
192✔
1066

1067
    if (has_axis) {
96✔
1068
        // When axis is provided, start and end should match axis array length
1069
        php_ort_status_flow(
16✔
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(
80✔
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);
80✔
1088

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

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

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

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

1114
    // Process based on whether axis is provided
1115
    if (has_axis) {
80✔
1116
        // Process each axis specification
1117
        zval *zaxis, *zstart, *zend;
16✔
1118
        zend_ulong iaxis = 0;
16✔
1119
        
1120
        ZEND_HASH_FOREACH_VAL(axis_ht, zaxis) {
32✔
1121
            php_ort_status_flow(
16✔
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);
16✔
1133

1134
            php_ort_status_flow(
16✔
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);
16✔
1148
            zend   = zend_hash_index_find(Z_ARRVAL_P(end),   iaxis);
16✔
1149
            
1150
            php_ort_status_flow(
16✔
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);
16✔
1165
            int64_t iend = Z_LVAL_P(zend);
16✔
1166
            
1167
            // Handle negative indices
1168
            if (istart < 0) istart += input->shape[idim];
16✔
1169
            if (iend < 0)   iend += input->shape[idim];
16✔
1170

1171
            // Validate bounds
1172
            php_ort_status_flow(
16✔
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;
16✔
1187
            ending[idim]   = iend;
16✔
1188
            slicing[idim]  = iend - istart;
16✔
1189

1190
            iaxis++;
16✔
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++) {
128✔
1195
            zval *zstart = zend_hash_index_find(Z_ARRVAL_P(start), idim);
96✔
1196
            zval *zend   = zend_hash_index_find(Z_ARRVAL_P(end),   idim);
96✔
1197

1198
            php_ort_status_flow(
96✔
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);
96✔
1212
            int64_t iend = Z_LVAL_P(zend);
96✔
1213

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

1218
            php_ort_status_flow(
96✔
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;
64✔
1234
            ending[idim]   = iend;
64✔
1235
            slicing[idim]  = iend - istart;
64✔
1236
        }
1237
    }
1238

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

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

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

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

1257
    return ort->object;
48✔
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)
96✔
1267
{
1268
    php_ort_tensor_t* ort =
96✔
1269
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
96✔
1270
    zval *start, *end, *axis = NULL;
96✔
1271

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

1279
    php_ort_tensor_slice(ort->object, start, end, axis, return_value);
96✔
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) {
66,432✔
1283
    // Special case for scalar tensors (0 dimensions)
1284
    if (tensor->dimensions == 0) {
66,432✔
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(
66,432✔
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(
66,432✔
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;
66,432✔
1310
    
1311
    // Calculate how many elements we can actually extract at this depth
1312
    int64_t elements_to_extract = tensor->shape[depth];
66,432✔
1313
    if (depth == tensor->dimensions - 1) {
66,432✔
1314
        // At leaf level, limit to remaining elements
1315
        elements_to_extract = (remaining_elements < (size_t)tensor->shape[depth]) ? 
64,000✔
1316
                             (int64_t)remaining_elements : tensor->shape[depth];
64,000✔
1317
    }
1318

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

1322
    if (depth == tensor->dimensions - 1) {
66,432✔
1323
        // Leaf level - extract scalar values
1324
        for (int64_t i = 0; i < elements_to_extract; i++) {
13,091,024✔
1325
            zval val;
13,027,024✔
1326
            
1327
            // Validate offset bounds before accessing data
1328
            php_ort_status_flow(
13,027,024✔
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);
13,027,024✔
1335

1336
            switch (tensor->type) {
13,027,024✔
1337
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
1,571,248✔
1338
                    ZVAL_DOUBLE(&val, *(float *)source);
1,571,248✔
1339
                    break;
1,571,248✔
1340

1341
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
1,980,928✔
1342
                    ZVAL_DOUBLE(&val, *(double *)source);
1,980,928✔
1343
                    break;
1,980,928✔
1344

1345
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
1,415,872✔
1346
                    ZVAL_LONG(&val, *(int8_t *)source);
1,415,872✔
1347
                    break;
1,415,872✔
1348

1349
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
1,342,432✔
1350
                    ZVAL_LONG(&val, *(int16_t *)source);
1,342,432✔
1351
                    break;
1,342,432✔
1352

1353
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
1,343,088✔
1354
                    ZVAL_LONG(&val, *(int32_t *)source);
1,343,088✔
1355
                    break;
1,343,088✔
1356

1357
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
1,344,736✔
1358
                    ZVAL_LONG(&val, *(int64_t *)source);
1,344,736✔
1359
                    break;
1,344,736✔
1360

1361
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
1,364,512✔
1362
                    ZVAL_LONG(&val, *(uint8_t *)source);
1,364,512✔
1363
                    break;
1,364,512✔
1364

1365
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
1,331,808✔
1366
                    ZVAL_LONG(&val, *(uint16_t *)source);
1,331,808✔
1367
                    break;
1,331,808✔
1368

1369
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
1,331,808✔
1370
                    ZVAL_LONG(&val, *(uint32_t *)source);
1,331,808✔
1371
                    break;
1,331,808✔
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);
592✔
1385
                    break;
592✔
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(
13,027,024✔
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)++;
13,027,024✔
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++) {
49,280✔
1410
            // Check if we still have elements remaining before processing
1411
            if (*offset >= tensor->elements) {
46,880✔
1412
                break; // No more elements available
1413
            }
1414
            
1415
            zval child;
46,848✔
1416
            
1417
            php_ort_status_flow(
46,848✔
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(
46,848✔
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)
20,752✔
1440
{
1441
    php_ort_tensor_t* ort =
20,752✔
1442
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
20,752✔
1443
    zend_long offset = 0, depth = 0;
20,752✔
1444

1445
    ZEND_PARSE_PARAMETERS_START(0, 2)
20,752✔
1446
        Z_PARAM_OPTIONAL
20,752✔
1447
        Z_PARAM_LONG(offset)
21,104✔
1448
        Z_PARAM_LONG(depth)
576✔
1449
    ZEND_PARSE_PARAMETERS_END();
20,752✔
1450

1451
    php_ort_status_flow(
20,752✔
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) {
20,704✔
1460
        // For scalar tensors, check if depth parameter was provided
1461
        php_ort_status_flow(
1,040✔
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) {
1,024✔
1469
            array_init(return_value);
16✔
1470
            return;
16✔
1471
        }
1472
        
1473
        // For scalar tensors, return a single-element array with the scalar value
1474
        array_init_size(return_value, 1);
1,008✔
1475
        zval val;
1,008✔
1476
        
1477
        void *source = ort->object->data;
1,008✔
1478
        
1479
        switch (ort->object->type) {
1,008✔
1480
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
208✔
1481
                ZVAL_DOUBLE(&val, *(float *)source);
208✔
1482
                break;
208✔
1483

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

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

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

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

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

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

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

1512
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
32✔
1513
                ZVAL_LONG(&val, *(uint32_t *)source);
32✔
1514
                break;
32✔
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);
64✔
1526
                break;
64✔
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);
1,008✔
1537
        return;
1,008✔
1538
    }
1539

1540
    // Normal case for non-scalar tensors
1541
    php_ort_status_flow(
19,664✔
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
    php_ort_status_flow(
39,168✔
1549
        (!php_ort_tensor_data(
1550
            ort->object, 
1551
            &offset,
1552
            php_ort_tensor_sizeof(ort->object), 
1553
            return_value, 
1554
            (size_t)depth)),
1555
        return,
1556
        php_ort_status_tensor_invaliddata_ce,
1557
        "failed to extract tensor data starting at offset %zd, depth %zd", offset, depth);
1558
}
1559

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

1571
        size_t len = zend_hash_num_elements(Z_ARRVAL_P(level));
832✔
1572

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

1580
        shape[dimension] = len;
800✔
1581

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

1593
        if (first && first_is_array) {
800✔
1594
            found_array = 1;
1595
        } else {
1596
            found_scalar = 1;
496✔
1597
        }
1598

1599
        size_t expected_len =
1,600✔
1600
            (first && first_is_array) ?
1601
                zend_hash_num_elements(Z_ARRVAL_P(first)) : 0;
800✔
1602

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

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

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

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

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

1664
    ZEND_PARSE_PARAMETERS_START(3, 3)
112✔
1665
        Z_PARAM_STR(name)
224✔
1666
        Z_PARAM_ARRAY(data)
224✔
1667
        Z_PARAM_LONG(type)
224✔
1668
    ZEND_PARSE_PARAMETERS_END();
112✔
1669

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

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

1680
    zval param;
80✔
1681
    array_init_size(&param, dimensions);
80✔
1682
    for (size_t i = 0; i < dimensions; i++) {
224✔
1683
        add_next_index_long(&param, shape[i]);
144✔
1684
    }
1685

1686
    zend_class_entry *scope =
80✔
1687
        zend_get_executed_scope();
80✔
1688
    object_init_ex(return_value, scope);
80✔
1689

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

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

1711
    zval_ptr_dtor(&constructor);
80✔
1712
    zval_ptr_dtor(&param);
80✔
1713
}
1714

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

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

1727
    ZEND_PARSE_PARAMETERS_START(2, 2)
448✔
1728
        Z_PARAM_ARRAY(data)
896✔
1729
        Z_PARAM_LONG(type)
896✔
1730
    ZEND_PARSE_PARAMETERS_END();
448✔
1731

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

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

1742
    zval param;
416✔
1743
    array_init_size(&param, dimensions);
416✔
1744
    for (size_t i = 0; i < dimensions; i++) {
1,040✔
1745
        add_next_index_long(&param, shape[i]);
624✔
1746
    }
1747

1748
    zend_class_entry *scope =
416✔
1749
        zend_get_executed_scope();
416✔
1750
    object_init_ex(return_value, scope);
416✔
1751

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

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

1772
    zval_ptr_dtor(&constructor);
416✔
1773
    zval_ptr_dtor(&param);
416✔
1774
}
1775

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

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

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

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

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

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

1822
zend_object* php_ort_tensor_create(zend_class_entry *type) {
59,888✔
1823
    php_ort_tensor_t *ort = ecalloc(1,
59,888✔
1824
        sizeof(php_ort_tensor_t) + zend_object_properties_size(type));
1825

1826
    zend_object_std_init(&ort->std, type);
59,888✔
1827

1828
    ort->std.handlers = &php_ort_tensor_handlers;
59,888✔
1829
    ort->object       = NULL;
59,888✔
1830

1831
    return &ort->std;
59,888✔
1832
}
1833

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

1838
    ALLOC_HASHTABLE(debug);
208✔
1839
    zend_hash_init(debug, 3, NULL, ZVAL_PTR_DTOR, 0);
208✔
1840

1841
    if (!ort->object) {
208✔
1842
        goto __php_ort_tensor_debug_return;
32✔
1843
    }
1844

1845
    zval persistent;
176✔
1846

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

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

1859
    if (ort->object->name) {
176✔
1860
        zval name;
176✔
1861

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

1867
    zval shape;
176✔
1868

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

1877
__php_ort_tensor_debug_return:
208✔
1878
    *temp = 1;
208✔
1879

1880
    return debug;
208✔
1881
}
1882

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

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

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

1905
    index = Z_LVAL_P(offset);
48✔
1906

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

1910
        index += ort->object->elements;
16✔
1911
    }
1912

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

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

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

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

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

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

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

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

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

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

1990
void php_ort_tensor_destroy(zend_object *o) {
59,888✔
1991
    php_ort_tensor_t *ort =
59,888✔
1992
        php_ort_tensor_fetch(o);
59,888✔
1993

1994
    ort_tensor_release(ort->object);
59,888✔
1995

1996
    zend_object_std_dtor(o);
59,888✔
1997
}
59,888✔
1998

1999
PHP_MINIT_FUNCTION(ORT_TENSOR)
2,784✔
2000
{
2001
    zend_class_entry ce;
2,784✔
2002

2003
#ifdef ZTS
2004
    php_ort_tensor_mutex = tsrm_mutex_alloc();
2005
#endif
2006

2007
    zend_hash_init(&php_ort_tensors, 16, NULL, php_ort_tensor_del, 1);
2,784✔
2008

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

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

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

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

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

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

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

2053
    zend_declare_class_constant_long(
2,784✔
2054
        php_ort_tensor_interface_ce,
2055
        "FLOAT", sizeof("FLOAT")-1,
2056
        ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
2057

2058
    zend_declare_class_constant_long(
2,784✔
2059
        php_ort_tensor_interface_ce,
2060
        "DOUBLE", sizeof("DOUBLE")-1,
2061
        ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE);
2062

2063
    zend_declare_class_constant_long(
2,784✔
2064
        php_ort_tensor_interface_ce,
2065
        "UINT8", sizeof("UINT8")-1,
2066
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8);
2067

2068
    zend_declare_class_constant_long(
2,784✔
2069
        php_ort_tensor_interface_ce,
2070
        "INT8", sizeof("INT8")-1,
2071
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8);
2072

2073
    zend_declare_class_constant_long(
2,784✔
2074
        php_ort_tensor_interface_ce,
2075
        "UINT16", sizeof("UINT16")-1,
2076
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16);
2077

2078
    zend_declare_class_constant_long(
2,784✔
2079
        php_ort_tensor_interface_ce,
2080
        "INT16", sizeof("INT16")-1,
2081
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16);
2082

2083
    zend_declare_class_constant_long(
2,784✔
2084
        php_ort_tensor_interface_ce,
2085
        "UINT32", sizeof("UINT32")-1,
2086
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32);
2087

2088
    zend_declare_class_constant_long(
2,784✔
2089
        php_ort_tensor_interface_ce,
2090
        "INT32", sizeof("INT32")-1,
2091
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32);
2092

2093
    zend_declare_class_constant_long(
2,784✔
2094
        php_ort_tensor_interface_ce,
2095
        "INT64", sizeof("INT64")-1,
2096
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64);
2097

2098
    zend_declare_class_constant_long(
2,784✔
2099
        php_ort_tensor_interface_ce,
2100
        "BOOL", sizeof("BOOL")-1,
2101
        ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL);
2102

2103
    return SUCCESS;
2,784✔
2104
}
2105

2106
PHP_MSHUTDOWN_FUNCTION(ORT_TENSOR)
2,784✔
2107
{
2108
    zend_hash_destroy(&php_ort_tensors);
2,784✔
2109

2110
#ifdef ZTS
2111
    tsrm_mutex_free(php_ort_tensor_mutex);
2112
#endif
2113

2114
    return SUCCESS;
2,784✔
2115
}
2116

2117
PHP_RINIT_FUNCTION(ORT_TENSOR)
×
2118
{
2119
    return SUCCESS;
×
2120
}
2121

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

© 2026 Coveralls, Inc