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