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

krakjoe / ort / 16541322699

26 Jul 2025 03:35PM UTC coverage: 91.525% (-1.1%) from 92.601%
16541322699

push

github

krakjoe
fix a bunch of windows stuff, improve configure arguments

16 of 16 new or added lines in 1 file covered. (100.0%)

12 existing lines in 4 files now uncovered.

3672 of 4012 relevant lines covered (91.53%)

138189.79 hits per line

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

90.82
/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

48
        switch (type) {
7,892,640✔
49
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
50
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
51
                return Z_TYPE_P(node) == IS_DOUBLE || Z_TYPE_P(node) == IS_LONG;
3,756,000✔
52
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
53
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
54
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
55
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
56
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
57
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
58
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
59
                return Z_TYPE_P(node) == IS_LONG;
5,991,056✔
60
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
61
                // UINT64 is not supported due to PHP's signed 64-bit integer limitation
62
                php_ort_status_flow(!SUCCESS,
×
63
                {
64
                    return 0;
65
                },
66
                php_ort_status_tensor_invalidtype_ce,
67
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
68
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
69
                return Z_TYPE_P(node) == IS_TRUE || Z_TYPE_P(node) == IS_FALSE;
47,056✔
70
            
71
                default: php_ort_status_flow(!SUCCESS,
48✔
72
                {
73
                    return 0;
74
                },
75
                php_ort_status_tensor_invalidtype_ce,
76
                "unknown data type (%zd) provided",
77
                (zend_long) type);
78
        }
79
    }
80

81
    if (Z_TYPE_P(node) != IS_ARRAY) {
92,208✔
82
        return 0;
83
    }
84

85
    HashTable *ht = Z_ARRVAL_P(node);
92,208✔
86
    if (zend_hash_num_elements(ht) != dimensions[depth]) {
92,208✔
87
        return 0;
88
    }
89

90
    for (zend_long i = 0; i < dimensions[depth]; i++) {
8,043,856✔
91
        zval *child = zend_hash_index_find(ht, i);
7,951,936✔
92
        if (!child || !php_ort_tensor_validate_next(
7,951,936✔
93
                type, rank, dimensions, child, depth + 1)) {
94
            return 0;
240✔
95
        }
96
    }
97

98
    return 1;
99
}
100

101
static zend_always_inline zend_bool php_ort_tensor_validate(zval *shape, zend_string *name, zval *data, ONNXTensorElementDataType type) {
33,728✔
102
    // Skip generator validation
103
    if (Z_TYPE_P(data) == IS_OBJECT && 
33,728✔
104
            instanceof_function(
66,960✔
105
                Z_OBJCE_P(data), php_ort_generator_ce)) {
480✔
106
        return 1;
107
    }
108

109
    zend_long dimensions[16];
33,248✔
110
    zend_long rank = 0;
33,248✔
111
    
112
    rank = zend_hash_num_elements(Z_ARRVAL_P(shape));
33,248✔
113
    
114
    // Handle scalar tensor case (rank 0, empty shape array)
115
    if (rank == 0) {
33,248✔
116
        // For scalar tensors, data must be a single-element array
117
        if (Z_TYPE_P(data) != IS_ARRAY) {
224✔
118
            php_ort_status_flow(!SUCCESS,
×
119
            {
120
                return 0;
121
            },
122
            php_ort_status_tensor_invaliddata_ce,
123
            "scalar tensor data must be provided as a single-element array");
124
        }
125
        
126
        // Check that the data array has exactly one element
127
        if (zend_hash_num_elements(Z_ARRVAL_P(data)) != 1) {
224✔
128
            php_ort_status_flow(!SUCCESS,
32✔
129
            {
130
                return 0;
131
            },
132
            php_ort_status_tensor_invaliddata_ce,
133
            "scalar tensor must have exactly one data element");
134
        }
135
        
136
        // Get the first (and only) element
137
        zval *scalar = zend_hash_index_find(Z_ARRVAL_P(data), 0);
192✔
138
        if (!scalar) {
192✔
139
            return 0;
140
        }
141
        
142
        // Validate the scalar element type
143
        switch (type) {
192✔
144
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
145
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
146
                return Z_TYPE_P(scalar) == IS_DOUBLE || Z_TYPE_P(scalar) == IS_LONG;
112✔
147
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
148
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
149
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
150
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
151
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
152
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
153
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
154
                return Z_TYPE_P(scalar) == IS_LONG;
48✔
155
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
156
                // UINT64 is not supported due to PHP's signed 64-bit integer limitation
157
                php_ort_status_flow(!SUCCESS,
×
158
                {
159
                    return 0;
160
                },
161
                php_ort_status_tensor_invalidtype_ce,
162
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
163
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
164
                return Z_TYPE_P(scalar) == IS_TRUE || Z_TYPE_P(scalar) == IS_FALSE;
32✔
165
            
166
            default: php_ort_status_flow(!SUCCESS,
×
167
            {
168
                return 0;
169
            },
170
            php_ort_status_tensor_invalidtype_ce,
171
            "unknown data type (%zd) provided",
172
            (zend_long) type);
173
        }
174
    }
175

176
    php_ort_status_flow(
33,024✔
177
        (rank > 16),
178
        return 0,
179
        php_ort_status_tensor_invalidshape_ce,
180
        "invalid shape information (must not exceed 16 dimensions)");
181

182
    // Extract and validate dimensions
183
    zend_long index = 0;
33,024✔
184
    zval *next;
33,024✔
185
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), next) {
74,128✔
186
        php_ort_status_flow(
41,200✔
187
            (Z_TYPE_P(next) != IS_LONG || Z_LVAL_P(next) <= 0),
188
            return 0,
189
            php_ort_status_tensor_invalidshape_ce,
190
            "shape information must be an array of positive integers");
191

192
        php_ort_status_flow(
41,104✔
193
            (!zend_hash_index_exists(Z_ARRVAL_P(shape), index)),
194
            return 0,
195
            php_ort_status_tensor_invalidshape_ce,
196
            "shape must be a packed array, index %zd is missing",
197
            index);
198

199
        dimensions[index++] = Z_LVAL_P(next);
41,088✔
200
    } ZEND_HASH_FOREACH_END();
201

202
    php_ort_status_flow(
32,912✔
203
        !php_ort_tensor_validate_next(type, rank, dimensions, data, 0),
204
        return 0,
205
        php_ort_status_tensor_invaliddata_ce,
206
        "validation of data according to the shape provided has failed");
207

208
    return 1;
209
}
210

211
void php_ort_tensor_store(ONNXTensorElementDataType type, void* target, zval* node) {
7,931,952✔
212
    // Enter into generator objects
213
    if (Z_TYPE_P(node) == IS_OBJECT && 
7,931,952✔
214
            instanceof_function(
7,951,648✔
215
                Z_OBJCE_P(node), php_ort_generator_ce)) {
19,696✔
216
        php_ort_generator_invoke(
19,696✔
217
            node, type, target);
218
        return;
19,696✔
219
    }
220

221
    switch (type) {
7,912,256✔
222
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
223
            *(float *)target = Z_TYPE_P(node) == IS_DOUBLE
1,009,696✔
224
                ? (float)Z_DVAL_P(node)
138,592✔
225
                : (float)Z_LVAL_P(node);
1,009,696✔
226
            break;
1,009,696✔
227

228
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
229
            *(double *)target = Z_TYPE_P(node) == IS_DOUBLE
875,968✔
230
                ? Z_DVAL_P(node)
231
                : (double)Z_LVAL_P(node);
875,968✔
232
            break;
875,968✔
233

234
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
883,536✔
235
            *(int8_t *)target = (int8_t)Z_LVAL_P(node);
883,536✔
236
            break;
883,536✔
237

238
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
858,784✔
239
            *(int16_t *)target = (int16_t)Z_LVAL_P(node);
858,784✔
240
            break;
858,784✔
241

242
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
860,528✔
243
            *(int32_t *)target = (int32_t)Z_LVAL_P(node);
860,528✔
244
            break;
860,528✔
245

246
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
859,632✔
247
            *(int64_t *)target = (int64_t)Z_LVAL_P(node);
859,632✔
248
            break;
859,632✔
249

250
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
846,992✔
251
            *(uint8_t *)target = (uint8_t)Z_LVAL_P(node);
846,992✔
252
            break;
846,992✔
253

254
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
846,752✔
255
            *(uint16_t *)target = (uint16_t)Z_LVAL_P(node);
846,752✔
256
            break;
846,752✔
257

258
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
846,816✔
259
            *(uint32_t *)target = (uint32_t)Z_LVAL_P(node);
846,816✔
260
            break;
846,816✔
261

262
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
×
263
            // UINT64 is not supported - this should not be reached due to validation
264
            php_ort_status_throw(
×
265
                php_ort_status_tensor_invalidtype_ce,
266
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
267
            return;
×
268

269
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
270
            *(uint8_t *)target = (Z_TYPE_P(node) == IS_TRUE) ? 1 : 0;
23,552✔
271
            break;
23,552✔
272

273
        default: 
×
274
            php_ort_status_throw(
×
275
                php_ort_status_tensor_invalidtype_ce,
276
                "unknown data type (%zd) provided",
277
                (zend_long) type);
278
    }
279
}
280

281
static zend_always_inline zval* php_ort_tensor_flatten_next(ort_tensor_t* tensor, zval* node, zend_long idx) {
7,974,208✔
282
    // Special case for generators
283
    if (Z_TYPE_P(node) == IS_OBJECT &&
7,974,208✔
284
            instanceof_function(
15,948,640✔
285
                Z_OBJCE_P(node), php_ort_generator_ce)) {
22,528✔
286
        return node;
287
    }
288

289
    // Special case for scalar tensor (0 dimensions)
290
    if (tensor->dimensions == 0) {
7,951,680✔
291
        // For scalar tensors, we expect the data to be a single-element array
292
        if (Z_TYPE_P(node) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(node)) != 1) {
192✔
293
            return NULL;
294
        }
295

296
        // Get the scalar value (first element of the array)
297
        return zend_hash_index_find(Z_ARRVAL_P(node), idx);
192✔
298
    }
299

300
    if (Z_TYPE_P(node) != IS_ARRAY) {
7,951,488✔
301
        return NULL;
302
    }
303

304
    return zend_hash_index_find(Z_ARRVAL_P(node), idx);
7,951,488✔
305
}
306

