Using rust to create PHP extensions

Source: Internet
Author: User
Tags format definition end ffi header php and string version

Last October, my colleagues at Etsy and I had a discussion about how to write extensions to an explanatory language like PHP, where Ruby or python should be easier than PHP. We talked about the hurdle of writing a successful extension is that they usually need to be written in C, but it's hard to be confident if you're not good at the C language.

Since then I have had the idea of writing one with rust, and have been trying for the past few days. I finally let it run this morning.
C or PHP in the rust

My basic starting point is to write some rust code that can be compiled into a library, and write it as a number of C header files, in C for the invoked PHP to do an extension. It's not very simple, but it's interesting.
Rust FFI (Foreign function interface)

The first thing I did was to tinker with the external function interface of the rust and C-connected rust. I have written a flexible library with a simple method (Hello_from_rust) with a single statement (a pointer to a C char, otherwise known as a string), and the following is the "Hello from rust" output after the input.

 
  
  
  1. Hello_from_rust.rs
  2. #! [Crate_type = "Staticlib"]
  3. #! [Feature (LIBC)]
  4. extern crate libc;
  5. Use STD::FFI::CSTR;
  6. #[no_mangle]
  7. Pub extern "C" FN hello_from_rust (name: *const Libc::c_char) {
  8. Let Buf_name = unsafe {cstr::from_ptr (name). To_bytes ()};
  9. Let Str_name = String::from_utf8 (Buf_name.to_vec ()). Unwrap ();
  10. Let C_name = format! ("Hello from Rust, {}", str_name);
  11. println! ("{}", c_name);
  12. }

I am from c (or other!) To split the rust library that is called in. Here's a good explanation for what happens next.

Compiling it will get a file of. A, libhello_from_rust.a. This is a static library that contains all of its own dependencies, and we link it when compiling a C program, which allows us to do follow-up things. Note: After we compile we will get the following output:

 
  
  
  1. Note:link against the following native artifacts when linking against this static library
  2. Note:the order and any duplication can is significant on some platforms, and I need to be preserved
  3. Note:library:Systemnote:library:pthread
  4. Note:library:c
  5. Note:library:m

This is the rust compiler tells us what to link when we don't use this dependency.
Call Rust from C

Now that we have a library, we have to do two things to make sure it is callable from C. First, we need to create a C header file for it, Hello_from_rust.h. Then link to it when we compile.

The following is a header file:

 
  
  
  1. Note:link against the following native artifacts when linking against this static library
  2. Note:the order and any duplication can is significant on some platforms, and I need to be preserved
  3. Note:library:Systemnote:library:pthread
  4. Note:library:c
  5. Note:library:m

This is a fairly basic header file that provides a signature/definition only for a simple function. Then we need to write a C program and use it.

 
  
  
  1. hello.c
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include "Hello_from_rust.h"
  5. int main (int argc, char *argv[]) {
  6. Hello_from_rust ("jared!");
  7. }

We compile it by running the code:

Gcc-wall-o Hello_c hello.c-l/USERS/JMCFARLAND/CODE/RUST/PHP-HELLO-RUST-LHELLO_FROM_RUST-LSYSTEM-LPTHREAD-LC-LM

Note that at the end of the-lsystem-lpthread-lc-lm tell GCC not to link those "local antiques", in order to compile our rust library when the rust compiler can provide it.

After running the following code we can get a binary file:

 
  
  
  1. $./hello_c
  2. Hello from Rust, jared!

Pretty! We have just called the Rust Library from C. Now we need to understand how the rust library is going into a php extension.

Call C from PHP

It took me some time to figure out that this is not the best document in the World for PHP extensions. The best part comes from a PHP source that binds a script Ext_skel (most of which represents an "extended skeleton") that generates most of the boilerplate code you need. In order for the code to run, I worked very hard to learn the PHP document, "Expand the skeleton."

You can start with the download, and the unused PHP source, write the code into the PHP directory and run:

 
  
  
  1. $ CD ext/
  2. $./ext_skel–extname=hello_from_rust

This will generate the basic skeleton needed to create the PHP extension. Now, move your folders everywhere you want to keep your extensions locally. and to move your

. Rust Source

. Rust Library

. C Header

into the same directory. So now you should look at a directory like this:

