From 95adf806d452ce3f7a431b25f357752877c0dfa9 Mon Sep 17 00:00:00 2001
From: Christian Dietrich <christian.dietrich@informatik.uni-erlangen.de>
Date: Tue, 20 Sep 2016 10:39:28 +0200
Subject: [PATCH] new analysis: blocking-time

The blocking time analysis measures the worst case execution time of all
interrupt-locked regions. Currently, the blocking-time tool only
accounts for intruction timings without caching.
---
 lib/analysis/ipet.rb       | 113 +++++++++++++++++++++
 lib/analysis/wca.rb        | 130 ++++++++++++++++--------
 lib/core/program.rb        |   3 +
 lib/ext/lpsolve.rb         |   3 +-
 lib/tools/blocking-time.rb | 200 +++++++++++++++++++++++++++++++++++++
 5 files changed, 404 insertions(+), 45 deletions(-)
 create mode 100755 lib/tools/blocking-time.rb

diff --git a/lib/analysis/ipet.rb b/lib/analysis/ipet.rb
index fba14c3..e97ea3b 100644
--- a/lib/analysis/ipet.rb
+++ b/lib/analysis/ipet.rb
@@ -736,6 +736,119 @@ class IPETBuilder
       succs
     end
   end
+  # Build Fragment
+  def build_fragment(entries, exits, blocks, flow_facts, cost_function)
+    @cost_function = cost_function
+    entries = entries.dup
+    exits = exits.dup
+    incoming = {}
+    outgoing = {}
+    entry_frequency = {}
+    exit_frequency = {}
+
+
+    blocks.each {|mbb|
+      incoming[mbb] = []
+      outgoing[mbb] = []
+    }
+    entry_edges = []
+    exit_edges = []
+    blocks.each { |mbb|
+      mbb.successors.each {|mbb2|
+        if ! blocks.member?(mbb2)
+          next
+        end
+        edge = IPETEdge.new(mbb, mbb2, :machinecode)
+        @ilp.add_variable(edge)
+        outgoing[mbb].push(edge)
+        incoming[mbb2].push(edge)
+      }
+
+      freq, freq_idx = mbb, 0
+      @ilp.add_variable(freq)
+      entry_frequency[mbb] = freq
+      exit_frequency[mbb] = freq
+
+      mbb.callsites.each { |cs|
+        call_edges = []
+        return_edges = []
+        assert("Return Pad for call site must reside in same block") {
+          cs.next.block == cs.block
+        }
+        cs.callees.each { |mf|
+          mf = @pml.machine_functions.by_label(mf)
+          next if incoming[mf.blocks.first].nil?
+
+          e = IPETEdge.new(freq, mf.blocks.first, :machinecode)
+          @ilp.add_variable(e)
+          incoming[mf.blocks.first].push(e)
+          call_edges.push(e)
+          # Find all return edges of target function
+          rets = mf.blocks.select {|b| b.must_return? }
+          rets.each { |ret|
+            next if ! blocks.member?(ret)
+            e = IPETEdge.new(ret, cs, :machinecode)
+            @ilp.add_variable(e)
+            outgoing[ret].push(e)
+            return_edges.push(e)
+          }
+        }
+        next if call_edges.length == 0
+        # Call is Executed as often as the last instruction frequency
+        @ilp.add_variable(cs)
+        cost = cost_function.call(mbb.instructions[freq_idx], cs)
+        @ilp.add_cost(cs, cost)
+        @ilp.add_constraint([[cs, 1], [freq, -1]], "equal", 0, "callsite_#{cs}", :callsite)
+        rhs = [[cs, -1]]
+        lhs = call_edges.map { |e| [e, 1] }
+        @ilp.add_constraint(lhs + rhs, "less-equal", 0, "call_#{cs}", :callsite)
+
+        # Call is Executed as often as the last instruction frequency
+        @ilp.add_variable(cs.next)
+        rhs = [[cs.next, 1]]
+        lhs = return_edges.map { |e| [e, -1] }
+        if exits.member?(cs)
+          exit_edges += return_edges
+          exits.delete(cs)
+        end
+        @ilp.add_constraint(lhs + rhs, "less-equal", 0, "returnsite_#{cs}", :callsite)
+
+        # Cant return more often than activated
+        @ilp.add_constraint([[cs, -1], [cs.next, 1]], "less-equal", 0, "call_ret_#{cs}", :callsite)
+        freq, freq_idx = cs.next, cs.next.index
+        exit_frequency[mbb] = cs.next
+      }
+
+      if mbb.instructions.last
+        cost = cost_function.call(mbb.instructions[freq_idx], mbb.instructions.last)
+        @ilp.add_cost(freq, cost)
+      end
+    }
+    entry_edges += entries.map { |mi|
+      e = IPETEdge.new(:entry, mi.block, :machinecode)
+      @ilp.add_variable(e)
+      incoming[mi.block].push(e)
+      e
+    }
+    exit_edges += exits.map { |mi|
+      e = IPETEdge.new(mi.block, :exit, :machinecode)
+      @ilp.add_variable(e)
+      outgoing[mi.block].push(e)
+      e
+    }
+    @ilp.add_constraint(entry_edges.map{|e| [e, 1]}, "equal", 1, "entry_constraint", :structural)
+    @ilp.add_constraint(exit_edges.map{|e| [e, 1]}, "equal", 1, "exit_constraint", :structural)
+
+    blocks.each {|mbb|
+      lhs = incoming[mbb].map{|e| [e, 1]}
+      rhs = [[entry_frequency[mbb], -1]]
+      @ilp.add_constraint(lhs+rhs, "equal", 0, "block_entry_#{mbb}", :structural)
+      lhs = outgoing[mbb].map{|e| [e, 1]}
+      rhs = [[exit_frequency[mbb], -1]]
+      @ilp.add_constraint(lhs+rhs, "equal", 0, "block_exit_#{mbb}", :structural)
+    }
+
+  end
 
   # Build basic IPET structure.
   # yields basic blocks, so the caller can compute their cost