307
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✔
308
    // Scalar tensors have 0 dimensions
309
    if (tensor->dimensions == 0) {        
8,007,408✔
310
        // Get the scalar value (first element of the array)
311
        zval *scalar = php_ort_tensor_flatten_next(tensor, node, 0);
224✔
312
        if (!scalar) {
224✔
313
            return 0;
×
314
        }
315

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

318
        php_ort_tensor_store(
224✔
319
            tensor->type, target, scalar);
320
        
321
        (*offset)++;
224✔
322
        return 1;
224✔
323
    }
324

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

328
        php_ort_tensor_store(
7,912,032✔
329
            tensor->type, target, node);
330
        
331
        (*offset)++;
7,912,032✔
332
        return 1;
7,912,032✔
333
    }
334

335
    for (int64_t i = 0; i < tensor->shape[depth]; i++) {
8,069,136✔
336
        zval *child = php_ort_tensor_flatten_next(tensor, node, i);
7,973,984✔
337
        if (!php_ort_tensor_flatten(tensor, offset, size, child, depth + 1)) {
7,973,984✔
338
            return 0;
339
        }
340
    }
341

342
    return 1;
343
}
344

345
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✔
346
    size_t i = 0, offset = 0;
1,360✔
347

348
    tensor->name       = php_ort_string_copy(name);
2,720✔
349
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
1,360✔
350
    tensor->type       = type;
1,360✔
351
    tensor->owner      = PHP_ORT_OWN_HEAP;
1,360✔
352

353
    // Handle scalar tensor case (empty shape array)
354
    if (tensor->dimensions == 0) {
1,360✔
355
        tensor->shape    = NULL;  // No shape array needed for scalars
32✔
356
        tensor->elements = 1;     // A scalar has exactly one element
32✔
357
        tensor->data     = ort_alloc(php_ort_tensor_sizeof(tensor), 1);
64✔
358
        
359
        return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
64✔
360
    }
361
    
362
    // Normal case for non-scalar tensors
363
    tensor->shape    = pemalloc(tensor->dimensions * sizeof(int64_t), 1);
1,328✔
364
    tensor->elements = 1;
1,328✔
365

366
    // Copy shape and compute total number of elements
367
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), zval *dim) {
3,760✔
368
        int64_t size = (int64_t)Z_LVAL_P(dim);
2,432✔
369
        tensor->shape[i++] = size;
2,432✔
370
        tensor->elements *= size;
2,432✔
371
    } ZEND_HASH_FOREACH_END();
372

373
    tensor->data = ort_alloc(
2,656✔
374
        php_ort_tensor_sizeof(tensor), tensor->elements);
375

376
    return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
2,656✔
377
}
378

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

382
    tensor->name       = NULL;
32,064✔
383
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
32,064✔
384
    tensor->type       = type;
32,064✔
385
    tensor->owner      = PHP_ORT_OWN_ZEND;
32,064✔
386

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

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

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

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

413
static void ort_tensor_free(ort_tensor_t *tensor) {
59,040✔
414
    zend_bool persistent = 
59,040✔
415
        (tensor->owner == PHP_ORT_OWN_HEAP) ? 1 : 0;
59,040✔
416

417
    if (tensor->shape) {
59,040✔
418
        pefree(tensor->shape, persistent);
57,936✔
419
    }
420

421
#ifdef HAVE_ONNXRUNTIME
422
    if (!tensor->parent && tensor->data && !tensor->value) {
423
        ort_free(tensor->data);
424
    }
425

426
    if (tensor->value) {
427
        api->ReleaseValue(tensor->value);
428
    }
429
#else
430
    if (!tensor->parent && tensor->data) {
59,040✔
431
        ort_free(tensor->data);
58,960✔
432
    }
433
#endif
434

435
    if (tensor->name && !persistent) {
59,040✔
436
        zend_string_free(tensor->name);
25,472✔
437
    }
438

439
    if (tensor->parent) {
59,040✔
440
        ort_tensor_release(tensor->parent);
80✔
441
    }
442

443
    pefree(tensor, persistent);
59,040✔
444
}
59,040✔
445

446
void ort_tensor_release(ort_tensor_t *tensor) {
61,328✔
447
    if (!tensor) {
61,328✔
448
        return;
449
    }
450

451
    if (php_ort_atomic_delref(&tensor->refcount) == 0){
60,512✔
452
        ort_tensor_free(tensor);
59,040✔
453
    }
454
}
455

456
static void php_ort_tensor_del(zval *zv) {
1,360✔
457
    ort_tensor_release(
1,360✔
458
        ((ort_tensor_t*)
459
            Z_PTR_P(zv)));
1,360✔
460
}
1,360✔
461

462
static zend_bool php_ort_tensor_construct_persistent(ort_tensor_t *tensor, zend_string *name, zval *shape, zval *data, ONNXTensorElementDataType type){
1,520✔
463
    if (!php_ort_tensor_validate(shape, name, data, type)) {
3,040✔
464
        return 0;
160✔
465
    }
466

467
    if (!php_ort_tensor_allocate_persistent(tensor, name, shape, data, type)) {
2,720✔
468
        return 0;
469
    }
470

471
    return 1;
472
}
473

474
static zend_bool php_ort_tensor_construct_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type){
32,208✔
475
    if (!php_ort_tensor_validate(shape, NULL, data, type)) {
64,416✔
476
        return 0;
144✔
477
    }
478

479
    if (!php_ort_tensor_allocate_transient(tensor, shape, data, type)) {
64,128✔
480
        return 0;
481
    }
482

483
    return 1;
484
}
485

486
#ifdef HAVE_ONNXRUNTIME
487
OrtValue* php_ort_tensor_value(php_ort_tensor_t* ort) {
488
    OrtMemoryInfo* mi;
489
    OrtValue* value = NULL;
490
    OrtStatus* status;
491

492
    php_ort_status_flow(
493
        (status = api->CreateCpuMemoryInfo(
494
            OrtArenaAllocator, OrtMemTypeDefault, &mi)),
495
        {
496
            api->ReleaseStatus(status);
497

498
            return NULL;
499
        },
500
        php_ort_status_tensor_invalidmemory_ce,
501
        "failed to allocate MemoryInfo* for Tensor conversion: %s",
502
        api->GetErrorMessage(status));
503

504
    php_ort_status_flow(
505
        (status = api->CreateTensorWithDataAsOrtValue(
506
            mi,
507
            ort->object->data,
508
            ort->object->elements * php_ort_tensor_sizeof(ort->object),
509
            ort->object->shape,
510
            ort->object->dimensions,
511
            ort->object->type,
512
            &value)),
513
        {
514
            api->ReleaseStatus(status);
515

516
            return NULL;
517
        },
518
        php_ort_status_tensor_invalidmemory_ce,
519
        "failed to allocate OrtValue* for Tensor conversion: %s",
520
        api->GetErrorMessage(status));
521

522
    api->ReleaseMemoryInfo(mi);
523

524
    return value;
525
}
526

527
ort_tensor_t* php_ort_tensor_object(OrtValue* value) {
528
    ort_tensor_t  *tensor = pecalloc(1, sizeof(ort_tensor_t), 0);
529

530
    tensor->refcount = 1;
531
    tensor->owner    = PHP_ORT_OWN_ZEND;
532
    tensor->value    = value;
533

534
    OrtTensorTypeAndShapeInfo* otsi;
535

536
    php_ort_status_flow(
537
        api->GetTensorTypeAndShape(value, &otsi), {
538
            ort_tensor_free(tensor);
539

540
            return NULL;
541
        },
542
        php_ort_status_tensor_invalidshape_ce,
543
        "failed to determine shape for OrtValue* conversion");
544

545
    php_ort_status_flow(
546
        api->GetTensorElementType(otsi, &tensor->type),
547
        {
548
            ort_tensor_free(tensor);
549

550
            return NULL;
551
        },
552
        php_ort_status_tensor_invalidshape_ce,
553
        "failed to determine type for OrtValue* conversion");
554

555
    php_ort_status_flow(
556
        api->GetDimensionsCount(otsi, &tensor->dimensions),
557
        {
558
            ort_tensor_free(tensor);
559

560
            return NULL;
561
        },
562
        php_ort_status_tensor_invalidshape_ce,
563
        "failed to determine dimension count for OrtValue* conversion");
564

565
    // For non-scalar tensors, allocate shape array and populate it
566
    if (tensor->dimensions > 0) {
567
        tensor->shape = pecalloc(tensor->dimensions, sizeof(int64_t), 0);
568

569
        php_ort_status_flow(
570
            api->GetDimensions(otsi, tensor->shape, tensor->dimensions),
571
            {
572
                ort_tensor_free(tensor);
573

574
                return NULL;
575
            },
576
            php_ort_status_tensor_invalidshape_ce,
577
            "failed to fetch dimensions for OrtValue* conversion");
578
    } else {
579
        // For scalar tensors, shape remains NULL
580
        tensor->shape = NULL;
581
    }
582

583
    php_ort_status_flow(
584
        api->GetTensorShapeElementCount(otsi, &tensor->elements),
585
        {
586
            ort_tensor_free(tensor);
587

588
            return NULL;
589
        },
590
        php_ort_status_tensor_invalidshape_ce,
591
        "failed to determine element count for OrtValue* conversion");
592

593
    php_ort_status_flow(
594
        api->GetTensorMutableData(value, &tensor->data),
595
        {
596
            ort_tensor_free(tensor);
597

598
            return NULL;
599
        },
600
        php_ort_status_tensor_invaliddata_ce,
601
        "failed to fetch data for OrtValue* conversion");
602

603
    api->ReleaseTensorTypeAndShapeInfo(otsi);
604

605
    return tensor;
606
}
607
#endif
608

609
static zend_always_inline size_t php_ort_tensor_indexof(ort_tensor_t *tensor, int64_t *coords) {
48✔
610
    // For scalar tensors, always return index 0
611
    if (tensor->dimensions == 0) {
48✔
612
        return 0;
613
    }
614
    
615
    size_t index = 0;
48✔
616
    size_t stride = 1;
48✔
617

618
    for (int64_t i = tensor->dimensions - 1; i >= 0; i--) {
144✔
619
        index += 
96✔
620
            coords[i] * stride;
96✔
621
        stride *= tensor->shape[i];
96✔
622
    }
623

624
    return index;
625
}
626

