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

CGO-less Foreign Function Interface With WebAss...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →
mathetake
October 07, 2022

CGO-less Foreign Function Interface With WebAssembly

GopherCon 2022

mathetake

October 07, 2022

Transcript

92 slides
  1. Foreign Function Interface(FFI) “A foreign function interface (FFI) is a

    mechanism by which a program written in one programming language can call routines or make use of services written in another.” – wikipedia
  2. func main () { rustFn() zigFn() …. } export fn

    zigFn() void { … } main.go lib.zig pub extern "C" fn rustFn() {...} lib.rs
  3. When/Why do we want FFI? • Reusing softwares in other

    languages ◦ Don’t want to rewrite 100k loc in C
  4. When/Why do we want FFI? • Reusing softwares in other

    languages ◦ Don’t want to rewrite 100k loc in C • Plugin System via FFI - Polyglot! ◦ Allow users to extend your app in any language
  5. func main () { rustFn() zigFn() …. } export fn

    zigFn() void { … } main.go lib.zig pub extern "C" fn rustFn() {...} lib.rs
  6. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool { … } pub extern "C" fn rustFn(v: u32) -> bool {...} lib.rs v v b b main.go lib.zig
  7. What’s the protocol between Go and another lang? How Go

    runtime behaves beyond Go world? CGO
  8. func main () { b := rustFn(v) {} b =

    zigFn(v) {} …. } export fn zigFn(v: u32) bool { … } main.go lib.zig pub extern "C" fn rustFn(v: u32) -> bool {...} lib.rs v b CGO v b
  9. “CGO is not Go” Gopherfest 2015 | Go Proverbs with

    Rob Pike https://youtu.be/PAAkCSZUG1c
  10. CGO troubles • Dynamic vs Static binary: portability issue •

    Cross compilation • CGO is slow • Security
  11. CGO troubles • Dynamic vs Static binary: portability issue •

    Cross compilation • CGO is slow • Security
  12. $ go build main.go $ ldd main not a dynamic

    executable package main func main() { println("hello") }
  13. $ go build main.go $ ldd main package main import

    "C" func main() { println("hello") }
  14. $ go build main.go $ ldd main linux-vdso.so.1 (0x00007ffd59db2000) libpthread.so.0

    => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fad32c3f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fad32a4d000) /lib64/ld-linux-x86-64.so.2 (0x00007fad32c83000) package main import "C" func main() { println("hello") }
  15. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b CGO v b Operating System
  16. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b CGO v b Operating System
  17. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b v b Operating System ? ? Sandbox Sandbox
  18. WebAssembly (Wasm) • Binary instruction format for a stack-based virtual

    machine (VM) • Polyglot • Security-oriented design ◦ Memory guard ◦ Deny system calls by default
  19. Wasm is not only for the browsers • Core spec

    is decoupled from the web concept • Embeddable in any application with VM implementation
  20. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b v b Operating System ? ? Sandbox Sandbox
  21. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b v b Operating System ?
  22. What is wazero? • Started out as my hobby project:

    now sponsored by Tetrate • The Wasm runtime with zero dependency • Written in pure Go, no CGO!
  23. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b v b Operating System ?
  24. func main () { b := rustFn(v) b = zigFn(v)

    …. } export fn zigFn(v: u32) bool {…} pub extern "C" fn rustFn(v: u32) -> bool {...} v b v b Operating System wazero
  25. FFI with wazero vs CGO • No CGO ◦ Static

    binary, cross compilation, etc • Zero dependency ◦ E.g. third party toolchains • Compile once, run everywhere • Sandbox environment ◦ Memory isolation ◦ Deny “system calls” by default
  26. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory
  27. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory make([]byte, N) make([]byte, M)
  28. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory make([]byte, N) make([]byte, M)
  29. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory make([]byte, N) make([]byte, M)
  30. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory func readFile(fd int, …) {...} func writeFile(fd int, …) {...} foo.txt bar.txt Operating System // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory read(2) write(2)
  31. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory func readFile(fd int, …) {...} func writeFile(fd int, …) {...} foo.txt bar.txt Operating System // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory read(2) write(2)
  32. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory func readFile(fd int, …) {...} func writeFile(fd int, …) {...} foo.txt bar.txt Operating System // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory read(2) write(2)
  33. Go program // Instantiate a Zig Wasm binary. zig :=

    r.InstantiateModuleFromBinary(...) Linear memory func readFile(fd int, …) {...} func writeFile(fd int, …) {...} foo.txt bar.txt Operating System // Instantiate a Rust Wasm binary. rust := r.InstantiateModuleFromBinary(...) Linear memory read(2) write(2)
  34. // Create a new WebAssembly Runtime. r := wazero.NewRuntime(ctx) //

    Instantiate a Rust Wasm binary. rust, _ := r.InstantiateModuleFromBinary(ctx, rustBinary) // Instantiate a Zig Wasm binary. zig, _ := r.InstantiateModuleFromBinary(ctx, zigBinary) // Call functions exported by Wasm modules. ... := rust.ExportedFunction("rustFn").Call(ctx, ...) ... := zig.ExportedFunction("zigFn").Call(ctx, ...)
  35. • Running a Wasm-compiled SQLite inside Go, without CGO •

    Possible implementation of CGO-less and sandboxed SQL Driver.
  36. • re2: a fast regular expression engine in C++ •

    Running Wasm-compiled re2, without CGO • In some cases, faster that regexp package in the Go std library!
  37. Cons of FFI with wazero vs CGO • Performance degradation

    ◦ Wasm == Virtualization ◦ Depends on runtime implementation • Needs to compile your FFI to Wasm ◦ Premature ecosystem ◦ Refactor in a Wasm-friendly way
  38. Cons of FFI with wazero vs CGO • Performance degradation

    ◦ Wasm == Virtualization ◦ Depends on runtime implementation • Needs to compile your FFI to Wasm ◦ Premature ecosystem ◦ Refactor in a Wasm-friendly way
  39. Cons of FFI with wazero vs CGO • Performance degradation

    ◦ Wasm == Virtualization ◦ Depends on runtime implementation • Needs to compile your FFI to Wasm ◦ Premature ecosystem ◦ Refactor in a Wasm-friendly way
  40. Interpreter mode • Runs on any platform (GOOS/GOARCH) • Fast

    startup time • Slow execution wazero.NewRuntimeConfigInterpreter()
  41. Ahead-Of-Time (AOT) compiler mode • Runs on {amd64,arm64} x {linux,darwin,windows,freebsd,etc}

    • Slow startup time ◦ AOT = compile Wasm binary into native machine code before execution • Fast execution (10x+ faster than interpreter) wazero.NewRuntimeConfigCompiler()
  42. How AOT compiler works 1. Creates native machine code semantically

    equivalent to Wasm binary 2. mmap the machine code []byte as executable 3. Jumps into the “executable” []byte via a Go Assembly function
  43. 1.Machine code gen 2.Mmap executable vm, err := r.InstantiateModuleFromBinary( ctx,

    wasmBinary, ) Heap Machine code … = vm.ExportedFunction("myFunc").Call(...) 3.Jump to machine code
  44. Challenges in AOT compiler implementation • Do not modify Goroutine-stack!

    (e.g. “call” instruction) • Do not access Goroutine-stack allocated variable from machine code • Debugging is extremely difficult • Single pass compiler: optimizations are TODOs
  45. Challenges in AOT compiler implementation • Do not modify Goroutine-stack!

    (e.g. “call” instruction) • Do not access Goroutine-stack allocated variable from machine code • Debugging is extremely difficult • Single pass compiler: optimizations are TODOs
  46. Heap Wasm function 1 Wasm function 2 Wasm function 3

    …. Function calls Wasm Linear memory []byte{...} allocated by Go runtime Module Information, etc A struct allocated by Go runtime Wasm stack []byte{...} allocated by Go runtime Goroutine stack 1 Goroutine stack N …. Generated machine codes
  47. Challenges in AOT compiler implementation • Do not modify Goroutine-stack!

    (e.g. “call” instruction) • Do not access Goroutine-stack allocated variable from machine code • Debugging is extremely difficult • Naive single pass compiler: optimizations are TODOs
  48. Wrap up! • FFI == Calling non-Go functions from Go

    • CGO works, but has some issues • CGO-less FFI is possible with wazero+WebAssembly • wazero is written in pure Go, zero dependency!

More Decks by mathetake

Other Decks in Programming