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

krakjoe / ort / 16279124805

14 Jul 2025 10:19PM UTC coverage: 92.598% (-0.3%) from 92.851%
16279124805

push

github

krakjoe
optmize memcpy where possible

50 of 72 new or added lines in 7 files covered. (69.44%)

1 existing line in 1 file now uncovered.

5354 of 5782 relevant lines covered (92.6%)

71749.28 hits per line

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

93.21
/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 "status.h"
24

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

29
static HashTable php_ort_tensors;
30

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

36
// Recursive shape/data validator
37
static inline zend_bool php_ort_tensor_validate_next(ONNXTensorElementDataType type, zend_long rank, zend_long *dimensions, zval *node, zend_long depth) {
8,033,024✔
38
    if (depth == rank) {
8,033,024✔
39
        // Leaf node — must match scalar type
40
        switch (type) {
7,943,376✔
41
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
42
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
43
                return Z_TYPE_P(node) == IS_DOUBLE || Z_TYPE_P(node) == IS_LONG;
3,865,728✔
44
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
45
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
46
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
47
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
48
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
49
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
50
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
51
                return Z_TYPE_P(node) == IS_LONG;
5,987,472✔
52
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
53
                // UINT64 is not supported due to PHP's signed 64-bit integer limitation
54
                php_ort_status_flow(!SUCCESS,
16✔
55
                {
56
                    return 0;
57
                },
58
                php_ort_status_tensor_invalidtype_ce,
59
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
60
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
61
                return Z_TYPE_P(node) == IS_TRUE || Z_TYPE_P(node) == IS_FALSE;
45,968✔
62
            
63
                default: php_ort_status_flow(!SUCCESS,
32✔
64
                {
65
                    return 0;
66
                },
67
                php_ort_status_tensor_invalidtype_ce,
68
                "unknown data type (%zd) provided",
69
                (zend_long) type);
70
        }
71
    }
72

73
    if (Z_TYPE_P(node) != IS_ARRAY) {
89,648✔
74
        return 0;
75
    }
76

77
    HashTable *ht = Z_ARRVAL_P(node);
89,648✔
78
    if (zend_hash_num_elements(ht) != dimensions[depth]) {
89,648✔
79
        return 0;
80
    }
81

82
    for (zend_long i = 0; i < dimensions[depth]; i++) {
8,090,592✔
83
        zval *child = zend_hash_index_find(ht, i);
8,001,184✔
84
        if (!child || !php_ort_tensor_validate_next(
8,001,184✔
85
                type, rank, dimensions, child, depth + 1)) {
86
            return 0;
192✔
87
        }
88
    }
89

90
    return 1;
91
}
92

93
static zend_always_inline zend_bool php_ort_tensor_validate(zval *shape, zend_string *name, zval *data, ONNXTensorElementDataType type) {
32,176✔
94
    zend_long dimensions[16];
32,176✔
95
    zend_long rank = 0;
32,176✔
96
    
97
    rank = zend_hash_num_elements(Z_ARRVAL_P(shape));
32,176✔
98
    
99
    // Handle scalar tensor case (rank 0, empty shape array)
100
    if (rank == 0) {
32,176✔
101
        // For scalar tensors, data must be a single-element array
102
        if (Z_TYPE_P(data) != IS_ARRAY) {
224✔
103
            php_ort_status_flow(!SUCCESS,
×
104
            {
105
                return 0;
106
            },
107
            php_ort_status_tensor_invaliddata_ce,
108
            "scalar tensor data must be provided as a single-element array");
109
        }
110
        
111
        // Check that the data array has exactly one element
112
        if (zend_hash_num_elements(Z_ARRVAL_P(data)) != 1) {
224✔
113
            php_ort_status_flow(!SUCCESS,
32✔
114
            {
115
                return 0;
116
            },
117
            php_ort_status_tensor_invaliddata_ce,
118
            "scalar tensor must have exactly one data element");
119
        }
120
        
121
        // Get the first (and only) element
122
        zval *scalar = zend_hash_index_find(Z_ARRVAL_P(data), 0);
192✔
123
        if (!scalar) {
192✔
124
            return 0;
125
        }
126
        
127
        // Validate the scalar element type
128
        switch (type) {
192✔
129
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
130
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
131
                return Z_TYPE_P(scalar) == IS_DOUBLE || Z_TYPE_P(scalar) == IS_LONG;
112✔
132
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
133
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
134
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
135
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
136
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
137
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
138
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
139
                return Z_TYPE_P(scalar) == IS_LONG;
48✔
140
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
141
                // UINT64 is not supported due to PHP's signed 64-bit integer limitation
142
                php_ort_status_flow(!SUCCESS,
×
143
                {
144
                    return 0;
145
                },
146
                php_ort_status_tensor_invalidtype_ce,
147
                "UINT64 tensor type is not supported (values exceed PHP integer range)");
148
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
149
                return Z_TYPE_P(scalar) == IS_TRUE || Z_TYPE_P(scalar) == IS_FALSE;
32✔
150
            
151
            default: php_ort_status_flow(!SUCCESS,
×
152
            {
153
                return 0;
154
            },
155
            php_ort_status_tensor_invalidtype_ce,
156
            "unknown data type (%zd) provided",
157
            (zend_long) type);
158
        }
159
    }
160

161
    php_ort_status_flow(
31,952✔
162
        (rank > 16),
163
        return 0,
164
        php_ort_status_tensor_invalidshape_ce,
165
        "invalid shape information (must not exceed 16 dimensions)");
166

167
    // Extract and validate dimensions
168
    zend_long index = 0;
31,952✔
169
    zval *next;
31,952✔
170
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), next) {
71,152✔
171
        php_ort_status_flow(
39,296✔
172
            (Z_TYPE_P(next) != IS_LONG || Z_LVAL_P(next) <= 0),
173
            return 0,
174
            php_ort_status_tensor_invalidshape_ce,
175
            "shape information must be an array of positive integers");
176

177
        php_ort_status_flow(
39,200✔
178
            (!zend_hash_index_exists(Z_ARRVAL_P(shape), index)),
179
            return 0,
180
            php_ort_status_tensor_invalidshape_ce,
181
            "shape must be a packed array, index %zd is missing",
182
            index);
183

184
        dimensions[index++] = Z_LVAL_P(next);
39,184✔
185
    } ZEND_HASH_FOREACH_END();
186

187
    php_ort_status_flow(
31,840✔
188
        !php_ort_tensor_validate_next(type, rank, dimensions, data, 0),
189
        return 0,
190
        php_ort_status_tensor_invaliddata_ce,
191
        "validation of data according to the shape provided has failed");
192

193
    return 1;
194
}
195

196
static inline zend_bool php_ort_tensor_flatten(ort_tensor_t* tensor, size_t *offset, size_t size, zval *node, size_t depth) {
8,032,720✔
197
    // Special case for scalar tensor (0 dimensions)
198
    if (tensor->dimensions == 0) {
8,032,720✔
199
        // For scalar tensors, we expect the data to be a single-element array
200
        if (Z_TYPE_P(node) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(node)) != 1) {
192✔
201
            return 0;
202
        }
203
        
204
        // Get the scalar value (first element of the array)
205
        zval *scalar = zend_hash_index_find(Z_ARRVAL_P(node), 0);
192✔
206
        if (!scalar) {
192✔
207
            return 0;
208
        }
209
        
210
        void *target = (char *)tensor->data + ((*offset) * size);
192✔
211
        switch (tensor->type) {
192✔
212
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
213
                *(float *)target = Z_TYPE_P(scalar) == IS_DOUBLE
96✔
214
                    ? (float)Z_DVAL_P(scalar)
32✔
215
                    : (float)Z_LVAL_P(scalar);
96✔
216
                break;
96✔
217

218
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
219
                *(double *)target = Z_TYPE_P(scalar) == IS_DOUBLE
16✔
220
                    ? Z_DVAL_P(scalar)
221
                    : (double)Z_LVAL_P(scalar);
16✔
222
                break;
16✔
223

224
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
×
225
                *(int8_t *)target = (int8_t)Z_LVAL_P(scalar);
×
226
                break;
×
227

228
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
×
229
                *(int16_t *)target = (int16_t)Z_LVAL_P(scalar);
×
230
                break;
×
231

232
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
×
233
                *(int32_t *)target = (int32_t)Z_LVAL_P(scalar);
×
234
                break;
×
235

236
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
48✔
237
                *(int64_t *)target = (int64_t)Z_LVAL_P(scalar);
48✔
238
                break;
48✔
239

240
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
×
241
                *(uint8_t *)target = (uint8_t)Z_LVAL_P(scalar);
×
242
                break;
×
243

244
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
×
245
                *(uint16_t *)target = (uint16_t)Z_LVAL_P(scalar);
×
246
                break;
×
247

248
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
×
249
                *(uint32_t *)target = (uint32_t)Z_LVAL_P(scalar);
×
250
                break;
×
251

252
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
253
                // UINT64 is not supported - this should not be reached due to validation
254
                return 0;
255

256
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
257
                *(uint8_t *)target = (Z_TYPE_P(scalar) == IS_TRUE) ? 1 : 0;
32✔
258
                break;
32✔
259
        }
260
        (*offset)++;
192✔
261
        return 1;
192✔
262
    }
263

