From 4e6319b7532574f80adf0f307854edd0374c4815 Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Wed, 11 Jun 2014 11:41:50 +0300 Subject: [PATCH] inrtoduce block passes. move the allocation there and implement those simple optimisations --- lib/elf/object_writer.rb | 2 +- lib/vm/boot_space.rb | 15 ++++++- lib/vm/function.rb | 18 +-------- lib/vm/instruction.rb | 2 + lib/vm/passes.rb | 86 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 lib/vm/passes.rb diff --git a/lib/elf/object_writer.rb b/lib/elf/object_writer.rb index e2c9d7f8..43fa7fab 100644 --- a/lib/elf/object_writer.rb +++ b/lib/elf/object_writer.rb @@ -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 = [] diff --git a/lib/vm/boot_space.rb b/lib/vm/boot_space.rb index 25b074d4..188aa6a8 100644 --- a/lib/vm/boot_space.rb +++ b/lib/vm/boot_space.rb @@ -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 diff --git a/lib/vm/function.rb b/lib/vm/function.rb index 80ba8fb9..19cfa932 100644 --- a/lib/vm/function.rb +++ b/lib/vm/function.rb @@ -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 diff --git a/lib/vm/instruction.rb b/lib/vm/instruction.rb index 1cdf5fd1..1e191020 100644 --- a/lib/vm/instruction.rb +++ b/lib/vm/instruction.rb @@ -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 diff --git a/lib/vm/passes.rb b/lib/vm/passes.rb new file mode 100644 index 00000000..593bbebd --- /dev/null +++ b/lib/vm/passes.rb @@ -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