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

krakjoe / ort / 16390146533

19 Jul 2025 03:31PM UTC coverage: 93.135% (-0.02%) from 93.152%
16390146533

push

github

krakjoe
skipif

5644 of 6060 relevant lines covered (93.14%)

119633.27 hits per line

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

94.44
/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) {
8,042,944✔
39
    if (Z_TYPE_P(node) == IS_OBJECT &&
8,042,944✔
40
            instanceof_function(
8,042,944✔
41
                Z_OBJCE_P(node), php_ort_generator_interface_ce)) {
×
42
        return 1;
×
43
    }
44

45
    if (depth == rank) {
8,042,944✔
46
        // Leaf node — must match scalar type
47

48
        switch (type) {
7,953,120✔
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,885,248✔
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,987,472✔
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;
45,968✔
70
            
71
                default: php_ort_status_flow(!SUCCESS,
32✔
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) {
89,824✔
82
        return 0;
83
    }
84

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

90
    for (zend_long i = 0; i < dimensions[depth]; i++) {
8,100,528✔
91
        zval *child = zend_hash_index_find(ht, i);
8,010,928✔
92
        if (!child || !php_ort_tensor_validate_next(
8,010,928✔
93
                type, rank, dimensions, child, depth + 1)) {
94
            return 0;
176✔
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) {
32,400✔
102
    // Skip generator validation
103
    if (Z_TYPE_P(data) == IS_OBJECT && 
32,400✔
104
            instanceof_function(
64,336✔
105
                Z_OBJCE_P(data), php_ort_generator_interface_ce)) {
48✔
106
        return 1;
107
    }
108

109
    zend_long dimensions[16];
32,352✔
110
    zend_long rank = 0;
32,352✔
111
    
112
    rank = zend_hash_num_elements(Z_ARRVAL_P(shape));
32,352✔
113
    
114
    // Handle scalar tensor case (rank 0, empty shape array)
115
    if (rank == 0) {
32,352✔
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(
32,128✔
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;
32,128✔
184
    zval *next;
32,128✔
185
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), next) {
71,504✔
186
        php_ort_status_flow(
39,472✔
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(
39,376✔
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);
39,360✔
200
    } ZEND_HASH_FOREACH_END();
201

202
    php_ort_status_flow(
32,016✔
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,961,392✔
212
    // Enter into generator objects
213
    if (Z_TYPE_P(node) == IS_OBJECT && 
7,961,392✔
214
            instanceof_function(
7,965,536✔
215
                Z_OBJCE_P(node), php_ort_generator_interface_ce)) {
4,144✔
216
        php_ort_generator_invoke(
4,144✔
217
            node, type, target);
218
        return;
4,144✔
219
    }
220

221
    switch (type) {
7,957,248✔
222
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
223
            *(float *)target = Z_TYPE_P(node) == IS_DOUBLE
1,073,072✔
224
                ? (float)Z_DVAL_P(node)
202,592✔
225
                : (float)Z_LVAL_P(node);
1,073,072✔
226
            break;
1,073,072✔
227

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

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

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

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

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

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

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

258
        case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
844,608✔
259
            *(uint32_t *)target = (uint32_t)Z_LVAL_P(node);
844,608✔
260
            break;
844,608✔
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,008✔
271
            break;
23,008✔
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) {
8,015,168✔
282
    // Special case for generators
283
    if (Z_TYPE_P(node) == IS_OBJECT &&
8,015,168✔
284
            instanceof_function(
16,030,560✔
285
                Z_OBJCE_P(node), php_ort_generator_interface_ce)) {
4,384✔
286
        return node;
287
    }
288

289
    // Special case for scalar tensor (0 dimensions)
290
    if (tensor->dimensions == 0) {
8,010,784✔
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) {
8,010,592✔
301
        return NULL;
302
    }
303

304
    return zend_hash_index_find(Z_ARRVAL_P(node), idx);
8,010,592✔
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,047,072✔
308
    // Scalar tensors have 0 dimensions
309
    if (tensor->dimensions == 0) {        
8,047,072✔
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,046,848✔
326
        void *target = (char *)tensor->data + ((*offset) * size);
7,957,024✔
327

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

335
    for (int64_t i = 0; i < tensor->shape[depth]; i++) {
8,104,768✔
336
        zval *child = php_ort_tensor_flatten_next(tensor, node, i);
8,014,944✔
337
        if (!php_ort_tensor_flatten(tensor, offset, size, child, depth + 1)) {
8,014,944✔
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,344✔
346
    size_t i = 0, offset = 0;
1,344✔
347
    size_t size;
1,344✔
348

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

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

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

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

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

380
static zend_always_inline zend_bool php_ort_tensor_allocate_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type) {
30,784✔
381
    size_t i = 0, offset = 0;
30,784✔
382
    size_t size;
30,784✔
383

384
    tensor->name       = NULL;
30,784✔
385
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
30,784✔
386
    tensor->type       = type;
30,784✔
387
    tensor->owner      = PHP_ORT_OWN_ZEND;
30,784✔
388

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

402
    // Copy shape and compute total number of elements
403
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), zval *dim) {
67,248✔
404
        int64_t size = (int64_t)Z_LVAL_P(dim);
36,656✔
405
        tensor->shape[i++] = size;
36,656✔
406
        tensor->elements *= size;
36,656✔
407
    } ZEND_HASH_FOREACH_END();
408

409
    tensor->data = ort_alloc(
61,184✔
410
        php_ort_tensor_sizeof(tensor), tensor->elements);
411

412
    return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
61,184✔
413
}
414

415
static void ort_tensor_free(ort_tensor_t *tensor) {
56,960✔
416
    zend_bool persistent = 
56,960✔
417
        (tensor->owner == PHP_ORT_OWN_HEAP) ? 1 : 0;
56,960✔
418

419
    if (tensor->shape) {
56,960✔
420
        pefree(tensor->shape, persistent);
56,016✔
421
    }
422

423
#ifdef HAVE_ONNXRUNTIME
424
    if (!tensor->parent && tensor->data && !tensor->value) {
56,960✔
425
        ort_free(tensor->data);
56,864✔
426
    }
427

428
    if (tensor->value) {
56,960✔
429
        api->ReleaseValue(tensor->value);
16✔
430
    }
431
#else
432
    if (!tensor->parent && tensor->data) {
433
        ort_free(tensor->data);
434
    }
435
#endif
436

437
    if (tensor->name && !persistent) {
56,960✔
438
        zend_string_free(tensor->name);
24,672✔
439
    }
440

441
    if (tensor->parent) {
56,960✔
442
        ort_tensor_release(tensor->parent);
80✔
443
    }
444

445
    pefree(tensor, persistent);
56,960✔
446
}
56,960✔
447

448
void ort_tensor_release(ort_tensor_t *tensor) {
59,184✔
449
    if (!tensor) {
59,184✔
450
        return;
451
    }
452

453
    if (php_ort_atomic_delref(&tensor->refcount) == 0){
58,416✔
454
        ort_tensor_free(tensor);
56,960✔
455
    }
456
}
457

458
static void php_ort_tensor_del(zval *zv) {
1,344✔
459
    ort_tensor_release(
1,344✔
460
        ((ort_tensor_t*)
461
            Z_PTR_P(zv)));
1,344✔
462
}
1,344✔
463

464
static zend_bool php_ort_tensor_construct_persistent(ort_tensor_t *tensor, zend_string *name, zval *shape, zval *data, ONNXTensorElementDataType type){
1,472✔
465
    if (!php_ort_tensor_validate(shape, name, data, type)) {
2,944✔
466
        return 0;
128✔
467
    }
468

469
    if (!php_ort_tensor_allocate_persistent(tensor, name, shape, data, type)) {
2,688✔
470
        return 0;
471
    }
472

473
    return 1;
474
}
475

476
static zend_bool php_ort_tensor_construct_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type){
30,928✔
477
    if (!php_ort_tensor_validate(shape, NULL, data, type)) {
61,856✔
478
        return 0;
144✔
479
    }
480

481
    if (!php_ort_tensor_allocate_transient(tensor, shape, data, type)) {
61,568✔
482
        return 0;
483
    }
484

485
    return 1;
486
}
487

488
#ifdef HAVE_ONNXRUNTIME
489
OrtValue* php_ort_tensor_value(php_ort_tensor_t* ort) {
32✔
490
    OrtMemoryInfo* mi;
32✔
491
    OrtValue* value = NULL;
32✔
492
    OrtStatus* status;
32✔
493

494
    php_ort_status_flow(
32✔
495
        (status = api->CreateCpuMemoryInfo(
496
            OrtArenaAllocator, OrtMemTypeDefault, &mi)),
497
        {
498
            api->ReleaseStatus(status);
499

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

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

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

524
    api->ReleaseMemoryInfo(mi);
32✔
525

526
    return value;
32✔
527
}
528

529
ort_tensor_t* php_ort_tensor_object(OrtValue* value) {
16✔
530
    ort_tensor_t  *tensor = pecalloc(1, sizeof(ort_tensor_t), 0);
16✔
531

532
    tensor->refcount = 1;
16✔
533
    tensor->owner    = PHP_ORT_OWN_ZEND;
16✔
534
    tensor->value    = value;
16✔
535

536
    OrtTensorTypeAndShapeInfo* otsi;
16✔
537

538
    php_ort_status_flow(
16✔
539
        api->GetTensorTypeAndShape(value, &otsi), {
540
            ort_tensor_free(tensor);
541

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

547
    php_ort_status_flow(
16✔
548
        api->GetTensorElementType(otsi, &tensor->type),
549
        {
550
            ort_tensor_free(tensor);
551

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

557
    php_ort_status_flow(
16✔
558
        api->GetDimensionsCount(otsi, &tensor->dimensions),
559
        {
560
            ort_tensor_free(tensor);
561

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

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

571
        php_ort_status_flow(
16✔
572
            api->GetDimensions(otsi, tensor->shape, tensor->dimensions),
573
            {
574
                ort_tensor_free(tensor);
575

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

585
    php_ort_status_flow(
16✔
586
        api->GetTensorShapeElementCount(otsi, &tensor->elements),
587
        {
588
            ort_tensor_free(tensor);
589

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

595
    php_ort_status_flow(
16✔
596
        api->GetTensorMutableData(value, &tensor->data),
597
        {
598
            ort_tensor_free(tensor);
599

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

605
    api->ReleaseTensorTypeAndShapeInfo(otsi);
16✔
606

607
    return tensor;
16✔
608
}
609
#endif
610

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

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

626
    return index;
627
}
628

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

636
PHP_METHOD(ONNX_Tensor_Persistent, __construct)
1,536✔
637
{
638
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
1,536✔
639

640
    zend_string *name;
1,536✔
641
    zval        *shape = NULL;
1,536✔
642
    zval        *data  = NULL;
1,536✔
643
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
1,536✔
644

645
    ZEND_PARSE_PARAMETERS_START(1, 4);
1,536✔
646
        Z_PARAM_STR(name)
3,072✔
647
        Z_PARAM_OPTIONAL
1,536✔
648
        Z_PARAM_ARRAY(shape)
3,008✔
649
        Z_PARAM_ZVAL(data)
1,472✔
650
        Z_PARAM_LONG(type)
2,928✔
651
    ZEND_PARSE_PARAMETERS_END();
1,536✔
652

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

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

687
        ort_tensor_t *tensor = 
1,472✔
688
            pecalloc(1, sizeof(ort_tensor_t), 1);
1,472✔
689

690
        tensor->refcount = 1;
1,472✔
691

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

703
        ort->object = zend_hash_add_ptr(
1,344✔
704
            &php_ort_tensors,
705
            tensor->name,
706
            tensor);
707

708
        ort->object->refcount++;
1,344✔
709
    } else {
710
        php_ort_atomic_addref(&ort->object->refcount);
32✔
711
    }
712

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

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

728
PHP_METHOD(ONNX_Tensor_Transient, __construct)
30,928✔
729
{
730
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
30,928✔
731

732
    zval        *shape = NULL;
30,928✔
733
    zval        *data  = NULL;
30,928✔
734
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
30,928✔
735

736
    ZEND_PARSE_PARAMETERS_START(2, 3);
30,928✔
737
        Z_PARAM_ARRAY(shape)
61,856✔
738
        Z_PARAM_ZVAL(data)
30,928✔
739
        Z_PARAM_OPTIONAL
30,928✔
740
        Z_PARAM_LONG(type)
61,824✔
741
    ZEND_PARSE_PARAMETERS_END();
30,928✔
742

743
    ort_tensor_t *tensor = ecalloc(1, sizeof(ort_tensor_t));
30,928✔
744
    tensor->refcount = 1;
30,928✔
745

746
    if (!php_ort_tensor_construct_transient(tensor, shape, data, type)) {
30,928✔
747
        efree(tensor);
144✔
748
        return;
144✔
749
    }
750

751
    ort->object = tensor;
30,784✔
752
}
753

754
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_isPersistent_arginfo, 0, 0, _IS_BOOL, 0)
755
ZEND_END_ARG_INFO()
756

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

762
    ZEND_PARSE_PARAMETERS_NONE();
16✔
763

764
    RETURN_BOOL(ort->object->owner == PHP_ORT_OWN_HEAP);
16✔
765
}
766

767
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getName_arginfo, 0, 0, IS_STRING, 1)
768
ZEND_END_ARG_INFO()
769

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

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

781
    RETURN_STR_COPY(ort->object->name);
48✔
782
}
783

784
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getType_arginfo, 0, 0, IS_LONG, 0)
785
ZEND_END_ARG_INFO()
786

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

792
    ZEND_PARSE_PARAMETERS_NONE();
880✔
793

794
    RETURN_LONG(ort->object->type);
880✔
795
}
796

797
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getTypeName_arginfo, 0, 0, IS_STRING, 0)
798
ZEND_END_ARG_INFO()
799

800
PHP_METHOD(ONNX_Tensor, getTypeName)
16,944✔
801
{
802
    php_ort_tensor_t* ort =
16,944✔
803
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
16,944✔
804

805
    ZEND_PARSE_PARAMETERS_NONE();
16,944✔
806

807
    /* @todo(krakjoe) this performs terribly ... */
808
    RETURN_STRING(php_ort_type_name(ort->object->type));
46,112✔
809
}
810

811
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getShape_arginfo, 0, 0, IS_ARRAY, 0)
812
ZEND_END_ARG_INFO()
813

814
PHP_METHOD(ONNX_Tensor, getShape)
17,504✔
815
{
816
    php_ort_tensor_t* ort =
17,504✔
817
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
17,504✔
818

819
    ZEND_PARSE_PARAMETERS_NONE();
17,504✔
820

821
    array_init(return_value);
17,504✔
822
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
36,256✔
823
        add_next_index_long(
18,752✔
824
            return_value, ort->object->shape[dimension]);
18,752✔
825
    }
826
}
827

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1027
    php_ort_tensor_transpose(ort->object, axis, return_value);
64✔
1028
}
1029

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

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

1055
    object_init_ex(return_value, php_ort_tensor_transient_ce);
80✔
1056

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

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

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

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

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

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

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

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

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

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

1154
            starting[idim] = istart;
16✔
1155
            ending[idim]   = iend;
16✔
1156
            slicing[idim]  = iend - istart;
16✔
1157

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

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

1179
            int64_t istart = Z_LVAL_P(zstart);
96✔
1180
            int64_t iend = Z_LVAL_P(zend);
96✔
1181

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

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

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

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

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

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

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

1222
    pefree(starting, 0);
48✔
1223
    pefree(ending, 0);
48✔
1224

1225
    return ort->object;
48✔
1226
}
1227

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

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

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

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

1250
static inline zend_bool php_ort_tensor_data(ort_tensor_t *tensor, size_t *offset, size_t size, zval *node, size_t depth) {
65,632✔
1251
    // Special case for scalar tensors (0 dimensions)
1252
    if (tensor->dimensions == 0) {
65,632✔
1253
        // This should not be called for scalar tensors
1254
        // The getData method directly handles scalar case
1255
        php_ort_status_flow(
×
1256
            !SUCCESS,
1257
            return 0,
1258
            php_ort_status_tensor_invalidshape_ce,
1259
            "php_ort_tensor_data should not be called for scalar tensors");
1260
    }
1261

1262
    // Validate depth bounds
1263
    php_ort_status_flow(
65,632✔
1264
        (depth >= tensor->dimensions),
1265
        return 0,
1266
        php_ort_status_tensor_invalidshape_ce,
1267
        "depth %zd exceeds tensor dimensions %zd", depth, tensor->dimensions);
1268

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

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

1287
    // Initialize array with actual size we'll extract
1288
    array_init_size(node, elements_to_extract);
65,632✔
1289

1290
    if (depth == tensor->dimensions - 1) {
65,632✔
1291
        // Leaf level - extract scalar values
1292
        for (int64_t i = 0; i < elements_to_extract; i++) {
13,220,048✔
1293
            zval val;
13,156,752✔
1294
            
1295
            // Validate offset bounds before accessing data
1296
            php_ort_status_flow(
13,156,752✔
1297
                (*offset >= tensor->elements),
1298
                return 0,
1299
                php_ort_status_tensor_invaliddata_ce,
1300
                "data offset %zd exceeds tensor element count %zd", *offset, tensor->elements);
1301

1302
            void *source = (char *)tensor->data + ((*offset) * size);
13,156,752✔
1303

1304
            switch (tensor->type) {
13,156,752✔
1305
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
1,636,880✔
1306
                    ZVAL_DOUBLE(&val, *(float *)source);
1,636,880✔
1307
                    break;
1,636,880✔
1308

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

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

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

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

1325
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
1,343,296✔
1326
                    ZVAL_LONG(&val, *(int64_t *)source);
1,343,296✔
1327
                    break;
1,343,296✔
1328

1329
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
1,430,048✔
1330
                    ZVAL_LONG(&val, *(uint8_t *)source);
1,430,048✔
1331
                    break;
1,430,048✔
1332

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

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

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

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

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

1365
            php_ort_status_flow(
13,156,752✔
1366
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &val) == NULL),
1367
                return 0,
1368
                php_ort_status_tensor_invaliddata_ce,
1369
                "failed to update array at index %zd", i);
1370

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

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

1399
    return 1;
1400
}
1401

1402
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getData_arginfo, 0, 0, IS_ARRAY, 0)
1403
    ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
1404
    ZEND_ARG_TYPE_INFO(0, depth,  IS_LONG, 0)
1405
ZEND_END_ARG_INFO()
1406

1407
PHP_METHOD(ONNX_Tensor, getData)
19,968✔
1408
{
1409
    php_ort_tensor_t* ort =
19,968✔
1410
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
19,968✔
1411
    zend_long offset = 0, depth = 0;
19,968✔
1412

1413
    ZEND_PARSE_PARAMETERS_START(0, 2)
19,968✔
1414
        Z_PARAM_OPTIONAL
19,968✔
1415
        Z_PARAM_LONG(offset)
20,320✔
1416
        Z_PARAM_LONG(depth)
576✔
1417
    ZEND_PARSE_PARAMETERS_END();
19,968✔
1418

1419
    php_ort_status_flow(
19,968✔
1420
        (offset < 0 || offset > ort->object->elements),
1421
        return,
1422
        php_ort_status_tensor_invaliddata_ce,
1423
        "offset %zd out of range [0, %zd]",
1424
        offset, ort->object->elements);
1425

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

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

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

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

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

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

1468
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
176✔
1469
                ZVAL_LONG(&val, *(int64_t *)source);
176✔
1470
                break;
176✔
1471

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

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

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

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

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

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

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

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

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

1540
        size_t len = zend_hash_num_elements(Z_ARRVAL_P(level));
624✔
1541

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

1549
        shape[dimension] = len;
608✔
1550

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

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

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

1613
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_from_arginfo, 0, 2, ORT\\Tensor, 0)
1614
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
1615
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
1616
ZEND_END_ARG_INFO()
1617

1618
PHP_METHOD(ONNX_Tensor, from)
432✔
1619
{
1620
    zval *data;
432✔
1621
    zend_long type;
432✔
1622
    size_t shape[32];
432✔
1623
    size_t dimensions = 0;
432✔
1624

1625
    ZEND_PARSE_PARAMETERS_START(2, 2)
432✔
1626
        Z_PARAM_ARRAY(data)
864✔
1627
        Z_PARAM_LONG(type)
864✔
1628
    ZEND_PARSE_PARAMETERS_END();
432✔
1629

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

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

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

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

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

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

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

1675
zend_function_entry php_ort_tensor_interface_methods[] = {
1676
    PHP_ABSTRACT_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo)
1677
    PHP_ABSTRACT_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo)
1678
    PHP_ABSTRACT_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo)
1679
    PHP_ABSTRACT_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo)
1680
    PHP_ABSTRACT_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo)
1681
    PHP_ABSTRACT_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo)
1682
    PHP_ABSTRACT_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo)
1683

1684
    PHP_ABSTRACT_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo)
1685
    PHP_FE_END
1686
};
1687

1688
zend_function_entry php_ort_tensor_persistent_methods[] = {
1689
    PHP_ME(ONNX_Tensor_Persistent, __construct,
1690
        php_ort_tensor_persistent_construct_arginfo, ZEND_ACC_PUBLIC)
1691
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1692
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1693
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1694
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1695
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1696
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1697
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1698
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1699

1700
    PHP_ME(ONNX_Tensor, from,         php_ort_tensor_from_arginfo,         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1701
    PHP_FE_END
1702
};
1703

1704
zend_function_entry php_ort_tensor_transient_methods[] = {
1705
    PHP_ME(ONNX_Tensor_Transient, __construct,
1706
        php_ort_tensor_transient_construct_arginfo, ZEND_ACC_PUBLIC)
1707
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1708
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1709
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1710
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1711
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1712
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1713
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1714
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1715

1716
    PHP_ME(ONNX_Tensor, from,         php_ort_tensor_from_arginfo,         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1717
    PHP_FE_END
1718
};
1719

1720
zend_object* php_ort_tensor_create(zend_class_entry *type) {
57,760✔
1721
    php_ort_tensor_t *ort = ecalloc(1,
57,760✔
1722
        sizeof(php_ort_tensor_t) + zend_object_properties_size(type));
1723

1724
    zend_object_std_init(&ort->std, type);
57,760✔
1725

1726
    ort->std.handlers = &php_ort_tensor_handlers;
57,760✔
1727
    ort->object       = NULL;
57,760✔
1728

1729
    return &ort->std;
57,760✔
1730
}
1731

1732
static HashTable* php_ort_tensor_debug(zend_object *zo, int *temp) {
208✔
1733
    php_ort_tensor_t *ort = php_ort_tensor_fetch(zo);
208✔
1734
    HashTable *debug;
208✔
1735

1736
    ALLOC_HASHTABLE(debug);
208✔
1737
    zend_hash_init(debug, 3, NULL, ZVAL_PTR_DTOR, 0);
208✔
1738

1739
    if (!ort->object) {
208✔
1740
        goto __php_ort_tensor_debug_return;
32✔
1741
    }
1742

1743
    zval persistent;
176✔
1744

1745
    ZVAL_BOOL(&persistent,
176✔
1746
        ort->object->owner == PHP_ORT_OWN_HEAP);
1747
    zend_hash_str_add(debug, 
176✔
1748
        "persistent", sizeof("persistent")-1, 
1749
        &persistent);
1750

1751
    zval type;
176✔
1752
    
1753
    ZVAL_LONG(&type, ort->object->type);
176✔
1754
    zend_hash_add(debug,
176✔
1755
        ZSTR_KNOWN(ZEND_STR_TYPE), &type);
176✔
1756

1757
    if (ort->object->name) {
176✔
1758
        zval name;
176✔
1759

1760
        ZVAL_STR_COPY(&name, ort->object->name);
176✔
1761
        zend_hash_add(debug,
176✔
1762
            ZSTR_KNOWN(ZEND_STR_NAME), &name);
176✔
1763
    }
1764

1765
    zval shape;
176✔
1766

1767
    array_init(&shape);
176✔
1768
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
480✔
1769
        add_next_index_long(
304✔
1770
            &shape, ort->object->shape[dimension]);
304✔
1771
    }
1772
    zend_hash_str_add(debug,
176✔
1773
        "shape", sizeof("shape")-1, &shape);
1774

1775
__php_ort_tensor_debug_return:
208✔
1776
    *temp = 1;
208✔
1777

1778
    return debug;
208✔
1779
}
1780

1781
void php_ort_tensor_destroy(zend_object *o) {
57,760✔
1782
    php_ort_tensor_t *ort =
57,760✔
1783
        php_ort_tensor_fetch(o);
57,760✔
1784

1785
    ort_tensor_release(ort->object);
57,760✔
1786

1787
    zend_object_std_dtor(o);
57,760✔
1788
}
57,760✔
1789

1790
PHP_MINIT_FUNCTION(ORT_TENSOR)
3,280✔
1791
{
1792
    zend_class_entry ce;
3,280✔
1793

1794
#ifdef ZTS
1795
    php_ort_tensor_mutex = tsrm_mutex_alloc();
1796
#endif
1797

1798
    zend_hash_init(&php_ort_tensors, 16, NULL, php_ort_tensor_del, 1);
3,280✔
1799

1800
    // Setup shared handlers for all tensor types
1801
    memcpy(&php_ort_tensor_handlers,
3,280✔
1802
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
1803

1804
    php_ort_tensor_handlers.offset = XtOffsetOf(php_ort_tensor_t, std);
3,280✔
1805
    php_ort_tensor_handlers.get_debug_info = php_ort_tensor_debug;
3,280✔
1806
    php_ort_tensor_handlers.free_obj = php_ort_tensor_destroy;
3,280✔
1807
    php_ort_tensor_handlers.clone_obj = NULL;
3,280✔
1808

1809
    // Register the interface
1810
    INIT_NS_CLASS_ENTRY(ce, "ORT", "Tensor", php_ort_tensor_interface_methods);
3,280✔
1811
    php_ort_tensor_interface_ce = zend_register_internal_interface(&ce);
3,280✔
1812

1813
    // Register persistent tensor class
1814
    INIT_NS_CLASS_ENTRY(ce, "ORT\\Tensor", "Persistent", php_ort_tensor_persistent_methods);
3,280✔
1815
    php_ort_tensor_persistent_ce = zend_register_internal_class(&ce);
3,280✔
1816
    php_ort_tensor_persistent_ce->create_object = php_ort_tensor_create;
3,280✔
1817
    zend_class_implements(php_ort_tensor_persistent_ce, 1, php_ort_tensor_interface_ce);
3,280✔
1818

1819
    // Register transient tensor class
1820
    INIT_NS_CLASS_ENTRY(ce, "ORT\\Tensor", "Transient", php_ort_tensor_transient_methods);
3,280✔
1821
    php_ort_tensor_transient_ce = zend_register_internal_class(&ce);
3,280✔
1822
    php_ort_tensor_transient_ce->create_object = php_ort_tensor_create;
3,280✔
1823
    zend_class_implements(php_ort_tensor_transient_ce, 1, php_ort_tensor_interface_ce);
3,280✔
1824

1825
#ifdef ZEND_ACC_NOT_SERIALIZABLE
1826
    php_ort_tensor_persistent_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
3,280✔
1827
    php_ort_tensor_transient_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
3,280✔
1828
#else
1829
    php_ort_tensor_persistent_ce->serialize = zend_class_serialize_deny;
1830
    php_ort_tensor_persistent_ce->unserialize = zend_class_unserialize_deny;
1831
    php_ort_tensor_transient_ce->serialize = zend_class_serialize_deny;
1832
    php_ort_tensor_transient_ce->unserialize = zend_class_unserialize_deny;
1833
#endif
1834

1835
    // Register constants on the interface
1836
    zend_declare_class_constant_long(
3,280✔
1837
        php_ort_tensor_interface_ce,
1838
        "UNDEFINED", sizeof("UNDEFINED")-1,
1839
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED);
1840

1841
    zend_declare_class_constant_long(
3,280✔
1842
        php_ort_tensor_interface_ce,
1843
        "FLOAT", sizeof("FLOAT")-1,
1844
        ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
1845

1846
    zend_declare_class_constant_long(
3,280✔
1847
        php_ort_tensor_interface_ce,
1848
        "DOUBLE", sizeof("DOUBLE")-1,
1849
        ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE);
1850

1851
    zend_declare_class_constant_long(
3,280✔
1852
        php_ort_tensor_interface_ce,
1853
        "UINT8", sizeof("UINT8")-1,
1854
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8);
1855

1856
    zend_declare_class_constant_long(
3,280✔
1857
        php_ort_tensor_interface_ce,
1858
        "INT8", sizeof("INT8")-1,
1859
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8);
1860

1861
    zend_declare_class_constant_long(
3,280✔
1862
        php_ort_tensor_interface_ce,
1863
        "UINT16", sizeof("UINT16")-1,
1864
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16);
1865

1866
    zend_declare_class_constant_long(
3,280✔
1867
        php_ort_tensor_interface_ce,
1868
        "INT16", sizeof("INT16")-1,
1869
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16);
1870

1871
    zend_declare_class_constant_long(
3,280✔
1872
        php_ort_tensor_interface_ce,
1873
        "UINT32", sizeof("UINT32")-1,
1874
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32);
1875

1876
    zend_declare_class_constant_long(
3,280✔
1877
        php_ort_tensor_interface_ce,
1878
        "INT32", sizeof("INT32")-1,
1879
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32);
1880

1881
    zend_declare_class_constant_long(
3,280✔
1882
        php_ort_tensor_interface_ce,
1883
        "INT64", sizeof("INT64")-1,
1884
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64);
1885

1886
    zend_declare_class_constant_long(
3,280✔
1887
        php_ort_tensor_interface_ce,
1888
        "BOOL", sizeof("BOOL")-1,
1889
        ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL);
1890

1891
    return SUCCESS;
3,280✔
1892
}
1893

1894
PHP_MSHUTDOWN_FUNCTION(ORT_TENSOR)
3,280✔
1895
{
1896
    zend_hash_destroy(&php_ort_tensors);
3,280✔
1897

1898
#ifdef ZTS
1899
    tsrm_mutex_free(php_ort_tensor_mutex);
1900
#endif
1901

1902
    return SUCCESS;
3,280✔
1903
}
1904

1905
PHP_RINIT_FUNCTION(ORT_TENSOR)
3,280✔
1906
{
1907
    return SUCCESS;
3,280✔
1908
}
1909

1910
PHP_RSHUTDOWN_FUNCTION(ORT_TENSOR)
3,280✔
1911
{
1912
    return SUCCESS;
3,280✔
1913
}
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