inrtoduce block passes. move the allocation there and implement those simple optimisations

This commit is contained in:
Torsten Ruger 2014-06-11 11:41:50 +03:00
parent 7cca50cd3a
commit 4e6319b753
5 changed files with 104 additions and 19 deletions

View File

@ -18,7 +18,7 @@ module Elf
@object.add_section @text
program.link_at( 0 , program.context )
program.run_passes
binary = program.assemble(StringIO.new )
blocks = []

View File

@ -39,9 +39,22 @@ module Vm
#main gets executed between entry and exit
@exit = RegisterMachine.instance.main_exit @context
boot_classes
@passes = [ MoveMoveReduction.new, LogicMoveReduction.new, SaveLocals.new ]
end
attr_reader :context , :main , :classes , :entry , :exit
def run_passes
@passes.each do |pass|
all = main.blocks
@classes.each_value do |c|
c.functions.each {|f| all += f.blocks }
end
all.each do |block|
pass.run(block)
end
end
end
def boot_classes
# very fiddly chicken 'n egg problem. Functions need to be in the right order, and in fact we have to define some
# dummies, just for the other to compile

View File

@ -1,4 +1,5 @@
require_relative "block"
require_relative "passes"
module Vm
@ -54,7 +55,6 @@ module Vm
@insert_at = @body
@entry = RegisterMachine.instance.function_entry( Vm::Block.new("entry" , self , @body) ,name )
@locals = []
@linked = false # incase link is called twice, we only calculate locals once
end
attr_reader :args , :entry , :exit , :body , :name , :return_type , :receiver
@ -162,22 +162,6 @@ module Vm
def link_at address , context
super #just sets the position
@entry.link_at address , context
return if @linked
@linked = true
blocks.each do |b|
if push = b.call_block?
locals = locals_at b
if(locals.empty?)
puts "Empty #{b.name}"
else
puts "PUSH #{push}"
push.set_registers(locals)
pop = b.next.codes.first
puts "POP #{pop}"
pop.set_registers(locals)
end
end
end
end
# position of the function is the position of the entry block

View File

@ -110,6 +110,7 @@ module Vm
@right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right
super(options)
end
attr_accessor :result , :left , :right
def uses
ret = []
ret << @left.used_register if @left and not @left.is_a? Constant
@ -142,6 +143,7 @@ module Vm
raise "move must have from set #{inspect}" unless from
super(options)
end
attr_accessor :to , :from
def uses
@from.is_a?(Constant) ? [] : [@from.used_register]
end

86
lib/vm/passes.rb Normal file
View File

@ -0,0 +1,86 @@
module Vm
# Passes, or BlockPasses, could have been procs that just get each block passed.
# Instead they are proper objects in case they want to save state.
# The idea is
# - reduce noise in the main code by having this code seperately (aspect/concern style)
# - abstract the iteration
# - allow not yet written code to hook in
class RemoveStubs
def run block
block.codes.dup.each_with_index do |kode , index|
next unless kode.is_a? StackInstruction
if kode.regiters.empty?
block.codes.delete(kode)
puts "deleted stack instruction in #{b.name}"
end
end
end
end
# Operators eg a + b , must assign their result somewhere and as such create temporary variables.
# but if code is c = a + b , the generated instructions would be more like tmp = a + b ; c = tmp
# SO if there is an move instruction just after a logic instruction where the result of the logic
# instruction is moved straight away, we can undo that mess and remove one instruction.
class LogicMoveReduction
def run block
org = block.codes.dup
org.each_with_index do |kode , index|
n = org[index+1]
next if n.nil?
next unless kode.is_a? LogicInstruction
next unless n.is_a? MoveInstruction
if kode.result == n.from
kode.result = n.to
block.codes.delete(n)
end
end
end
end
# Sometimes there are double moves ie mov a, b and mov b , c . We reduce that to move a , c
# (but don't check if that improves register allocation. Yet ?)
class MoveMoveReduction
def run block
org = block.codes.dup
org.each_with_index do |kode , index|
n = org[index+1]
next if n.nil?
next unless kode.is_a? MoveInstruction
next unless n.is_a? MoveInstruction
if kode.to == n.from
kode.to = n.to
block.codes.delete(n)
end
end
end
end
# We insert push/pops as dummies to fill them later in CallSaving
# as we can not know ahead of time which locals wil be live in the code to come
# and also we don't want to "guess" later where the push/pops should be
# Here we check which registers need saving and add them
# Or sometimes just remove the push/pops, when no locals needed saving
class SaveLocals
def run block
unless block.function
puts "No function for #{block.name}"
end
push = block.call_block?
return unless push
locals = block.function.locals_at block
pop = block.next.codes.first
if(locals.empty?)
puts "Empty #{block.name}"
block.codes.delete(push)
block.next.codes.delete(pop)
else
puts "PUSH #{push}"
push.set_registers(locals)
puts "POP #{pop}"
pop.set_registers(locals)
end
end
end
end