264
    if (depth == tensor->dimensions) {
8,032,528✔
265
        void *target = (char *)tensor->data + ((*offset) * size);
7,943,168✔
266
        switch (tensor->type) {
7,943,168✔
267
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
268
                *(float *)target = Z_TYPE_P(node) == IS_DOUBLE
1,064,000✔
269
                    ? (float)Z_DVAL_P(node)
193,584✔
270
                    : (float)Z_LVAL_P(node);
1,064,000✔
271
                break;
1,064,000✔
272

273
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
274
                *(double *)target = Z_TYPE_P(node) == IS_DOUBLE
868,864✔
275
                    ? Z_DVAL_P(node)
276
                    : (double)Z_LVAL_P(node);
868,864✔
277
                break;
868,864✔
278

279
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
881,328✔
280
                *(int8_t *)target = (int8_t)Z_LVAL_P(node);
881,328✔
281
                break;
881,328✔
282

283
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
856,576✔
284
                *(int16_t *)target = (int16_t)Z_LVAL_P(node);
856,576✔
285
                break;
856,576✔
286

287
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
858,224✔
288
                *(int32_t *)target = (int32_t)Z_LVAL_P(node);
858,224✔
289
                break;
858,224✔
290

291
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
857,264✔
292
                *(int64_t *)target = (int64_t)Z_LVAL_P(node);
857,264✔
293
                break;
857,264✔
294

295
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
844,784✔
296
                *(uint8_t *)target = (uint8_t)Z_LVAL_P(node);
844,784✔
297
                break;
844,784✔
298

299
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
844,544✔
300
                *(uint16_t *)target = (uint16_t)Z_LVAL_P(node);
844,544✔
301
                break;
844,544✔
302

303
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
844,608✔
304
                *(uint32_t *)target = (uint32_t)Z_LVAL_P(node);
844,608✔
305
                break;
844,608✔
306

307
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
308
                // UINT64 is not supported - this should not be reached due to validation
309
                return 0;
310

311
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
312
                *(uint8_t *)target = (Z_TYPE_P(node) == IS_TRUE) ? 1 : 0;
22,976✔
313
                break;
22,976✔
314
        }
315
        (*offset)++;
7,943,168✔
316
        return 1;
7,943,168✔
317
    }
318

319
    for (int64_t i = 0; i < tensor->shape[depth]; i++) {
8,090,192✔
320
        zval *child = zend_hash_index_find(Z_ARRVAL_P(node), i);
8,000,832✔
321
        if (!php_ort_tensor_flatten(tensor, offset, size, child, depth + 1)) {
8,000,832✔
322
            return 0;
323
        }
324
    }
325

326
    return 1;
327
}
328

329
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✔
330
    size_t i = 0, offset = 0;
1,344✔
331
    size_t size;
1,344✔
332

333
    tensor->name       = php_ort_string_copy(name);
2,688✔
334
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
1,344✔
335
    tensor->type       = type;
1,344✔
336
    tensor->owner      = PHP_ORT_OWN_HEAP;
1,344✔
337

338
    // Handle scalar tensor case (empty shape array)
339
    if (tensor->dimensions == 0) {
1,344✔
340
        tensor->shape    = NULL;  // No shape array needed for scalars
32✔
341
        tensor->elements = 1;     // A scalar has exactly one element
32✔
342
        tensor->data     = ort_alloc(php_ort_tensor_sizeof(tensor), 1);
64✔
343
        
344
        return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
64✔
345
    }
346
    
347
    // Normal case for non-scalar tensors
348
    tensor->shape    = pemalloc(tensor->dimensions * sizeof(int64_t), 1);
1,312✔
349
    tensor->elements = 1;
1,312✔
350

351
    // Copy shape and compute total number of elements
352
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), zval *dim) {
3,760✔
353
        int64_t size = (int64_t)Z_LVAL_P(dim);
2,448✔
354
        tensor->shape[i++] = size;
2,448✔
355
        tensor->elements *= size;
2,448✔
356
    } ZEND_HASH_FOREACH_END();
357

358
    tensor->data = ort_alloc(
2,624✔
359
        php_ort_tensor_sizeof(tensor), tensor->elements);
360

361
    return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
2,624✔
362
}
363

364
static zend_always_inline zend_bool php_ort_tensor_allocate_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type) {
30,544✔
365
    size_t i = 0, offset = 0;
30,544✔
366
    size_t size;
30,544✔
367

368
    tensor->name       = NULL;
30,544✔
369
    tensor->dimensions = zend_hash_num_elements(Z_ARRVAL_P(shape));
30,544✔
370
    tensor->type       = type;
30,544✔
371
    tensor->owner      = PHP_ORT_OWN_ZEND;
30,544✔
372

373
    // Handle scalar tensor case (empty shape array)
374
    if (tensor->dimensions == 0) {
30,544✔
375
        tensor->shape    = NULL;  // No shape array needed for scalars
160✔
376
        tensor->elements = 1;     // A scalar has exactly one element
160✔
377
        tensor->data     = ort_alloc(1, php_ort_tensor_sizeof(tensor));
320✔
378
        
379
        return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
320✔
380
    }
381
    
382
    // Normal case for non-scalar tensors
383
    tensor->shape    = ecalloc(tensor->dimensions, sizeof(int64_t));
30,384✔
384
    tensor->elements = 1;
30,384✔
385

386
    // Copy shape and compute total number of elements
387
    ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), zval *dim) {
66,816✔
388
        int64_t size = (int64_t)Z_LVAL_P(dim);
36,432✔
389
        tensor->shape[i++] = size;
36,432✔
390
        tensor->elements *= size;
36,432✔
391
    } ZEND_HASH_FOREACH_END();
392

393
    tensor->data = ort_alloc(
60,768✔
394
        php_ort_tensor_sizeof(tensor), tensor->elements);
395

396
    return php_ort_tensor_flatten(tensor, &offset, php_ort_tensor_sizeof(tensor), data, 0);
60,768✔
397
}
398

399
static void ort_tensor_free(ort_tensor_t *tensor) {
56,512✔
400
    zend_bool persistent = 
56,512✔
401
        (tensor->owner == PHP_ORT_OWN_HEAP) ? 1 : 0;
56,512✔
402

403
    if (tensor->shape) {
56,512✔
404
        pefree(tensor->shape, persistent);
55,600✔
405
    }
406

407
    if (!tensor->parent && tensor->data && !tensor->value) {
56,512✔
408
        ort_free(tensor->data);
56,416✔
409
    }
410

411
    if (tensor->value) {
56,512✔
412
        api->ReleaseValue(tensor->value);
16✔
413
    }
414

415
    if (tensor->name && !persistent) {
56,512✔
416
        zend_string_free(tensor->name);
24,464✔
417
    }
418

419
    if (tensor->parent) {
56,512✔
420
        ort_tensor_release(tensor->parent);
80✔
421
    }
422

423
    pefree(tensor, persistent);
56,512✔
424
}
56,512✔
425

426
void ort_tensor_release(ort_tensor_t *tensor) {
58,752✔
427
    if (!tensor) {
58,752✔
428
        return;
429
    }
430

431
    if (php_ort_atomic_delref(&tensor->refcount) == 0){
57,968✔
432
        ort_tensor_free(tensor);
56,512✔
433
    }
434
}
435

436
static void php_ort_tensor_del(zval *zv) {
1,344✔
437
    ort_tensor_release(
1,344✔
438
        ((ort_tensor_t*)
439
            Z_PTR_P(zv)));
1,344✔
440
}
1,344✔
441

442
static zend_bool php_ort_tensor_construct_persistent(ort_tensor_t *tensor, zend_string *name, zval *shape, zval *data, ONNXTensorElementDataType type){
1,488✔
443
    if (!php_ort_tensor_validate(shape, name, data, type)) {
2,976✔
444
        return 0;
144✔
445
    }
446

447
    if (!php_ort_tensor_allocate_persistent(tensor, name, shape, data, type)) {
2,688✔
448
        return 0;
449
    }
450

451
    return 1;
452
}
453

454
static zend_bool php_ort_tensor_construct_transient(ort_tensor_t *tensor, zval *shape, zval *data, ONNXTensorElementDataType type){
30,688✔
455
    if (!php_ort_tensor_validate(shape, NULL, data, type)) {
61,376✔
456
        return 0;
144✔
457
    }
458

459
    if (!php_ort_tensor_allocate_transient(tensor, shape, data, type)) {
61,088✔
460
        return 0;
461
    }
462

463
    return 1;
464
}
465

466
OrtValue* php_ort_tensor_value(php_ort_tensor_t* ort) {
32✔
467
    OrtMemoryInfo* mi;
32✔
468
    OrtValue* value = NULL;
32✔
469
    OrtStatus* status;
32✔
470

471
    php_ort_status_flow(
32✔
472
        (status = api->CreateCpuMemoryInfo(
473
            OrtArenaAllocator, OrtMemTypeDefault, &mi)),
474
        {
475
            api->ReleaseStatus(status);
476

477
            return NULL;
478
        },
479
        php_ort_status_tensor_invalidmemory_ce,
480
        "failed to allocate MemoryInfo* for Tensor conversion: %s",
481
        api->GetErrorMessage(status));
482

483
    php_ort_status_flow(
64✔
484
        (status = api->CreateTensorWithDataAsOrtValue(
485
            mi,
486
            ort->object->data,
487
            ort->object->elements * php_ort_tensor_sizeof(ort->object),
488
            ort->object->shape,
489
            ort->object->dimensions,
490
            ort->object->type,
491
            &value)),
492
        {
493
            api->ReleaseStatus(status);
494

495
            return NULL;
496
        },
497
        php_ort_status_tensor_invalidmemory_ce,
498
        "failed to allocate OrtValue* for Tensor conversion: %s",
499
        api->GetErrorMessage(status));
500

501
    api->ReleaseMemoryInfo(mi);
32✔
502

503
    return value;
32✔
504
}
505

