new register allocation generates good looking push/pop

This commit is contained in:
Torsten Ruger 2014-06-09 19:24:09 +03:00
parent b66c4157d5
commit d7a60f2803
7 changed files with 85 additions and 41 deletions

View File

@ -22,12 +22,12 @@ module Ast
raise "No receiver error #{inspect}:#{value_receiver}:#{name}" if (value_receiver.nil?) raise "No receiver error #{inspect}:#{value_receiver}:#{name}" if (value_receiver.nil?)
call = Vm::CallSite.new( name , value_receiver , params , function) call = Vm::CallSite.new( name , value_receiver , params , function)
current_function = context.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.load_args into
call.do_call into call.do_call into
after = into.new_block("#{into.name}_call#{@@counter+=1}") after = into.new_block("#{into.name}_call#{@@counter+=1}")
into.insert_at after 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}" puts "compile call #{function.return_type}"
function.return_type function.return_type
end end

View File

@ -11,7 +11,8 @@ module Ast
puts "compiling if condition #{cond}" puts "compiling if condition #{cond}"
cond_val = cond.compile(context , into) cond_val = cond.compile(context , into)
into.b true_block , condition_code: cond_val.operator into.b true_block , condition_code: cond_val.operator
into.branch = true_block
if_false.each do |part| if_false.each do |part|
puts "compiling in if false #{part}" puts "compiling in if false #{part}"
last = part.compile(context , false_block ) last = part.compile(context , false_block )

View File

@ -7,6 +7,8 @@ module Ast
puts "compiling while condition #{condition}" puts "compiling while condition #{condition}"
cond_val = condition.compile(context , while_block) cond_val = condition.compile(context , while_block)
while_block.b ret , condition_code: cond_val.not_operator while_block.b ret , condition_code: cond_val.not_operator
while_block.branch = ret
last = nil last = nil
body.each do |part| body.each do |part|
puts "compiling in while #{part}" puts "compiling in while #{part}"

View File

@ -25,6 +25,7 @@ module Vm
@function = function @function = function
@name = name.to_sym @name = name.to_sym
@next = next_block @next = next_block
@branch = nil
@codes = [] @codes = []
@insert_at = self @insert_at = self
# keeping track of register usage, left (assigns) or right (uses) # keeping track of register usage, left (assigns) or right (uses)
@ -33,15 +34,27 @@ module Vm
end end
attr_reader :name , :next , :codes , :function , :assigns , :uses 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) def add_code(kode)
raise "alarm #{kode}" if kode.is_a? Word raise "alarm #{kode}" if kode.is_a? Word
raise "alarm #{kode.class} #{kode}" unless kode.is_a? Code raise "alarm #{kode.class} #{kode}" unless kode.is_a? Code
@assigns += kode.assigns @insert_at.do_add kode
@uses += kode.uses
@insert_at.codes << kode
self self
end 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 alias :<< :add_code
# create a new linear block after this block. Linear means there is no brach needed from this one # 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) add_code RegisterMachine.instance.send(meth , *args)
end 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 # 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) # 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 end
@next.assemble(io) if @next @next.assemble(io) if @next
end 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
end end

View File

@ -56,7 +56,7 @@ module Vm
@body = Block.new("body", self , @return) @body = Block.new("body", self , @return)
@entry = Core::Kernel::function_entry( Vm::Block.new("entry" , self , @body) ,name ) @entry = Core::Kernel::function_entry( Vm::Block.new("entry" , self , @body) ,name )
@locals = [] @locals = []
@blocks = [] @linked = false # incase link is called twice, we only calculate locals once
end end
attr_reader :args , :entry , :exit , :body , :name , :return_type , :receiver attr_reader :args , :entry , :exit , :body , :name , :return_type , :receiver
@ -81,46 +81,53 @@ module Vm
@locals << l @locals << l
l l
end end
#BUG - must save receiver
def save_locals context , into # return a list of registers that are still in use after the given block
save = args.collect{|a| a.register_symbol } + @locals.collect{|l| l.register_symbol} # a call_site uses pushes and pops these to make them available for code after a call
into.push(save) unless save.empty? def locals_at l_block
end used =[]
assigned = []
def restore_locals context , into l_block.reachable.each do |b|
#TODO assumes allocation in order, as the pop must be get regs in ascending order (also push) b.uses.each {|u|
restore = args.collect{|a| a.register_symbol } + @locals.collect{|l| l.register_symbol} (used << u) unless assigned.include?(u)
into.pop(restore) unless restore.empty? }
end assigned += b.assigns
end
def new_block name used.uniq
block = Block.new(name , self)
@blocks << block
block
end end
# return a list of the blocks that are addressable, ie entry and @blocks and all next # return a list of the blocks that are addressable, ie entry and @blocks and all next
def blocks def blocks
ret = [] ret = []
(@blocks << @entry).each do |b| b = @entry
while b while b
ret << b ret << b
b = b.next b = b.next
end end
end
ret ret
end end
# following id the Code interface # following id the Code interface
# to link we link the entry and then any blocks. The entry links the straight line # to link we link the entry and then any blocks. The entry links the straight line
def link_at address , context def link_at address , context
super #just sets the position super #just sets the position
@entry.link_at address , context @entry.link_at address , context
address += @entry.length return if @linked
@blocks.each do |block| @linked = true
block.link_at(pos , context) blocks.each do |b|
pos += block.length 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
end end
@ -132,16 +139,12 @@ module Vm
# length of a function is the entry block length (includes the straight line behind it) # 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 # plus any out of line blocks that have been added
def length def length
@blocks.inject(@entry.length) {| sum , item | sum + item.length} @entry.length
end end
# assembling assembles the entry (straight line/ no branch line) + any additional branches # assembling assembles the entry (straight line/ no branch line) + any additional branches
def assemble io def assemble io
@entry.assemble(io) @entry.assemble(io)
@blocks.each do |block|
block.assemble io
end
end end
end end
end end

View File

@ -62,6 +62,10 @@ module Vm
@first = first @first = first
super(options) super(options)
end 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? def is_push?
opcode == :push opcode == :push
end end

View File

@ -27,16 +27,16 @@ module Vm
def get_function name def get_function name
name = name.to_sym name = name.to_sym
f = @functions.detect{ |f| f.name == name } 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 f
end end
# way of creating new functions that have not been parsed. # way of creating new functions that have not been parsed.
def get_or_create_function name def get_or_create_function name
fun = get_function name fun = get_function name
unless fun or name == :Object 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 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 end
fun fun
end end