Slide 1

Slide 1 text

Making PHP extremely fast Syrus Akbary, Edoardo Marangoni March 2025 And how we got here…

Slide 2

Slide 2 text

Who we are?

Slide 3

Slide 3 text

Syrus Edo

Slide 4

Slide 4 text

such cool WOW It’s sounds like Wasm + Docker

Slide 5

Slide 5 text

Speed is our obsession

Slide 6

Slide 6 text

In Wasm I/O 2024 we presented py2wasm Making Python 3x faster

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

We want to run WordPress at the Edge Could we use the same strategy?

Slide 9

Slide 9 text

Lets just try WordPress! *Using Cranelift + Asyncify

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

⏱ 600ms to load

Slide 13

Slide 13 text

Why PHP *was Slow • It didn’t have Opcache • Used Cranelift • Used asyncify for longjmp/setjmp 600ms

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Why PHP *was Slow • It didn’t have Opcache Embed Opcache • Used Cranelift • Used asyncify for longjmp/setjmp 1.5-2x speedup 380ms

Slide 16

Slide 16 text

Use LLVM Cranelift has regressed on speed, LLVM on the other hand has gotten much better in the last versions

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

What is Asyncify?

Slide 19

Slide 19 text

Asyncify Brings Pause and Resume to WebAssembly programs $ wasm-opt input.wasm -O1 --asyncify -o output.wasm output.wasm 1.5 bigger 2x slower

Slide 20

Slide 20 text

Use Wasm Exceptions

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

A Deep Dive into Wasm Exceptions

Slide 23

Slide 23 text

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/

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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;))))))

Slide 27

Slide 27 text

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))))

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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?

Slide 31

Slide 31 text

• 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! 🧂

Slide 32

Slide 32 text

• 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

Slide 33

Slide 33 text

So we added support for V8 + EH in Wasmer

Slide 34

Slide 34 text

Can it be faster?

Slide 35

Slide 35 text

• 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 🥁

Slide 36

Slide 36 text

Live demo! Praise for the gods to make the demo successful / Terms and conditions may apply

Slide 37

Slide 37 text

But now booting was slow…

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

One last thing… Instaboot 🎉 Bringing cold start times to ZERO thanks to WebAssembly

Slide 40

Slide 40 text

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 →

Slide 41

Slide 41 text

Demo time!

Slide 42

Slide 42 text

Deploy WordPress for free →

Slide 43

Slide 43 text

Thank you 🙏 @syrusakbary / (Edo only lives in the physical world) / @wasmerio