506
ort_tensor_t* php_ort_tensor_object(OrtValue* value) {
16✔
507
    ort_tensor_t  *tensor = pecalloc(1, sizeof(ort_tensor_t), 0);
16✔
508

509
    tensor->refcount = 1;
16✔
510
    tensor->owner    = PHP_ORT_OWN_ZEND;
16✔
511
    tensor->value    = value;
16✔
512

513
    OrtTensorTypeAndShapeInfo* otsi;
16✔
514

515
    php_ort_status_flow(
16✔
516
        api->GetTensorTypeAndShape(value, &otsi), {
517
            ort_tensor_free(tensor);
518

519
            return NULL;
520
        },
521
        php_ort_status_tensor_invalidshape_ce,
522
        "failed to determine shape for OrtValue* conversion");
523

524
    php_ort_status_flow(
16✔
525
        api->GetTensorElementType(otsi, &tensor->type),
526
        {
527
            ort_tensor_free(tensor);
528

529
            return NULL;
530
        },
531
        php_ort_status_tensor_invalidshape_ce,
532
        "failed to determine type for OrtValue* conversion");
533

534
    php_ort_status_flow(
16✔
535
        api->GetDimensionsCount(otsi, &tensor->dimensions),
536
        {
537
            ort_tensor_free(tensor);
538

539
            return NULL;
540
        },
541
        php_ort_status_tensor_invalidshape_ce,
542
        "failed to determine dimension count for OrtValue* conversion");
543

544
    // For non-scalar tensors, allocate shape array and populate it
545
    if (tensor->dimensions > 0) {
16✔
546
        tensor->shape = pecalloc(tensor->dimensions, sizeof(int64_t), 0);
16✔
547

548
        php_ort_status_flow(
16✔
549
            api->GetDimensions(otsi, tensor->shape, tensor->dimensions),
550
            {
551
                ort_tensor_free(tensor);
552

553
                return NULL;
554
            },
555
            php_ort_status_tensor_invalidshape_ce,
556
            "failed to fetch dimensions for OrtValue* conversion");
557
    } else {
558
        // For scalar tensors, shape remains NULL
559
        tensor->shape = NULL;
×
560
    }
561

562
    php_ort_status_flow(
16✔
563
        api->GetTensorShapeElementCount(otsi, &tensor->elements),
564
        {
565
            ort_tensor_free(tensor);
566

567
            return NULL;
568
        },
569
        php_ort_status_tensor_invalidshape_ce,
570
        "failed to determine element count for OrtValue* conversion");
571

572
    php_ort_status_flow(
16✔
573
        api->GetTensorMutableData(value, &tensor->data),
574
        {
575
            ort_tensor_free(tensor);
576

577
            return NULL;
578
        },
579
        php_ort_status_tensor_invaliddata_ce,
580
        "failed to fetch data for OrtValue* conversion");
581

582
    api->ReleaseTensorTypeAndShapeInfo(otsi);
16✔
583

584
    return tensor;
16✔
585
}
586

587
static zend_always_inline size_t php_ort_tensor_indexof(ort_tensor_t *tensor, int64_t *coords) {
48✔
588
    // For scalar tensors, always return index 0
589
    if (tensor->dimensions == 0) {
48✔
590
        return 0;
591
    }
592
    
593
    size_t index = 0;
48✔
594
    size_t stride = 1;
48✔
595

596
    for (int64_t i = tensor->dimensions - 1; i >= 0; i--) {
144✔
597
        index += 
96✔
598
            coords[i] * stride;
96✔
599
        stride *= tensor->shape[i];
96✔
600
    }
601

602
    return index;
603
}
604

605
ZEND_BEGIN_ARG_INFO_EX(php_ort_tensor_persistent_construct_arginfo, 0, 0, 1)
606
    ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
607
    ZEND_ARG_TYPE_INFO(0, shape, IS_ARRAY, 0)
608
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
609
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
610
ZEND_END_ARG_INFO()
611

612
PHP_METHOD(ONNX_Tensor_Persistent, __construct)
1,552✔
613
{
614
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
1,552✔
615

616
    zend_string *name;
1,552✔
617
    zval        *shape = NULL;
1,552✔
618
    zval        *data  = NULL;
1,552✔
619
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
1,552✔
620

621
    ZEND_PARSE_PARAMETERS_START(1, 4);
1,552✔
622
        Z_PARAM_STR(name)
3,104✔
623
        Z_PARAM_OPTIONAL
1,552✔
624
        Z_PARAM_ARRAY(shape)
3,040✔
625
        Z_PARAM_ARRAY(data)
2,976✔
626
        Z_PARAM_LONG(type)
2,960✔
627
    ZEND_PARSE_PARAMETERS_END();
1,552✔
628

629
#ifdef ZTS
630
    php_ort_status_flow(
631
        tsrm_mutex_lock(php_ort_tensor_mutex) != SUCCESS,
632
        return,
633
        php_ort_status_safetyerror_ce,
634
        "it was not possible to acquire the tensor mutex, something is terribly wrong");
635
#endif
636

637
    if (!(ort->object = zend_hash_find_ptr(&php_ort_tensors, name))) {
3,104✔
638
#ifdef ZTS
639
        php_ort_status_flow(
640
            (!shape || !data),
641
            {
642
                php_ort_status_flow(
643
                    tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
644
                    return,
645
                    php_ort_status_safetyerror_ce,
646
                    "it was not possible to release the tensor mutex, something is terribly wrong");
647
                return;
648
            },
649
            php_ort_status_tensor_notfound_ce, 
650
            "Could not find the Tensor named \"%s\"",
651
                ZSTR_VAL(name));
652
#else
653
        php_ort_status_flow(
1,520✔
654
            (!shape || !data),
655
            {
656
                return;
657
            },
658
            php_ort_status_tensor_notfound_ce, 
659
            "Could not find the Tensor named \"%s\"",
660
                ZSTR_VAL(name));
661
#endif
662

663
        ort_tensor_t *tensor = 
1,488✔
664
            pecalloc(1, sizeof(ort_tensor_t), 1);
1,488✔
665

666
        tensor->refcount = 1;
1,488✔
667

668
        if (!php_ort_tensor_construct_persistent(tensor, name, shape, data, type)) {
1,488✔
669
#ifdef ZTS
670
            php_ort_status_flow(
671
                tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
672
                return,
673
                php_ort_status_safetyerror_ce,
674
                "it was not possible to release the tensor mutex, something is terribly wrong");
675
#endif
676
            return;
677
        }
678

679
        ort->object = zend_hash_add_ptr(
1,344✔
680
            &php_ort_tensors,
681
            tensor->name,
682
            tensor);
683

684
        ort->object->refcount++;
1,344✔
685
    } else {
686
        php_ort_atomic_addref(&ort->object->refcount);
32✔
687
    }
688

689
#ifdef ZTS
690
    php_ort_status_flow(
691
        tsrm_mutex_unlock(php_ort_tensor_mutex) != SUCCESS,
692
        return,
693
        php_ort_status_safetyerror_ce,
694
        "it was not possible to release the tensor mutex, something is terribly wrong");
695
#endif
696
}
697

698
ZEND_BEGIN_ARG_INFO_EX(php_ort_tensor_transient_construct_arginfo, 0, 0, 2)
699
    ZEND_ARG_TYPE_INFO(0, shape, IS_ARRAY, 0)
700
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
701
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
702
ZEND_END_ARG_INFO()
703

704
PHP_METHOD(ONNX_Tensor_Transient, __construct)
30,688✔
705
{
706
    php_ort_tensor_t *ort = php_ort_tensor_fetch(Z_OBJ(EX(This)));
30,688✔
707

708
    zval        *shape = NULL;
30,688✔
709
    zval        *data  = NULL;
30,688✔
710
    zend_long    type  = ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64;
30,688✔
711

712
    ZEND_PARSE_PARAMETERS_START(2, 3);
30,688✔
713
        Z_PARAM_ARRAY(shape)
61,376✔
714
        Z_PARAM_ARRAY(data)
61,376✔
715
        Z_PARAM_OPTIONAL
30,688✔
716
        Z_PARAM_LONG(type)
61,376✔
717
    ZEND_PARSE_PARAMETERS_END();
30,688✔
718

719
    ort_tensor_t *tensor = ecalloc(1, sizeof(ort_tensor_t));
30,688✔
720
    tensor->refcount = 1;
30,688✔
721

722
    if (!php_ort_tensor_construct_transient(tensor, shape, data, type)) {
30,688✔
723
        efree(tensor);
144✔
724
        return;
144✔
725
    }
726

727
    ort->object = tensor;
30,544✔
728
}
729

730
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_isPersistent_arginfo, 0, 0, _IS_BOOL, 0)
731
ZEND_END_ARG_INFO()
732

733
PHP_METHOD(ONNX_Tensor, isPersistent)
16✔
734
{
735
    php_ort_tensor_t* ort =
16✔
736
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
16✔
737

738
    ZEND_PARSE_PARAMETERS_NONE();
16✔
739

740
    RETURN_BOOL(ort->object->owner == PHP_ORT_OWN_HEAP);
16✔
741
}
742

743
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getName_arginfo, 0, 0, IS_STRING, 1)
744
ZEND_END_ARG_INFO()
745

746
PHP_METHOD(ONNX_Tensor, getName)
48✔
747
{
748
    php_ort_tensor_t* ort =
48✔
749
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
48✔
750
    
751
    ZEND_PARSE_PARAMETERS_NONE();
48✔
752

753
    if (!ort->object->name) {
48✔
754
        return;
755
    }
756

757
    RETURN_STR_COPY(ort->object->name);
48✔
758
}
759

760
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getType_arginfo, 0, 0, IS_LONG, 0)
761
ZEND_END_ARG_INFO()
762

763
PHP_METHOD(ONNX_Tensor, getType)
944✔
764
{
765
    php_ort_tensor_t* ort =
944✔
766
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
944✔
767

768
    ZEND_PARSE_PARAMETERS_NONE();
944✔
769

770
    RETURN_LONG(ort->object->type);
944✔
771
}
772

773
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getTypeName_arginfo, 0, 0, IS_STRING, 0)
774
ZEND_END_ARG_INFO()
775

776
PHP_METHOD(ONNX_Tensor, getTypeName)
16,672✔
777
{
778
    php_ort_tensor_t* ort =
16,672✔
779
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
16,672✔
780

781
    ZEND_PARSE_PARAMETERS_NONE();
16,672✔
782

783
    /* @todo(krakjoe) this performs terribly ... */
784
    RETURN_STRING(php_ort_type_name(ort->object->type));
45,408✔
785
}
786

