inrtoduce block passes. move the allocation there and implement those simple optimisations
This commit is contained in:
parent
7cca50cd3a
commit
4e6319b753
@ -18,7 +18,7 @@ module Elf
|
|||||||
@object.add_section @text
|
@object.add_section @text
|
||||||
|
|
||||||
program.link_at( 0 , program.context )
|
program.link_at( 0 , program.context )
|
||||||
|
program.run_passes
|
||||||
binary = program.assemble(StringIO.new )
|
binary = program.assemble(StringIO.new )
|
||||||
|
|
||||||
blocks = []
|
blocks = []
|
||||||
|
@ -39,9 +39,22 @@ module Vm
|
|||||||
#main gets executed between entry and exit
|
#main gets executed between entry and exit
|
||||||
@exit = RegisterMachine.instance.main_exit @context
|
@exit = RegisterMachine.instance.main_exit @context
|
||||||
boot_classes
|
boot_classes
|
||||||
|
@passes = [ MoveMoveReduction.new, LogicMoveReduction.new, SaveLocals.new ]
|
||||||
end
|
end
|
||||||
attr_reader :context , :main , :classes , :entry , :exit
|
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
|
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
|
# 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
|
# dummies, just for the other to compile
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
require_relative "block"
|
require_relative "block"
|
||||||
|
require_relative "passes"
|
||||||
|
|
||||||
module Vm
|
module Vm
|
||||||
|
|
||||||
@ -54,7 +55,6 @@ module Vm
|
|||||||
@insert_at = @body
|
@insert_at = @body
|
||||||
@entry = RegisterMachine.instance.function_entry( Vm::Block.new("entry" , self , @body) ,name )
|
@entry = RegisterMachine.instance.function_entry( Vm::Block.new("entry" , self , @body) ,name )
|
||||||
@locals = []
|
@locals = []
|
||||||
@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
|
||||||
@ -162,22 +162,6 @@ module Vm
|
|||||||
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
|
||||||
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
|
end
|
||||||
|
|
||||||
# position of the function is the position of the entry block
|
# position of the function is the position of the entry block
|
||||||
|
@ -110,6 +110,7 @@ module Vm
|
|||||||
@right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right
|
@right = right.is_a?(Fixnum) ? IntegerConstant.new(right) : right
|
||||||
super(options)
|
super(options)
|
||||||
end
|
end
|
||||||
|
attr_accessor :result , :left , :right
|
||||||
def uses
|
def uses
|
||||||
ret = []
|
ret = []
|
||||||
ret << @left.used_register if @left and not @left.is_a? Constant
|
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
|
raise "move must have from set #{inspect}" unless from
|
||||||
super(options)
|
super(options)
|
||||||
end
|
end
|
||||||
|
attr_accessor :to , :from
|
||||||
def uses
|
def uses
|
||||||
@from.is_a?(Constant) ? [] : [@from.used_register]
|
@from.is_a?(Constant) ? [] : [@from.used_register]
|
||||||
end
|
end
|
||||||
|
86
lib/vm/passes.rb
Normal file
86
lib/vm/passes.rb
Normal 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
|
Loading…
x
Reference in New Issue
Block a user