627
ZEND_BEGIN_ARG_INFO_EX(php_ort_tensor_persistent_construct_arginfo, 0, 0, 1)
628
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
629
    ZEND_ARG_TYPE_INFO(0, shape, IS_ARRAY, 0)
630
    ZEND_ARG_TYPE_MASK(0, data, MAY_BE_ARRAY | MAY_BE_OBJECT, NULL)
631
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
632
ZEND_END_ARG_INFO()
633

634
PHP_METHOD(ONNX_Tensor_Persistent, __construct)
1,584✔
635
{
636
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
1,584✔
637

638
    zend_string *name;
1,584✔
639
    zval        *shape = NULL;
1,584✔
640
    zval        *data  = NULL;
1,584✔
641
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
1,584✔
642

643
    ZEND_PARSE_PARAMETERS_START(1, 4);
1,584✔
644
        Z_PARAM_STR(name)
3,168✔
645
        Z_PARAM_OPTIONAL
1,584✔
646
        Z_PARAM_ARRAY(shape)
3,104✔
647
        Z_PARAM_ZVAL(data)
1,520✔
648
        Z_PARAM_LONG(type)
3,024✔
649
    ZEND_PARSE_PARAMETERS_END();
1,584✔
650

651
#ifdef ZTS
652
    php_ort_status_flow(
653
        tsrm_mutex_lock(php_ort_tensor_mutex) != SUCCESS,
654
        return,
655
        php_ort_status_safetyerror_ce,
656
        "it was not possible to acquire the tensor mutex, something is terribly wrong");
657
#endif
658

659
    if (!(ort->object = zend_hash_find_ptr(&php_ort_tensors, name))) {
3,168✔
660
#ifdef ZTS
661
        php_ort_status_flow(
662
            (!shape || !data),
663
            {
664
                php_ort_status_flow(
665
                    tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
666
                    return,
667
                    php_ort_status_safetyerror_ce,
668
                    "it was not possible to release the tensor mutex, something is terribly wrong");
669
                return;
670
            },
671
            php_ort_status_tensor_notfound_ce, 
672
            "Could not find the Tensor named \"%s\"",
673
                ZSTR_VAL(name));
674
#else
675
        php_ort_status_flow(
1,552✔
676
            (!shape || !data),
677
            {
678
                return;
679
            },
680
            php_ort_status_tensor_notfound_ce, 
681
            "Could not find the Tensor named \"%s\"",
682
                ZSTR_VAL(name));
683
#endif
684

685
        ort_tensor_t *tensor = 
1,520✔
686
            pecalloc(1, sizeof(ort_tensor_t), 1);
1,520✔
687

688
        tensor->refcount = 1;
1,520✔
689

690
        if (!php_ort_tensor_construct_persistent(tensor, name, shape, data, type)) {
1,520✔
691
#ifdef ZTS
692
            php_ort_status_flow(
693
                tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
694
                return,
695
                php_ort_status_safetyerror_ce,
696
                "it was not possible to release the tensor mutex, something is terribly wrong");
697
#endif
698
            return;
699
        }
700

701
        ort->object = zend_hash_add_ptr(
1,360✔
702
            &php_ort_tensors,
703
            tensor->name,
704
            tensor);
705

706
        ort->object->refcount++;
1,360✔
707
    } else {
708
        php_ort_atomic_addref(&ort->object->refcount);
32✔
709
    }
710

711
#ifdef ZTS
712
    php_ort_status_flow(
713
        tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
714
        return,
715
        php_ort_status_safetyerror_ce,
716
        "it was not possible to release the tensor mutex, something is terribly wrong");
717
#endif
718
}
719

720
ZEND_BEGIN_ARG_INFO_EX(php_ort_tensor_transient_construct_arginfo, 0, 0, 2)
721
    ZEND_ARG_TYPE_INFO(0, shape, IS_ARRAY, 0)
722
    ZEND_ARG_TYPE_MASK(0, data, MAY_BE_ARRAY|MAY_BE_OBJECT, NULL)
723
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
724
ZEND_END_ARG_INFO()
725

726
PHP_METHOD(ONNX_Tensor_Transient, __construct)
32,208✔
727
{
728
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
32,208✔
729

730
    zval        *shape = NULL;
32,208✔
731
    zval        *data  = NULL;
32,208✔
732
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
32,208✔
733

734
    ZEND_PARSE_PARAMETERS_START(2, 3);
32,208✔
735
        Z_PARAM_ARRAY(shape)
64,416✔
736
        Z_PARAM_ZVAL(data)
32,208✔
737
        Z_PARAM_OPTIONAL
32,208✔
738
        Z_PARAM_LONG(type)
64,384✔
739
    ZEND_PARSE_PARAMETERS_END();
32,208✔
740

741
    ort_tensor_t *tensor = ecalloc(1, sizeof(ort_tensor_t));
32,208✔
742
    tensor->refcount = 1;
32,208✔
743

744
    if (!php_ort_tensor_construct_transient(tensor, shape, data, type)) {
32,208✔
745
        efree(tensor);
144✔
746
        return;
144✔
747
    }
748

749
    ort->object = tensor;
32,064✔
750
}
751

752
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_isPersistent_arginfo, 0, 0, _IS_BOOL, 0)
753
ZEND_END_ARG_INFO()
754

755
PHP_METHOD(ONNX_Tensor, isPersistent)
16✔
756
{
757
    php_ort_tensor_t* ort =
16✔
758
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
16✔
759

760
    ZEND_PARSE_PARAMETERS_NONE();
16✔
761

762
    RETURN_BOOL(ort->object->owner == PHP_ORT_OWN_HEAP);
16✔
763
}
764

765
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getName_arginfo, 0, 0, IS_STRING, 1)
766
ZEND_END_ARG_INFO()
767

768
PHP_METHOD(ONNX_Tensor, getName)
48✔
769
{
770
    php_ort_tensor_t* ort =
48✔
771
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
48✔
772
    
773
    ZEND_PARSE_PARAMETERS_NONE();
48✔
774

775
    if (!ort->object->name) {
48✔
776
        return;
777
    }
778

779
    RETURN_STR_COPY(ort->object->name);
48✔
780
}
781

782
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getType_arginfo, 0, 0, IS_LONG, 0)
783
ZEND_END_ARG_INFO()
784

785
PHP_METHOD(ONNX_Tensor, getType)
880✔
786
{
787
    php_ort_tensor_t* ort =
880✔
788
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
880✔
789

790
    ZEND_PARSE_PARAMETERS_NONE();
880✔
791

792
    RETURN_LONG(ort->object->type);
880✔
793
}
794

795
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getTypeName_arginfo, 0, 0, IS_STRING, 0)
796
ZEND_END_ARG_INFO()
797

798
PHP_METHOD(ONNX_Tensor, getTypeName)
17,744✔
799
{
800
    php_ort_tensor_t* ort =
17,744✔
801
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
17,744✔
802

803
    ZEND_PARSE_PARAMETERS_NONE();
17,744✔
804

805
    /* @todo(krakjoe) this performs terribly ... */
806
    RETURN_STRING(php_ort_type_name(ort->object->type));
48,512✔
807
}
808

809
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getShape_arginfo, 0, 0, IS_ARRAY, 0)
810
ZEND_END_ARG_INFO()
811

812
PHP_METHOD(ONNX_Tensor, getShape)
18,352✔
813
{
814
    php_ort_tensor_t* ort =
18,352✔
815
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
18,352✔
816

817
    ZEND_PARSE_PARAMETERS_NONE();
18,352✔
818

819
    array_init(return_value);
18,352✔
820
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
37,984✔
821
        add_next_index_long(
19,632✔
822
            return_value, ort->object->shape[dimension]);
19,632✔
823
    }
824
}
825

826
static zend_always_inline ort_tensor_t* php_ort_tensor_transpose(ort_tensor_t* input, zval *axis, zval* return_value) {
64✔
827
    int64_t ndim = input->dimensions;
64✔
828
    int64_t* perm = NULL;
64✔
829
    int64_t* inv_perm = NULL;
64✔
830
    int64_t* out_shape = NULL;
64✔
831
    int64_t* in_strides = NULL;
64✔
832
    int64_t* out_strides = NULL;
64✔
833
    size_t type_size = php_ort_tensor_sizeof(input);
128✔
834
    size_t numel = input->elements;
64✔
835
    ort_tensor_t* result = NULL;
64✔
836

837
    // Argument validation and axes parsing
838
    if (ndim == 0) {
64✔
839
        // Scalar: transpose is a no-op, just return a copy
840
        result = pecalloc(1, sizeof(ort_tensor_t), 0);
×
841
        result->refcount = 1;
×
842
        result->owner = PHP_ORT_OWN_ZEND;
×
843
        result->type = input->type;
×
844
        result->dimensions = 0;
×
845
        result->elements = 1;
×
846
        result->shape = NULL;
×
847
        result->data = ort_alloc(type_size, 1);
×
848
        ort_memcpy(result->data, input->data, type_size);
×
849
        goto __php_ort_tensor_transpose_done;
×
850
    }
851

852
    perm = ecalloc(ndim, sizeof(int64_t));
64✔
853
    out_shape = ecalloc(ndim, sizeof(int64_t));
64✔
854
    in_strides = ecalloc(ndim, sizeof(int64_t));
64✔
855
    out_strides = ecalloc(ndim, sizeof(int64_t));
64✔
856
    inv_perm = ecalloc(ndim, sizeof(int64_t));
64✔
857

858
    // Parse axes argument
859
    if (axis) {
64✔
860
        php_ort_status_flow(
48✔
861
            (zend_hash_num_elements(Z_ARRVAL_P(axis)) != ndim),
862
            {
863
                goto __php_ort_tensor_transpose_failed;
864
            },
865
            php_ort_status_tensor_invalidshape_ce,
866
            "axes array must be an array of length %zd", ndim);
867

868
        // Fill perm from user axes, handle negatives, check for duplicates
869
        zend_bool* seen = ecalloc(ndim, sizeof(zend_bool));
48✔
870
        int64_t i = 0;
48✔
871
        zval* zv;
48✔
872
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(axis), zv) {
144✔
873
            php_ort_status_flow(
96✔
874
                (Z_TYPE_P(zv) != IS_LONG),
875
                {
876
                    efree(seen);
877
                    goto __php_ort_tensor_transpose_failed;
878
                },
879
                php_ort_status_tensor_invalidshape_ce,
880
                "axes array must contain only integers");
881
            
882
            int64_t ax = Z_LVAL_P(zv);
96✔
883
            if (ax < 0)
96✔
884
                ax += ndim;
32✔
885
            
886
            php_ort_status_flow(
96✔
887
                (ax < 0 || ax >= ndim),
888
                {
889
                    efree(seen);
890
                    goto __php_ort_tensor_transpose_failed;
891
                },
892
                php_ort_status_tensor_invalidshape_ce,
893
                "axis value %zd out of range [0, %zd)", ax, ndim);
894

895
            php_ort_status_flow(
96✔
896
                (seen[ax]),
897
                {
898
                    efree(seen);
899
                    goto __php_ort_tensor_transpose_failed;
900
                },
901
                php_ort_status_tensor_invalidshape_ce,
902
                "duplicate axis value %zd in axes", ax);
903

904
            seen[ax] = 1;
96✔
905
            perm[i++] = ax;
96✔
906
        } ZEND_HASH_FOREACH_END();
907
        efree(seen);
48✔
908
    } else {
909
        // Default: reverse axes
910
        for (int64_t i = 0; i < ndim; i++) {
48✔
911
            perm[i] = ndim - 1 - i;
32✔
912
        }
913
    }