787
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getShape_arginfo, 0, 0, IS_ARRAY, 0)
788
ZEND_END_ARG_INFO()
789

790
PHP_METHOD(ONNX_Tensor, getShape)
17,296✔
791
{
792
    php_ort_tensor_t* ort =
17,296✔
793
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
17,296✔
794

795
    ZEND_PARSE_PARAMETERS_NONE();
17,296✔
796

797
    array_init(return_value);
17,296✔
798
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
35,840✔
799
        add_next_index_long(
18,544✔
800
            return_value, ort->object->shape[dimension]);
18,544✔
801
    }
802
}
803

804
static zend_always_inline ort_tensor_t* php_ort_tensor_transpose(ort_tensor_t* input, zval *axis, zval* return_value) {
64✔
805
    int64_t ndim = input->dimensions;
64✔
806
    int64_t* perm = NULL;
64✔
807
    int64_t* inv_perm = NULL;
64✔
808
    int64_t* out_shape = NULL;
64✔
809
    int64_t* in_strides = NULL;
64✔
810
    int64_t* out_strides = NULL;
64✔
811
    size_t type_size = php_ort_tensor_sizeof(input);
128✔
812
    size_t numel = input->elements;
64✔
813
    ort_tensor_t* result = NULL;
64✔
814

815
    // Argument validation and axes parsing
816
    if (ndim == 0) {
64✔
817
        // Scalar: transpose is a no-op, just return a copy
818
        result = pecalloc(1, sizeof(ort_tensor_t), 0);
×
819
        result->refcount = 1;
×
820
        result->owner = PHP_ORT_OWN_ZEND;
×
821
        result->type = input->type;
×
822
        result->dimensions = 0;
×
823
        result->elements = 1;
×
824
        result->shape = NULL;
×
825
        result->data = ort_alloc(type_size, 1);
×
NEW
826
        ort_memcpy(result->data, input->data, type_size);
×
827
        goto __php_ort_tensor_transpose_done;
×
828
    }
829

830
    perm = ecalloc(ndim, sizeof(int64_t));
64✔
831
    out_shape = ecalloc(ndim, sizeof(int64_t));
64✔
832
    in_strides = ecalloc(ndim, sizeof(int64_t));
64✔
833
    out_strides = ecalloc(ndim, sizeof(int64_t));
64✔
834
    inv_perm = ecalloc(ndim, sizeof(int64_t));
64✔
835

836
    // Parse axes argument
837
    if (axis) {
64✔
838
        php_ort_status_flow(
48✔
839
            (zend_hash_num_elements(Z_ARRVAL_P(axis)) != ndim),
840
            {
841
                goto __php_ort_tensor_transpose_failed;
842
            },
843
            php_ort_status_tensor_invalidshape_ce,
844
            "axes array must be an array of length %zd", ndim);
845

846
        // Fill perm from user axes, handle negatives, check for duplicates
847
        zend_bool* seen = ecalloc(ndim, sizeof(zend_bool));
48✔
848
        int64_t i = 0;
48✔
849
        zval* zv;
48✔
850
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(axis), zv) {
144✔
851
            php_ort_status_flow(
96✔
852
                (Z_TYPE_P(zv) != IS_LONG),
853
                {
854
                    efree(seen);
855
                    goto __php_ort_tensor_transpose_failed;
856
                },
857
                php_ort_status_tensor_invalidshape_ce,
858
                "axes array must contain only integers");
859
            
860
            int64_t ax = Z_LVAL_P(zv);
96✔
861
            if (ax < 0)
96✔
862
                ax += ndim;
32✔
863
            
864
            php_ort_status_flow(
96✔
865
                (ax < 0 || ax >= ndim),
866
                {
867
                    efree(seen);
868
                    goto __php_ort_tensor_transpose_failed;
869
                },
870
                php_ort_status_tensor_invalidshape_ce,
871
                "axis value %zd out of range [0, %zd)", ax, ndim);
872

873
            php_ort_status_flow(
96✔
874
                (seen[ax]),
875
                {
876
                    efree(seen);
877
                    goto __php_ort_tensor_transpose_failed;
878
                },
879
                php_ort_status_tensor_invalidshape_ce,
880
                "duplicate axis value %zd in axes", ax);
881

882
            seen[ax] = 1;
96✔
883
            perm[i++] = ax;
96✔
884
        } ZEND_HASH_FOREACH_END();
885
        efree(seen);
48✔
886
    } else {
887
        // Default: reverse axes
888
        for (int64_t i = 0; i < ndim; i++) {
48✔
889
            perm[i] = ndim - 1 - i;
32✔
890
        }
891
    }
892

893
    // Compute output shape and strides
894
    for (int64_t i = 0; i < ndim; i++) {
192✔
895
        out_shape[i] = input->shape[perm[i]];
128✔
896
    }
897
    // Input strides
898
    in_strides[ndim-1] = 1;
64✔
899
    for (int64_t i = ndim-2; i >= 0; i--) {
128✔
900
        in_strides[i] = in_strides[i+1] * input->shape[i+1];
64✔
901
    }
902
    // Output strides
903
    out_strides[ndim-1] = 1;
64✔
904
    for (int64_t i = ndim-2; i >= 0; i--) {
128✔
905
        out_strides[i] = out_strides[i+1] * out_shape[i+1];
64✔
906
    }
907
    // Inverse permutation: inv_perm[perm[i]] = i
908
    for (int64_t i = 0; i < ndim; i++) {
192✔
909
        inv_perm[perm[i]] = i;
128✔
910
    }
911

912
    // Allocate result tensor
913
    result = pecalloc(1, sizeof(ort_tensor_t), 0);
64✔
914
    result->refcount = 1;
64✔
915
    result->owner = PHP_ORT_OWN_ZEND;
64✔
916
    result->type = input->type;
64✔
917
    result->dimensions = ndim;
64✔
918
    result->elements = numel;
64✔
919
    result->shape = pecalloc(ndim, sizeof(int64_t), 0);
64✔
920
    memcpy(result->shape,
64✔
921
        out_shape, ndim * sizeof(int64_t));
922
    result->data = ort_alloc(type_size, numel);
64✔
923

924
    // Main permutation loop
925
    int64_t* in_coords =
64✔
926
        ecalloc(ndim, sizeof(int64_t));
64✔
927
    int64_t* out_coords =
64✔
928
        ecalloc(ndim, sizeof(int64_t));
64✔
929
    for (size_t idx = 0; idx < numel; idx++) {
640✔
930
        // Compute input coordinates from flat idx
931
        size_t rem = idx;
932
        for (int64_t i = 0; i < ndim; i++) {
1,728✔
933
            in_coords[i] = rem / in_strides[i];
1,152✔
934
            rem = rem % in_strides[i];
1,152✔
935
        }
936
        // Permute coordinates
937
        for (int64_t i = 0; i < ndim; i++) {
1,728✔
938
            out_coords[i] = in_coords[perm[i]];
1,152✔
939
        }
940
        // Compute output flat index
941
        size_t out_idx = 0;
942
        for (int64_t i = 0; i < ndim; i++) {
1,728✔
943
            out_idx += out_coords[i] * out_strides[i];
1,152✔
944
        }
945
        // Copy element
946
        ort_memcpy(
576✔
947
            (char*)result->data + out_idx * type_size,
576✔
948
            (char*)input->data + idx * type_size,
576✔
949
            type_size);
950
    }
951
    efree(in_coords);
64✔
952
    efree(out_coords);
64✔
953

954
__php_ort_tensor_transpose_done:
64✔
955
    object_init_ex(return_value,
64✔
956
        php_ort_tensor_transient_ce);
957
    php_ort_tensor_t* rv =
64✔
958
        php_ort_tensor_fetch(
64✔
959
            Z_OBJ_P(return_value));
960
    rv->object = result;
64✔
961

962
    if (perm)
64✔
963
        efree(perm);
64✔
964
    if (out_shape)
64✔
965
        efree(out_shape);
64✔
966
    if (in_strides)
64✔
967
        efree(in_strides);
64✔
968
    if (out_strides)
64✔
969
        efree(out_strides);
64✔
970
    if (inv_perm)
64✔
971
        efree(inv_perm);
64✔
972
    return result;
973

974
__php_ort_tensor_transpose_failed:
×
975
    if (perm)
×
976
        efree(perm);
×
977
    if (out_shape)
×
978
        efree(out_shape);
×
979
    if (in_strides)
×
980
        efree(in_strides);
×
981
    if (out_strides)
×
982
        efree(out_strides);
×
983
    if (inv_perm)
×
984
        efree(inv_perm);
×
985
    return NULL;
986
}
987

988
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_transpose_arginfo, 0, 0, ONNX\\Tensor, 0)
989
    ZEND_ARG_TYPE_INFO(0, axis, IS_ARRAY, 1)
990
ZEND_END_ARG_INFO()
991

992
PHP_METHOD(ONNX_Tensor, transpose)
64✔
993
{
994
    php_ort_tensor_t* ort =
64✔
995
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
64✔
996
    zval* axis = NULL;
64✔
997

998
    ZEND_PARSE_PARAMETERS_START(0, 1);
64✔
999
        Z_PARAM_OPTIONAL
64✔
1000
        Z_PARAM_ZVAL(axis)
64✔
1001
    ZEND_PARSE_PARAMETERS_END();
64✔
1002

1003
    php_ort_tensor_transpose(ort->object, axis, return_value);
64✔
1004
}
1005

1006
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✔
1007
    // Handle axis parameter - if provided, validate it
1008
    zend_bool has_axis = (axis != NULL);
96✔
1009
    HashTable *axis_ht = has_axis ? Z_ARRVAL_P(axis) : NULL;
192✔
1010

