(lambda (args) body) (call f (args))The former will return the function - for now just the address - instead of executing the body. "call" will call the address passed with the arguments it's given, no surprise there. So how does this differ from "real" closures? The tricky part about real closures is that if you refer to variables from the surrounding scope, they are supposed to "stay live" for the next invocation. That is, they are meant to be kept around whenever you call the closure later on. It should be clear that returning just the address to the function isn't sufficient to achieve that. So let's look at one way of doing it, so you get an idea of the work involved (it's not huge, but not trivial either):
def compile_lambda args, body name = "lambda__#{@seq}" @seq += 1 compile_defun(name, args,body) puts "\tmovl\t$#{name},%eax" return [:subexpr] endHopefully this is pretty self-explanatory. All it does is create a function name on the form lambda__[number] that we'll use to refer to the function, since we won't actually output it inline. Nothing really prevents us from spitting it out inline, but I find it messy, so for now anyway, I'll treat it as just another function. We then call compile_defun to generate just another named function - so it's only anonymous for the user. We then move the address of the function into %eax, which is where we've established the result of our subexpressions go. Note that this is yet another shortcut - eventually we'll need to do something more sophisticated to handle more complex expressions properly, but register allocation is a complex subject (and the alternative, to push everything onto the stack works but is slow). Last we return [:subexpr] to indicate to the caller where/how to find the result of this expression. Next we'll do some refactoring. You may have noticed that the inner loop of #compile_exp was getting ugly, handling different types of arguments. So we extract this:
def compile_eval_arg arg atype, aparam = get_arg(arg) return "$.LC#{aparam}" if atype == :strconst return "$#{aparam}" if atype == :int return aparam.to_s if atype == :atom return "%eax" endNotice the appearance of :atom, too. This allows us to take the address of C functions and pass them to :call. It was so simple to add, I figured why not. To go with that, we add this to #get_arg:
return [:atom, a] if (a.is_a?(Symbol))Next up, as part of the refactoring, :call falls out almost by itself:
def compile_call func, args stack_adjustment = PTR_SIZE + (((args.length+0.5)*PTR_SIZE/(4.0*PTR_SIZE)).round) * (4*PTR_SIZE) puts "\tsubl\t$#{stack_adjustment}, %esp" args.each_with_index do |a,i| param = compile_eval_arg(a) puts "\tmovl\t#{param},#{i>0 ? i*4 : ""}(%esp)" end res = compile_eval_arg(func) res = "*%eax" if res == "%eax" # Ugly. Would be nicer to retain some knowledge of what "res" contains puts "\tcall\t#{res}" puts "\taddl\t$#{stack_adjustment}, %esp" return [:subexpr] endThis might look familiar. It's because it's the guts of #compile_exp, with #compile_eval_arg replacing some of the more ugly bits. The main other change is that it calls compile_eval_arg to get the function too, and does something weird to "%eax" - it prepends a "*". Either you're confused now, or you might start seeing possibilities - both of doing nice things and of shooting yourself in the foot. The above is the equivalent of casting an arbitrary expression into a pointer and jumping to it with no validation. It makes it deliciously easy to cause a segfault by jumping to random addresses. It also, incidentally, makes things like a virtual pointer table for a class system trivially easy, and a number of other things. Safety will have to come later. As it turns out, the "*" is needed in front of indirect "call"'s. So what does #compile_exp look like now? The obvious answer is "a hell of a lot cleaner":
def compile_do(*exp) exp.each { |e| compile_exp(e) } return [:subexpr] end def compile_exp(exp) return if !exp || exp.size == 0 return compile_do(*exp[1..-1]) if exp[0] == :do return compile_defun(*exp[1..-1]) if (exp[0] == :defun) return compile_ifelse(*exp[1..-1]) if (exp[0] == :if) return compile_lambda(*exp[1..-1]) if (exp[0] == :lambda) return compile_call(exp[1],exp[2]) if (exp[0] == :call) return compile_call(exp[0],exp[1..-1]) endNice, isn't it? Compile_call, as it turns out does almost exactly the same as what the guts of compile_exp used to do, apart from what was farmed out to the other functions. So we do a little test:
prog = [:do, [:call, [:lambda, [], [:puts, "Test"]], [] ] ](yeah, not exactly earth-shattering) Compile and run:
$ ruby step6.rb >step6.s $ make step6 cc step6.s -o step6 $ ./step6 TestThe full code for this step is here
void foo1(unsigned short a) {}
void foo2(signed short a) { }
void foo3(unsigned int a) {}
void foo4(signed int a) { }
void foo5(unsigned long a) {}
void foo6(signed long a) {}
void foo7(unsigned long long a) { }
void foo8(signed long long a) {}
int main()
{
foo1(1);
foo2(2);
foo3(3);
foo4(4);
foo5(5);
foo6(6);
foo7(7);
}
I'm omitting most of the gcc output, you can run gcc -S yourself if you care. The interesting bit is the calls to the various functions, which end up like this:
movl $1, (%esp)
call foo1
movl $2, (%esp)
call foo2
movl $3, (%esp)
call foo3
movl $4, (%esp)
call foo4
movl $5, (%esp)
call foo5
movl $6, (%esp)
call foo6
movl $7, (%esp)
movl $0, 4(%esp)
call foo7
In other words, for the purposes of a function call at least, gcc threats all types except long long as a single 32 bit value. We'll conveniently forget about long long and stick to values up to 32 bit then, since that means we can keep ignoring types completely.
Laziness is a virtue.
We'll also blatantly ignore floats? Why? Because you can easily write a compiler with only integer math, and so adding float support now would only be a waste of time, when things are bound to change down the line anyway.
Besides, when I was young we didn't have those fancy FPU's, sonny, and we did just fine anyway with fixed point calculations using integers.
So what do we actually change?
In #get_arg, before we handle string constants, we add this:
return [:int, a] if (a.is_a?(Fixnum))In #compile_exp, we add this to use the return values from #get_arg:
elsif atype == :int then param = "$#{aparam}"Aaand, we're done. That was almost to easy. Here's a test:
prog = [:do, [:printf,"'hello world' takes %ld bytes\\n",[:strlen, "hello world"]], [:printf,"The above should show _%ld_ bytes\\n",11] ]
void foo() {}
void bar() {}
int main()
{
if (baz()) {
foo();
} else {
bar();
}
}
Giving the relevant part:
call baz
testl %eax, %eax
je .L6
call foo
jmp .L10
.L6:
call bar
.L10:
This is a pretty much universal pattern for compiling if .. then ... else, across a huge number of languages and architectures:
def ifelse cond, if_arm,else_arm compile_exp(cond) puts "\ttestl %eax, %eax" @seq += 2 else_arm_seq = @seq - 1 end_if_arm_seq = @seq puts "\tje .L#{else_arm_seq}" compile_exp(if_arm) puts "\tjmp .L#{end_if_arm_seq}" puts ".L#{else_arm_seq}:" compile_exp(else_arm) puts ".L#{end_if_arm_seq}:" endIt should be pretty self explanatory - it's basically calling compile_exp for each of the condition, if-arm and else-arm and weaving that into the above code, using @seq to generate unique labels. To hook it in, we add this right after "return defun ...." in #compile_exp:
return ifelse(*exp[1..-1]) if (exp[0] == :if)And here's a simple test:
prog = [:do, [:if, [:strlen,""], [:puts, "IF: The string was not empty"], [:puts, "ELSE: The string was empty"] ], [:if, [:strlen,"Test"], [:puts, "Second IF: The string was not empty"], [:puts, "Second IF: The string was empty"] ] ]Here is the result As usual you run it like this:
$ ruby step5.rb >step5.s $ make step5 cc step5.s -o step5 $ ./step5 ELSE: The string was empty Second IF: The string was not empty $
[:defun, :loop, [], [:do, [:puts, "I am all loopy"], [:loop] ], [:loop]In other words, tail call elimination means that for any function on the form "(defun foo () (do bar foo))", stack usage goes from being proportional to the depth of the tail calls to being 1 frame deep at that point... The above will run with the current compiler, but it'll quickly overrun the stack and crash. Not good. I sense a disturbance in the force. All two geeks reading this shuddering at the thought of the stack growing with each iteration.. For now, we'll just ignore this, and not do anything to abuse the stack too much. Instead we'll later implement a proper loop construct as a primitive. At least for now - if/when I add tail call elimination we can consider ripping it out again and make it part of the runtime library instead. So we can do non-terminating loops... That doesn't do us much good now does it?. Well, we can already do while loops too:
(defun some-while-loop () (if condition (some-while-loop) ()))Not very satisfying, but it works. It's too ugly to be an acceptable workaround, though, so a "while" primitive is on the list. (As the realization that I've used Lisp like syntax sinks in, there's another disturbance in the force as the same two geeks as before shriek in agony and bolt for the door...) I'm not a Lisp'er. I can't handle the parentheses overload... But Lisp syntax is convenient when the language doesn't really have a syntax of it's own yet - that'll come after bootstrapping when it's time to write a proper parser. Incidentally, most of what I've implemented and will implement will bear some kind of resemblance to badly broken LISP - as it turns out, Lisp constructs are very powerful if you can stand the syntax, and even if you don't want to program in it, learning more about Lisp and/or Scheme is worthwhile. Personally I don't heed my own advice, and most of the Lisp-like ideas I'll be using are probably based on incomplete understanding from the very limited time I've spent looking at Lisp. Next time: Anonymous functions, maybe more.
void foo()
{
puts("Hello world");
}
.. results in this, with gcc:
.globl foo
.type foo, @function
foo:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $.LC0, (%esp)
call puts
leave
ret
.size foo, .-foo
The function call is easy enough to recognize, and that leaves us with a small prolog and epilog to the function:
The prolog stores %ebp onto the stack, and then copies %esp into %ebp. "leave" undoes the damage, and "ret" pops the return address off the stack and returns to where the function was called from. Why does it store %esp (the stack pointer) in %ebp? Well, one obvious advantage is that you can mess up the stack as much as you like, and just copy %ebp back again at the end and all is well. We can see above that GCC takes advantage of that by not freeing the stack space it allocated for the call to puts again, and leaving that to the "leave" instruction - it'd just be wasteful otherwise.
So if that's all, the changes should be fairly simple. And they are.
First we modify Compiler#initialize to create a Hash to store our functions in:
def initialize @global_functions = {}Then we add a method to output the functions:
def output_functions @global_functions.each do |name,data| puts ".globl #{name}" puts ".type #{name}, @function" puts "#{name}:" puts "\tpushl %ebp" puts "\tmovl %esp, %ebp" compile_exp(data[1]) puts "\tleave" puts "\tret" puts "\t.size #{name}, .-#{name}" puts end endYou see we include the ".globl" and ".type" and ".size" bits too. ".globl" indicates that the function should be visible outside of the specific file you're assembling, which is important if you want to link multiple object files together. ".type" and ".size" I believe are there primarily for debugging, indicating the the symbol represents a function, and the size respectively. Apart from that, this function is pretty trivial - it calls "compile_exp" to do the actual work. We add a little helper to define the functions:
def defun name, args, body @global_functions[name] = [args,body] endWe add a couple of lines to #compile_exp:
return if !exp || exp.size == 0
return defun(*exp[1..-1]) if (exp[0] == :defun)
The first line is there partly for robustness, partly to let us pass nil or empty arrays as "do nothing", just as you'd expect to be able to write an empty function declaration. It makes sense in the context of the next line, that doesn't check if it's about to define an empty function.
You may or may not have noticed that this thing lets us define functions recursively. [:defun,:foo,[:defun, :bar, []]] is completely legal. You may also have noticed that this currently leads to defining two functions that are both globally accessible. Oh well. It doesn't do any harm, so we'll deal with that later ("that" being either preventing it, or making the inner function only visible inside the outer function - I haven't decided yet which I prefer).
All that remains is to output the functions, so we add this to #compile, right before output_constants:
output_functions
def compile(exp) compile_main([:do, DO_BEFORE, exp, DO_AFTER]) endThen we define the constants DO_BEFORE and DO_AFTER (put them in a separate file if you prefer, for now I've just put them at the top):
DO_BEFORE= [:do, [:defun, :hello_world,[], [:puts, "Hello World"]] ] DO_AFTER= []Just admit it, you'd expected something more advanced. But that would defeat the goals set out at the start. The above is sufficient to define a proper runtime library. If you want something that'd need to be implemented in assembler or C, that works too: Just link to an object file containing the functions in question, since we still respect C calling conventions. Lets test it. Add this before Compiler.new.compile(prog):
prog = [:hello_world]And compile and run:
$ ruby step4.rb >step4.s $ make step4 cc step4.s -o step4 $ ./step4 Hello World $You can find the result here
I can't stand parser generators that don't make the input really terse. For most simple languages writing a terse recursive descent parser that is extremely readable can be done by implementing a tiny little reusable set of parser combinators (functions or objects that handle a tiny aspect of parsing, such as "or" and defer the rest to other objects, that can be combined by function- or object references or writing functions). If the input to the parser generator isn't terse, much of the advantage (certainly not all) goes out the window.
grammar ParseGen rules { start< :grammar> { "grammar" @name @rules } rules { "rules" "{" $rule* "}" } rule { @name ("<" @var ">")? "{" @expr* "}"? } expr { $or_expr } or_expr { post_expr<@left> ("|" or_expr<@right>)? } post_expr { $cut | $glue | $simple } simple { @primary ("<" @var ">")? ("?" | "*" | "+")<@modifier>? } primary { paren<@expr> | @keywords | @name | @string | @number | @set } parent { "(" $rule+ ")" } keywords { "ANY" | "EOF" } string { (('"' _ ([~\"]*)<$str> _' "') | ("'"_ ([~\']*)<$str> _ "'")) } var { ('@'|'$'|':') _ <@vartype> @name } number { @base10 | @base16 } name { ([a-zA-Z][a-zA-Z0-9_]*) } set { '[' ('~'? ("\\]"|[~\]])*) ']' ] cut { "!"< :cut > } glue { "_"< :glue > } base10 { [0-9]+ } base16 { 'x'_[0-9a-fA-F]+ } WHITESPACE { 9 | 13 | 10 | ('#' [~\n]* 10) } }The observant might notice that the syntax appears to steal a lot of elements from Ruby. That's not a coincidence. I like the way it looks, and it also allows me to use Ruby syntax highlighting to make it look good. I've tried not abusing it too much in terms of changing meaning around. Yes, I'm too lazy to figure out how to write my own syntax highlighter in elisp, so sue me. Here are some notes on what the syntax is intended to achieve:
E-mail: vidar@hokstad.com
Skype: vhokstad
View my LinkedIn profile
I was born April 21st, 1975, in Oslo, Norway. Since 2000 I've been living in London, UK. I'm married.
I'm working for Aardvark Media as Director of Technology. I'm also currently on the board of SpatialQ, a startup in the GIS space, and an advisor to Skoach, a startup doing a time management app for people with ADD.