914

915
    // Compute output shape and strides
916
    for (int64_t i = 0; i < ndim; i++) {
192✔
917
        out_shape[i] = input->shape[perm[i]];
128✔
918
    }
919
    // Input strides
920
    in_strides[ndim-1] = 1;
64✔
921
    for (int64_t i = ndim-2; i >= 0; i--) {
128✔
922
        in_strides[i] = in_strides[i+1] * input->shape[i+1];
64✔
923
    }
924
    // Output strides
925
    out_strides[ndim-1] = 1;
64✔
926
    for (int64_t i = ndim-2; i >= 0; i--) {
128✔
927
        out_strides[i] = out_strides[i+1] * out_shape[i+1];
64✔
928
    }
929
    // Inverse permutation: inv_perm[perm[i]] = i
930
    for (int64_t i = 0; i < ndim; i++) {
192✔
931
        inv_perm[perm[i]] = i;
128✔
932
    }
933

934
    // Allocate result tensor
935
    result = pecalloc(1, sizeof(ort_tensor_t), 0);
64✔
936
    result->refcount = 1;
64✔
937
    result->owner = PHP_ORT_OWN_ZEND;
64✔
938
    result->type = input->type;
64✔
939
    result->dimensions = ndim;
64✔
940
    result->elements = numel;
64✔
941
    result->shape = pecalloc(ndim, sizeof(int64_t), 0);
64✔
942
    memcpy(result->shape,
64✔
943
        out_shape, ndim * sizeof(int64_t));
944
    result->data = ort_alloc(type_size, numel);
64✔
945

946
    // Main permutation loop
947
    int64_t* in_coords =
64✔
948
        ecalloc(ndim, sizeof(int64_t));
64✔
949
    int64_t* out_coords =
64✔
950
        ecalloc(ndim, sizeof(int64_t));
64✔
951
    for (size_t idx = 0; idx < numel; idx++) {
640✔
952
        // Compute input coordinates from flat idx
953
        size_t rem = idx;
954
        for (int64_t i = 0; i < ndim; i++) {
1,728✔
955
            in_coords[i] = rem / in_strides[i];
1,152✔
956
            rem = rem % in_strides[i];
1,152✔
957
        }
958
        // Permute coordinates
959
        for (int64_t i = 0; i < ndim; i++) {
1,728✔
960
            out_coords[i] = in_coords[perm[i]];
1,152✔
961
        }
962
        // Compute output flat index
963
        size_t out_idx = 0;
964
        for (int64_t i = 0; i < ndim; i++) {
1,728✔
965
            out_idx += out_coords[i] * out_strides[i];
1,152✔
966
        }
967
        // Copy element
968
        ort_memcpy(
576✔
969
            (char*)result->data + out_idx * type_size,
576✔
970
            (char*)input->data + idx * type_size,
576✔
971
            type_size);
972
    }
973
    efree(in_coords);
64✔
974
    efree(out_coords);
64✔
975

976
__php_ort_tensor_transpose_done:
64✔
977
    object_init_ex(return_value,
64✔
978
        php_ort_tensor_transient_ce);
979
    php_ort_tensor_t* rv =
64✔
980
        php_ort_tensor_fetch(
64✔
981
            Z_OBJ_P(return_value));
982
    rv->object = result;
64✔
983

984
    if (perm)
64✔
985
        efree(perm);
64✔
986
    if (out_shape)
64✔
987
        efree(out_shape);
64✔
988
    if (in_strides)
64✔
989
        efree(in_strides);
64✔
990
    if (out_strides)
64✔
991
        efree(out_strides);
64✔
992
    if (inv_perm)
64✔
993
        efree(inv_perm);
64✔
994
    return result;
995

996
__php_ort_tensor_transpose_failed:
×
997
    if (perm)
×
998
        efree(perm);
×
999
    if (out_shape)
×
1000
        efree(out_shape);
×
1001
    if (in_strides)
×
1002
        efree(in_strides);
×
1003
    if (out_strides)
×
1004
        efree(out_strides);
×
1005
    if (inv_perm)
×
1006
        efree(inv_perm);
×
1007
    return NULL;
1008
}
1009

1010
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_transpose_arginfo, 0, 0, ORT\\Tensor, 0)
1011
    ZEND_ARG_TYPE_INFO(0, axis, IS_ARRAY, 1)
1012
ZEND_END_ARG_INFO()
1013

1014
PHP_METHOD(ONNX_Tensor, transpose)
64✔
1015
{
1016
    php_ort_tensor_t* ort =
64✔
1017
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
64✔
1018
    zval* axis = NULL;
64✔
1019

1020
    ZEND_PARSE_PARAMETERS_START(0, 1);
64✔
1021
        Z_PARAM_OPTIONAL
64✔
1022
        Z_PARAM_ZVAL(axis)
64✔
1023
    ZEND_PARSE_PARAMETERS_END();
64✔
1024

1025
    php_ort_tensor_transpose(ort->object, axis, return_value);
64✔
1026
}
1027

1028
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✔
1029
    // Handle axis parameter - if provided, validate it
1030
    zend_bool has_axis = (axis != NULL);
96✔
1031
    HashTable *axis_ht = has_axis ? Z_ARRVAL_P(axis) : NULL;
192✔
1032

1033
    if (has_axis) {
96✔
1034
        // When axis is provided, start and end should match axis array length
1035
        php_ort_status_flow(
16✔
1036
            (zend_hash_num_elements(Z_ARRVAL_P(start)) != zend_hash_num_elements(axis_ht) ||
1037
             zend_hash_num_elements(Z_ARRVAL_P(end))   != zend_hash_num_elements(axis_ht)), 
1038
            return NULL,
1039
            php_ort_status_tensor_invalidshape_ce,
1040
            "when axis is provided, start and end arrays must have same length as axis array (%zd)",
1041
            zend_hash_num_elements(axis_ht));
1042
    } else {
1043
        // When no axis provided, start and end must match tensor dimensions
1044
        php_ort_status_flow(
80✔
1045
            (zend_hash_num_elements(Z_ARRVAL_P(start)) != input->dimensions ||
1046
             zend_hash_num_elements(Z_ARRVAL_P(end))   != input->dimensions), 
1047
            return NULL,
1048
            php_ort_status_tensor_invalidshape_ce,
1049
            "start and end arrays must have same length as tensor dimensions (%zd)",
1050
            input->dimensions);
1051
    }
1052

1053
    object_init_ex(return_value, php_ort_tensor_transient_ce);
80✔
1054

1055
    php_ort_tensor_t* ort = php_ort_tensor_fetch(Z_OBJ_P(return_value));
80✔
1056
    
1057
    // Allocate new tensor structure
1058
    ort->object = pecalloc(1, sizeof(ort_tensor_t), 0);
80✔
1059
    
1060
    // Set up parent relationship
1061
    ort->object->parent = input;
80✔
1062
    php_ort_atomic_addref(
80✔
1063
        &ort->object->parent->refcount);
1064

1065
    ort->object->refcount = 1;
80✔
1066
    ort->object->owner = PHP_ORT_OWN_ZEND;
80✔
1067
    ort->object->type = input->type;
80✔
1068

1069
    int64_t *starting = pecalloc(input->dimensions, sizeof(int64_t), 0);
80✔
1070
    int64_t *ending = pecalloc(input->dimensions, sizeof(int64_t), 0);
80✔
1071
    int64_t *slicing = pecalloc(input->dimensions, sizeof(int64_t), 0);
80✔
1072

1073
    // Initialize with full range for all dimensions
1074
    for (int64_t idim = 0; idim < input->dimensions; idim++) {
240✔
1075
        starting[idim] = 0;
160✔
1076
        ending[idim]   = input->shape[idim];
160✔
1077
        slicing[idim]  = input->shape[idim];
160✔
1078
    }
1079

1080
    // Process based on whether axis is provided