1011
    if (has_axis) {
96✔
1012
        // When axis is provided, start and end should match axis array length
1013
        php_ort_status_flow(
16✔
1014
            (zend_hash_num_elements(Z_ARRVAL_P(start)) != zend_hash_num_elements(axis_ht) ||
1015
             zend_hash_num_elements(Z_ARRVAL_P(end))   != zend_hash_num_elements(axis_ht)), 
1016
            return NULL,
1017
            php_ort_status_tensor_invalidshape_ce,
1018
            "when axis is provided, start and end arrays must have same length as axis array (%zd)",
1019
            zend_hash_num_elements(axis_ht));
1020
    } else {
1021
        // When no axis provided, start and end must match tensor dimensions
1022
        php_ort_status_flow(
80✔
1023
            (zend_hash_num_elements(Z_ARRVAL_P(start)) != input->dimensions ||
1024
             zend_hash_num_elements(Z_ARRVAL_P(end))   != input->dimensions), 
1025
            return NULL,
1026
            php_ort_status_tensor_invalidshape_ce,
1027
            "start and end arrays must have same length as tensor dimensions (%zd)",
1028
            input->dimensions);
1029
    }
1030

1031
    object_init_ex(return_value, php_ort_tensor_transient_ce);
80✔
1032

1033
    php_ort_tensor_t* ort = php_ort_tensor_fetch(Z_OBJ_P(return_value));
80✔
1034
    
1035
    // Allocate new tensor structure
1036
    ort->object = pecalloc(1, sizeof(ort_tensor_t), 0);
80✔
1037
    
1038
    // Set up parent relationship
1039
    ort->object->parent = input;
80✔
1040
    php_ort_atomic_addref(
80✔
1041
        &ort->object->parent->refcount);
1042

1043
    ort->object->refcount = 1;
80✔
1044
    ort->object->owner = PHP_ORT_OWN_ZEND;
80✔
1045
    ort->object->type = input->type;
80✔
1046

1047
    int64_t *starting = pecalloc(input->dimensions, sizeof(int64_t), 0);
80✔
1048
    int64_t *ending = pecalloc(input->dimensions, sizeof(int64_t), 0);
80✔
1049
    int64_t *slicing = pecalloc(input->dimensions, sizeof(int64_t), 0);
80✔
1050

1051
    // Initialize with full range for all dimensions
1052
    for (int64_t idim = 0; idim < input->dimensions; idim++) {
240✔
1053
        starting[idim] = 0;
160✔
1054
        ending[idim]   = input->shape[idim];
160✔
1055
        slicing[idim]  = input->shape[idim];
160✔
1056
    }
1057

1058
    // Process based on whether axis is provided
1059
    if (has_axis) {
80✔
1060
        // Process each axis specification
1061
        zval *zaxis, *zstart, *zend;
16✔
1062
        zend_ulong iaxis = 0;
16✔
1063
        
1064
        ZEND_HASH_FOREACH_VAL(axis_ht, zaxis) {
32✔
1065
            php_ort_status_flow(
16✔
1066
                (Z_TYPE_P(zaxis) != IS_LONG),
1067
                {
1068
                    pefree(starting, 0);
1069
                    pefree(ending, 0);
1070
                    pefree(slicing, 0);
1071
                    return NULL;
1072
                },
1073
                php_ort_status_tensor_invalidshape_ce,
1074
                "axis array must contain integers");
1075
            
1076
            int64_t idim = Z_LVAL_P(zaxis);
16✔
1077

1078
            php_ort_status_flow(
16✔
1079
                (idim < 0 || idim >= input->dimensions),
1080
                {
1081
                    pefree(starting, 0);
1082
                    pefree(ending, 0);
1083
                    pefree(slicing, 0);
1084

1085
                    return NULL;
1086
                },
1087
                php_ort_status_tensor_invalidshape_ce,
1088
                "axis value %zd out of range [0, %zd)", idim, input->dimensions);
1089
            
1090
            // Get corresponding start and end values using the same index
1091
            zstart = zend_hash_index_find(Z_ARRVAL_P(start), iaxis);
16✔
1092
            zend   = zend_hash_index_find(Z_ARRVAL_P(end),   iaxis);
16✔
1093
            
1094
            php_ort_status_flow(
16✔
1095
                (!zstart || !zend || 
1096
                  Z_TYPE_P(zstart) != IS_LONG || 
1097
                  Z_TYPE_P(zend)   != IS_LONG),
1098
                {
1099
                    pefree(starting, 0);
1100
                    pefree(ending, 0);
1101
                    pefree(slicing, 0);
1102

1103
                    return NULL;
1104
                },
1105
                php_ort_status_tensor_invalidshape_ce,
1106
                "start and end values must be integers");
1107

1108
            int64_t istart = Z_LVAL_P(zstart);
16✔
1109
            int64_t iend = Z_LVAL_P(zend);
16✔
1110
            
1111
            // Handle negative indices
1112
            if (istart < 0) istart += input->shape[idim];
16✔
1113
            if (iend < 0)   iend += input->shape[idim];
16✔
1114

1115
            // Validate bounds
1116
            php_ort_status_flow(
16✔
1117
                (istart < 0 || istart >= input->shape[idim] ||
1118
                 iend < 0   || iend    > input->shape[idim] ||
1119
                 istart >= iend),
1120
                {
1121
                    pefree(starting, 0);
1122
                    pefree(ending, 0);
1123
                    pefree(slicing, 0);
1124
                    return NULL;
1125
                },
1126
                php_ort_status_tensor_invalidshape_ce,
1127
                "invalid slice bounds for axis %zd: start=%zd, end=%zd, shape=%zd",
1128
                idim, istart, iend, input->shape[idim]);
1129

1130
            starting[idim] = istart;
16✔
1131
            ending[idim]   = iend;
16✔
1132
            slicing[idim]  = iend - istart;
16✔
1133

1134
            iaxis++;
16✔
1135
        } ZEND_HASH_FOREACH_END();
1136
    } else {
1137
        // No axis specified, use start and end arrays directly for all dimensions
1138
        for (int64_t idim = 0; idim < input->dimensions; idim++) {
128✔
1139
            zval *zstart = zend_hash_index_find(Z_ARRVAL_P(start), idim);
96✔
1140
            zval *zend   = zend_hash_index_find(Z_ARRVAL_P(end),   idim);
96✔
1141

1142
            php_ort_status_flow(
96✔
1143
                (!zstart || !zend ||
1144
                 Z_TYPE_P(zstart) != IS_LONG ||
1145
                 Z_TYPE_P(zend)   != IS_LONG),
1146
                {
1147
                    pefree(starting, 0);
1148
                    pefree(ending, 0);
1149
                    pefree(slicing, 0);
1150
                    return NULL;
1151
                },
1152
                php_ort_status_tensor_invalidshape_ce,
1153
                "start and end values must be integers");
1154

1155
            int64_t istart = Z_LVAL_P(zstart);
96✔
1156
            int64_t iend = Z_LVAL_P(zend);
96✔
1157

1158
            // Handle negative indices
1159
            if (istart < 0) istart += input->shape[idim];
96✔
1160
            if (iend < 0)   iend   += input->shape[idim];
96✔
1161

1162
            php_ort_status_flow(
96✔
1163
                (istart < 0 || istart >= input->shape[idim] ||
1164
                 iend < 0   || iend   >  input->shape[idim] ||
1165
                 istart     >= iend),
1166
                {
1167
                    pefree(starting, 0);
1168
                    pefree(ending, 0);
1169
                    pefree(slicing, 0);
1170

1171
                    return NULL;
1172
                },
1173
                php_ort_status_tensor_invalidshape_ce,
1174
                "invalid slice bounds for dimension %zd: start=%zd, end=%zd, shape=%zd",
1175
                idim, istart, iend, input->shape[idim]);
1176

1177
            starting[idim] = istart;
64✔
1178
            ending[idim]   = iend;
64✔
1179
            slicing[idim]  = iend - istart;
64✔
1180
        }
1181
    }
1182

1183
    // Set up slice tensor metadata
1184
    ort->object->dimensions = input->dimensions;
48✔
1185
    ort->object->shape      = slicing; // Transfer ownership
48✔
1186

1187
    // Calculate number of elements in slice
1188
    ort->object->elements = 1;
48✔
1189
    for (int64_t idim = 0; idim < ort->object->dimensions; idim++) {
144✔
1190
        ort->object->elements *= ort->object->shape[idim];
96✔
1191
    }
1192

1193
    // Set data pointer to offset into parent's data (readonly view)
1194
    ort->object->data = (char*)input->data + 
48✔
1195
        (php_ort_tensor_indexof(input, starting) * 
48✔
1196
            php_ort_tensor_sizeof(input));
48✔
1197

1198
    pefree(starting, 0);
48✔
1199
    pefree(ending, 0);
48✔
1200

1201
    return ort->object;
48✔
1202
}
1203

1204
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_getSlice_arginfo, 0, 2, ONNX\\Tensor, 0)
1205
    ZEND_ARG_TYPE_INFO(0, start, IS_ARRAY, 0)
1206
    ZEND_ARG_TYPE_INFO(0, end,   IS_ARRAY, 0)
1207
    ZEND_ARG_TYPE_INFO(0, axis,  IS_ARRAY, 0)
1208
ZEND_END_ARG_INFO()
1209

1210
PHP_METHOD(ONNX_Tensor, getSlice)
96✔
1211
{
1212
    php_ort_tensor_t* ort =
96✔
1213
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
96✔
1214
    zval *start, *end, *axis = NULL;
96✔
1215

1216
    ZEND_PARSE_PARAMETERS_START(2, 3)
96✔
1217
        Z_PARAM_ARRAY(start)
192✔
1218
        Z_PARAM_ARRAY(end)
192✔
1219
        Z_PARAM_OPTIONAL
96✔
1220
        Z_PARAM_ARRAY(axis)
112✔
1221
    ZEND_PARSE_PARAMETERS_END();
96✔
1222

1223
    php_ort_tensor_slice(ort->object, start, end, axis, return_value);
96✔
1224
}
1225

