33
Write your own JVM compiler OSCON Java 2011 Ian Dees @undees Hi, I’m Ian. I’m here to show that there’s never been a better time than now to write your own compiler.

Write Your Own JVM Compiler

Embed Size (px)

Citation preview

Page 1: Write Your Own JVM Compiler

Write your ownJVM compiler

OSCON Java 2011Ian Dees@undees

Hi, I’m Ian. I’m here to show that there’s never been a better time than now to write your own compiler.

Page 2: Write Your Own JVM Compiler

Time

Abs

trac

tion

compilers

the three paths

Why would someone want to do this? That depends on the direction you’re coming from.

Page 3: Write Your Own JVM Compiler

Time

Abs

trac

tion

compilers

e-

xor eax, eax

If, like me, you came from a hardware background, compilers are the next logical step up the ladder of abstraction from programming.

Page 4: Write Your Own JVM Compiler

Time

Abs

trac

tion

compilers

grammarslogic λx.x

e-

xor eax, eax

If you come from a computer science background, compilers and parsers are practical tools to get things done (such as reading crufty ad hoc data formats at work).

Page 5: Write Your Own JVM Compiler

Time

Abs

trac

tion

compilers

grammarslogic λx.x

e-

xor eax, eax

If you’re following the self-made path, compilers are one of many computer science topics you may come to naturally on your travels.

Page 6: Write Your Own JVM Compiler

where we’re headed

By the end of this talk, we’ll have seen the basics of how to use JRuby to create a compiler for a fictional programming language.

Page 7: Write Your Own JVM Compiler

Thnada fictional programming language

Since all the good letters for programming languages are taken (C, D, K, R, etc.), let’s use a fictional letter. “Thnad,” a letter that comes to us from Dr. Seuss, will do nicely.

Page 8: Write Your Own JVM Compiler

function factorial(n) { if(eq(n, 1)) { 1 } else { times(n, factorial(minus(n, 1))) }}

print(factorial(4))

Here’s a simple Thnad program. As you can see, it has only integers, functions, and conditionals. No variables, type definitions, or anything else. But this bare minimum will keep us plenty busy for the next half hour.

Page 9: Write Your Own JVM Compiler

before we see the code

hand-waving

Before we jump into the code, let’s look at the general view of where we’re headed.

Page 10: Write Your Own JVM Compiler

minus(n, 1)

:funcall

:args

‘minus’

:name :number

:name

‘n’ ’1’

:arg :arg

{ :funcall => { :name => 'minus' }, { :args => [ { :arg => { :name => 'n' } }, { :arg => { :number => '1' } } } ] }

We need to take a piece of program text like “minus(n, 1)” and break it down into something like a sentence’s parts of speech: this is a function call, this is a parameter, and so on. The code in the middle is how we might represent this breakdown in Ruby, but really we should be thinking about it graphically, like the tree at the bottom.

Page 11: Write Your Own JVM Compiler

minus(n, 1)

Funcall

@args

‘minus’

@name

@name

‘n’

Usage Number

@value

1

Funcall.new 'minus', Usage.new('n'), Number.new(1)

The representation on the previous slide used plain arrays, strings, and so on. It will be helpful to transform these into custom Ruby objects that know how to emit JVM bytecode. Here’s the kind of thing we’ll be aiming for. Notice that the tree looks really similar to the one we just saw—the only differences are the names in the nodes.

Page 12: Write Your Own JVM Compiler

1. parse

2. transform

3. emit

Each stage of our compiler will transform one of our program representations—original program text, generic Ruby objects, custom Ruby objects, JVM bytecode—into the next.

Page 13: Write Your Own JVM Compiler

1. parse

First, let’s look at parsing the original program text into generic Ruby objects.

Page 14: Write Your Own JVM Compiler

describe Thnad::Parser do before do @parser = Thnad::Parser.new end

it 'reads a number' do expected = { :number => '42' } @parser.number.parse('42').must_equal expected endend

Here’s a unit test written with minitest, a unit testing framework that ships with Ruby 1.9 (and therefore JRuby). We want the text “42” to be parsed into the Ruby hash shown here.

Page 15: Write Your Own JVM Compiler

tool #1: Parslethttp://kschiess.github.com/parslet

How are we going to parse our programming language into that internal representation? By using Parslet, a parsing tool written in Ruby. For the curious, Parslet is a PEG (Parsing Expression Grammar) parser.

Page 16: Write Your Own JVM Compiler

require 'parslet'

module Thnad

class Parser < Parslet::Parser

# gotta see it all

rule(:space) { match('\s').repeat(1) }

rule(:space?) { space.maybe }