1081
    if (has_axis) {
80✔
1082
        // Process each axis specification
1083
        zval *zaxis, *zstart, *zend;
16✔
1084
        zend_ulong iaxis = 0;
16✔
1085
        
1086
        ZEND_HASH_FOREACH_VAL(axis_ht, zaxis) {
32✔
1087
            php_ort_status_flow(
16✔
1088
                (Z_TYPE_P(zaxis) != IS_LONG),
1089
                {
1090
                    pefree(starting, 0);
1091
                    pefree(ending, 0);
1092
                    pefree(slicing, 0);
1093
                    return NULL;
1094
                },
1095
                php_ort_status_tensor_invalidshape_ce,
1096
                "axis array must contain integers");
1097
            
1098
            int64_t idim = Z_LVAL_P(zaxis);
16✔
1099

1100
            php_ort_status_flow(
16✔
1101
                (idim < 0 || idim >= input->dimensions),
1102
                {
1103
                    pefree(starting, 0);
1104
                    pefree(ending, 0);
1105
                    pefree(slicing, 0);
1106

1107
                    return NULL;
1108
                },
1109
                php_ort_status_tensor_invalidshape_ce,
1110
                "axis value %zd out of range [0, %zd)", idim, input->dimensions);
1111
            
1112
            // Get corresponding start and end values using the same index
1113
            zstart = zend_hash_index_find(Z_ARRVAL_P(start), iaxis);
16✔
1114
            zend   = zend_hash_index_find(Z_ARRVAL_P(end),   iaxis);
16✔
1115
            
1116
            php_ort_status_flow(
16✔
1117
                (!zstart || !zend || 
1118
                  Z_TYPE_P(zstart) != IS_LONG || 
1119
                  Z_TYPE_P(zend)   != IS_LONG),
1120
                {
1121
                    pefree(starting, 0);
1122
                    pefree(ending, 0);
1123
                    pefree(slicing, 0);
1124

1125
                    return NULL;
1126
                },
1127
                php_ort_status_tensor_invalidshape_ce,
1128
                "start and end values must be integers");
1129

1130
            int64_t istart = Z_LVAL_P(zstart);
16✔
1131
            int64_t iend = Z_LVAL_P(zend);
16✔
1132
            
1133
            // Handle negative indices
1134
            if (istart < 0) istart += input->shape[idim];
16✔
1135
            if (iend < 0)   iend += input->shape[idim];
16✔
1136

1137
            // Validate bounds
1138
            php_ort_status_flow(
16✔
1139
                (istart < 0 || istart >= input->shape[idim] ||
1140
                 iend < 0   || iend    > input->shape[idim] ||
1141
                 istart >= iend),
1142
                {
1143
                    pefree(starting, 0);
1144
                    pefree(ending, 0);
1145
                    pefree(slicing, 0);
1146
                    return NULL;
1147
                },
1148
                php_ort_status_tensor_invalidshape_ce,
1149
                "invalid slice bounds for axis %zd: start=%zd, end=%zd, shape=%zd",
1150
                idim, istart, iend, input->shape[idim]);
1151

1152
            starting[idim] = istart;
16✔
1153
            ending[idim]   = iend;
16✔
1154
            slicing[idim]  = iend - istart;
16✔
1155

1156
            iaxis++;
16✔
1157
        } ZEND_HASH_FOREACH_END();
1158
    } else {
1159
        // No axis specified, use start and end arrays directly for all dimensions
1160
        for (int64_t idim = 0; idim < input->dimensions; idim++) {
128✔
1161
            zval *zstart = zend_hash_index_find(Z_ARRVAL_P(start), idim);
96✔
1162
            zval *zend   = zend_hash_index_find(Z_ARRVAL_P(end),   idim);
96✔
1163

1164
            php_ort_status_flow(
96✔
1165
                (!zstart || !zend ||
1166
                 Z_TYPE_P(zstart) != IS_LONG ||
1167
                 Z_TYPE_P(zend)   != IS_LONG),
1168
                {
1169
                    pefree(starting, 0);
1170
                    pefree(ending, 0);
1171
                    pefree(slicing, 0);
1172
                    return NULL;
1173
                },
1174
                php_ort_status_tensor_invalidshape_ce,
1175
                "start and end values must be integers");
1176

1177
            int64_t istart = Z_LVAL_P(zstart);
96✔
1178
            int64_t iend = Z_LVAL_P(zend);
96✔
1179

1180
            // Handle negative indices
1181
            if (istart < 0) istart += input->shape[idim];
96✔
1182
            if (iend < 0)   iend   += input->shape[idim];
96✔
1183

1184
            php_ort_status_flow(
96✔
1185
                (istart < 0 || istart >= input->shape[idim] ||
1186
                 iend < 0   || iend   >  input->shape[idim] ||
1187
                 istart     >= iend),
1188
                {
1189
                    pefree(starting, 0);
1190
                    pefree(ending, 0);
1191
                    pefree(slicing, 0);
1192

1193
                    return NULL;
1194
                },
1195
                php_ort_status_tensor_invalidshape_ce,
1196
                "invalid slice bounds for dimension %zd: start=%zd, end=%zd, shape=%zd",
1197
                idim, istart, iend, input->shape[idim]);
1198

1199
            starting[idim] = istart;
64✔
1200
            ending[idim]   = iend;
64✔
1201
            slicing[idim]  = iend - istart;
64✔
1202
        }
1203
    }
1204

1205
    // Set up slice tensor metadata
1206
    ort->object->dimensions = input->dimensions;
48✔
1207
    ort->object->shape      = slicing; // Transfer ownership
48✔
1208

1209
    // Calculate number of elements in slice
1210
    ort->object->elements = 1;
48✔
1211
    for (int64_t idim = 0; idim < ort->object->dimensions; idim++) {
144✔
1212
        ort->object->elements *= ort->object->shape[idim];
96✔
1213
    }
1214

1215
    // Set data pointer to offset into parent's data (readonly view)
1216
    ort->object->data = (char*)input->data + 
48✔
1217
        (php_ort_tensor_indexof(input, starting) * 
48✔
1218
            php_ort_tensor_sizeof(input));
48✔
1219

1220
    pefree(starting, 0);
48✔
1221
    pefree(ending, 0);
48✔
1222

1223
    return ort->object;
48✔
1224
}
1225

1226
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_getSlice_arginfo, 0, 2, ORT\\Tensor, 0)
1227
    ZEND_ARG_TYPE_INFO(0, start, IS_ARRAY, 0)
1228
    ZEND_ARG_TYPE_INFO(0, end,   IS_ARRAY, 0)
1229
    ZEND_ARG_TYPE_INFO(0, axis,  IS_ARRAY, 0)
1230
ZEND_END_ARG_INFO()
1231

1232
PHP_METHOD(ONNX_Tensor, getSlice)
96✔
1233
{
1234
    php_ort_tensor_t* ort =
96✔
1235
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
96✔
1236
    zval *start, *end, *axis = NULL;
96✔
1237

1238
    ZEND_PARSE_PARAMETERS_START(2, 3)
96✔
1239
        Z_PARAM_ARRAY(start)
192✔
1240
        Z_PARAM_ARRAY(end)
192✔
1241
        Z_PARAM_OPTIONAL
96✔
1242
        Z_PARAM_ARRAY(axis)
112✔
1243
    ZEND_PARSE_PARAMETERS_END();
96✔
1244

1245
    php_ort_tensor_slice(ort->object, start, end, axis, return_value);
96✔
1246
}
1247

1248
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✔
1249
    // Special case for scalar tensors (0 dimensions)
1250
    if (tensor->dimensions == 0) {
66,432✔
1251
        // This should not be called for scalar tensors
1252
        // The getData method directly handles scalar case
1253
        php_ort_status_flow(
×
1254
            !SUCCESS,
1255
            return 0,
1256
            php_ort_status_tensor_invalidshape_ce,
1257
            "php_ort_tensor_data should not be called for scalar tensors");
1258
    }
1259

1260
    // Validate depth bounds
1261
    php_ort_status_flow(
66,432✔
1262
        (depth >= tensor->dimensions),
1263
        return 0,
1264
        php_ort_status_tensor_invalidshape_ce,
1265
        "depth %zd exceeds tensor dimensions %zd", depth, tensor->dimensions);
1266

1267
    // Validate shape at current depth
1268
    php_ort_status_flow(
66,432✔
1269
        (tensor->shape[depth] <= 0),
1270
        return 0,
1271
        php_ort_status_tensor_invalidshape_ce,
1272
        "invalid shape at dimension %zd: %zd", depth, tensor->shape[depth]);
1273

1274
    // Calculate remaining elements from current offset
1275
    size_t remaining_elements = (*offset < tensor->elements) ? (tensor->elements - *offset) : 0;
66,432✔
1276
    
1277
    // Calculate how many elements we can actually extract at this depth
1278
    int64_t elements_to_extract = tensor->shape[depth];
66,432✔
1279
    if (depth == tensor->dimensions - 1) {
66,432✔
1280
        // At leaf level, limit to remaining elements
1281
        elements_to_extract = (remaining_elements < (size_t)tensor->shape[depth]) ? 
64,000✔
1282
                             (int64_t)remaining_elements : tensor->shape[depth];
64,000✔
1283
    }
1284

1285
    // Initialize array with actual size we'll extract
1286
    array_init_size(node, elements_to_extract);
66,432✔
1287

1288
    if (depth == tensor->dimensions - 1) {
66,432✔
1289
        // Leaf level - extract scalar values
1290
        for (int64_t i = 0; i < elements_to_extract; i++) {
13,091,024✔
1291
            zval val;
13,027,024✔
1292
            
1293
            // Validate offset bounds before accessing data
1294
            php_ort_status_flow(
13,027,024✔
1295
                (*offset >= tensor->elements),
1296
                return 0,
1297
                php_ort_status_tensor_invaliddata_ce,
1298
                "data offset %zd exceeds tensor element count %zd", *offset, tensor->elements);
1299

1300
            void *source = (char *)tensor->data + ((*offset) * size);
13,027,024✔
1301

1302
            switch (tensor->type) {
13,027,024✔
1303
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
1,571,248✔
1304
                    ZVAL_DOUBLE(&val, *(float *)source);
1,571,248✔
1305
                    break;
1,571,248✔
1306

1307
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
1,980,928✔
1308
                    ZVAL_DOUBLE(&val, *(double *)source);
1,980,928✔
1309
                    break;
1,980,928✔
1310

1311
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
1,415,872✔
1312
                    ZVAL_LONG(&val, *(int8_t *)source);
1,415,872✔
1313
                    break;
1,415,872✔
1314

1315
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
1,342,432✔
1316
                    ZVAL_LONG(&val, *(int16_t *)source);
1,342,432✔
1317
                    break;
1,342,432✔
1318

1319
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
1,343,088✔
1320
                    ZVAL_LONG(&val, *(int32_t *)source);
1,343,088✔
1321
                    break;
1,343,088✔
1322

1323
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
1,344,736✔
1324
                    ZVAL_LONG(&val, *(int64_t *)source);
1,344,736✔
1325
                    break;
1,344,736✔
1326

1327
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
1,364,512✔
1328
                    ZVAL_LONG(&val, *(uint8_t *)source);
1,364,512✔
1329
                    break;
1,364,512✔
1330

1331
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
1,331,808✔
1332
                    ZVAL_LONG(&val, *(uint16_t *)source);
1,331,808✔
1333
                    break;
1,331,808✔
1334

1335
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
1,331,808✔
1336
                    ZVAL_LONG(&val, *(uint32_t *)source);
1,331,808✔
1337
                    break;
1,331,808✔
1338

1339
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
1340
                    // UINT64 is not supported - this should not be reached
1341
                    php_ort_status_flow(
×
1342
                        !SUCCESS,
1343
                        {
1344
                            return 0;
1345
                        },
1346
                        php_ort_status_tensor_invalidtype_ce,
1347
                        "UINT64 tensor type is not supported (values exceed PHP integer range)");
1348

1349
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
1350
                    ZVAL_BOOL(&val, *(uint8_t *)source ? 1 : 0);
592✔
1351
                    break;
592✔
1352

1353
                default:
1354
                    php_ort_status_flow(
×
1355
                        !SUCCESS,
1356
                        {
1357
                            return 0;
1358
                        },
1359
                        php_ort_status_tensor_invaliddata_ce,
1360
                        "unsupported tensor data type: %d", tensor->type);
1361
            }
1362

1363
            php_ort_status_flow(
13,027,024✔
1364
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &val) == NULL),
1365
                return 0,
1366
                php_ort_status_tensor_invaliddata_ce,
1367
                "failed to update array at index %zd", i);