1226
static inline zend_bool php_ort_tensor_data(ort_tensor_t *tensor, size_t *offset, size_t size, zval *node, size_t depth) {
65,424✔
1227
    // Special case for scalar tensors (0 dimensions)
1228
    if (tensor->dimensions == 0) {
65,424✔
1229
        // This should not be called for scalar tensors
1230
        // The getData method directly handles scalar case
1231
        php_ort_status_flow(
×
1232
            !SUCCESS,
1233
            return 0,
1234
            php_ort_status_tensor_invalidshape_ce,
1235
            "php_ort_tensor_data should not be called for scalar tensors");
1236
    }
1237

1238
    // Validate depth bounds
1239
    php_ort_status_flow(
65,424✔
1240
        (depth >= tensor->dimensions),
1241
        return 0,
1242
        php_ort_status_tensor_invalidshape_ce,
1243
        "depth %zd exceeds tensor dimensions %zd", depth, tensor->dimensions);
1244

1245
    // Validate shape at current depth
1246
    php_ort_status_flow(
65,424✔
1247
        (tensor->shape[depth] <= 0),
1248
        return 0,
1249
        php_ort_status_tensor_invalidshape_ce,
1250
        "invalid shape at dimension %zd: %zd", depth, tensor->shape[depth]);
1251

1252
    // Calculate remaining elements from current offset
1253
    size_t remaining_elements = (*offset < tensor->elements) ? (tensor->elements - *offset) : 0;
65,424✔
1254
    
1255
    // Calculate how many elements we can actually extract at this depth
1256
    int64_t elements_to_extract = tensor->shape[depth];
65,424✔
1257
    if (depth == tensor->dimensions - 1) {
65,424✔
1258
        // At leaf level, limit to remaining elements
1259
        elements_to_extract = (remaining_elements < (size_t)tensor->shape[depth]) ? 
63,088✔
1260
                             (int64_t)remaining_elements : tensor->shape[depth];
63,088✔
1261
    }
1262

1263
    // Initialize array with actual size we'll extract
1264
    array_init_size(node, elements_to_extract);
65,424✔
1265

1266
    if (depth == tensor->dimensions - 1) {
65,424✔
1267
        // Leaf level - extract scalar values
1268
        for (int64_t i = 0; i < elements_to_extract; i++) {
13,111,776✔
1269
            zval val;
13,048,688✔
1270
            
1271
            // Validate offset bounds before accessing data
1272
            php_ort_status_flow(
13,048,688✔
1273
                (*offset >= tensor->elements),
1274
                return 0,
1275
                php_ort_status_tensor_invaliddata_ce,
1276
                "data offset %zd exceeds tensor element count %zd", *offset, tensor->elements);
1277

1278
            void *source = (char *)tensor->data + ((*offset) * size);
13,048,688✔
1279

1280
            switch (tensor->type) {
13,048,688✔
1281
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
1,533,696✔
1282
                    ZVAL_DOUBLE(&val, *(float *)source);
1,533,696✔
1283
                    break;
1,533,696✔
1284

1285
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
1,976,048✔
1286
                    ZVAL_DOUBLE(&val, *(double *)source);
1,976,048✔
1287
                    break;
1,976,048✔
1288

1289
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
1,415,872✔
1290
                    ZVAL_LONG(&val, *(int8_t *)source);
1,415,872✔
1291
                    break;
1,415,872✔
1292

1293
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
1,342,432✔
1294
                    ZVAL_LONG(&val, *(int16_t *)source);
1,342,432✔
1295
                    break;
1,342,432✔
1296

1297
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
1,343,088✔
1298
                    ZVAL_LONG(&val, *(int32_t *)source);
1,343,088✔
1299
                    break;
1,343,088✔
1300

1301
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
1,343,296✔
1302
                    ZVAL_LONG(&val, *(int64_t *)source);
1,343,296✔
1303
                    break;
1,343,296✔
1304

1305
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
1,430,048✔
1306
                    ZVAL_LONG(&val, *(uint8_t *)source);
1,430,048✔
1307
                    break;
1,430,048✔
1308

1309
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
1,331,808✔
1310
                    ZVAL_LONG(&val, *(uint16_t *)source);
1,331,808✔
1311
                    break;
1,331,808✔
1312

1313
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32:
1,331,808✔
1314
                    ZVAL_LONG(&val, *(uint32_t *)source);
1,331,808✔
1315
                    break;
1,331,808✔
1316

1317
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
1318
                    // UINT64 is not supported - this should not be reached
1319
                    php_ort_status_flow(
×
1320
                        !SUCCESS,
1321
                        {
1322
                            return 0;
1323
                        },
1324
                        php_ort_status_tensor_invalidtype_ce,
1325
                        "UINT64 tensor type is not supported (values exceed PHP integer range)");
1326

1327
                case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
1328
                    ZVAL_BOOL(&val, *(uint8_t *)source ? 1 : 0);
592✔
1329
                    break;
592✔
1330

1331
                default:
1332
                    php_ort_status_flow(
×
1333
                        !SUCCESS,
1334
                        {
1335
                            return 0;
1336
                        },
1337
                        php_ort_status_tensor_invaliddata_ce,
1338
                        "unsupported tensor data type: %d", tensor->type);
1339
            }
1340

1341
            php_ort_status_flow(
13,048,688✔
1342
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &val) == NULL),
1343
                return 0,
1344
                php_ort_status_tensor_invaliddata_ce,
1345
                "failed to update array at index %zd", i);
1346

1347
            (*offset)++;
13,048,688✔
1348
        }
1349
    } else {
1350
        // Recursive case - process sub-dimensions
1351
        // For non-leaf levels, we need to check if we have enough remaining elements
1352
        // to fill the expected sub-structures
1353
        for (int64_t i = 0; i < elements_to_extract; i++) {
49,008✔
1354
            // Check if we still have elements remaining before processing
1355
            if (*offset >= tensor->elements) {
46,704✔
1356
                break; // No more elements available
1357
            }
1358
            
1359
            zval child;
46,672✔
1360
            
1361
            php_ort_status_flow(
46,672✔
1362
                (!php_ort_tensor_data(tensor, offset, size, &child, depth + 1)),
1363
                return 0,
1364
                php_ort_status_tensor_invaliddata_ce,
1365
                "failed to extract data at dimension %zd, index %zd", depth, i);
1366

1367
            php_ort_status_flow(
46,672✔
1368
                (zend_hash_index_update(Z_ARRVAL_P(node), i, &child) == NULL),
1369
                return 0,
1370
                php_ort_status_tensor_invaliddata_ce,
1371
                "failed to update array at dimension %zd, index %zd", depth, i);
1372
        }
1373
    }
1374

1375
    return 1;
1376
}
1377

1378
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_ort_tensor_getData_arginfo, 0, 0, IS_ARRAY, 0)
1379
    ZEND_ARG_TYPE_INFO(0, offset, IS_LONG, 0)
1380
    ZEND_ARG_TYPE_INFO(0, depth,  IS_LONG, 0)
1381
ZEND_END_ARG_INFO()
1382

1383
PHP_METHOD(ONNX_Tensor, getData)
19,728✔
1384
{
1385
    php_ort_tensor_t* ort =
19,728✔
1386
        php_ort_tensor_fetch(Z_OBJ(EX(This)));
19,728✔
1387
    zend_long offset = 0, depth = 0;
19,728✔
1388

1389
    ZEND_PARSE_PARAMETERS_START(0, 2)
19,728✔
1390
        Z_PARAM_OPTIONAL
19,728✔
1391
        Z_PARAM_LONG(offset)
20,080✔
1392
        Z_PARAM_LONG(depth)
576✔
1393
    ZEND_PARSE_PARAMETERS_END();
19,728✔
1394

1395
    php_ort_status_flow(
19,728✔
1396
        (offset < 0 || offset > ort->object->elements),
1397
        return,
1398
        php_ort_status_tensor_invaliddata_ce,
1399
        "offset %zd out of range [0, %zd]",
1400
        offset, ort->object->elements);
1401

1402
    // Special case for scalar tensors (0 dimensions)
1403
    if (ort->object->dimensions == 0) {
19,680✔
1404
        // For scalar tensors, check if depth parameter was provided
1405
        php_ort_status_flow(
848✔
1406
            (ZEND_NUM_ARGS() > 1 && depth != 0),
1407
            return,
1408
            php_ort_status_tensor_invalidshape_ce,
1409
            "depth parameter cannot be used with scalar tensors");
1410

1411
        // Handle offset parameter - for scalar tensors, offset 0 returns the value, any other offset returns empty array
1412
        if (offset > 0) {
832✔
1413
            array_init(return_value);
16✔
1414
            return;
16✔
1415
        }
1416
        
1417
        // For scalar tensors, return a single-element array with the scalar value
1418
        array_init_size(return_value, 1);
816✔
1419
        zval val;
816✔
1420
        
1421
        void *source = ort->object->data;
816✔
1422
        
1423
        switch (ort->object->type) {
816✔
1424
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT:
208✔
1425
                ZVAL_DOUBLE(&val, *(float *)source);
208✔
1426
                break;
208✔
1427

1428
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE:
208✔
1429
                ZVAL_DOUBLE(&val, *(double *)source);
208✔
1430
                break;
208✔
1431

1432
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8:
32✔
1433
                ZVAL_LONG(&val, *(int8_t *)source);
32✔
1434
                break;
32✔
1435

1436
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16:
32✔
1437
                ZVAL_LONG(&val, *(int16_t *)source);
32✔
1438
                break;
32✔
1439

1440
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32:
32✔
1441
                ZVAL_LONG(&val, *(int32_t *)source);
32✔
1442
                break;
32✔
1443

1444
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:
144✔
1445
                ZVAL_LONG(&val, *(int64_t *)source);
144✔
1446
                break;
144✔
1447

1448
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8:
32✔
1449
                ZVAL_LONG(&val, *(uint8_t *)source);
32✔
1450
                break;
32✔
1451

1452
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16:
32✔
1453
                ZVAL_LONG(&val, *(uint16_t *)source);
32✔
1454
                break;
32✔
1455

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

1460
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64:
1461
                // UINT64 is not supported - this should not be reached
1462
                php_ort_status_flow(
×
1463
                    !SUCCESS,
1464
                    return,
1465
                    php_ort_status_tensor_invalidtype_ce,
1466
                    "UINT64 tensor type is not supported (values exceed PHP integer range)");
1467

1468
            case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL:
1469
                ZVAL_BOOL(&val, *(uint8_t *)source ? 1 : 0);
64✔
1470
                break;
64✔
1471

1472
            default:
1473
                php_ort_status_flow(
×
1474
                    !SUCCESS,
1475
                    return,
1476
                    php_ort_status_tensor_invaliddata_ce,
1477
                    "unsupported tensor data type: %d", ort->object->type);
1478
        }
1479
        
1480
        add_index_zval(return_value, 0, &val);
816✔
1481
        return;
816✔
1482
    }
1483

1484
    // Normal case for non-scalar tensors
1485
    php_ort_status_flow(
18,832✔
1486
        (depth < 0 || depth >= ort->object->dimensions),
1487
        return,
1488
        php_ort_status_tensor_invalidshape_ce,
1489
        "depth %zd out of range [0, %zd)",
1490
        depth, ort->object->dimensions);
1491

1492
    php_ort_status_flow(
37,504✔
1493
        (!php_ort_tensor_data(
1494
            ort->object, 
1495
            &offset,
1496
            php_ort_tensor_sizeof(ort->object), 
1497
            return_value, 
1498
            (size_t)depth)),
1499
        return,
1500
        php_ort_status_tensor_invaliddata_ce,
1501
        "failed to extract tensor data starting at offset %zd, depth %zd", offset, depth);
1502
}
1503