# numeric values

rule(:number) { match('[0-9]').repeat(1).as(:number) >> space? }

end

end

Here’s a set of Parslet rules that will get our unit test to pass. Notice that the rules read a little bit like a mathematical notation: “a number is a sequence of one or more digits, possibly followed by trailing whitespace.”

Page 17: Write Your Own JVM Compiler

2. transform

The next step is to transform the Ruby arrays and hashes into more sophisticated objects that will (eventually) be able to generate bytecode.

Page 18: Write Your Own JVM Compiler

input = { :number => '42' } expected = Thnad::Number.new(42) @transform.apply(input).must_equal expected

Here’s a unit test for that behavior. We start with the result of the previous step (a Ruby hash) and expect a custom Number object.

Page 19: Write Your Own JVM Compiler

tool #2: Parslet(again)

How are we going to transform the data? We’ll use Parslet again.

Page 20: Write Your Own JVM Compiler

module Thnad class Transform < Parslet::Transform rule(:number => simple(:value)) { Number.new(value.to_i) } endend

This rule takes any simple Ruby hash with a :number key and transforms it into a Number object.

Page 21: Write Your Own JVM Compiler

require 'parslet'

module Thnad class Number < Struct.new(:value) # more to come...

endend

Of course, we’ll need to define the Number class. Once we’ve done so, the unit tests will pass.

Page 22: Write Your Own JVM Compiler

3. emit

Finally, we need to emit JVM bytecode.

Page 23: Write Your Own JVM Compiler

bytecode

ldc 42

Here’s the bytecode we want to generate when our compiler encounters a Number object with 42 as the value. It just pushes the value right onto the stack.

Page 24: Write Your Own JVM Compiler

describe 'Emit' do before do @builder = mock @context = Hash.new end

it 'emits a number' do input = Thnad::Number.new 42 @builder.expects(:ldc).with(42) input.eval @context, @builder endend

And here’s the unit test that specifies this behavior. We’re using mock objects to say in effect, “Imagine a Ruby object that can generate bytecode; our compiler should call it like this.”

Page 25: Write Your Own JVM Compiler

require 'parslet'

module Thnad class Number < Struct.new(:value) def eval(context, builder) builder.ldc value end endend

Our Number class will now need an “eval” method that takes a context (useful for carrying around information such as parameter names) and a bytecode builder (which we’ll get to on the next slide).

Page 26: Write Your Own JVM Compiler

tool #3: BiteScripthttps://github.com/headius/bitescript

A moment ago, we saw that we’ll need a Ruby object that knows how to emit JVM bytecode. The BiteScript library for JRuby will give us just such an object.

Page 27: Write Your Own JVM Compiler

iload 0ldc 1invokestatic example, 'minus', int, int, intireturn

This, for example, is a chunk of BiteScript that writes a Java .class file containing a function call—in this case, the equivalent of the “minus(n, 1)” Thnad code we saw earlier.

Page 28: Write Your Own JVM Compiler

$ javap -c exampleCompiled from "example.bs"public class example extends java.lang.Object{public static int doSomething(int); Code: 0: iload_0 1: iconst_1 2: invokestatic #11; //Method minus:(II)I 5: ireturn

If you run that through the BiteScript compiler and then use the standard Java tools to view the resulting bytecode, you can see it’s essentially the same program.

Page 29: Write Your Own JVM Compiler

enough hand-wavinglet’s see the code!

Armed with this background information, we can now jump into the code.

Page 30: Write Your Own JVM Compiler

a copy of our home gamehttps://github.com/undees/thnad

At this point in the talk, I fired up TextMate and navigated through the project, choosing the direction based on whim and audience questions. You can follow along at home by looking at the various commits made to this project on GitHub.

Page 31: Write Your Own JVM Compiler

more on BiteScripthttp://www.slideshare.net/CharlesNutter/redev-2010-jvm-bytecode-for-dummies

There are two resources I found helpful during this project. The first was Charles Nutter’s primer on Java bytecode, which was the only JVM reference material necessary to write this compiler.

Page 32: Write Your Own JVM Compiler

see alsohttp://createyourproglang.com

Marc-André Cournoyer

The second helpful resource was Marc-André Cournoyer’s e-book How to Create Your Own Freaking Awesome Programming Language. It’s geared more toward interpreters than compilers, but was a great way to check my progress after I’d completed the major stages of this project.

Page 33: Write Your Own JVM Compiler

hopes

My hope for us as a group today is that, after our time together, you feel it might be worth giving one of these tools a shot—either for a fun side project, or to solve some protocol parsing need you may encounter one day in your work.