1368

1369
            (*offset)++;
13,027,024✔
1370
        }
1371
    } else {
1372
        // Recursive case - process sub-dimensions
1373
        // For non-leaf levels, we need to check if we have enough remaining elements
1374
        // to fill the expected sub-structures
1375
        for (int64_t i = 0; i < elements_to_extract; i++) {
49,280✔
1376
            // Check if we still have elements remaining before processing
1377
            if (*offset >= tensor->elements) {
46,880✔
1378
                break; // No more elements available
1379
            }
1380
            
1381
            zval child;
46,848✔
1382
            
1383
            php_ort_status_flow(
46,848✔
1384
                (!php_ort_tensor_data(tensor, offset, size, &child, depth + 1)),
1385
                return 0,
1386
                php_ort_status_tensor_invaliddata_ce,
1387
                "failed to extract data at dimension %zd, index %zd", depth, i);
1388

1389
            php_ort_status_flow(
46,848✔
1390
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &child) == NULL),
1391
                return 0,
1392
                php_ort_status_tensor_invaliddata_ce,
1393
                "failed to update array at dimension %zd, index %zd", depth, i);
1394
        }
1395
    }
1396

1397
    return 1;
1398
}
1399

1400
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getData_arginfo, 0, 0, IS_ARRAY, 0)
1401
    ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
1402
    ZEND_ARG_TYPE_INFO(0, depth,  IS_LONG, 0)
1403
ZEND_END_ARG_INFO()
1404

1405
PHP_METHOD(ONNX_Tensor, getData)
20,752✔
1406
{
1407
    php_ort_tensor_t* ort =
20,752✔
1408
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
20,752✔
1409
    zend_long offset = 0, depth = 0;
20,752✔
1410

1411
    ZEND_PARSE_PARAMETERS_START(0, 2)
20,752✔
1412
        Z_PARAM_OPTIONAL
20,752✔
1413
        Z_PARAM_LONG(offset)
21,104✔
1414
        Z_PARAM_LONG(depth)
576✔
1415
    ZEND_PARSE_PARAMETERS_END();
20,752✔
1416

1417
    php_ort_status_flow(
20,752✔
1418
        (offset < 0 || offset > ort->object->elements),
1419
        return,
1420
        php_ort_status_tensor_invaliddata_ce,
1421
        "offset %zd out of range [0, %zd]",
1422
        offset, ort->object->elements);
1423

1424
    // Special case for scalar tensors (0 dimensions)
1425
    if (ort->object->dimensions == 0) {
20,704✔
1426
        // For scalar tensors, check if depth parameter was provided
1427
        php_ort_status_flow(
1,040✔
1428
            (ZEND_NUM_ARGS() > 1 && depth != 0),
1429
            return,
1430
            php_ort_status_tensor_invalidshape_ce,
1431
            "depth parameter cannot be used with scalar tensors");
1432

1433
        // Handle offset parameter - for scalar tensors, offset 0 returns the value, any other offset returns empty array
1434
        if (offset > 0) {
1,024✔
1435
            array_init(return_value);
16✔
1436
            return;
16✔
1437
        }
1438
        
1439
        // For scalar tensors, return a single-element array with the scalar value
1440
        array_init_size(return_value, 1);
1,008✔
1441
        zval val;
1,008✔
1442
        
1443
        void *source = ort->object->data;
1,008✔
1444
        
1445
        switch (ort->object->type) {
1,008✔
1446
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
208✔
1447
                ZVAL_DOUBLE(&val, *(float *)source);
208✔
1448
                break;
208✔
1449

1450
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
208✔
1451
                ZVAL_DOUBLE(&val, *(double *)source);
208✔
1452
                break;
208✔
1453

1454
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
32✔
1455
                ZVAL_LONG(&val, *(int8_t *)source);
32✔
1456
                break;
32✔
1457

1458
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
32✔
1459
                ZVAL_LONG(&val, *(int16_t *)source);
32✔
1460
                break;
32✔
1461

1462
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
32✔
1463
                ZVAL_LONG(&val, *(int32_t *)source);
32✔
1464
                break;
32✔
1465

1466
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
336✔
1467
                ZVAL_LONG(&val, *(int64_t *)source);
336✔
1468
                break;
336✔
1469

1470
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
32✔
1471
                ZVAL_LONG(&val, *(uint8_t *)source);
32✔
1472
                break;
32✔
1473

1474
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
32✔
1475
                ZVAL_LONG(&val, *(uint16_t *)source);
32✔
1476
                break;
32✔
1477

1478
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
32✔
1479
                ZVAL_LONG(&val, *(uint32_t *)source);
32✔
1480
                break;
32✔
1481

1482
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
1483
                // UINT64 is not supported - this should not be reached
1484
                php_ort_status_flow(
×
1485
                    !SUCCESS,
1486
                    return,
1487
                    php_ort_status_tensor_invalidtype_ce,
1488
                    "UINT64 tensor type is not supported (values exceed PHP integer range)");
1489

1490
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
1491
                ZVAL_BOOL(&val, *(uint8_t *)source ? 1 : 0);
64✔
1492
                break;
64✔
1493

1494
            default:
1495
                php_ort_status_flow(
×
1496
                    !SUCCESS,
1497
                    return,
1498
                    php_ort_status_tensor_invaliddata_ce,
1499
                    "unsupported tensor data type: %d", ort->object->type);
1500
        }
1501
        
1502
        add_index_zval(return_value, 0, &val);
1,008✔
1503
        return;
1,008✔
1504
    }
1505

1506
    // Normal case for non-scalar tensors
1507
    php_ort_status_flow(
19,664✔
1508
        (depth < 0 || depth >= ort->object->dimensions),
1509
        return,
1510
        php_ort_status_tensor_invalidshape_ce,
1511
        "depth %zd out of range [0, %zd)",
1512
        depth, ort->object->dimensions);
1513

1514
    php_ort_status_flow(
39,168✔
1515
        (!php_ort_tensor_data(
1516
            ort->object, 
1517
            &offset,
1518
            php_ort_tensor_sizeof(ort->object), 
1519
            return_value, 
1520
            (size_t)depth)),
1521
        return,
1522
        php_ort_status_tensor_invaliddata_ce,
1523
        "failed to extract tensor data starting at offset %zd, depth %zd", offset, depth);
1524
}
1525

1526
// Helper: Recursively infer shape from a PHP array
1527
// Robust recursive shape inference with raggedness and type checks
1528
static zend_bool php_ort_infer_shape(zval *data, size_t *shape, size_t max, size_t *dimensions) {
560✔
1529
    size_t dimension = 0;
560✔
1530
    zval *level = data;
560✔
1531
    while (Z_TYPE_P(level) == IS_ARRAY) {
832✔
1532
        php_ort_status_flow(
832✔
1533
            (dimension >= max),
1534
            return 0,
1535
            php_ort_status_tensor_invaliddata_ce,
1536
            "shape exceeds maximum allowed dimensions (%zd)", max);
1537

1538
        size_t len = zend_hash_num_elements(Z_ARRVAL_P(level));
832✔
1539

1540
        php_ort_status_flow(
832✔
1541
            len == 0,
1542
            return 0,
1543
            php_ort_status_tensor_invaliddata_ce,
1544
            "empty array encountered at dimension %zd (ragged or empty tensor)",
1545
            dimension);
1546

1547
        shape[dimension] = len;
800✔
1548

1549
        // Check all elements at this level are arrays or all are scalars
1550
        zend_bool found_array = 0, found_scalar = 0;
800✔
1551
        zval *first = NULL;
800✔
1552
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(level), first) { 
800✔
1553
            break; 
1554
        } ZEND_HASH_FOREACH_END();
1555
        
1556
        if (first && Z_TYPE_P(first) == IS_ARRAY) {
800✔
1557
            found_array = 1;
1558
        } else {
1559
            found_scalar = 1;
1560
        }
1561
        
1562
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(level), zval *sub) {
102,128✔
1563
            if (Z_TYPE_P(sub) == IS_ARRAY) {
101,360✔
1564
                found_array = 1;
1,296✔
1565
                php_ort_status_flow(
1,296✔
1566
                    (zend_hash_num_elements(Z_ARRVAL_P(sub)) !=
1567
                        zend_hash_num_elements(Z_ARRVAL_P(first))),
1568
                    return 0,
1569
                    php_ort_status_tensor_invaliddata_ce,
1570
                    "ragged array: sub-array at dimension %zd has length %zd, expected %zd",
1571
                    dimension+1,
1572
                    zend_hash_num_elements(Z_ARRVAL_P(sub)),
1573
                    zend_hash_num_elements(Z_ARRVAL_P(first)));
1574
            } else {
1575
                found_scalar = 1;
1576
            }
1577

1578
            php_ort_status_flow(
101,328✔
1579
                (Z_TYPE_P(sub) != IS_ARRAY &&
1580
                Z_TYPE_P(sub) != IS_LONG &&
1581
                Z_TYPE_P(sub) != IS_DOUBLE &&
1582
                !(Z_TYPE_P(sub) == IS_TRUE || Z_TYPE_P(sub) == IS_FALSE)),
1583
                return 0,
1584
                php_ort_status_tensor_invaliddata_ce,
1585
                "unsupported type at dimension %zd: %s",
1586
                dimension+1,
1587
                zend_zval_type_name(sub));
1588

1589
            php_ort_status_flow(
101,328✔
1590
                (found_array && found_scalar),
1591
                return 0,
1592
                php_ort_status_tensor_invaliddata_ce,
1593
                "mixed array/scalar types at dimension %zd (ragged tensor)",
1594
                dimension+1);
1595
        } ZEND_HASH_FOREACH_END();
