Artichoke is a Ruby Made with Rust

๐Ÿ’Ž๐Ÿฆ€๐Ÿš€

๐ŸŽข artichoke.github.io/rubyconf/2019
๐ŸŽก artichoke.run

Artichoke Ruby

Why Build a Ruby? ๐Ÿ—๐Ÿ’Ž

Contributing

github.com/artichoke

๐ŸŽก artichoke.run

E-easy E-help-wanted

Goals

  • Build for WebAssembly ๐ŸŽฏ
  • Execute untrusted code ๐Ÿ”ฆ๐Ÿ”
  • Package single-binary apps ๐Ÿงณ

WebAssembly

  • Sandboxed by default ๐Ÿ–
  • Multi-platform ๐Ÿ‘ป๐Ÿ‘ฝ๐Ÿ‘พ๐Ÿค–
  • Ruby in the browser ๐Ÿญ

Untrusted Code

"Code execution as a service"
๐ŸŽฒ๐ŸŽฐ

Why Execute Untrusted Code?

Single Binary Apps

Dead simple, hermetic deployments ๐Ÿฑ

Single Binary Apps

Apps are hard to package ๐Ÿ“ฆ
especially for the web ๐Ÿ’

Single Binary Apps

  • Ruby, stdlib, gems load shared objects โ—๏ธ
  • Stdlib, gems, apps load Ruby code ๐Ÿ’”
  • Apps load Ruby code, config, and assets ๐Ÿ’ฅ

Goals

  • Build for WebAssembly ๐ŸŽฏ
  • Execute untrusted code ๐Ÿ”ฆ๐Ÿ”
  • Package single-binary apps ๐Ÿงณ

Artichoke ๐Ÿ’Žโค๏ธ๐Ÿฆ€ Rust

WebAssembly Compilation

Build on WebAssembly targets with a
native compiler toolchain ๐Ÿ› 

Building WebAssembly

rustup add target wasm32-unknown-emscripten
cargo build --release --target wasm32-unknown-emscripten

Deep Web Integration

Static Linking

Drop a single binary in FROM scratch Docker container and run a full Rails app ๐Ÿ’Ž๐Ÿณ

Static Linking

Statically linked dependencies enable
single binary app distributions ๐Ÿ“ป

Static Linking

Statically linked libc enables
single binary app distributions ๐Ÿฅ”


x86_64-unknown-linux-musl

From Rust to Ruby

๐Ÿฆ€ โžก๏ธ ๐Ÿ’Ž

Ruby Core

ENV['DRY_RUN'] = '0'
ary = Array.new(1024, 'Artichoke Ruby')
fixture = File.read(
  'artichoke-frontend/ruby/fixtures/learnxinyminutes.txt'
)
/function/.match(fixture)

Ruby Core

  • Multiple implementations ๐Ÿ˜บ๐Ÿ˜ธ๐Ÿ˜ป
  • Configurable at compile time ๐Ÿ˜

Untrusted ENV Access

ENV

  • System backend โ†”๏ธ OS ๐Ÿ–ฅ
  • In-memory backend โ†”๏ธ HashMap ๐ŸŽŸ

In-Memory ENV

Build on WebAssembly targets that
do not have a system environ ๐Ÿ”ง

In-Memory ENV

Run untrusted code by
restricting system access โ›“

Capturable IO

IO

  • Capturable $stdout and $stderr ๐Ÿงค
  • Optional IO#popen ๐Ÿšช
  • Optional Kernel#open("|date") ๐Ÿ“…

Capturable IO

Build on WebAssembly targets that
do not have file descriptors ๐Ÿ”ง

Disabled IO pipes

Run untrusted code by disabling
subprocess capabilities โ›“

Virtual Kernel#require

File Access

  • System backend โ†”๏ธ OS ๐Ÿ—„๐Ÿ“
  • In-memory backend โ†”๏ธ HashMap ๐Ÿ—ƒ๐Ÿ—‚

Pure Rust Regexp

Regexp

  • Pure Rust engine for WebAssembly ๐Ÿ’ง
  • Oniguruma engine for compatibility ๐Ÿš—

From Core to Fast

๐ŸŽ๐Ÿ’จ ๐ŸŽ๐ŸŽ

Benchmarks run on AWS c5.2xlarge ๐Ÿ–ฅ ๐Ÿ–ฅ

String#scan

String pattern over 6.8MB of Unicode text ๐Ÿ“š

raise unless $fixture.scan('http://').length == 3539
raise unless $fixture.scan('https://').length == 1865
raise unless $fixture.scan('่กจ่พพๅผ').length == 120
raise unless $fixture.scan("\r\n").empty?

String#scan

String#scan

Artichoke
66
ms / iteration
MRI
82
ms / iteration

String#scan

Regexp pattern over 6.8MB of Unicode text ๐Ÿ“š

raise unless $fixture.scan(%r{https?://}).length == 3539+1865
raise unless $fixture.scan(/่กจ่พพๅผ/).length == 120
raise unless $fixture.scan(/\r\n/).empty?

String#scan

String#scan

Artichoke
48
ms / iteration
MRI
89
ms / iteration

Sparse Array Allocation

a = []
a[1_000_000_000_000_000] = 'quadrillion'
a.concat((0..1000).to_a)

100.times do |i|
  a.unshift(i)
end

b = a.reverse
b[123_456_789, 500_000] = [Object.new, /Array/, 100.0]

puts a.length # => 1000000000001102
puts b.length # => 999999999501105

Sparse Array Allocation

Artichoke
7
MB / iteration
MRI
failed to allocate memory (NoMemoryError)

Upcoming Core Work

  • Hash with small vec backend ๐Ÿณ
  • Range and Range-based Array ๐Ÿฆ‰
  • File with in-memory and OS backends ๐Ÿงฉ

Contributing

github.com/artichoke

๐ŸŽก artichoke.run

E-easy E-help-wanted