diff --git a/lib/analysis/wca.rb b/lib/analysis/wca.rb
index e1dfd58..9904abf 100644
--- a/lib/analysis/wca.rb
+++ b/lib/analysis/wca.rb
@@ -19,50 +19,10 @@ class WCA
     @pml, @options = pml, options
   end
 
-  def analyze(entry_label)
-
-    # Builder and Analysis Entry
-    ilp = GurobiILP.new(@options) if @options.use_gurobi
-    ilp = LpSolveILP.new(@options) unless ilp
-
-    gcfg = @pml.analysis_gcfg(@options)
-    machine_entry = gcfg.get_entry()['machinecode'].first
-
-    # PLAYING: VCFGs
-    #bcffs,mcffs = ['bitcode','machinecode'].map { |level|
-    #  @pml.flowfacts.filter(@pml,@options.flow_fact_selection,@options.flow_fact_srcs,level)
-    #}
-    #ctxm = ContextManager.new(@options.callstring_length,1,1,2)
-    #mc_model = ControlFlowModel.new(@pml.machine_functions, machine_entry, mcffs, ctxm, @pml.arch)
-    #mc_model.build_ipet(ilp) do |edge|
-      # pseudo cost (1 cycle per instruction)
-    #  if (edge.kind_of?(Block))
-    #    edge.instructions.length
-    #  else
-    #    edge.source.instructions.length
-    #  end
-    #end
-
-    #cfbc = ControlFlowModel.new(@pml.bitcode_functions, bitcode_entry, bcffs,
-    #                            ContextManager.new(@options.callstring_length), GenericArchitecture.new)
-
-    # BEGIN: remove me soon
-    # builder
-    builder = IPETBuilder.new(@pml, @options, ilp)
-
-    # flow facts
-    ff_levels = ["machinecode", "gcfg"]
-    flowfacts = @pml.flowfacts.filter(@pml,
-                                     @options.flow_fact_selection,
-                                     @options.flow_fact_srcs,
-                                     ff_levels,
-                                     true)
-
-    # Build IPET using costs from @pml.arch
-    builder.build_gcfg(gcfg, flowfacts) do |edge|
-      # get list of executed instructions
-      branch_index = nil
-      ilist =
+  def edge_cost(edge)
+    # get list of executed instructions
+    branch_index = nil
+    ilist =
         if (edge.kind_of?(Block))
           edge.instructions
         else
