From d7a60f2803ff4001b8af8865f0e1b717d8b59739 Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Mon, 9 Jun 2014 19:24:09 +0300 Subject: [PATCH] new register allocation generates good looking push/pop --- lib/ast/call_site_expression.rb | 4 +- lib/ast/if_expression.rb | 3 +- lib/ast/while_expression.rb | 2 + lib/vm/block.rb | 40 ++++++++++++++++++-- lib/vm/function.rb | 67 +++++++++++++++++---------------- lib/vm/instruction.rb | 4 ++ lib/vm/meta_class.rb | 6 +-- 7 files changed, 85 insertions(+), 41 deletions(-) diff --git a/lib/ast/call_site_expression.rb b/lib/ast/call_site_expression.rb index 81b0bf9e..19cb3a8a 100644 --- a/lib/ast/call_site_expression.rb +++ b/lib/ast/call_site_expression.rb @@ -22,12 +22,12 @@ module Ast raise "No receiver error #{inspect}:#{value_receiver}:#{name}" if (value_receiver.nil?) call = Vm::CallSite.new( name , value_receiver , params , function) current_function = context.function - current_function.save_locals(context , into) if current_function + into.push([]) unless current_function.nil? call.load_args into call.do_call into after = into.new_block("#{into.name}_call#{@@counter+=1}") into.insert_at after - current_function.restore_locals(context , after) if current_function + after.pop([]) unless current_function.nil? puts "compile call #{function.return_type}" function.return_type end diff --git a/lib/ast/if_expression.rb b/lib/ast/if_expression.rb index 6872899e..cf41c43f 100644 --- a/lib/ast/if_expression.rb +++ b/lib/ast/if_expression.rb @@ -11,7 +11,8 @@ module Ast puts "compiling if condition #{cond}" cond_val = cond.compile(context , into) into.b true_block , condition_code: cond_val.operator - + into.branch = true_block + if_false.each do |part| puts "compiling in if false #{part}" last = part.compile(context , false_block ) diff --git a/lib/ast/while_expression.rb b/lib/ast/while_expression.rb index d372907a..de84ac6a 100644 --- a/lib/ast/while_expression.rb +++ b/lib/ast/while_expression.rb @@ -7,6 +7,8 @@ module Ast puts "compiling while condition #{condition}" cond_val = condition.compile(context , while_block) while_block.b ret , condition_code: cond_val.not_operator + while_block.branch = ret + last = nil body.each do |part| puts "compiling in while #{part}" diff --git a/lib/vm/block.rb b/lib/vm/block.rb index 74186655..73f09b77 100644 --- a/lib/vm/block.rb +++ b/lib/vm/block.rb @@ -25,6 +25,7 @@ module Vm @function = function @name = name.to_sym @next = next_block + @branch = nil @codes = [] @insert_at = self # keeping track of register usage, left (assigns) or right (uses) @@ -33,15 +34,27 @@ module Vm end attr_reader :name , :next , :codes , :function , :assigns , :uses + attr_accessor :branch + + def reachable + ret = [] + add_next ret + add_branch ret + ret + end def add_code(kode) raise "alarm #{kode}" if kode.is_a? Word raise "alarm #{kode.class} #{kode}" unless kode.is_a? Code - @assigns += kode.assigns - @uses += kode.uses - @insert_at.codes << kode + @insert_at.do_add kode self end + def do_add kode + kode.assigns.each { |a| (@assigns << a) unless @assigns.include?(a) } + kode.uses.each { |use| (@uses << use) unless (@assigns.include?(use) or @uses.include?(use)) } + #puts "IN ADD #{name}#{uses}" + @codes << kode + end alias :<< :add_code # create a new linear block after this block. Linear means there is no brach needed from this one @@ -85,6 +98,13 @@ module Vm add_code RegisterMachine.instance.send(meth , *args) end + # returns if this is a block that ends in a call (and thus needs local variable handling) + def call_block? + return false unless codes.last.is_a?(CallInstruction) + return false unless codes.last.opcode == :call + codes.dup.reverse.find{ |c| c.is_a? StackInstruction } + end + # Code interface follows. Note position is inheitted as is from Code # length of the block is the length of it's codes, plus any next block (ie no branch follower) @@ -116,5 +136,19 @@ module Vm end @next.assemble(io) if @next end + + private + def add_next ret + return if @next.nil? + return if ret.include? @next + ret << @next + ret + @next.reachable + end + def add_branch ret + return if @branch.nil? + return if ret.include? @branch + ret << @branch + ret + @branch.reachable + end end end \ No newline at end of file diff --git a/lib/vm/function.rb b/lib/vm/function.rb index b2deaf16..d4c835d2 100644 --- a/lib/vm/function.rb +++ b/lib/vm/function.rb @@ -56,7 +56,7 @@ module Vm @body = Block.new("body", self , @return) @entry = Core::Kernel::function_entry( Vm::Block.new("entry" , self , @body) ,name ) @locals = [] - @blocks = [] + @linked = false # incase link is called twice, we only calculate locals once end attr_reader :args , :entry , :exit , :body , :name , :return_type , :receiver @@ -81,46 +81,53 @@ module Vm @locals << l l end - #BUG - must save receiver - def save_locals context , into - save = args.collect{|a| a.register_symbol } + @locals.collect{|l| l.register_symbol} - into.push(save) unless save.empty? - end - - def restore_locals context , into - #TODO assumes allocation in order, as the pop must be get regs in ascending order (also push) - restore = args.collect{|a| a.register_symbol } + @locals.collect{|l| l.register_symbol} - into.pop(restore) unless restore.empty? - end - - def new_block name - block = Block.new(name , self) - @blocks << block - block + # return a list of registers that are still in use after the given block + # a call_site uses pushes and pops these to make them available for code after a call + def locals_at l_block + used =[] + assigned = [] + l_block.reachable.each do |b| + b.uses.each {|u| + (used << u) unless assigned.include?(u) + } + assigned += b.assigns + end + used.uniq end # return a list of the blocks that are addressable, ie entry and @blocks and all next def blocks ret = [] - (@blocks << @entry).each do |b| - while b - ret << b - b = b.next - end - end + b = @entry + while b + ret << b + b = b.next + end ret end + # following id the Code interface # to link we link the entry and then any blocks. The entry links the straight line def link_at address , context super #just sets the position @entry.link_at address , context - address += @entry.length - @blocks.each do |block| - block.link_at(pos , context) - pos += block.length + return if @linked + @linked = true + blocks.each do |b| + if push = b.call_block? + locals = locals_at b + if(locals.empty?) + puts "Empty #{b}" + else + puts "PUSH #{push}" + push.set_registers(locals) + pop = b.next.codes.first + puts "POP #{pop}" + pop.set_registers(locals) + end + end end end @@ -132,16 +139,12 @@ module Vm # length of a function is the entry block length (includes the straight line behind it) # plus any out of line blocks that have been added def length - @blocks.inject(@entry.length) {| sum , item | sum + item.length} + @entry.length end # assembling assembles the entry (straight line/ no branch line) + any additional branches def assemble io @entry.assemble(io) - @blocks.each do |block| - block.assemble io - end end - end end \ No newline at end of file diff --git a/lib/vm/instruction.rb b/lib/vm/instruction.rb index 00922a2e..1cdf5fd1 100644 --- a/lib/vm/instruction.rb +++ b/lib/vm/instruction.rb @@ -62,6 +62,10 @@ module Vm @first = first super(options) end + # when calling we place a dummy push/pop in the stream and calculate later what registers actually need saving + def set_registers regs + @first = regs.collect{ |r| r.symbol } + end def is_push? opcode == :push end diff --git a/lib/vm/meta_class.rb b/lib/vm/meta_class.rb index 17efffeb..1fd4a738 100644 --- a/lib/vm/meta_class.rb +++ b/lib/vm/meta_class.rb @@ -27,16 +27,16 @@ module Vm def get_function name name = name.to_sym f = @functions.detect{ |f| f.name == name } - puts "no function for #{name} in Meta #{@me_self.inspect}" unless f + puts "no function for :#{name} in Meta #{@me_self.inspect}" unless f f end # way of creating new functions that have not been parsed. def get_or_create_function name fun = get_function name unless fun or name == :Object - supr = @context.object_space.get_or_create_class(@super_class) + supr = @me_self.context.object_space.get_or_create_class(@super_class) fun = supr.get_function name - puts "#{supr.functions.collect(&:name)} for #{name} GOT #{fun.class}" if name == :index_of + puts "#{supr.functions.collect(&:name)} for #{name} GOT #{fun.class}" end fun end