1504
// Helper: Recursively infer shape from a PHP array
1505
// Robust recursive shape inference with raggedness and type checks
1506
static zend_bool php_ort_infer_shape(zval *data, size_t *shape, size_t max, size_t *dimensions) {
432✔
1507
    size_t dimension = 0;
432✔
1508
    zval *level = data;
432✔
1509
    while (Z_TYPE_P(level) == IS_ARRAY) {
624✔
1510
        php_ort_status_flow(
624✔
1511
            (dimension >= max),
1512
            return 0,
1513
            php_ort_status_tensor_invaliddata_ce,
1514
            "shape exceeds maximum allowed dimensions (%zd)", max);
1515

1516
        size_t len = zend_hash_num_elements(Z_ARRVAL_P(level));
624✔
1517

1518
        php_ort_status_flow(
624✔
1519
            len == 0,
1520
            return 0,
1521
            php_ort_status_tensor_invaliddata_ce,
1522
            "empty array encountered at dimension %zd (ragged or empty tensor)",
1523
            dimension);
1524

1525
        shape[dimension] = len;
608✔
1526

1527
        // Check all elements at this level are arrays or all are scalars
1528
        zend_bool found_array = 0, found_scalar = 0;
608✔
1529
        zval *first = NULL;
608✔
1530
        zend_ulong idx = 0;
608✔
1531
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(level), first) { 
608✔
1532
            break; 
1533
        } ZEND_HASH_FOREACH_END();
1534
        
1535
        if (first && Z_TYPE_P(first) == IS_ARRAY) {
608✔
1536
            found_array = 1;
1537
        } else {
1538
            found_scalar = 1;
1539
        }
1540
        
1541
        ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(level), zval *sub) {
167,104✔
1542
            if (Z_TYPE_P(sub) == IS_ARRAY) {
166,512✔
1543
                found_array = 1;
1,104✔
1544
                php_ort_status_flow(
1,104✔
1545
                    (zend_hash_num_elements(Z_ARRVAL_P(sub)) !=
1546
                        zend_hash_num_elements(Z_ARRVAL_P(first))),
1547
                    return 0,
1548
                    php_ort_status_tensor_invaliddata_ce,
1549
                    "ragged array: sub-array at dimension %zd has length %zd, expected %zd",
1550
                    dimension+1,
1551
                    zend_hash_num_elements(Z_ARRVAL_P(sub)),
1552
                    zend_hash_num_elements(Z_ARRVAL_P(first)));
1553
            } else {
1554
                found_scalar = 1;
1555
            }
1556

1557
            php_ort_status_flow(
166,496✔
1558
                (Z_TYPE_P(sub) != IS_ARRAY &&
1559
                Z_TYPE_P(sub) != IS_LONG &&
1560
                Z_TYPE_P(sub) != IS_DOUBLE &&
1561
                !(Z_TYPE_P(sub) == IS_TRUE || Z_TYPE_P(sub) == IS_FALSE)),
1562
                return 0,
1563
                php_ort_status_tensor_invaliddata_ce,
1564
                "unsupported type at dimension %zd: %s",
1565
                dimension+1,
1566
                zend_zval_type_name(sub));
1567

1568
            php_ort_status_flow(
166,496✔
1569
                (found_array && found_scalar),
1570
                return 0,
1571
                php_ort_status_tensor_invaliddata_ce,
1572
                "mixed array/scalar types at dimension %zd (ragged tensor)",
1573
                dimension+1);
1574
        } ZEND_HASH_FOREACH_END();
1575
        if (found_array) {
592✔
1576
            // Go one level deeper
1577
            level = first;
192✔
1578
            dimension++;
192✔
1579
        } else {
1580
            // All scalars at this level, this is the last dimension
1581
            dimension++;
400✔
1582
            break;
400✔
1583
        }
1584
    }
1585
    *dimensions = dimension;
400✔
1586
    return 1;
400✔
1587
}
1588

1589
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(php_ort_tensor_from_arginfo, 0, 2, ONNX\\Tensor, 0)
1590
    ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0)
1591
    ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0)
1592
ZEND_END_ARG_INFO()
1593

1594
PHP_METHOD(ONNX_Tensor, from)
432✔
1595
{
1596
    zval *data;
432✔
1597
    zend_long type;
432✔
1598
    size_t shape[32];
432✔
1599
    size_t dimensions = 0;
432✔
1600

1601
    ZEND_PARSE_PARAMETERS_START(2, 2)
432✔
1602
        Z_PARAM_ARRAY(data)
864✔
1603
        Z_PARAM_LONG(type)
864✔
1604
    ZEND_PARSE_PARAMETERS_END();
432✔
1605

1606
    if (!php_ort_infer_shape(data, shape, 32, &dimensions)) {
432✔
1607
        return;
1608
    }
1609

1610
    php_ort_status_flow(
400✔
1611
        (dimensions == 0),
1612
        return,
1613
        php_ort_status_tensor_invaliddata_ce,
1614
        "empty tensor data provided (no dimensions inferred)");
1615

1616
    zval param;
400✔
1617
    array_init_size(&param, dimensions);
400✔
1618
    for (size_t i = 0; i < dimensions; i++) {
992✔
1619
        add_next_index_long(&param, shape[i]);
592✔
1620
    }
1621

1622
    zend_class_entry *scope =
400✔
1623
        zend_get_executed_scope();
400✔
1624
    object_init_ex(return_value, scope);
400✔
1625

1626
    zval retval;
400✔
1627
    zval params[3];
400✔
1628
    ZVAL_ARR(&params[0], Z_ARRVAL(param));
400✔
1629
    ZVAL_ARR(&params[1], Z_ARRVAL_P(data));
400✔
1630
    ZVAL_LONG(&params[2], type);
400✔
1631

1632
    zval constructor;
400✔
1633
    ZVAL_STRING(&constructor, "__construct");
400✔
1634
    zval result;
400✔
1635
    if (SUCCESS == call_user_function(
400✔
1636
            EG(function_table),
1637
            return_value,
1638
            &constructor,
1639
            &result,
1640
            3,
1641
            params)) {
1642
        zval_ptr_dtor(&result);
400✔
1643
    } else {
1644
        zval_ptr_dtor(return_value);
×
1645
    }
1646

1647
    zval_ptr_dtor(&constructor);
400✔
1648
    zval_ptr_dtor(&param);
400✔
1649
}
1650

1651
zend_function_entry php_ort_tensor_interface_methods[] = {
1652
    PHP_ABSTRACT_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo)
1653
    PHP_ABSTRACT_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo)
1654
    PHP_ABSTRACT_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo)
1655
    PHP_ABSTRACT_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo)
1656
    PHP_ABSTRACT_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo)
1657
    PHP_ABSTRACT_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo)
1658
    PHP_ABSTRACT_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo)
1659

1660
    PHP_ABSTRACT_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo)
1661
    PHP_FE_END
1662
};
1663