@@ -112,6 +72,88 @@ class WCA
       end
       debug(@options,:costs) { "WCET edge costs for #{edge}: #{path_wcet} block, #{edge_wcet} edge" }
       path_wcet + edge_wcet
+  end
+
+  def analyze_fragment(entries, exists, blocks, &cost)
+    # Builder and Analysis Entry
+    ilp = GurobiILP.new(@options) if @options.use_gurobi
+    ilp = LpSolveILP.new(@options) unless ilp
+
+    # flow facts
+    ff_levels = ["machinecode", "gcfg"]
+    flowfacts = @pml.flowfacts.filter(@pml,
+                                      @options.flow_fact_selection,
+                                      @options.flow_fact_srcs,
+                                      ff_levels,
+                                      true)
+
+    builder = IPETBuilder.new(@pml, @options, ilp)
+    builder.build_fragment(entries, exists, blocks, flowfacts, cost)
+
+    x = @pml.machine_functions.by_label("unexpected_interrupt")
+    ilp.add_constraint([[x.blocks.first, 1]], "equal", 0, "no_unexpected_interrupts", :archane)
+
+
+    # Solve ILP
+    begin
+      cycles, freqs = ilp.solve_max
+    rescue Exception => ex
+      warn("WCA: ILP failed: #{ex}") unless @options.disable_ipet_diagnosis
+      cycles,freqs = -1, {}
+    end
+
+    if @options.verbose
+      freqs.each {|edge, freq|
+        next if freq * ilp.get_cost(edge) == 0
+        p [edge, freq, ilp.get_cost(edge)]
+      }
+    end
+    cycles
+  end
+
+
+  def analyze(entry_label)
+
+    # Builder and Analysis Entry
+    ilp = GurobiILP.new(@options) if @options.use_gurobi
+    ilp = LpSolveILP.new(@options) unless ilp
+
+    gcfg = @pml.analysis_gcfg(@options)
+    machine_entry = gcfg.get_entry()['machinecode'].first
+
+    # PLAYING: VCFGs
+    #bcffs,mcffs = ['bitcode','machinecode'].map { |level|
+    #  @pml.flowfacts.filter(@pml,@options.flow_fact_selection,@options.flow_fact_srcs,level)
+    #}
+    #ctxm = ContextManager.new(@options.callstring_length,1,1,2)
+    #mc_model = ControlFlowModel.new(@pml.machine_functions, machine_entry, mcffs, ctxm, @pml.arch)
+    #mc_model.build_ipet(ilp) do |edge|
+      # pseudo cost (1 cycle per instruction)
+    #  if (edge.kind_of?(Block))
+    #    edge.instructions.length
+    #  else
+    #    edge.source.instructions.length
+    #  end
+    #end
+
+    #cfbc = ControlFlowModel.new(@pml.bitcode_functions, bitcode_entry, bcffs,
+    #                            ContextManager.new(@options.callstring_length), GenericArchitecture.new)
+
+    # BEGIN: remove me soon
+    # builder
+    builder = IPETBuilder.new(@pml, @options, ilp)
+
+    # flow facts
+    ff_levels = ["machinecode", "gcfg"]
+    flowfacts = @pml.flowfacts.filter(@pml,
+                                     @options.flow_fact_selection,
+                                     @options.flow_fact_srcs,
+                                     ff_levels,
+                                     true)
+
+    # Build IPET using costs from @pml.arch
+    builder.build_gcfg(gcfg, flowfacts) do |edge|
+      edge_cost(edge)
     end
 
     # run cache analyses
diff --git a/lib/core/program.rb b/lib/core/program.rb
index 6421bef..23ebebc 100644
--- a/lib/core/program.rb
+++ b/lib/core/program.rb
@@ -1403,6 +1403,9 @@ private
       build_index
     end
     def by_label_or_name(name)
