Skip to main content

torsh_ffi/
csharp.rs

1//! C# P/Invoke bindings for ToRSh
2//!
3//! This module provides C#-compatible FFI bindings using Platform Invoke (P/Invoke).
4//! C# can call these functions directly through DllImport attributes.
5
6#![allow(dead_code)]
7
8use crate::c_api::*;
9use std::ffi::CStr;
10use std::os::raw::{c_char, c_float, c_int, c_void};
11use std::ptr;
12
13/// C#-specific wrapper for tensor creation with marshaling hints
14#[no_mangle]
15pub unsafe extern "C" fn csharp_tensor_new(
16    data: *const c_void,
17    shape: *const c_int,
18    ndim: c_int,
19    dtype: c_int,
20) -> *mut TorshTensor {
21    // Convert C# int to usize for shape
22    let shape_vec: Vec<usize> = std::slice::from_raw_parts(shape, ndim as usize)
23        .iter()
24        .map(|&x| x as usize)
25        .collect();
26
27    // Convert C# int to TorshDType
28    let torsh_dtype = match dtype {
29        0 => TorshDType::F32,
30        1 => TorshDType::F64,
31        2 => TorshDType::I32,
32        3 => TorshDType::I64,
33        4 => TorshDType::U8,
34        _ => TorshDType::F32, // Default fallback
35    };
36
37    torsh_tensor_new(data, shape_vec.as_ptr(), shape_vec.len(), torsh_dtype)
38}
39
40/// C#-specific wrapper for tensor creation from float array
41#[no_mangle]
42pub unsafe extern "C" fn csharp_tensor_from_float_array(
43    data: *const c_float,
44    _data_len: c_int,
45    shape: *const c_int,
46    ndim: c_int,
47) -> *mut TorshTensor {
48    // Convert C# int to usize for shape
49    let shape_vec: Vec<usize> = std::slice::from_raw_parts(shape, ndim as usize)
50        .iter()
51        .map(|&x| x as usize)
52        .collect();
53
54    torsh_tensor_new(
55        data as *const c_void,
56        shape_vec.as_ptr(),
57        shape_vec.len(),
58        TorshDType::F32,
59    )
60}
61
62/// C#-specific wrapper for tensor creation from double array
63#[no_mangle]
64pub unsafe extern "C" fn csharp_tensor_from_double_array(
65    data: *const c_float, // Note: Using c_float for simplicity, should be c_double
66    _data_len: c_int,
67    shape: *const c_int,
68    ndim: c_int,
69) -> *mut TorshTensor {
70    let shape_vec: Vec<usize> = std::slice::from_raw_parts(shape, ndim as usize)
71        .iter()
72        .map(|&x| x as usize)
73        .collect();
74
75    torsh_tensor_new(
76        data as *const c_void,
77        shape_vec.as_ptr(),
78        shape_vec.len(),
79        TorshDType::F64,
80    )
81}
82
83/// C#-specific wrapper for tensor creation from int array
84#[no_mangle]
85pub unsafe extern "C" fn csharp_tensor_from_int_array(
86    data: *const c_int,
87    _data_len: c_int,
88    shape: *const c_int,
89    ndim: c_int,
90) -> *mut TorshTensor {
91    let shape_vec: Vec<usize> = std::slice::from_raw_parts(shape, ndim as usize)
92        .iter()
93        .map(|&x| x as usize)
94        .collect();
95
96    torsh_tensor_new(
97        data as *const c_void,
98        shape_vec.as_ptr(),
99        shape_vec.len(),
100        TorshDType::I32,
101    )
102}
103
104/// C#-specific wrapper for tensor addition
105#[no_mangle]
106pub unsafe extern "C" fn csharp_tensor_add(
107    a: *mut TorshTensor,
108    b: *mut TorshTensor,
109) -> *mut TorshTensor {
110    // Perform in-place addition
111    let error = torsh_tensor_add(a, b, a);
112    if error != TorshError::Success {
113        return std::ptr::null_mut();
114    }
115    a
116}
117
118/// C#-specific wrapper for tensor subtraction
119#[no_mangle]
120pub unsafe extern "C" fn csharp_tensor_subtract(
121    a: *mut TorshTensor,
122    b: *mut TorshTensor,
123) -> *mut TorshTensor {
124    // Perform in-place subtraction
125    let error = torsh_tensor_sub(a, b, a);
126    if error != TorshError::Success {
127        return std::ptr::null_mut();
128    }
129    a
130}
131
132/// C#-specific wrapper for tensor multiplication
133#[no_mangle]
134pub unsafe extern "C" fn csharp_tensor_multiply(
135    a: *mut TorshTensor,
136    b: *mut TorshTensor,
137) -> *mut TorshTensor {
138    // Perform in-place multiplication
139    let error = torsh_tensor_mul(a, b, a);
140    if error != TorshError::Success {
141        return std::ptr::null_mut();
142    }
143    a
144}
145
146/// C#-specific wrapper for matrix multiplication
147#[no_mangle]
148pub unsafe extern "C" fn csharp_tensor_matmul(
149    a: *mut TorshTensor,
150    b: *mut TorshTensor,
151) -> *mut TorshTensor {
152    // Perform in-place matrix multiplication
153    let error = torsh_tensor_matmul(a, b, a);
154    if error != TorshError::Success {
155        return std::ptr::null_mut();
156    }
157    a
158}
159
160/// C#-specific wrapper for ReLU activation
161#[no_mangle]
162pub unsafe extern "C" fn csharp_tensor_relu(tensor: *mut TorshTensor) -> *mut TorshTensor {
163    // Create output tensor (we'll reuse input tensor as output for simplicity)
164    let result = torsh_tensor_relu(tensor as *const TorshTensor, tensor);
165    match result {
166        crate::c_api::TorshError::Success => tensor,
167        _ => std::ptr::null_mut(),
168    }
169}
170
171/// C#-specific wrapper for getting tensor shape with int output
172#[no_mangle]
173pub unsafe extern "C" fn csharp_tensor_get_shape(
174    tensor: *mut TorshTensor,
175    shape: *mut c_int,
176    max_dims: c_int,
177    actual_dims: *mut c_int,
178) -> c_int {
179    let mut temp_shape = vec![0usize; max_dims as usize];
180    let mut ndim = 0usize;
181
182    let result = torsh_tensor_shape(tensor, temp_shape.as_mut_ptr(), &mut ndim);
183
184    if result == TorshError::Success {
185        *actual_dims = ndim as c_int;
186
187        // Copy shape data, converting usize to c_int
188        for i in 0..std::cmp::min(ndim, max_dims as usize) {
189            *shape.add(i) = temp_shape[i] as c_int;
190        }
191
192        0 // Success
193    } else {
194        1 // Error
195    }
196}
197
198/// C#-specific wrapper for getting tensor data as float array
199#[no_mangle]
200pub unsafe extern "C" fn csharp_tensor_get_float_data(
201    tensor: *mut TorshTensor,
202    data: *mut c_float,
203    max_elements: c_int,
204    actual_elements: *mut c_int,
205) -> c_int {
206    // Note: This is a simplified implementation
207    // In practice, you would need to determine the actual size first
208    let result = torsh_tensor_data(tensor as *const TorshTensor);
209
210    if !result.is_null() {
211        // Copy data from result to the output buffer
212        // This is simplified - in practice would check actual tensor size
213        let src_data = std::slice::from_raw_parts(result as *const c_float, max_elements as usize);
214        let dst_data = std::slice::from_raw_parts_mut(data, max_elements as usize);
215        dst_data.copy_from_slice(src_data);
216        *actual_elements = max_elements; // Simplified - should be actual count
217        0 // Success
218    } else {
219        1 // Error
220    }
221}
222
223/// C#-specific wrapper for tensor cleanup
224#[no_mangle]
225pub unsafe extern "C" fn csharp_tensor_dispose(tensor: *mut TorshTensor) {
226    torsh_tensor_free(tensor)
227}
228
229/// C#-specific wrapper for creating a linear layer
230#[no_mangle]
231pub unsafe extern "C" fn csharp_linear_new(
232    in_features: c_int,
233    out_features: c_int,
234    bias: c_int, // 0 = false, 1 = true
235) -> *mut TorshModule {
236    torsh_linear_new(in_features as usize, out_features as usize, bias != 0)
237}
238
239/// C#-specific wrapper for linear layer forward pass
240#[no_mangle]
241pub unsafe extern "C" fn csharp_linear_forward(
242    module: *mut TorshModule,
243    input: *mut TorshTensor,
244) -> *mut TorshTensor {
245    // For simplicity, we'll reuse the input tensor as output
246    // In a real implementation, you might want to create a new tensor
247    let result = torsh_linear_forward(
248        module as *const TorshModule,
249        input as *const TorshTensor,
250        input,
251    );
252    match result {
253        crate::c_api::TorshError::Success => input,
254        _ => std::ptr::null_mut(),
255    }
256}
257
258/// C#-specific wrapper for module cleanup
259#[no_mangle]
260pub unsafe extern "C" fn csharp_module_dispose(module: *mut TorshModule) {
261    torsh_module_free(module)
262}
263
264/// C#-specific wrapper for SGD optimizer
265#[no_mangle]
266pub unsafe extern "C" fn csharp_sgd_new(
267    learning_rate: c_float,
268    momentum: c_float,
269) -> *mut TorshOptimizer {
270    torsh_sgd_new(learning_rate, momentum)
271}
272
273/// C#-specific wrapper for Adam optimizer
274#[no_mangle]
275pub unsafe extern "C" fn csharp_adam_new(
276    learning_rate: c_float,
277    beta1: c_float,
278    beta2: c_float,
279    epsilon: c_float,
280) -> *mut TorshOptimizer {
281    torsh_adam_new(learning_rate, beta1, beta2, epsilon)
282}
283
284/// C#-specific wrapper for optimizer step with simplified parameter handling
285#[no_mangle]
286pub unsafe extern "C" fn csharp_optimizer_step(
287    optimizer: *mut TorshOptimizer,
288    _parameters: *mut *mut TorshTensor,
289    _gradients: *mut *mut TorshTensor,
290    _param_count: c_int,
291) -> c_int {
292    // Note: C API only takes optimizer, additional parameters are ignored for now
293    let result = torsh_optimizer_step(optimizer);
294
295    match result {
296        TorshError::Success => 0,
297        _ => 1,
298    }
299}
300
301/// C#-specific wrapper for optimizer cleanup
302#[no_mangle]
303pub unsafe extern "C" fn csharp_optimizer_dispose(optimizer: *mut TorshOptimizer) {
304    torsh_optimizer_free(optimizer)
305}
306
307/// C#-specific wrapper for getting last error as string
308#[no_mangle]
309pub unsafe extern "C" fn csharp_get_last_error(buffer: *mut c_char, buffer_size: c_int) -> c_int {
310    let error_ptr = torsh_get_last_error();
311
312    if error_ptr.is_null() {
313        return 0; // No error
314    }
315
316    let error_cstr = CStr::from_ptr(error_ptr);
317    let error_bytes = error_cstr.to_bytes();
318
319    let copy_len = std::cmp::min(error_bytes.len(), (buffer_size - 1) as usize);
320
321    if copy_len > 0 {
322        ptr::copy_nonoverlapping(error_bytes.as_ptr(), buffer as *mut u8, copy_len);
323        *buffer.add(copy_len) = 0; // Null terminator
324    }
325
326    error_bytes.len() as c_int
327}
328
329/// C#-specific wrapper for clearing last error
330#[no_mangle]
331pub unsafe extern "C" fn csharp_clear_last_error() {
332    torsh_clear_last_error()
333}
334
335/// C#-specific wrapper for version information
336#[no_mangle]
337pub unsafe extern "C" fn csharp_get_version(buffer: *mut c_char, buffer_size: c_int) -> c_int {
338    let version_ptr = torsh_version();
339
340    if version_ptr.is_null() {
341        return 0;
342    }
343
344    let version_cstr = CStr::from_ptr(version_ptr);
345    let version_bytes = version_cstr.to_bytes();
346
347    let copy_len = std::cmp::min(version_bytes.len(), (buffer_size - 1) as usize);
348
349    if copy_len > 0 {
350        ptr::copy_nonoverlapping(version_bytes.as_ptr(), buffer as *mut u8, copy_len);
351        *buffer.add(copy_len) = 0; // Null terminator
352    }
353
354    version_bytes.len() as c_int
355}
356
357/// C#-specific wrapper for CUDA availability check
358#[no_mangle]
359pub unsafe extern "C" fn csharp_cuda_is_available() -> c_int {
360    torsh_cuda_is_available()
361}
362
363/// C#-specific wrapper for CUDA device count
364#[no_mangle]
365pub unsafe extern "C" fn csharp_cuda_device_count() -> c_int {
366    torsh_cuda_device_count()
367}
368
369/// C#-specific helper for creating zero tensors
370#[no_mangle]
371pub unsafe extern "C" fn csharp_tensor_zeros(
372    shape: *const c_int,
373    ndim: c_int,
374    dtype: c_int,
375) -> *mut TorshTensor {
376    let shape_vec: Vec<usize> = std::slice::from_raw_parts(shape, ndim as usize)
377        .iter()
378        .map(|&x| x as usize)
379        .collect();
380
381    let total_elements: usize = shape_vec.iter().product();
382    let zeros_data = vec![0.0f32; total_elements];
383
384    let torsh_dtype = match dtype {
385        0 => TorshDType::F32,
386        1 => TorshDType::F64,
387        2 => TorshDType::I32,
388        3 => TorshDType::I64,
389        4 => TorshDType::U8,
390        _ => TorshDType::F32,
391    };
392
393    torsh_tensor_new(
394        zeros_data.as_ptr() as *const c_void,
395        shape_vec.as_ptr(),
396        shape_vec.len(),
397        torsh_dtype,
398    )
399}
400
401/// C#-specific helper for creating ones tensors
402#[no_mangle]
403pub unsafe extern "C" fn csharp_tensor_ones(
404    shape: *const c_int,
405    ndim: c_int,
406    dtype: c_int,
407) -> *mut TorshTensor {
408    let shape_vec: Vec<usize> = std::slice::from_raw_parts(shape, ndim as usize)
409        .iter()
410        .map(|&x| x as usize)
411        .collect();
412
413    let total_elements: usize = shape_vec.iter().product();
414    let ones_data = vec![1.0f32; total_elements];
415
416    let torsh_dtype = match dtype {
417        0 => TorshDType::F32,
418        1 => TorshDType::F64,
419        2 => TorshDType::I32,
420        3 => TorshDType::I64,
421        4 => TorshDType::U8,
422        _ => TorshDType::F32,
423    };
424
425    torsh_tensor_new(
426        ones_data.as_ptr() as *const c_void,
427        shape_vec.as_ptr(),
428        shape_vec.len(),
429        torsh_dtype,
430    )
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_csharp_tensor_creation() {
439        let data = vec![1.0f32, 2.0, 3.0, 4.0];
440        let shape = vec![2i32, 2i32];
441
442        unsafe {
443            let tensor = csharp_tensor_from_float_array(
444                data.as_ptr(),
445                data.len() as c_int,
446                shape.as_ptr(),
447                shape.len() as c_int,
448            );
449
450            assert!(!tensor.is_null());
451
452            // Test shape retrieval
453            let mut retrieved_shape = vec![0i32; 2];
454            let mut actual_dims = 0i32;
455            let result =
456                csharp_tensor_get_shape(tensor, retrieved_shape.as_mut_ptr(), 2, &mut actual_dims);
457
458            assert_eq!(result, 0); // Success
459            assert_eq!(actual_dims, 2);
460            assert_eq!(retrieved_shape, shape);
461
462            // Clean up
463            csharp_tensor_dispose(tensor);
464        }
465    }
466
467    #[test]
468    fn test_csharp_tensor_operations() {
469        let data1 = vec![1.0f32, 2.0, 3.0, 4.0];
470        let data2 = vec![5.0f32, 6.0, 7.0, 8.0];
471        let shape = vec![2i32, 2i32];
472
473        unsafe {
474            let tensor1 = csharp_tensor_from_float_array(
475                data1.as_ptr(),
476                data1.len() as c_int,
477                shape.as_ptr(),
478                shape.len() as c_int,
479            );
480
481            let tensor2 = csharp_tensor_from_float_array(
482                data2.as_ptr(),
483                data2.len() as c_int,
484                shape.as_ptr(),
485                shape.len() as c_int,
486            );
487
488            assert!(!tensor1.is_null());
489            assert!(!tensor2.is_null());
490
491            // Test addition
492            let result = csharp_tensor_add(tensor1, tensor2);
493            assert!(!result.is_null());
494
495            // Clean up
496            csharp_tensor_dispose(tensor1);
497            csharp_tensor_dispose(tensor2);
498            csharp_tensor_dispose(result);
499        }
500    }
501
502    #[test]
503    fn test_csharp_zeros_ones() {
504        let shape = vec![3i32, 3i32];
505
506        unsafe {
507            // Test zeros creation
508            let zeros = csharp_tensor_zeros(shape.as_ptr(), shape.len() as c_int, 0);
509            assert!(!zeros.is_null());
510
511            // Test ones creation
512            let ones = csharp_tensor_ones(shape.as_ptr(), shape.len() as c_int, 0);
513            assert!(!ones.is_null());
514
515            // Clean up
516            csharp_tensor_dispose(zeros);
517            csharp_tensor_dispose(ones);
518        }
519    }
520
521    #[test]
522    fn test_csharp_linear_layer() {
523        unsafe {
524            // Test linear layer creation
525            let linear = csharp_linear_new(4, 2, 1);
526            assert!(!linear.is_null());
527
528            // Test with dummy input
529            let input_data = vec![1.0f32, 2.0, 3.0, 4.0];
530            let input_shape = vec![1i32, 4i32];
531
532            let input_tensor = csharp_tensor_from_float_array(
533                input_data.as_ptr(),
534                input_data.len() as c_int,
535                input_shape.as_ptr(),
536                input_shape.len() as c_int,
537            );
538
539            let output = csharp_linear_forward(linear, input_tensor);
540            assert!(!output.is_null());
541
542            // Clean up
543            csharp_tensor_dispose(input_tensor);
544            csharp_tensor_dispose(output);
545            csharp_module_dispose(linear);
546        }
547    }
548
549    #[test]
550    fn test_csharp_optimizers() {
551        unsafe {
552            // Test SGD optimizer creation
553            let sgd = csharp_sgd_new(0.01, 0.9);
554            assert!(!sgd.is_null());
555
556            // Test Adam optimizer creation
557            let adam = csharp_adam_new(0.001, 0.9, 0.999, 1e-8);
558            assert!(!adam.is_null());
559
560            // Clean up
561            csharp_optimizer_dispose(sgd);
562            csharp_optimizer_dispose(adam);
563        }
564    }
565}