.
├──credits
├──experimental
├──config.m4
├──config.w32
├──hello_from_rust.c
├──hello_from_rust.h
├──hello_from_rust.php
├──hello_from_rust.rs
├──libhello_from_rust.a
├──php_hello_from_rust.h
└──tests
└──001.phpt

One directory, 11 files

You can see a good description of these files on the PHP docs. Create an extended file. We're going to start by editing config.m4.

Do not explain, the following is my results:

 
  
  
  1. Php_arg_with (Hello_from_rust, for hello_from_rust support,
  2. [--with-hello_from_rust Include hello_from_rust support])
  3. if test "$PHP _hello_from_rust"!= "no"; Then
  4. Php_subst (Hello_from_rust_shared_libadd)
  5. Php_add_library_with_path (Hello_from_rust,., Hello_from_rust_shared_libadd)
  6. Php_new_extension (Hello_from_rust, hello_from_rust.c, $ext _shared)
  7. Fi

As I understand it, these are basic macro commands. However, the documentation for these macro commands is rather bad (for example, Google "Php_add_library_with_path" does not appear to have been written by the PHP team). I happen to have this Php_add_library_path macro command in the previous thread that some people are talking about linking a static library in a PHP expansion. Other recommended macro commands in the comments were generated after I ran the Ext_skel.

Now that we have the configuration settings, we need to actually call the library from the PHP script. For this we have to modify the automatically generated files, hello_from_rust.c. First we add the Hello_from_rust.h header file to the include command. Then we want to modify the Confirm_hello_from_rust_compiled definition method.

 
 
  1. #include "Hello_from_rust.h"
  2. A bunch of comments and code removed ...
  3. Php_function (confirm_hello_from_rust_compiled)
  4. {
  5. char *arg = NULL;
  6. int Arg_len, Len;
  7. Char *STRG;
  8. if (Zend_parse_parameters (Zend_num_args () tsrmls_cc, "s", &arg, &arg_len) = = failure) {
  9. Return
  10. }
  11. Hello_from_rust ("Jared (from php!!)!");
  12. Len = spprintf (&STRG, 0, "congratulations! You have successfully modified EXT/%.78S/CONFIG.M4. Module%.78s is now compiled into PHP. "," Hello_from_rust ", Arg);
  13. Return_stringl (STRG, Len, 0);
  14. }

Note: I added Hello_from_rust ("Jared (fromphp!!)!");

Now, we can try to build our extension:

 
  
  
  1. $ phpize
  2. $./configure
  3. $ sudo make install

That's it, generate our meta configuration, run the generated configuration commands, and then install the extension. When I install, I have to use sudo myself, because my users don't have PHP extensions for the installation directory.

Now, we can run it!

 
  
  
  1. $ PHP hello_from_rust.php
  2. Functions available in the test extension:
  3. Confirm_hello_from_rust_compiled
  4. Hello from Rust, Jared (from php!!)!
  5. congratulations! You have successfully modified EXT/HELLO_FROM_RUST/CONFIG.M4. Module Hello_from_rust is now compiled into PHP.
  6. Segmentation fault:11

Not bad, PHP has entered our C extension, see our list of application methods and call. Then, the C extension has entered our rust library and started printing our strings. That's funny! But...... What happened to the wrong ending?

As I mentioned, here is the use of rust related println! macros, but I didn't do any further debugging on it. If we delete and return a char* substitution from our rust library, the segment error disappears.

Here is the code for rust:

 
  
  
  1. #! [Crate_type = "Staticlib"]
  2. #! [Feature (LIBC)]
  3. extern crate libc;
  4. Use Std::ffi::{cstr, CString};
  5. #[no_mangle]
  6. Pub extern "C" FN hello_from_rust (name: *const libc::c_char)-> *const Libc::c_char {
  7. Let Buf_name = unsafe {cstr::from_ptr (name). To_bytes ()};
  8. Let Str_name = String::from_utf8 (Buf_name.to_vec ()). Unwrap ();
  9. Let C_name = format! ("Hello from Rust, {}", str_name);
  10. Cstring::new (C_name). Unwrap (). As_ptr ()
  11. }

and change the C header file:

 
  
  
  1. #ifndef __hello
  2. #define __hello
  3. const char * hello_from_rust (const char *name);
  4. #endif

Also change the C extension file:

 
 
  1. Php_function (confirm_hello_from_rust_compiled)
  2. {
  3. char *arg = NULL;
  4. int Arg_len, Len;
  5. Char *STRG;
  6. if (Zend_parse_parameters (Zend_num_args () tsrmls_cc, "s", &arg, &arg_len) = = failure) {
  7. Return
  8. }
  9. Char *str;
  10. str = Hello_from_rust ("Jared (from php!!)!");
  11. printf ("%s/n", str);
  12. Len = spprintf (&STRG, 0, "congratulations! You have successfully modified EXT/%.78S/CONFIG.M4. Module%.78s is now compiled into PHP. "," Hello_from_rust ", Arg);
  13. Return_stringl (STRG, Len, 0);
  14. }

A useless micro-benchmark

So why are you doing this? I really didn't use it in the real world. But I really think the Fibonacci algorithm is a good example of how a PHP expansion is fundamental. Usually straightforward (in Ruby):

 
  
  
  1. def fib (at) do
  2. if (at = = 1 at = = 0)
  3. return at
  4. Else
  5. return fib (at-1) + fib (at-2)
  6. End
  7. End

And you can improve this bad performance by not using a recursive return:

 
  
  
  1. def fib (at) do
  2. if (at = = 1 at = = 0)
  3. return at
  4. elsif (val = @cache [at]) present?
  5. Return Val
  6. End
  7. Total = 1
  8. Parent = 1
  9. GP = 1
  10. (1..at).
  11. Total = parent + GP
  12. GP = Parent
  13. Parent = Total
  14. End
  15. Return Total
  16. End

So we're going to write two examples around it, one in PHP, one in rust. See which one is faster. Here is the PHP version:

 
 
  1. def fib (at) do
  2. if (at = = 1 at = = 0)
  3. return at
  4. elsif (val = @cache [at]) present?
  5. Return Val
  6. End
  7. Total = 1
  8. Parent = 1
  9. GP = 1
  10. (1..at).
  11. Total = parent + GP
  12. GP = Parent
  13. Parent = Total
  14. End
  15. Return Total
  16. End
  17. This is the result of its operation:
  18. $ Time PHP php_fib.php
  19. Real 0m2.046s
  20. User 0m1.823s
  21. SYS 0m0.207s
  22. Now let's do the rust version. The following are library resources:
  23. #! [Crate_type = "Staticlib"]
  24. fn FIB (at:usize)-> usize {
  25. If at = = 0 {
  26. return 0;
  27. else if at = = 1 {
  28. return 1;
  29. }
  30. Let mut total = 1;
  31. Let mut parent = 1;
  32. Let mut gp = 0;
  33. For _ in 1. at {
  34. Total = parent + GP;
  35. GP = parent;
  36. parent = total;
  37. }
  38. return total;
  39. }
  40. #[no_mangle]
  41. Pub extern "C" FN rust_fib (at:usize)-> usize {
  42. FIB (at)
  43. }
  44. Note that I compiled the library rustc–o rust_lib.rs to make the compiler optimize (because we are here standard). Here is the C extension source (related excerpt):
  45. Php_function (confirm_rust_fib_compiled)
  46. {
  47. Long number;
  48. if (Zend_parse_parameters (Zend_num_args () tsrmls_cc, "L", &number) = = failure) {
  49. Return
  50. }
  51. Return_long (RUST_FIB (number));
  52. }

To run a PHP script:

 
  
  
  1. <?php
  2. $BR = (Php_sapi_name () = = "CLI")? "": "<br>";
  3. if (!extension_loaded (' Rust_fib ')) {
  4. DL (' Rust_fib. ') Php_shlib_suffix);
  5. }
  6. for ($i = 0; $i < 100000; $i + +) {
  7. Confirm_rust_fib_compiled (92);
  8. }
  9. ?>
  10. This is the result of its operation:
  11. $ Time PHP rust_fib.php
  12. Real 0m0.586s
  13. User 0m0.342s
  14. SYS 0m0.221s

You can see it three times times faster than the former! The perfect rust Micro benchmark!

Summarize

There is hardly any conclusion here. I'm not sure it's a good idea to write a php extension on rust, but it's a good way to spend some time researching rust,php and C.

If you want to view all the code or view the change record, you can access GitHub repo.



Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.