1664
zend_function_entry php_ort_tensor_persistent_methods[] = {
1665
    PHP_ME(ONNX_Tensor_Persistent, __construct,
1666
        php_ort_tensor_persistent_construct_arginfo, ZEND_ACC_PUBLIC)
1667
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1668
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1669
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1670
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1671
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1672
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1673
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1674
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1675

1676
    PHP_ME(ONNX_Tensor, from,         php_ort_tensor_from_arginfo,         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1677
    PHP_FE_END
1678
};
1679

1680
zend_function_entry php_ort_tensor_transient_methods[] = {
1681
    PHP_ME(ONNX_Tensor_Transient, __construct,
1682
        php_ort_tensor_transient_construct_arginfo, ZEND_ACC_PUBLIC)
1683
    PHP_ME(ONNX_Tensor, isPersistent, php_ort_tensor_isPersistent_arginfo, ZEND_ACC_PUBLIC)
1684
    PHP_ME(ONNX_Tensor, getName,      php_ort_tensor_getName_arginfo,      ZEND_ACC_PUBLIC)
1685
    PHP_ME(ONNX_Tensor, getType,      php_ort_tensor_getType_arginfo,      ZEND_ACC_PUBLIC)
1686
    PHP_ME(ONNX_Tensor, getTypeName,  php_ort_tensor_getTypeName_arginfo,  ZEND_ACC_PUBLIC)
1687
    PHP_ME(ONNX_Tensor, getShape,     php_ort_tensor_getShape_arginfo,     ZEND_ACC_PUBLIC)
1688
    PHP_ME(ONNX_Tensor, getSlice,     php_ort_tensor_getSlice_arginfo,     ZEND_ACC_PUBLIC)
1689
    PHP_ME(ONNX_Tensor, getData,      php_ort_tensor_getData_arginfo,      ZEND_ACC_PUBLIC)
1690
    PHP_ME(ONNX_Tensor, transpose,    php_ort_tensor_transpose_arginfo,    ZEND_ACC_PUBLIC)
1691

1692
    PHP_ME(ONNX_Tensor, from,         php_ort_tensor_from_arginfo,         ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
1693
    PHP_FE_END
1694
};
1695

1696
zend_object* php_ort_tensor_create(zend_class_entry *type) {
57,328✔
1697
    php_ort_tensor_t *ort = ecalloc(1,
57,328✔
1698
        sizeof(php_ort_tensor_t) + zend_object_properties_size(type));
1699

1700
    zend_object_std_init(&ort->std, type);
57,328✔
1701

1702
    ort->std.handlers = &php_ort_tensor_handlers;
57,328✔
1703
    ort->object       = NULL;
57,328✔
1704

1705
    return &ort->std;
57,328✔
1706
}
1707

1708
static HashTable* php_ort_tensor_debug(zend_object *zo, int *temp) {
208✔
1709
    php_ort_tensor_t *ort = php_ort_tensor_fetch(zo);
208✔
1710
    HashTable *debug;
208✔
1711

1712
    ALLOC_HASHTABLE(debug);
208✔
1713
    zend_hash_init(debug, 3, NULL, ZVAL_PTR_DTOR, 0);
208✔
1714

1715
    if (!ort->object) {
208✔
1716
        goto __php_ort_tensor_debug_return;
32✔
1717
    }
1718

1719
    zval persistent;
176✔
1720

1721
    ZVAL_BOOL(&persistent,
176✔
1722
        ort->object->owner == PHP_ORT_OWN_HEAP);
1723
    zend_hash_str_add(debug, 
176✔
1724
        "persistent", sizeof("persistent")-1, 
1725
        &persistent);
1726

1727
    zval type;
176✔
1728
    
1729
    ZVAL_LONG(&type, ort->object->type);
176✔
1730
    zend_hash_add(debug,
176✔
1731
        ZSTR_KNOWN(ZEND_STR_TYPE), &type);
176✔
1732

1733
    if (ort->object->name) {
176✔
1734
        zval name;
176✔
1735

1736
        ZVAL_STR_COPY(&name, ort->object->name);
176✔
1737
        zend_hash_add(debug,
176✔
1738
            ZSTR_KNOWN(ZEND_STR_NAME), &name);
176✔
1739
    }
1740

1741
    zval shape;
176✔
1742

1743
    array_init(&shape);
176✔
1744
    for (int64_t dimension = 0; dimension < ort->object->dimensions; dimension++) {
480✔
1745
        add_next_index_long(
304✔
1746
            &shape, ort->object->shape[dimension]);
304✔
1747
    }
1748
    zend_hash_str_add(debug,
176✔
1749
        "shape", sizeof("shape")-1, &shape);
1750

1751
__php_ort_tensor_debug_return:
208✔
1752
    *temp = 1;
208✔
1753

1754
    return debug;
208✔
1755
}
1756

1757
void php_ort_tensor_destroy(zend_object *o) {
57,328✔
1758
    php_ort_tensor_t *ort =
57,328✔
1759
        php_ort_tensor_fetch(o);
57,328✔
1760

1761
    ort_tensor_release(ort->object);
57,328✔
1762

1763
    zend_object_std_dtor(o);
57,328✔
1764
}
57,328✔
1765

1766
PHP_MINIT_FUNCTION(ORT_TENSOR)
3,168✔
1767
{
1768
    zend_class_entry ce;
3,168✔
1769

1770
#ifdef ZTS
1771
    php_ort_tensor_mutex = tsrm_mutex_alloc();
1772
#endif
1773

1774
    zend_hash_init(&php_ort_tensors, 16, NULL, php_ort_tensor_del, 1);
3,168✔
1775

1776
    // Setup shared handlers for all tensor types
1777
    memcpy(&php_ort_tensor_handlers,
3,168✔
1778
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
1779

1780
    php_ort_tensor_handlers.offset = XtOffsetOf(php_ort_tensor_t, std);
3,168✔
1781
    php_ort_tensor_handlers.get_debug_info = php_ort_tensor_debug;
3,168✔
1782
    php_ort_tensor_handlers.free_obj = php_ort_tensor_destroy;
3,168✔
1783
    php_ort_tensor_handlers.clone_obj = NULL;
3,168✔
1784

1785
    // Register the interface
1786
    INIT_NS_CLASS_ENTRY(ce, "ONNX", "Tensor", php_ort_tensor_interface_methods);
3,168✔
1787
    php_ort_tensor_interface_ce = zend_register_internal_interface(&ce);
3,168✔
1788

1789
    // Register persistent tensor class
1790
    INIT_NS_CLASS_ENTRY(ce, "ONNX\\Tensor", "Persistent", php_ort_tensor_persistent_methods);
3,168✔
1791
    php_ort_tensor_persistent_ce = zend_register_internal_class(&ce);
3,168✔
1792
    php_ort_tensor_persistent_ce->create_object = php_ort_tensor_create;
3,168✔
1793
    zend_class_implements(php_ort_tensor_persistent_ce, 1, php_ort_tensor_interface_ce);
3,168✔
1794

1795
    // Register transient tensor class
1796
    INIT_NS_CLASS_ENTRY(ce, "ONNX\\Tensor", "Transient", php_ort_tensor_transient_methods);
3,168✔
1797
    php_ort_tensor_transient_ce = zend_register_internal_class(&ce);
3,168✔
1798
    php_ort_tensor_transient_ce->create_object = php_ort_tensor_create;
3,168✔
1799
    zend_class_implements(php_ort_tensor_transient_ce, 1, php_ort_tensor_interface_ce);
3,168✔
1800

1801
#ifdef ZEND_ACC_NOT_SERIALIZABLE
1802
    php_ort_tensor_persistent_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
3,168✔
1803
    php_ort_tensor_transient_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE;
3,168✔
1804
#else
1805
    php_ort_tensor_persistent_ce->serialize = zend_class_serialize_deny;
1806
    php_ort_tensor_persistent_ce->unserialize = zend_class_unserialize_deny;
1807
    php_ort_tensor_transient_ce->serialize = zend_class_serialize_deny;
1808
    php_ort_tensor_transient_ce->unserialize = zend_class_unserialize_deny;
1809
#endif
1810

1811
    // Register constants on the interface
1812
    zend_declare_class_constant_long(
3,168✔
1813
        php_ort_tensor_interface_ce,
1814
        "UNDEFINED", sizeof("UNDEFINED")-1,
1815
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED);
1816

1817
    zend_declare_class_constant_long(
3,168✔
1818
        php_ort_tensor_interface_ce,
1819
        "FLOAT", sizeof("FLOAT")-1,
1820
        ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT);
1821

1822
    zend_declare_class_constant_long(
3,168✔
1823
        php_ort_tensor_interface_ce,
1824
        "DOUBLE", sizeof("DOUBLE")-1,
1825
        ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE);
1826

1827
    zend_declare_class_constant_long(
3,168✔
1828
        php_ort_tensor_interface_ce,
1829
        "UINT8", sizeof("UINT8")-1,
1830
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8);
1831

1832
    zend_declare_class_constant_long(
3,168✔
1833
        php_ort_tensor_interface_ce,
1834
        "INT8", sizeof("INT8")-1,
1835
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8);
1836

1837
    zend_declare_class_constant_long(
3,168✔
1838
        php_ort_tensor_interface_ce,
1839
        "UINT16", sizeof("UINT16")-1,
1840
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16);
1841

1842
    zend_declare_class_constant_long(
3,168✔
1843
        php_ort_tensor_interface_ce,
1844
        "INT16", sizeof("INT16")-1,
1845
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16);
1846

1847
    zend_declare_class_constant_long(
3,168✔
1848
        php_ort_tensor_interface_ce,
1849
        "UINT32", sizeof("UINT32")-1,
1850
        ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32);
1851

1852
    zend_declare_class_constant_long(
3,168✔
1853
        php_ort_tensor_interface_ce,
1854
        "INT32", sizeof("INT32")-1,
1855
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32);
1856

1857
    zend_declare_class_constant_long(
3,168✔
1858
        php_ort_tensor_interface_ce,
1859
        "INT64", sizeof("INT64")-1,
1860
        ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64);
1861

1862
    zend_declare_class_constant_long(
3,168✔
1863
        php_ort_tensor_interface_ce,
1864
        "BOOL", sizeof("BOOL")-1,
1865
        ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL);
1866

1867
    return SUCCESS;
3,168✔
1868
}
1869

1870
PHP_MSHUTDOWN_FUNCTION(ORT_TENSOR)
3,168✔
1871
{
1872
    zend_hash_destroy(&php_ort_tensors);
3,168✔
1873

1874
#ifdef ZTS
1875
    tsrm_mutex_free(php_ort_tensor_mutex);
1876
#endif
1877

1878
    return SUCCESS;
3,168✔
1879
}
1880

1881
PHP_RINIT_FUNCTION(ORT_TENSOR)
3,168✔
1882
{
1883
    return SUCCESS;
3,168✔
1884
}
1885

1886
PHP_RSHUTDOWN_FUNCTION(ORT_TENSOR)
3,168✔
1887
{
1888
    return SUCCESS;
3,168✔
1889
}
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