1596
        if (found_array) {
768✔
1597
            // Go one level deeper
1598
            level = first;
272✔
1599
            dimension++;
272✔
1600
        } else {
1601
            // All scalars at this level, this is the last dimension
1602
            dimension++;
496✔
1603
            break;
496✔
1604
        }
1605
    }
1606
    *dimensions = dimension;
496✔
1607
    return 1;
496✔
1608
}
1609

1610
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_persistent_from_arginfo, 0, 3, ORT\\Tensor, 0)
1611
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
1612
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
1613
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
1614
ZEND_END_ARG_INFO()
1615

1616
PHP_METHOD(ONNX_Tensor_Persistent, from)
112✔
1617
{
1618
    zend_string* name;
112✔
1619
    zval *data;
112✔
1620
    zend_long type;
112✔
1621
    size_t shape[32];
112✔
1622
    size_t dimensions = 0;
112✔
1623

1624
    ZEND_PARSE_PARAMETERS_START(3, 3)
112✔
1625
        Z_PARAM_STR(name)
224✔
1626
        Z_PARAM_ARRAY(data)
224✔
1627
        Z_PARAM_LONG(type)
224✔
1628
    ZEND_PARSE_PARAMETERS_END();
112✔
1629

1630
    if (!php_ort_infer_shape(data, shape, 32, &dimensions)) {
112✔
1631
        return;
1632
    }
1633

1634
    php_ort_status_flow(
80✔
1635
        (dimensions == 0),
1636
        return,
1637
        php_ort_status_tensor_invaliddata_ce,
1638
        "empty tensor data provided (no dimensions inferred)");
1639

1640
    zval param;
80✔
1641
    array_init_size(&param, dimensions);
80✔
1642
    for (size_t i = 0; i < dimensions; i++) {
224✔
1643
        add_next_index_long(&param, shape[i]);
144✔
1644
    }
1645

1646
    zend_class_entry *scope =
80✔
1647
        zend_get_executed_scope();
80✔
1648
    object_init_ex(return_value, scope);
80✔
1649

1650
    zval params[4];
80✔
1651
    ZVAL_STR(&params[0], name);
80✔
1652
    ZVAL_ARR(&params[1], Z_ARRVAL(param));
80✔
1653
    ZVAL_ARR(&params[2], Z_ARRVAL_P(data));
80✔
1654
    ZVAL_LONG(&params[3], type);
80✔
1655

1656
    zval constructor;
80✔
1657
    ZVAL_STRING(&constructor, "__construct");
80✔
1658
    zval retval;
80✔
1659
    if (SUCCESS == call_user_function(
80✔
1660
            EG(function_table),
1661
            return_value,
1662
            &constructor,
1663
            &retval,
1664
            4,
1665
            params)) {
1666
        zval_ptr_dtor(&retval);
80✔
1667
    } else {
1668
        zval_ptr_dtor(return_value);
×
1669
    }
1670

1671
    zval_ptr_dtor(&constructor);
80✔
1672
    zval_ptr_dtor(&param);
80✔
1673
}
1674

1675
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_transient_from_arginfo, 0, 2, ORT\\Tensor, 0)
1676
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
1677
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
1678
ZEND_END_ARG_INFO()
1679

1680
PHP_METHOD(ONNX_Tensor_Transient, from)
448✔
1681
{
1682
    zval *data;
448✔
1683
    zend_long type;
448✔
1684
    size_t shape[32];
448✔
1685
    size_t dimensions = 0;
448✔
1686

1687
    ZEND_PARSE_PARAMETERS_START(2, 2)
448✔
1688
        Z_PARAM_ARRAY(data)
896✔
1689
        Z_PARAM_LONG(type)
896✔
1690
    ZEND_PARSE_PARAMETERS_END();
448✔
1691

1692
    if (!php_ort_infer_shape(data, shape, 32, &dimensions)) {
448✔
1693
        return;
1694
    }
1695

1696
    php_ort_status_flow(
416✔
1697
        (dimensions == 0),
1698
        return,
1699
        php_ort_status_tensor_invaliddata_ce,
1700
        "empty tensor data provided (no dimensions inferred)");
1701

1702
    zval param;
416✔
1703
    array_init_size(&param, dimensions);
416✔
1704
    for (size_t i = 0; i < dimensions; i++) {
1,040✔
1705
        add_next_index_long(&param, shape[i]);
624✔
1706
    }
1707

1708
    zend_class_entry *scope =
416✔
1709
        zend_get_executed_scope();
416✔
1710
    object_init_ex(return_value, scope);
416✔
1711

1712
    zval params[3];
416✔
1713
    ZVAL_ARR(&params[0], Z_ARRVAL(param));
416✔
1714
    ZVAL_ARR(&params[1], Z_ARRVAL_P(data));
416✔
1715
    ZVAL_LONG(&params[2], type);
416✔
1716

1717
    zval constructor;
416✔
1718
    ZVAL_STRING(&constructor, "__construct");
416✔
1719
    zval retval;
416✔
1720
    if (SUCCESS == call_user_function(
416✔
1721
            EG(function_table),
1722
            return_value,
1723
            &constructor,
1724
            &retval,
1725
            3,
1726
            params)) {
1727
        zval_ptr_dtor(&retval);
416✔
1728
    } else {
1729
        zval_ptr_dtor(return_value);
×
1730
    }
1731

1732
    zval_ptr_dtor(&constructor);
416✔
1733
    zval_ptr_dtor(&param);
416✔
1734
}
1735

1736
zend_function_entry php_ort_tensor_interface_methods[] = {
1737
    PHP_ABSTRACT_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo)
1738
    PHP_ABSTRACT_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo)
1739
    PHP_ABSTRACT_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo)
1740
    PHP_ABSTRACT_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo)
1741
    PHP_ABSTRACT_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo)
1742
    PHP_ABSTRACT_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo)
1743
    PHP_ABSTRACT_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo)
1744

1745
    PHP_ABSTRACT_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo)
1746
    PHP_FE_END
1747
};
1748

1749
zend_function_entry php_ort_tensor_persistent_methods[] = {
1750
    PHP_ME(ONNX_Tensor_Persistent, __construct,
1751
        php_ort_tensor_persistent_construct_arginfo, ZEND_ACC_PUBLIC)
1752
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1753
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1754
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1755
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1756
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1757
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1758
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1759
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1760

1761
    PHP_ME(ONNX_Tensor_Persistent, from,
1762
        php_ort_tensor_persistent_from_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)    PHP_FE_END
1763
};
1764

1765
zend_function_entry php_ort_tensor_transient_methods[] = {
1766
    PHP_ME(ONNX_Tensor_Transient, __construct,
1767
        php_ort_tensor_transient_construct_arginfo, ZEND_ACC_PUBLIC)
1768
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1769
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1770
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1771
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1772
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1773
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1774
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1775
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1776

1777
    PHP_ME(ONNX_Tensor_Transient, from,
1778
        php_ort_tensor_transient_from_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1779
    PHP_FE_END
1780
};
1781

1782
zend_object* php_ort_tensor_create(zend_class_entry *type) {
59,888✔
1783
    php_ort_tensor_t *ort = ecalloc(1,
59,888✔
1784
        sizeof(php_ort_tensor_t) + zend_object_properties_size(type));
1785

1786
    zend_object_std_init(&ort->std, type);
59,888✔
1787

1788
    ort->std.handlers = &php_ort_tensor_handlers;
59,888✔
1789
    ort->object       = NULL;
59,888✔
1790

1791
    return &ort->std;
59,888✔
1792
}
1793

1794
static HashTable* php_ort_tensor_debug(zend_object *zo, int *temp) {
208✔
1795
    php_ort_tensor_t *ort = php_ort_tensor_fetch(zo);
208✔
1796
    HashTable *debug;
208✔
1797

1798
    ALLOC_HASHTABLE(debug);
208✔
1799
    zend_hash_init(debug, 3, NULL, ZVAL_PTR_DTOR, 0);
208✔
1800

1801
    if (!ort->object) {
208✔
1802
        goto __php_ort_tensor_debug_return;
32✔
1803
    }
1804

1805
    zval persistent;
176✔
1806

1807
    ZVAL_BOOL(&persistent,
176✔
1808
        ort->object->owner == PHP_ORT_OWN_HEAP);
1809
    zend_hash_str_add(debug, 
176✔
1810
        "persistent", sizeof("persistent")-1, 
1811
        &persistent);
1812

1813
    zval type;
176✔
1814
    
1815
    ZVAL_LONG(&type, ort->object->type);
176✔
1816
    zend_hash_add(debug,
176✔
1817
        ZSTR_KNOWN(ZEND_STR_TYPE), &type);
176✔
1818

1819
    if (ort->object->name) {
176✔
1820
        zval name;
176✔
1821

1822
        ZVAL_STR_COPY(&name, ort->object->name);
176✔
1823
        zend_hash_add(debug,
176✔
1824
            ZSTR_KNOWN(ZEND_STR_NAME), &name);
176✔
1825
    }
1826

1827
    zval shape;
176✔
1828

1829
    array_init(&shape);
176✔
1830
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
480✔
1831
        add_next_index_long(
304✔
1832
            &shape, ort->object->shape[dimension]);
304✔
1833
    }
1834
    zend_hash_str_add(debug,
176✔
1835
        "shape", sizeof("shape")-1, &shape);
1836

1837
__php_ort_tensor_debug_return:
208✔
1838
    *temp = 1;
208✔
1839

1840
    return debug;
208✔
1841
}
1842

