new register allocation generates good looking push/pop
This commit is contained in:
parent
b66c4157d5
commit
d7a60f2803
@ -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
|
||||||
|
@ -11,6 +11,7 @@ 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}"
|
||||||
|
@ -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}"
|
||||||
|
@ -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
|
@ -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
|
||||||
|
used =[]
|
||||||
|
assigned = []
|
||||||
|
l_block.reachable.each do |b|
|
||||||
|
b.uses.each {|u|
|
||||||
|
(used << u) unless assigned.include?(u)
|
||||||
|
}
|
||||||
|
assigned += b.assigns
|
||||||
end
|
end
|
||||||
|
used.uniq
|
||||||
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
|
|
||||||
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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user