Slide 1

Slide 1 text

Hello there!

Slide 2

Slide 2 text

Who?! Thorsten Ball Software Developer thorstenball.com mrnugget / @thorstenball

Slide 3

Slide 3 text

I wrote a book! Get it: interpreterbook.com Coupon code for 20% off: aigudegophers

Slide 4

Slide 4 text

++++++++[>++++[>++>+++>+++>+<< <<-]>+>+>- >>+[<]<-]>>.>---.+++ ++++..+++.>>. <-.<.+++.------.- -------. >>+.> + + .

Slide 5

Slide 5 text

>++++++++++>+>+[ [+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[ [-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- [>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>> ]<<< ]

Slide 6

Slide 6 text

Brainfuck — Invented by Urban Müller — It's a teaching language, not a joke — ... well, okay, it's a joke, too

Slide 7

Slide 7 text

A Virtual Brainfuck Machine

Slide 8

Slide 8 text

Understanding Brainfuck Programming languages live in different worlds.

Slide 9

Slide 9 text

Programming in C

Slide 10

Slide 10 text

Programming in Go

Slide 11

Slide 11 text

Programming in Forth 3 4 + .

Slide 12

Slide 12 text

Programming in Ruby — ... or Java — ... or PHP — ... or JavaScript You don't have to worry. Just bounce around.

Slide 13

Slide 13 text

Brainfuck's View of the World

Slide 14

Slide 14 text

Brainfuck's View of the World — Memory: 30000 cells initialized to 0 — Data pointer: Points to cell — Code: The program that's executed by the machine — Instruction pointer: Points to the next instruction — Input and output streams: STDIN and STDOUT — CPU: Executes the code

Slide 15

Slide 15 text

The Instruction Set — > - Increment the data pointer by 1 — < - Decrement the data pointer by 1 — + - Increment the value in the current cell — - - Decrement the value in the current cell — . - Print current cell — , - Read a character to current cell — [ - If the current cell contains a zero, jump to matching ] — ] - If the current cell does not contain a zero, jump to matching [

Slide 16

Slide 16 text

The Instruction Set - Hello World ++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>--- .+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

Slide 17

Slide 17 text

The Instruction Set - Hello World + + + + + + + + [ >

Slide 18

Slide 18 text

The Instruction Set - Hello World PLUS PLUS PLUS PLUS PLUS PLUS PLUS PLUS JUMP_IF_ZERO RIGHT

Slide 19

Slide 19 text

Starting our Interpreter — Interpreters give meaning to symbols by doing what they are supposed to mean — The Brainfuck interpreter manipulates the Brainfuck machine — We need to build the Brainfuck machine

Slide 20

Slide 20 text

type Machine struct { code string ip int memory [30000]int dp int input io.Reader output io.Writer } func NewMachine(code string, in io.Reader, out io.Writer) *Machine { return &Machine{ code: code, input: in, output: out, } }

Slide 21

Slide 21 text

func (m *Machine) Execute() { for m.ip < len(m.code) { ins := m.code[m.ip] switch ins { case '+': m.memory[m.dp]++ case '-': m.memory[m.dp]-- case '>': m.dp++ case '<': m.dp-- } m.ip++ } }

Slide 22

Slide 22 text

type Machine struct { // [...] buf []byte } func NewMachine(code string, in io.Reader, out io.Writer) *Machine { return &Machine{ // [...] buf: make([]byte, 1), } }

Slide 23

Slide 23 text

func (m *Machine) readChar() { n, err := m.input.Read(m.buf) if err != nil { panic(err) } if n != 1 { panic("wrong num bytes read") } m.memory[m.dp] = int(m.buf[0]) } func (m *Machine) putChar() { m.buf[0] = byte(m.memory[m.dp]) n, err := m.output.Write(m.buf) if err != nil { panic(err) } if n != 1 { panic("wrong num bytes written") } }

Slide 24

Slide 24 text

func (m *Machine) Execute() { for m.ip < len(m.code) { // [...] case ',': m.readChar() case '.': m.putChar() // [...] } }

Slide 25

Slide 25 text

Pseudo looping in pseudo code switch currentInstruction { case '[': if currentMemoryCellValue() == 0 { positionOfMatchingBracket = findMatching("]") instructionPointer = positionOfMatchingBracket + 1 } case ']': if currentMemoryCellValue() != 0 { positionOfMatchingBracket = findMatching("[") instructionPointer = positionOfMatchingBracket + 1 } }

Slide 26

Slide 26 text

Problem: Brackets can be nested.

Slide 27

Slide 27 text

The simplest and slowest solution case '[': if m.memory[m.dp] == 0 { depth := 1 for depth != 0 { m.ip++ switch m.code[m.ip] { case '[': depth++ case ']': depth-- } } }

Slide 28

Slide 28 text

func (m *Machine) Execute() { for m.ip < len(m.code) { ins := m.code[m.ip] switch ins { // [...] case '[': if m.memory[m.dp] == 0 { depth := 1 for depth != 0 { m.ip++ switch m.code[m.ip] { case '[': depth++ case ']': depth-- } } } case ']': if m.memory[m.dp] != 0 { depth := 1 for depth != 0 { m.ip-- switch m.code[m.ip] { case ']': depth++ case '[': depth-- } } } } m.ip++ } }

Slide 29

Slide 29 text

Done!

Slide 30

Slide 30 text

Hello World! $ cat ./hello_world.b ++++++++[>++++[>++>+++>+++>+<< <<-]>+>+>->>+[<]<-]>>.>---.+++ ++++..+++.>>.<-.<.+++.------.- -------.>>+.>++. $ go build -o machine && ./machine ./hello_world.b Hello World!

Slide 31

Slide 31 text

Slow Brainfuck! Bad Brainfuck!

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

The Mandelbrot Benchmark $ time ./machine ./mandelbrot.b >/dev/null 68.24s user 0.18s system 99% cpu 1:08.60 total mandelbrot.b A Mandelbrot fractal viewer in Brainfuck Written by Eric Bosman

Slide 35

Slide 35 text

Slowing us down 1. Repeated instructions 2. Brackets

Slide 36

Slide 36 text

What if... — ... we had instructions that said "increase by 5" instead of "increase" 5 times? — ... we had an instruction that said "go to this matching bracket if current memory cell is empty"? Spoiler: we'd be faster!

Slide 37

Slide 37 text

New Instruction Set type InsType byte const ( Plus InsType = '+' Minus InsType = '-' Right InsType = '>' Left InsType = '<' PutChar InsType = '.' ReadChar InsType = ',' JumpIfZero InsType = '[' JumpIfNotZero InsType = ']' ) type Instruction struct { Type InsType Argument int }

Slide 38

Slide 38 text

New Machine type Machine struct { code []*Instruction // <--- WOOP WOOP! ip int memory [30000]int dp int input io.Reader output io.Writer readBuf []byte } func NewMachine(instructions []*Instruction, in io.Reader, out io.Writer) *Machine { return &Machine{ code: instructions, input: in, output: out, readBuf: make([]byte, 1), } }

Slide 39

Slide 39 text

Execute - Part 1 func (m *Machine) Execute() { for m.ip < len(m.code) { ins := m.code[m.ip] switch ins.Type { case Plus: m.memory[m.dp] += ins.Argument case Minus: m.memory[m.dp] -= ins.Argument case Right: m.dp += ins.Argument case Left: m.dp -= ins.Argument // ... } m.ip++ } }

Slide 40

Slide 40 text

Execute - Part 2 func (m *Machine) Execute() { // ... case PutChar: for i := 0; i < ins.Argument; i++ { m.putChar() } case ReadChar: for i := 0; i < ins.Argument; i++ { m.readChar() } // ... }

Slide 41

Slide 41 text

Execute - Part 3 func (m *Machine) Execute() { // ... case JumpIfZero: if m.memory[m.dp] == 0 { m.ip = ins.Argument continue } case JumpIfNotZero: if m.memory[m.dp] != 0 { m.ip = ins.Argument continue } } // ... m.ip++ } }

Slide 42

Slide 42 text

But, wait, ... how?

Slide 43

Slide 43 text

Brainfuck | ? | New Instruction Set

Slide 44

Slide 44 text

Compiler Wikipedia says: a computer program (or a set of programs) that transforms source code written in a programming language (the source language) into another computer language (the target language)

Slide 45

Slide 45 text

Let's do this! type Compiler struct { code string // <--- Brainfuck code codeLength int position int instructions []*Instruction // <--- New instruction set } func NewCompiler(code string) *Compiler { return &Compiler{ code: code, codeLength: len(code), instructions: []*Instruction{}, } }

Slide 46

Slide 46 text

func (c *Compiler) Compile() []*Instruction { for c.position < c.codeLength { current := c.code[c.position] switch current { case '+': c.CompileFoldableInstruction('+', Plus) case '-': c.CompileFoldableInstruction('-', Minus) case '<': c.CompileFoldableInstruction('<', Left) case '>': c.CompileFoldableInstruction('>', Right) case '.': c.CompileFoldableInstruction('.', PutChar) case ',': c.CompileFoldableInstruction(',', ReadChar) } c.position++ } return c.instructions }

Slide 47

Slide 47 text

func (c *Compiler) CompileFoldableInstruction(char byte, insType InsType) { count := 1 for c.position < c.codeLength-1 && c.code[c.position+1] == char { count++ c.position++ } c.EmitWithArg(insType, count) } func (c *Compiler) EmitWithArg(insType InsType, arg int) int { ins := &Instruction{Type: insType, Argument: arg} c.instructions = append(c.instructions, ins) return len(c.instructions) - 1 }

Slide 48

Slide 48 text

Problems when compiling loops — NOT foldable. We can't turn [[[]]] into [] — NOT countable. Instructions in beetween might change. — NOT stateless. We have to remember positions.

Slide 49

Slide 49 text

Solution — "[" — emit a JumpIfZero instruction — Argument will be 0 -- a placeholder value — "]" — emit JumpIfNotZero with correct argument — change JumpIfZero argument to correct position

Slide 50

Slide 50 text

Solution — "[" — emit a JumpIfZero instruction — Argument will be 0 -- a placeholder value — "]" — emit JumpIfNotZero with correct argument — change JumpIfZero argument to correct position How do we keep track of JumpIfZero instructions? Solution to problem in solution: with a stack! Stack, the data structure. First in, last out.

Slide 51

Slide 51 text

func (c *Compiler) Compile() []*Instruction { loopStack := []int{} for c.position < c.codeLength { current := c.code[c.position] switch current { case '[': insPos := c.EmitWithArg(JumpIfZero, 0) loopStack = append(loopStack, insPos) // [...] } c.position++ } return c.instructions }

Slide 52

Slide 52 text

func (c *Compiler) Compile() []*Instruction { // [...] case ']': // Pop position of last JumpIfZero ("[") instruction off stack openInstruction := loopStack[len(loopStack)-1] loopStack = loopStack[:len(loopStack)-1] // Emit the new JumpIfNotZero ("]") instruction, // with correct position as argument closeInstructionPos := c.EmitWithArg(JumpIfNotZero, openInstruction) // Patch the old JumpIfZero ("[") instruction with new position c.instructions[openInstruction].Argument = closeInstructionPos // [...] }

Slide 53

Slide 53 text

This really works! Input: +++[---[+]>>>]<<< Output: []*Instruction{ &Instruction{Type: Plus, Argument: 3}, &Instruction{Type: JumpIfZero, Argument: 7}, &Instruction{Type: Minus, Argument: 3}, &Instruction{Type: JumpIfZero, Argument: 5}, &Instruction{Type: Plus, Argument: 1}, &Instruction{Type: JumpIfNotZero, Argument: 3}, &Instruction{Type: Right, Argument: 3}, &Instruction{Type: JumpIfNotZero, Argument: 1}, &Instruction{Type: Left, Argument: 3}, }

Slide 54

Slide 54 text

How much faster does this make my production Brainfuck code?

Slide 55

Slide 55 text

$ time ./machine ./mandelbrot.b >/dev/null 13.43s user 0.04s system 99% cpu 13.496 total

Slide 56

Slide 56 text

$ time ./machine ./mandelbrot.b >/dev/null 13.43s user 0.04s system 99% cpu 13.496 total 13.496 total! before: 1:08.60 total!

Slide 57

Slide 57 text

Why am I here?

Slide 58

Slide 58 text

— Instruction Set — The Switch — Virtual Machine — Bytecode Compiler Not too bad, right?

Slide 59

Slide 59 text

Make Eric proud! — brainfuck optimzation strategies: http://calmerthanyouare.org/ 2015/01/07/optimizing-brainfuck.html — Hello, JIT World: The Joy Of Simple JITs: http:// blog.reverberate.org/2012/12/hello-jit-world-joy-of-simple-jits.html — interpreter, compiler, jit: https://nickdesaulniers.github.io/blog/ 2015/05/25/interpreter-compiler-jit/ — a optimized brainfuck compiler written in sed: https://github.com/ stedolan/bf.sed — the original Brainfuck distribution: https://gist.github.com/ rdebath/0ca09ec0fdcf3f82478f — there's much, much more