1843
static void php_ort_tensor_write(zend_object* object, zval* offset, zval* value) {
16✔
1844
    php_ort_status_flow(!SUCCESS, {
16✔
1845
        return;
1846
    },
1847
    php_ort_status_tensor_invalidaccess_ce,
1848
    "Tensors are immutable, illegal write operation cannot be performed");
1849
}
1850

1851
static zval* php_ort_tensor_read(zend_object* object, zval* offset, int type, zval* rv) {
64✔
1852
    php_ort_tensor_t *ort = php_ort_tensor_fetch(object);
64✔
1853
    zend_long index;
64✔
1854

1855
    if (Z_TYPE_P(offset) != IS_LONG) {
64✔
1856
        php_ort_status_flow(!SUCCESS, {
16✔
1857
                ZVAL_UNDEF(rv);
1858
                return rv;
1859
            },
1860
            php_ort_status_tensor_invalidoffset_ce,
1861
            "Tensor element must be an integer");
1862
        return NULL;
1863
    }
1864

1865
    index = Z_LVAL_P(offset);
48✔
1866

1867
    if (index < 0) {
48✔
1868
        // Negative index means offset from the end
1869

1870
        index += ort->object->elements;
16✔
1871
    }
1872

1873
    php_ort_status_flow(
48✔
1874
        (index >= ort->object->elements),
1875
        {
1876
            ZVAL_UNDEF(rv);
1877
            return rv;
1878
        },
1879
        php_ort_status_tensor_invalidoffset_ce,
1880
        "Tensor element %zd out of range [0, %zd]",
1881
        index, ort->object->elements);
1882

1883
    switch (ort->object->type) {
32✔
1884
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
×
1885
            ZVAL_DOUBLE(rv,
×
1886
                ((float*)ort->object->data)[index]);
1887
        break;
×
1888

1889
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
×
1890
            ZVAL_DOUBLE(rv,
×
1891
                ((double*)ort->object->data)[index]);
1892
            break;
×
1893

1894
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
×
1895
            ZVAL_LONG(rv,
×
1896
                ((int8_t*)ort->object->data)[index]);
1897
            break;
×
1898
        
1899
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
×
1900
            ZVAL_LONG(rv,
×
1901
                ((int16_t*)ort->object->data)[index]);
1902
            break;
×
1903

1904
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
32✔
1905
            ZVAL_LONG(rv,
32✔
1906
                ((int32_t*)ort->object->data)[index]);
1907
            break;
32✔
1908

1909
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
×
1910
            ZVAL_LONG(rv,
×
1911
                ((int64_t*)ort->object->data)[index]);
1912
            break;
×
1913

1914
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
×
1915
            ZVAL_LONG(rv,
×
1916
                ((uint8_t*)ort->object->data)[index]);
1917
            break;
×
1918
        
1919
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
×
1920
            ZVAL_LONG(rv,
×
1921
                ((uint16_t*)ort->object->data)[index]);
1922
            break;
×
1923
        
1924
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
×
1925
            ZVAL_LONG(rv,
×
1926
                ((uint32_t*)ort->object->data)[index]);
1927
            break;
×
1928

1929
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
×
1930
            ZVAL_BOOL(rv,
×
1931
                ((zend_bool*)ort->object->data)[index] ? 1 : 0);
1932
            break;
×
1933

1934
        default: {
1935
            /* unreachable */
1936
        }
1937
            
1938
    }
1939
    return rv;
1940
}
1941

1942
static zend_result php_ort_tensor_count(zend_object* object, zend_long *count) {
16✔
1943
    php_ort_tensor_t *ort =
16✔
1944
        php_ort_tensor_fetch(object);
16✔
1945
    *count =
16✔
1946
        ort->object->elements;
16✔
1947
    return SUCCESS;
16✔
1948
}
1949

1950
void php_ort_tensor_destroy(zend_object *o) {
59,888✔
1951
    php_ort_tensor_t *ort =
59,888✔
1952
        php_ort_tensor_fetch(o);
59,888✔
1953

1954
    ort_tensor_release(ort->object);
59,888✔
1955

1956
    zend_object_std_dtor(o);
59,888✔
1957
}
59,888✔
1958

1959
PHP_MINIT_FUNCTION(ORT_TENSOR)
2,784✔
1960
{
1961
    zend_class_entry ce;
2,784✔
1962

1963
#ifdef ZTS
1964
    php_ort_tensor_mutex = tsrm_mutex_alloc();
1965
#endif
1966

1967
    zend_hash_init(&php_ort_tensors, 16, NULL, php_ort_tensor_del, 1);
2,784✔
1968

1969
    // Setup shared handlers for all tensor types
1970
    memcpy(&php_ort_tensor_handlers,
2,784✔
1971
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
1972

1973
    php_ort_tensor_handlers.offset = XtOffsetOf(php_ort_tensor_t, std);
2,784✔
1974
    php_ort_tensor_handlers.get_debug_info = php_ort_tensor_debug;
2,784✔
1975
    php_ort_tensor_handlers.free_obj = php_ort_tensor_destroy;
2,784✔
1976
    php_ort_tensor_handlers.read_dimension = php_ort_tensor_read;
2,784✔
1977
    php_ort_tensor_handlers.write_dimension = php_ort_tensor_write;
2,784✔
1978
    php_ort_tensor_handlers.count_elements = php_ort_tensor_count;
2,784✔
1979
    php_ort_tensor_handlers.clone_obj = NULL;
2,784✔
1980

1981
    // Register the interface
1982
    INIT_NS_CLASS_ENTRY(ce, "ORT", "Tensor", php_ort_tensor_interface_methods);
2,784✔
1983
    php_ort_tensor_interface_ce = zend_register_internal_interface(&ce);
2,784✔
1984

1985
    // Register persistent tensor class
1986
    INIT_NS_CLASS_ENTRY(ce, "ORT\\Tensor", "Persistent", php_ort_tensor_persistent_methods);
2,784✔
1987
    php_ort_tensor_persistent_ce = zend_register_internal_class(&ce);
2,784✔
1988
    php_ort_tensor_persistent_ce->create_object = php_ort_tensor_create;
2,784✔
1989
    zend_class_implements(php_ort_tensor_persistent_ce, 1, php_ort_tensor_interface_ce);
2,784✔
1990

1991
    // Register transient tensor class
1992
    INIT_NS_CLASS_ENTRY(ce, "ORT\\Tensor", "Transient", php_ort_tensor_transient_methods);
2,784✔
1993
    php_ort_tensor_transient_ce = zend_register_internal_class(&ce);
2,784✔
1994
    php_ort_tensor_transient_ce->create_object = php_ort_tensor_create;
2,784✔
1995
    zend_class_implements(php_ort_tensor_transient_ce, 1, php_ort_tensor_interface_ce);
2,784✔
1996

1997
#ifdef ZEND_ACC_NOT_SERIALIZABLE
1998
    php_ort_tensor_persistent_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
2,784✔
1999
    php_ort_tensor_transient_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
2,784✔
2000
#else
2001
    php_ort_tensor_persistent_ce->serialize = zend_class_serialize_deny;
2002
    php_ort_tensor_persistent_ce->unserialize = zend_class_unserialize_deny;
2003
    php_ort_tensor_transient_ce->serialize = zend_class_serialize_deny;
2004
    php_ort_tensor_transient_ce->unserialize = zend_class_unserialize_deny;
2005
#endif
2006

2007
    // Register constants on the interface
2008
    zend_declare_class_constant_long(
2,784✔
2009
        php_ort_tensor_interface_ce,
2010
        "UNDEFINED", sizeof("UNDEFINED")-1,
2011
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED);
2012

2013
    zend_declare_class_constant_long(
2,784✔
2014
        php_ort_tensor_interface_ce,
2015
        "FLOAT", sizeof("FLOAT")-1,
2016
        ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
2017

2018
    zend_declare_class_constant_long(
2,784✔
2019
        php_ort_tensor_interface_ce,
2020
        "DOUBLE", sizeof("DOUBLE")-1,
2021
        ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE);
2022

2023
    zend_declare_class_constant_long(
2,784✔
2024
        php_ort_tensor_interface_ce,
2025
        "UINT8", sizeof("UINT8")-1,
2026
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8);
2027

2028
    zend_declare_class_constant_long(
2,784✔
2029
        php_ort_tensor_interface_ce,
2030
        "INT8", sizeof("INT8")-1,
2031
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8);
2032

2033
    zend_declare_class_constant_long(
2,784✔
2034
        php_ort_tensor_interface_ce,
2035
        "UINT16", sizeof("UINT16")-1,
2036
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16);
2037

2038
    zend_declare_class_constant_long(
2,784✔
2039
        php_ort_tensor_interface_ce,
2040
        "INT16", sizeof("INT16")-1,
2041
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16);
2042

2043
    zend_declare_class_constant_long(
2,784✔
2044
        php_ort_tensor_interface_ce,
2045
        "UINT32", sizeof("UINT32")-1,
2046
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32);
2047

2048
    zend_declare_class_constant_long(
2,784✔
2049
        php_ort_tensor_interface_ce,
2050
        "INT32", sizeof("INT32")-1,
2051
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32);
2052

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

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

2063
    return SUCCESS;
2,784✔
2064
}
2065

2066
PHP_MSHUTDOWN_FUNCTION(ORT_TENSOR)
2,784✔
2067
{
2068
    zend_hash_destroy(&php_ort_tensors);
2,784✔
2069

2070
#ifdef ZTS
2071
    tsrm_mutex_free(php_ort_tensor_mutex);
2072
#endif
2073

2074
    return SUCCESS;
2,784✔
2075
}
2076

UNCOV
2077
PHP_RINIT_FUNCTION(ORT_TENSOR)
×
2078
{
UNCOV
2079
    return SUCCESS;
×
2080
}
2081

UNCOV
2082
PHP_RSHUTDOWN_FUNCTION(ORT_TENSOR)
×
2083
{
UNCOV
2084
    return SUCCESS;
×
2085
}
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