From 400197def854ef261cb3a57bbfe9e5b6ab954cdf Mon Sep 17 00:00:00 2001 From: luoliwoshang <2643523683@qq.com> Date: Sun, 14 Jul 2024 18:12:21 +0800 Subject: [PATCH 1/4] doc:Rust-to-LLGO-Migration-Guide.md --- doc/Rust-to-LLGO-Migration-Guide.md | 125 ++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 doc/Rust-to-LLGO-Migration-Guide.md diff --git a/doc/Rust-to-LLGO-Migration-Guide.md b/doc/Rust-to-LLGO-Migration-Guide.md new file mode 100644 index 00000000..253aca82 --- /dev/null +++ b/doc/Rust-to-LLGO-Migration-Guide.md @@ -0,0 +1,125 @@ +# Rust to LLGO Migration Document + +### Add Dependencies & Build Configuration + +Edit `Cargo.toml` to include necessary dependencies and configuration: + +```toml +[dependencies] +libc = "0.2" +csv = "1.1" + +[lib] +crate-type = ["cdylib"] # The generated dynamic library will conform to the C standard +``` + +### Import C Language Types + +Use types from the libc package for interoperability with C: + +```rust +use libc::{c_int, c_char, strlen}; +``` + +### Function Decoration and Attributes + +To ensure that Rust functions can be correctly called by C and LLGO, use the following decorators: + +- `#[no_mangle]` prevents the compiler from mangling the function name. + +- `unsafe` is used to mark operations that are unsafe, especially when dealing with raw pointers. + +- `extern "C"` specifies the use of C calling conventions. + +### Memory Management + +Use `Box` to manage dynamic memory to ensure correct memory release between Rust and C: + +```rust +pub unsafe extern "C" fn sled_create_config() -> \*mut Config { +Box::into_raw(Box::new(Config::new())) +} + +#[no_mangle] +pub unsafe extern "C" fn sled_free_config(config: \*mut Config) { +drop(Box::from_raw(config)); +} +``` + +### Handling Generic Pointers + +Address the interfacing issues between generic pointers in C and Rust: + +```rust +#[no_mangle] +pub extern "C" fn csv_reader_new(file_path: *const c_char) -> *mut c_void { /* ... */ } + +#[no_mangle] +pub extern "C" fn csv_reader_free(ptr: *mut c_void) { /* ... */ } +``` + +### String Handling + +Convert strings between C and Rust: + +```rust +#[no_mangle] +pub extern "C" fn csv_reader_read_record(ptr: *mut c_void) -> *const c_char { /* ... */ } + +#[no_mangle] +pub extern "C" fn free_string(s: *mut c_char) { + unsafe { + let c_string = CString::from_raw(s); + drop(c_string); + } +} +``` + +### Compilation and Installation + +Build the dynamic library and use `dylib-installer` to install it to the system path: + +```sh +cargo build --release +cargo install --git https://github.com/hackerchai/dylib-installer +sudo dylib_installer ./target/release/ +``` + +### Generate Header File + +Use cbindgen to generate a C header file, automating this process through a `build.rs` script: + +```rust +fn main() { + let config = cbindgen::Config::from_file("cbindgen.toml").expect("Config file not found."); + cbindgen::generate_with_config(&crate_dir, config).unwrap().write_to_file("target/include/csv_wrapper.h"); +} + +``` + +### LLGO Package Mapping + +Map functions from the Rust library to an LLGO package, ensuring type consistency: + +```go +const ( + LLGoPackage = "link: $(pkg-config --libs csv_wrapper); -lcsv_wrapper" +) + +type Reader struct { + Unused [8]byte +} + +//go:linkname NewReader C.csv_reader_new +func NewReader(file_path *c.Char) *Reader + +//llgo:link (*Reader).Free C.csv_reader_free +func (reader *Reader) Free() {} + +//llgo:link (*Reader).ReadRecord C.csv_reader_read_record +func (reader *Reader) ReadRecord() *c.Char { return nil } +``` + +### Writing Examples and README + +Provide example code and a detailed README file to help users understand how to use the generated library. From c0eeedc71ac4360f31f1963ce9926ff17a5ad2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E8=8B=B1=E6=9D=B0?= Date: Wed, 17 Jul 2024 10:00:48 +0800 Subject: [PATCH 2/4] llgo/rust/readme: update readme --- doc/Rust-to-LLGO-Migration-Guide.md | 149 +++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 15 deletions(-) diff --git a/doc/Rust-to-LLGO-Migration-Guide.md b/doc/Rust-to-LLGO-Migration-Guide.md index 253aca82..48ae4609 100644 --- a/doc/Rust-to-LLGO-Migration-Guide.md +++ b/doc/Rust-to-LLGO-Migration-Guide.md @@ -11,6 +11,9 @@ csv = "1.1" [lib] crate-type = ["cdylib"] # The generated dynamic library will conform to the C standard + +[build-dependencies] +cbindgen = "0.26.0" ``` ### Import C Language Types @@ -26,11 +29,37 @@ use libc::{c_int, c_char, strlen}; To ensure that Rust functions can be correctly called by C and LLGO, use the following decorators: - `#[no_mangle]` prevents the compiler from mangling the function name. - - `unsafe` is used to mark operations that are unsafe, especially when dealing with raw pointers. - - `extern "C"` specifies the use of C calling conventions. +For example, in Rust, we write CSV code like this: + +```rust +use csv::ReaderBuilder; +use std::error::Error; + +fn main() -> Result<(), Box> { + // Define the CSV file path + let file_path = "example.csv"; + + // Creating a CSV Reader + let mut reader = ReaderBuilder::new().from_path(file_path)?; + + // Define a container to store records + let mut record = csv::StringRecord::new(); + + // Read records one by one + while reader.read_record(&mut record)? { + // Print each record + println!("{:?}", record); + } + + Ok(()) +} +``` + +If we need to migrate the CSV library to LLGO, we need to encapsulate each CSV method with a C API, such as the following: + ### Memory Management Use `Box` to manage dynamic memory to ensure correct memory release between Rust and C: @@ -75,18 +104,16 @@ pub extern "C" fn free_string(s: *mut c_char) { } ``` -### Compilation and Installation - -Build the dynamic library and use `dylib-installer` to install it to the system path: - -```sh -cargo build --release -cargo install --git https://github.com/hackerchai/dylib-installer -sudo dylib_installer ./target/release/ -``` - ### Generate Header File +Edit `cbindgen.toml` to configure the header file generation rules: + +```toml +# See https://github.com/mozilla/cbindgen/blob/master/docs.md#cbindgentoml for +# a list of possible configuration values. +language = "C" +``` + Use cbindgen to generate a C header file, automating this process through a `build.rs` script: ```rust @@ -94,32 +121,124 @@ fn main() { let config = cbindgen::Config::from_file("cbindgen.toml").expect("Config file not found."); cbindgen::generate_with_config(&crate_dir, config).unwrap().write_to_file("target/include/csv_wrapper.h"); } +``` +### Compilation and Installation + +Build the dynamic library: + +```sh +cargo build --release +``` + +### Install dylib-installer + +Install the [dylib-installer](https://github.com/hackerchai/dylib-installer) tool, which is used to install dynamic libraries: + +```SH +brew tap hackerchai/tap +brew install dylib-installer +``` + +Or you can install it using Cargo: + +```SH +cargo install dylib_installer +``` + +### Install Dynamic Library + +Use dylib-installer to install the built dynamic library and the header file into the system directory: + +```SH +sudo dylib_installer +``` + +### Check the Installation + +You can check the installation by running the following command: + +```SH +pkg-config --libs --cflags +``` + +if everything is installed correctly, you will see the output like this (depending on your system): + +```SH +-I/usr/local/include -L/usr/local/lib -l ``` ### LLGO Package Mapping Map functions from the Rust library to an LLGO package, ensuring type consistency: +- LLGoPackage + +Specify `LLGoPackage` and use `pkg-config` to find the location of the lib library. + ```go const ( LLGoPackage = "link: $(pkg-config --libs csv_wrapper); -lcsv_wrapper" ) +``` +- Type + +If you want to use variables inside the struct, you can add them accordingly. +If it can't be represented directly or is not needed, it can be represented in the form `Unused []byte`, the length of the array is determined by its size, and if the struct is only used as a pointer, then the array length can be `0`. + +```go type Reader struct { - Unused [8]byte + Unused [0]byte } +// type Reader struct { +// Unused [8]byte +// } +``` + +- Ordinary functions + +Ordinary functions can be mapped in the form of `//go:linkname`. + +```go //go:linkname NewReader C.csv_reader_new func NewReader(file_path *c.Char) *Reader +``` -//llgo:link (*Reader).Free C.csv_reader_free +- Method + +Methods need to be mapped in the form of `// llgo:link (*Receiver)`. + +```go +// llgo:link (*Reader).Free C.csv_reader_free func (reader *Reader) Free() {} -//llgo:link (*Reader).ReadRecord C.csv_reader_read_record +// llgo:link (*Reader).ReadRecord C.csv_reader_read_record func (reader *Reader) ReadRecord() *c.Char { return nil } ``` +- Function pointer + +If you use a function pointer, that is, declare the function as a type separately, you need to use `// llgo:type C`to declare it. + +```go +// llgo:type C +type IoReadCallback func(c.Pointer, *Context, *uint8, uintptr) uintptr + +// llgo:link (*Io).SetRead C.hyper_io_set_read +func (io *Io) SetRead(callback IoReadCallback) {} +``` + +Or declare the function directly in the parameter. + +```go +// llgo:link (*Io).SetRead C.hyper_io_set_read +func (io *Io) SetRead(callback func(c.Pointer, *Context, *uint8, uintptr) uintptr) {} +``` + ### Writing Examples and README Provide example code and a detailed README file to help users understand how to use the generated library. + +You can find the migrated instance from [llgoexamples](https://github.com/goplus/llgoexamples), in the lib directory is the migrated Rust library, and in the rust directory, the migrated mapping file and go demo. From 9379a41b371c3d7dd7b3b0f64c8a091b008b79ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E8=8B=B1=E6=9D=B0?= <2635879218@qq.com> Date: Thu, 25 Jul 2024 09:07:33 +0800 Subject: [PATCH 3/4] llgo/rust/readme: improve Rust-to-LLGO-Migration-Guide --- doc/Rust-to-LLGO-Migration-Guide.md | 158 ++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 35 deletions(-) diff --git a/doc/Rust-to-LLGO-Migration-Guide.md b/doc/Rust-to-LLGO-Migration-Guide.md index 48ae4609..9f3c5940 100644 --- a/doc/Rust-to-LLGO-Migration-Guide.md +++ b/doc/Rust-to-LLGO-Migration-Guide.md @@ -32,38 +32,31 @@ To ensure that Rust functions can be correctly called by C and LLGO, use the fol - `unsafe` is used to mark operations that are unsafe, especially when dealing with raw pointers. - `extern "C"` specifies the use of C calling conventions. -For example, in Rust, we write CSV code like this: - ```rust -use csv::ReaderBuilder; -use std::error::Error; - -fn main() -> Result<(), Box> { - // Define the CSV file path - let file_path = "example.csv"; - - // Creating a CSV Reader - let mut reader = ReaderBuilder::new().from_path(file_path)?; - - // Define a container to store records - let mut record = csv::StringRecord::new(); - - // Read records one by one - while reader.read_record(&mut record)? { - // Print each record - println!("{:?}", record); - } - - Ok(()) +pub fn add_numbers(a: i32, b: i32) -> i32 { + a + b } ``` -If we need to migrate the CSV library to LLGO, we need to encapsulate each CSV method with a C API, such as the following: +After packaging: + +```rust +#[no_mangle] +pub unsafe extern "C" fn add_numbers_c(a: i32, b: i32) -> i32 { + add_numbers(a, b) +} +``` ### Memory Management Use `Box` to manage dynamic memory to ensure correct memory release between Rust and C: +```rust +let config = Config::new(); +``` + +After packaging: + ```rust pub unsafe extern "C" fn sled_create_config() -> \*mut Config { Box::into_raw(Box::new(Config::new())) @@ -79,12 +72,39 @@ drop(Box::from_raw(config)); Address the interfacing issues between generic pointers in C and Rust: -```rust -#[no_mangle] -pub extern "C" fn csv_reader_new(file_path: *const c_char) -> *mut c_void { /* ... */ } +```Rust +let mut reader = ReaderBuilder::new().from_path(file_path)?; +``` +After packaging: + +```rust +// Create a new CSV reader for the specified file path. #[no_mangle] -pub extern "C" fn csv_reader_free(ptr: *mut c_void) { /* ... */ } +pub extern "C" fn csv_reader_new(file_path: *const c_char) -> *mut c_void { + let file_path = unsafe { + if file_path.is_null() { return ptr::null_mut(); } + match CStr::from_ptr(file_path).to_str() { + Ok(s) => s, + Err(_) => return ptr::null_mut(), + } + }; + + let reader = csv::ReaderBuilder::new().from_path(file_path); + match reader { + Ok(r) => Box::into_raw(Box::new(r)) as *mut c_void, + Err(_) => ptr::null_mut(), + } +} + +// Free the memory allocated for the CSV reader. +#[no_mangle] +pub extern "C" fn csv_reader_free(ptr: *mut c_void) { + if !ptr.is_null() { + let reader: Box> = unsafe { Box::from_raw(ptr as *mut csv::Reader) }; + std::mem::drop(reader); + } +} ``` ### String Handling @@ -92,14 +112,44 @@ pub extern "C" fn csv_reader_free(ptr: *mut c_void) { /* ... */ } Convert strings between C and Rust: ```rust -#[no_mangle] -pub extern "C" fn csv_reader_read_record(ptr: *mut c_void) -> *const c_char { /* ... */ } +let mut record = csv::StringRecord::new(); +while reader.read_record(&mut record)? { + // Print each record + println!("{:?}", record); +} +``` + +After packaging: + +```rust +// Read the next record from the CSV reader and return it as a C string. +#[no_mangle] +pub extern "C" fn csv_reader_read_record(ptr: *mut c_void) -> *const c_char { + let reader = unsafe { + assert!(!ptr.is_null()); + &mut *(ptr as *mut csv::Reader) + }; + + let mut record = csv::StringRecord::new(); + match reader.read_record(&mut record) { + Ok(true) => match CString::new(format!("{:?}\n", record)) { + Ok(c_string) => c_string.into_raw(), + Err(_) => ptr::null(), + }, + _ => ptr::null(), + } +} + +// Free the memory allocated for a C string returned by other functions. #[no_mangle] pub extern "C" fn free_string(s: *mut c_char) { + if s.is_null() { + return; + } unsafe { let c_string = CString::from_raw(s); - drop(c_string); + std::mem::drop(c_string); } } ``` @@ -173,7 +223,7 @@ if everything is installed correctly, you will see the output like this (dependi Map functions from the Rust library to an LLGO package, ensuring type consistency: - LLGoPackage - + Specify `LLGoPackage` and use `pkg-config` to find the location of the lib library. ```go @@ -197,19 +247,41 @@ type Reader struct { // } ``` +If we want to calculate the size of this structure, we can use the following C code: + +```C +printf("%d\n", sizeof(csv_reader)); +``` + - Ordinary functions Ordinary functions can be mapped in the form of `//go:linkname`. +```C +csv_reader *csv_reader_new(const char *file_path); +``` + +After mapping: + ```go //go:linkname NewReader C.csv_reader_new func NewReader(file_path *c.Char) *Reader ``` - Method - + Methods need to be mapped in the form of `// llgo:link (*Receiver)`. +```C +void csv_reader_free(csv_reader *reader); + +const char *csv_reader_read_record(csv_reader *reader); +``` + +After mapping: + +We can extract the first parameter as Receiver: + ```go // llgo:link (*Reader).Free C.csv_reader_free func (reader *Reader) Free() {} @@ -219,9 +291,17 @@ func (reader *Reader) ReadRecord() *c.Char { return nil } ``` - Function pointer - + If you use a function pointer, that is, declare the function as a type separately, you need to use `// llgo:type C`to declare it. +```c +typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); + +void hyper_io_set_read(struct hyper_io *io, hyper_io_read_callback func); +``` + +After mapping: + ```go // llgo:type C type IoReadCallback func(c.Pointer, *Context, *uint8, uintptr) uintptr @@ -234,11 +314,19 @@ Or declare the function directly in the parameter. ```go // llgo:link (*Io).SetRead C.hyper_io_set_read -func (io *Io) SetRead(callback func(c.Pointer, *Context, *uint8, uintptr) uintptr) {} +func (io *Io) SetRead(ioSetReadCb func(c.Pointer, *Context, *uint8, uintptr) uintptr) {} ``` ### Writing Examples and README -Provide example code and a detailed README file to help users understand how to use the generated library. +Finally, provide example code and a detailed README file to help users understand how to use the generated library. + +### Example Code You can find the migrated instance from [llgoexamples](https://github.com/goplus/llgoexamples), in the lib directory is the migrated Rust library, and in the rust directory, the migrated mapping file and go demo. + +Such as: + +- CSV: [csv.rs](https://github.com/goplus/llgoexamples/blob/main/lib/rust/csv-wrapper/src/lib.rs) --> [csv.go](https://github.com/goplus/llgoexamples/blob/main/rust/csv/csv.go) +- Sled: [sled.rs](https://github.com/goplus/llgoexamples/blob/main/lib/rust/sled/src/lib.rs) --> [sled.go](https://github.com/goplus/llgoexamples/blob/main/rust/sled/sled.go) +- ... From 9741574516da1ecd0454451c98734dcc140b29de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E8=8B=B1=E6=9D=B0?= <2635879218@qq.com> Date: Thu, 25 Jul 2024 12:03:01 +0800 Subject: [PATCH 4/4] llgo/rust/readme: Some modifications --- doc/Rust-to-LLGO-Migration-Guide.md | 44 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/doc/Rust-to-LLGO-Migration-Guide.md b/doc/Rust-to-LLGO-Migration-Guide.md index 9f3c5940..c6b3a3c8 100644 --- a/doc/Rust-to-LLGO-Migration-Guide.md +++ b/doc/Rust-to-LLGO-Migration-Guide.md @@ -1,6 +1,6 @@ # Rust to LLGO Migration Document -### Add Dependencies & Build Configuration +## Add Dependencies & Build Configuration Edit `Cargo.toml` to include necessary dependencies and configuration: @@ -16,6 +16,8 @@ crate-type = ["cdylib"] # The generated dynamic library will conform to the C st cbindgen = "0.26.0" ``` +## C-style wrapper for Rust + ### Import C Language Types Use types from the libc package for interoperability with C: @@ -59,12 +61,12 @@ After packaging: ```rust pub unsafe extern "C" fn sled_create_config() -> \*mut Config { -Box::into_raw(Box::new(Config::new())) + Box::into_raw(Box::new(Config::new())) } #[no_mangle] pub unsafe extern "C" fn sled_free_config(config: \*mut Config) { -drop(Box::from_raw(config)); + drop(Box::from_raw(config)); } ``` @@ -72,7 +74,7 @@ drop(Box::from_raw(config)); Address the interfacing issues between generic pointers in C and Rust: -```Rust +```rust let mut reader = ReaderBuilder::new().from_path(file_path)?; ``` @@ -154,7 +156,7 @@ pub extern "C" fn free_string(s: *mut c_char) { } ``` -### Generate Header File +## Generate Header File Edit `cbindgen.toml` to configure the header file generation rules: @@ -173,9 +175,9 @@ fn main() { } ``` -### Compilation and Installation +## Compilation and Installation -Build the dynamic library: +### Build the dynamic library: ```sh cargo build --release @@ -185,14 +187,14 @@ cargo build --release Install the [dylib-installer](https://github.com/hackerchai/dylib-installer) tool, which is used to install dynamic libraries: -```SH +```sh brew tap hackerchai/tap brew install dylib-installer ``` Or you can install it using Cargo: -```SH +```sh cargo install dylib_installer ``` @@ -200,7 +202,7 @@ cargo install dylib_installer Use dylib-installer to install the built dynamic library and the header file into the system directory: -```SH +```sh sudo dylib_installer ``` @@ -208,17 +210,17 @@ sudo dylib_installer You can check the installation by running the following command: -```SH +```sh pkg-config --libs --cflags ``` -if everything is installed correctly, you will see the output like this (depending on your system): +If everything is installed correctly, you will see the output like this (depending on your system): -```SH +```sh -I/usr/local/include -L/usr/local/lib -l ``` -### LLGO Package Mapping +## LLGO Mapping Map functions from the Rust library to an LLGO package, ensuring type consistency: @@ -249,7 +251,7 @@ type Reader struct { If we want to calculate the size of this structure, we can use the following C code: -```C +```c printf("%d\n", sizeof(csv_reader)); ``` @@ -257,7 +259,7 @@ printf("%d\n", sizeof(csv_reader)); Ordinary functions can be mapped in the form of `//go:linkname`. -```C +```c csv_reader *csv_reader_new(const char *file_path); ``` @@ -272,7 +274,7 @@ func NewReader(file_path *c.Char) *Reader Methods need to be mapped in the form of `// llgo:link (*Receiver)`. -```C +```c void csv_reader_free(csv_reader *reader); const char *csv_reader_read_record(csv_reader *reader); @@ -292,7 +294,7 @@ func (reader *Reader) ReadRecord() *c.Char { return nil } - Function pointer -If you use a function pointer, that is, declare the function as a type separately, you need to use `// llgo:type C`to declare it. +If you use a function pointer, that is, declare the function as a type separately, you need to use `// llgo:type C` to declare it. ```c typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); @@ -317,13 +319,13 @@ Or declare the function directly in the parameter. func (io *Io) SetRead(ioSetReadCb func(c.Pointer, *Context, *uint8, uintptr) uintptr) {} ``` -### Writing Examples and README +## Writing Examples and README Finally, provide example code and a detailed README file to help users understand how to use the generated library. -### Example Code +## Example Code -You can find the migrated instance from [llgoexamples](https://github.com/goplus/llgoexamples), in the lib directory is the migrated Rust library, and in the rust directory, the migrated mapping file and go demo. +You can find the migrated examples in the [llgoexamples](https://github.com/goplus/llgoexamples). The migrated Rust libraries are in the `lib` directory, and the migrated mapping files and Go demos are in the `rust` directory. Such as: