Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Making PHP extremely fast in WebAssembly

Making PHP extremely fast in WebAssembly

In this talk we'll explore the strategies that we did in Wasmer to make PHP run almost at native speeds, from switching compilers from Cranelift to LLVM, to enable opcache to using WebAssembly Exceptions to bring 10x faster performance.

Syrus Akbary

March 28, 2025
Tweet

More Decks by Syrus Akbary

Other Decks in Technology

Transcript

  1. Why PHP *was Slow • It didn’t have Opcache •

    Used Cranelift • Used asyncify for longjmp/setjmp 600ms
  2. Embed Opcache More than 350 PHP f iles read and

    parsed for rendering the homepage of WordPress! Was challenging because PHP modules required dlsym/dlopen. Opcache could be a huge win
  3. Why PHP *was Slow • It didn’t have Opcache Embed

    Opcache • Used Cranelift • Used asyncify for longjmp/setjmp 1.5-2x speedup 380ms
  4. Use LLVM Cranelift has regressed on speed, LLVM on the

    other hand has gotten much better in the last versions
  5. Why PHP *was Slow • It didn’t have Opcache Embed

    Opcache • Used Cranelift Use LLVM • Used asyncify for longjmp/setjmp 1.5-2x speedup 2.5-3x speedup 120ms
  6. Asyncify Brings Pause and Resume to WebAssembly programs $ wasm-opt

    input.wasm -O1 --asyncify -o output.wasm output.wasm 1.5 bigger 2x slower
  7. Why PHP *was Slow • It didn’t have Opcache Embed

    Opcache • Used Cranelift Use LLVM • Used asyncify for longjmp/setjmp Used Wasm Exceptions 1.5-2x speedup 2.5-3x speedup 2x speedup 60ms
  8. A bit more about EH • Two f lavours of

    Exception Handling: “legacy” and “exnref”s (we implemented the “exnref” f lavour) • It resembles the behaviour of C++’s exception handling mechanism • It adds two new types (`tag` and `exnref`) and a number of control- f low instructions to throw and catch exceptions Source: https://webassembly.org/features/
  9. Wasm Exceptions - The text version Exception handling allows code

    to break control f low when an exception is thrown. (module)
  10. Wasm Exceptions - The text version Exception handling allows code

    to break control f low when an exception is thrown. (module (tag $e0))
  11. Wasm Exceptions - The text version Exception handling allows code

    to break control f low when an exception is thrown. (module (tag $e0) (func $check_nz (param i32) (if (i32.eqz (local.get 0) (then (throw $e0)) (else (;do nothing;))))))
  12. Wasm Exceptions - The text version Exception handling allows code

    to break control f low when an exception is thrown. (module (tag $e0) (func $check_nz (param i32) (if (i32.eqz (local.get 0) (then (throw $e0)) (else (;do nothing;))))) (func (param i32) (result i32) (local.get 0) (block $h (try_table (catch_all $h) (call $check_nz) (i32.const 0) (return)) ;; Ouch! (i32.const 1) (return))))
  13. Wasm Exceptions - The text version Exception handling allows code

    to break control f low when an exception is thrown. (module (tag $e0) (func $check_nz (param i32) (if (i32.eqz (local.get 0) (then (throw $e0)) (else (;do nothing;))))) (func (param i32) (result i32) (local.get 0) (block $h (try_table (catch_all $h) (call $check_nz) (i32.const 0) (return)) ;; Ouch! (i32.const 1) (return)))) This (roughly) translates to the following C++ code
  14. How it works • It is a “zero cost” abstraction

    (you don’t pay if you don’t throw!) • It is based on the same structure that DWARF uses (sections..) • C++-like exception handling works as result of the cooperation between the runtimes, object formats and libraries like libunwind
  15. EH in a nutshell (-nix) • It is a bit

    of an obscure corner of runtimes — shout out to Nico Brailovsky! • In short: • You have a function • You compile it into an object f ile • The object f ile has a “.eh_frame” • The .eh_frame section shall contain 1 or more Call Frame Information (CFI) records • The number of records present shall be determined by size of the section as contained in the section header • Each CFI record contains a Common Information Entry (CIE) record followed by 1 or more Frame Description Entry (FDE) records • You learn the LEB128 format • You parse the Augmentation String f ield in the CIE • You see if there is anything to check in the LSDA • You quux the zot! Don’t forget to foo the bar! I am rubber, you are glue! This f loor is lovely. Is it parquet?
  16. • Let’s try again. Someone throws an exception at PC

    %pc: • The runtime creates an exception object e. It encodes the type of the exception and the “domain” the exception belongs to; • The runtime calls libunwind which gets the %pc and e and looks at speci f ic sections of the object f ile* to see if the function %pc belongs to has a personality function to call; • The personality function is given %pc, %e and a mechanism to get access to the LSDA, which contains a mapping between PCs and “catch blocks”. • The personality function parses the LSDA and check if there is a catch block that can catch exceptions from that PC. • If there is one, it checks that type of the exception the catch block can catch matches that of %e. • If all works out, execution is restored from that catch block by manually “installing the context”. • If not, returns control to libunwind, which repeats the procedure with the parent function. EH in a nutshell (-nix) Take it with a wheelbarrow’s worth of salt! 🧂
  17. • Add support for EH-backed SjLj in wasix-libc, compile PHP,

    then f ind a runtime to use it! • …V8 supports the exnref proposal! How we did it
  18. • We also have a backend based on LLVM (Wasm

    -> LLVM IR -> Native asm) • LLVM has the nifty landingpad intrinsic used to represent exactly C++-style exception handling • Map instances of try_table and catch_* Wasm operators to “that” • Create vm intrinsics to allocate, destroy, throw, and rethrow exceptions • Create the wasmer_eh_personality personality function • Mark each LLVM function we generate with that personality function • ??? • Pro f it! LLVM 🥁
  19. Live demo! Praise for the gods to make the demo

    successful / Terms and conditions may apply
  20. Why WP Cold starts *were Slow • Parsing 350 f

    iles at startup • A lot of initialization systemcalls for starting the server (sockets, f ilesystem, …) 1.2s Cold starts
  21. How Instaboot works • It snapshots the whole state of

    execution into a journal • It avoids both the re-parsing of f iles, as well as most of the initialization syscalls 90ms Cold starts Read more here →