+      if name =~ /^GCFG:(.*)/
+        name = $1
+      end
       by_name(name)
     end
   end
diff --git a/lib/ext/lpsolve.rb b/lib/ext/lpsolve.rb
index 74a3864..691dc32 100644
--- a/lib/ext/lpsolve.rb
+++ b/lib/ext/lpsolve.rb
@@ -141,7 +141,7 @@ class LpSolveILP < ILP
     debug(options, :ilp) { "#{lp_solve_error_msg(problem)} PROBLEM - starting diagnosis" }
     @do_diagnose = false
     variables.each do |v|
-      next if not v.kind_of?(GlobalProgramPoint)
+      next if v.kind_of?(GlobalProgramPoint)
       add_constraint([[v,1]],"less-equal",BIGM,"__debug_upper_bound_v#{index(v)}",:debug)
     end
     @eps = 1.0
@@ -149,6 +149,7 @@ class LpSolveILP < ILP
     unbounded = freq.map { |v,k|
       (k >= BIGM - 1.0) ? v : nil
     }.compact
+    freq.each{|v| p v }
     unbounded_functions, unbounded_loops = Set.new, Set.new
     unbounded.each { |v|
       next unless v.kind_of?(IPETEdge) && v.source.kind_of?(Block)
diff --git a/lib/tools/blocking-time.rb b/lib/tools/blocking-time.rb
new file mode 100755
index 0000000..2810b12
--- /dev/null
+++ b/lib/tools/blocking-time.rb
@@ -0,0 +1,200 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+#
+# platin tool set
+#
+# Interprets the results of the aiT analysis in PML
+#
+
+require 'platin'
+require 'analysis/wca'
+require 'tools/wca'
+
+include PML
+
+class BlockingTimeTool
+  DEFAULT_DISABLE_INTERRUPTS = "_ZN7Machine18disable_interruptsEv"
+  DEFAULT_ENABLE_INTERRUPTS = "_ZN7Machine17enable_interruptsEv"
+
+  def BlockingTimeTool.add_config_options(opts)
+    opts.on("--disable-interrupts FUNC", "Function that disables interrupts") { |v|
+      opts.options.disable_interrupts = v
+    }
+
+    opts.on("--enable-interrupts FUNC", "Function that enables interrupts") { |v|
+      opts.options.enable_interrupts = v
+    }
+    opts.on("--enable-interrupts FUNC", "Function that enables interrupts") { |v|
+      opts.options.enable_interrupts = v
+    }
+    opts.on("--gcfg CIRCUIT", "Use the following timing Circuit") { |v|
+      opts.options.use_gcfg = v
+    }
+    opts.add_check { |options|
+      opts.options.disable_interrupts = DEFAULT_DISABLE_INTERRUPTS unless opts.options.disable_interrupts
+      opts.options.enable_interrupts = DEFAULT_ENABLE_INTERRUPTS unless opts.options.enable_interrupts
+    }
+  end
+  def BlockingTimeTool.add_options(opts, mandatory = true)
+    BlockingTimeTool.add_config_options(opts)
+  end
+
+  def BlockingTimeTool.instrs_from_function(mf, callee)
+    ret = []
+    mf.blocks.each {|mbb|
+      mbb.instructions.each {|instr|
+        if instr.callees.member?(callee)
+          ret.push(instr)
+        end
+      }
+    }
+    ret
+  end
+
+  def BlockingTimeTool.instrs_from_mbb(mbb, callee)
+    ret = []
+    mbb.instructions.each {|instr|
+      p instr.callees, callee
+      if instr.callees.member?(callee)
+        ret.push(instr)
+      end
+    }
+    ret
+  end
+
+  def BlockingTimeTool.process(region, instr, visited, after_switch = false)
+    # No Endless Recursion
+    queue = [[instr,after_switch]]
+
+    any_after_switch = after_switch
+
+    while queue != []
+      instr, after_switch = queue.pop
+      next if visited.member?(instr)
+      visited.add(instr)
+
+      while instr do
+        region.blocks.add(instr.block)
+
+        if after_switch
+          region.after_switch.add(instr)
+        end
+
+        if region.enable.member?(instr)
+          last_instr = nil
+          break
+        end
+
+        if instr.block.mapsto =~ /^switch_context/
+          region.switch.add(instr)
+          after_switch = true
+          any_after_switch = true
+        end
+
+        # Go to called functions
+        instr.callees.map { |mf|
+          next if mf =~ /StartOS|timing_dump_trap/
+          mf = region.pml.machine_functions.by_label(mf)
+          x = process(region, mf.instructions.first, visited, after_switch)
+          after_switch ||= x
+          any_after_switch ||= x
+        }
+
+        last_instr = instr
+        instr = instr.next
+      end
+
+      if last_instr
+        last_instr.block.successors.each { |mbb|
+          while mbb.instructions.first.nil? do
+            region.blocks.add(mbb)
+            mbb = mbb.next
+          end
+          queue.push([mbb.instructions.first, after_switch])
+        }
+      end
+    end
+    return any_after_switch
+  end
+
+  def BlockingTimeTool.run(pml, options)
+    r = OpenStruct.new
+    r.pml = pml
+    r.blocks = Set.new
+    r.disable = Set.new
+    r.enable = Set.new
+    r.switch = Set.new
+    r.after_switch = Set.new
+
+    pml.machine_functions.each { |mf|
+      # We will ignore a few functions here.
+      next if mf.label =~ /StartOS|arch_startup|test_trace_assert|init_generic|os_main|OSEKOS.*Interrupts/
+      r.disable += instrs_from_function(mf, options.disable_interrupts)
+      r.enable += instrs_from_function(mf, options.enable_interrupts)
+      if mf.label =~ /^OSEKOS_TASK_FUNC/
+        r.switch.add(mf.blocks.first.instructions.first)
+      end
+      if mf.label =~ /irq_entry/
+        r.disable += [mf.blocks.first.instructions.first]
+        r.enable += [mf.blocks.last.instructions.first]
+      end
+    }
+    (r.disable | r.switch).each {|instr|
+      # Find end Instruction
+      process(r, instr, Set.new, r.switch.member?(instr))
+    }
+    r.enable.each {|instr|
+      r.blocks.add(instr.block)
+      if instr.callees.length > 0
+        r.blocks.merge(pml.machine_functions.by_label(instr.callees[0]).blocks)
+      end
+    }
+    r.delete_field("pml")
+    # r.after_switch.each { |b| p [b.object_id, b] }
+
+    begin
+      tmpdir = nil
+      tmpdir = options.outdir = Dir.mktmpdir() unless options.outdir
+      wca = WCA.new(pml, options)
+
+      disable_enable = wca.analyze_fragment(r.disable, r.enable, r.blocks) do |a, b|
+        b.index - a.index
+      end
+      puts "Disable->Enable: #{disable_enable} cycles"
+      disable_switch = wca.analyze_fragment(r.disable, r.switch, r.blocks) do |a, b|
+        b.index - a.index
+      end
+      puts "Disable->Switch: #{disable_switch} cycles"
+      switch_enable = wca.analyze_fragment(r.disable, r.enable, r.blocks) do |a, b|
+        if r.after_switch.member?(a)
+          b.index - a.index
+        else
+          0
+        end
+      end
+      puts "Switch->Enable: #{switch_enable} cycles"
+
+      @options = options
+      statistics("BLOCKING", {"disable enable" => disable_enable,
+                              "disable switch" => disable_switch,
+                              "switch enable" => switch_enable,
+                              "maximum"=> [disable_enable, disable_switch+switch_enable].max
+                             })
+    ensure
+      FileUtils.remove_entry tmpdir if tmpdir
+    end
+
+  end
+end
+
+if __FILE__ == $0
+SYNOPSIS=<<EOF if __FILE__ == $0
+Calculate the Interrupt Blocking Time
+EOF
+  options, args = PML::optparse([], "", SYNOPSIS) do |opts|
+    opts.needs_pml
+    BlockingTimeTool.add_options(opts)
+    WcaTool.add_options(opts)
+  end
+  BlockingTimeTool.run(PMLDoc.from_files(options.input), options)
+end
-- 
GitLab