This is part of a series I started in March 2008 - you may want to go back and look at older parts if you're new to this series.
At this point it's worth looking briefly at what is required to implement something more "serious" without a lot of excessive pain. It's really quite easy:(defun real_func (arg,localvar1,localvar2) ( ... ) ) (defun func (arg) (real_func arg 0 0))The three points above will therefore be the focus of the next couple of parts.
DO_BEFORE= [:do, [:defun, :array, [:size],[:malloc,[:mul,:size,4]] ]We're just calling the C-library's "malloc" and multiplying the input by 4 (yes, I'm assuming 32 bit pointers again here, so sue me - certainly this needs to be abstracted out at some point, but by then this code will be unrecognizable). Note that depending on platform and typical array size this can be horribly inefficient for small allocations, but that's for later. So we can create arrays, but not access them. We could easily enough do this in C, BUT there's one constraint: We want to be able to assign to the array with the "assign" primitive instead of calling a special function to access arrays.
def compile_index scope,arr,index source = compile_eval_arg(scope, arr) @e.movl(source,:edx) source = compile_eval_arg(scope, index) @e.save_result(source) @e.sall(2,:eax) @e.addl(:eax,:edx) return [:indirect,:edx] endNotice that this gets quite a bit of x86 specific stuff back in compiler.rb, but we'll worry about that later. The logic is fairly straightforward:
return compile_index(scope,*exp[1..-1]) if (exp[0] == :index)So, lets deal with :indirect. We need to change two places. First and foremost, we need to add the following line to #compile_eval_arg:
@e.emit(:movl,"(%#{aparam.to_s})",@e.result_value) if atype == :indirectThis takes care of the case when and :index occurs anywhere but as the target of an :assign. It reads the value pointed to. Then we need to change #compile_assign to store the result in a different way when :indirect is used. Here's a diff of #compile_assign
def compile_assign scope, left, right source = compile_eval_arg(scope, right) atype, aparam = get_arg(scope,left) - raise "Expected an argument on left hand side of assignment" if atype != :arg - @e.save_to_arg(source,aparam) + if atype == :indirect + @e.emit(:movl,source,"(%#{aparam})") + elsif atype == :arg + @e.save_to_arg(source,aparam) + else + raise "Expected an argument on left hand side of assignment" + end return [:subexpr] end
require 'compiler' prog = [:do, [[:lambda, [:i], [:do, [:assign, :i, [:array, 2]], [:printf, "i=%p\n",:i], [:assign, [:index, :i, 0], 2], [:assign, [:index, :i, 1], 42], [:printf, "i[0]=%ld, i[1]=%ld\n",[:index,:i,0],[:index,:i,1]] ] ],10] ] Compiler.new.compile(prog)Here's the [:index, :i, 0] expression, with some comments:
movl 8(%ebp), %eax ; Move "i" to %eax movl %eax, %edx ; Save it to %edx movl $0, %eax ; The 0 from [:index, :i, 0] sall $2, %eax ; Multiply by 4 addl %eax, %edx ; Add the offset to the address saved in %edx movl $2, (%edx) ; Copy the value 2 into the address of the beginning of "i"It's a pretty good example of complete lack of optimization, and both some trivial optimization of the original program as well as peephole optimization of the assembly could reduce it to next to nothing, but it works... So lets run it:
$ make testarray ruby testarray.rb >testarray.s as -o testarray.o testarray.s testarray.s: Assembler messages: testarray.s:97: Warning: unterminated string; newline inserted cc -c -o runtime.o runtime.c gcc -o testarray testarray.o runtime.o $ ./testarray i=0x889a008 i[0]=2, i[1]=42