text: #!/usr/bin/env python
  # coding: utf-8
  
  import copy, asohelper
  
  #This module contains all the optimization modules.
  #Each module takes a function (and some the config file), copies the function
  #and modifies the properties.
  #Modules can easily be combined. E.g. BranchModule(MemoryModule(function))
  
  #==================================================================================
  #			Instruction Level Parallellism
  #==================================================================================
  def ILPModule(function, config):
    frequency                     = float(config["perf_model/core/frequency"])
    optimizedfunction             = copy.deepcopy(function)
    current_time                  = function["nonidle_elapsed_time"]
    current_instruction_count     = function["instruction_count"]
    current_cpiBase               = function["cpiBase"] #cpiBase is in femtoseconds
    #calculate CPI of 0.25 as (instructions/4)*clock_period 
    clock_period                  = (1/frequency)*1e6 #femtoseconds
    new_cpiBase                   = (current_instruction_count / 4)*clock_period
    time_gain                     = current_cpiBase - new_cpiBase
    if time_gain > 0:
      #changing properties of function
      optimizedfunction["cpiBase"]                = new_cpiBase
      optimizedfunction["time_won_back"]          += time_gain
      optimizedfunction["nonidle_elapsed_time"]   -= time_gain
      optimizedfunction["optimizations"].append(dict(optimization="ILP",timegain=time_gain))
      optimizedfunction["originalfunction"]       = function
    return optimizedfunction
  
  
  #==================================================================================
  #			Branching
  #==================================================================================
  def BranchModule(function):
    optimizedfunction             = copy.deepcopy(function)
    cpiBranchPredictor            = function["cpiBranchPredictor"]
    #changing properties of function
    #we can now remove the branchPredictor cpicomponent
    optimizedfunction["cpiBranchPredictor"]       = 0
    optimizedfunction["time_won_back"]            += cpiBranchPredictor
    optimizedfunction["nonidle_elapsed_time"]     -= cpiBranchPredictor
    optimizedfunction["optimizations"].append(dict(optimization="Branch", timegain=cpiBranchPredictor))
    optimizedfunction["originalfunction"]         = function
    return optimizedfunction
  
  #==================================================================================
  #			Removing Non-floating point instructions
  #==================================================================================
  def NonFPModule(function):
    instruction_count     = function["instruction_count"]
    nonidle_elapsed_time  = function["nonidle_elapsed_time"]
    fp_addsub             = asohelper.get_fp_addsub(function)
    fp_muldiv             = asohelper.get_fp_muldiv(function)
    cpiBase               = function["cpiBase"]
    cpiMem                = function["cpiMem"]
    cpiBranchPredictor    = function["cpiBranchPredictor"]
    cpiOther              = nonidle_elapsed_time - cpiBase - cpiMem - cpiBranchPredictor
    fp_instructions       = fp_addsub + fp_muldiv
    non_fp_instructions   = instruction_count - fp_instructions
    non_fp_fraction       = non_fp_instructions / instruction_count #the fraction of non-fp instructions
    if instruction_count > 0:
      non_fp_time         = non_fp_fraction*(cpiBase + cpiMem + cpiBranchPredictor + cpiOther)
    else:
      non_fp_time         = 0
    optimizedfunction     = copy.deepcopy(function)
    if non_fp_time > 0:
      #changing properties of function
      optimizedfunction["instruction_count"]      -=non_fp_instructions
      optimizedfunction["time_won_back"]          +=non_fp_time
      optimizedfunction["nonidle_elapsed_time"]   -=non_fp_time
      optimizedfunction["cpiBase"]                -=non_fp_fraction*cpiBase
      optimizedfunction["cpiMem"]                 -=non_fp_fraction*cpiMem
      optimizedfunction["cpiBranchPredictor"]     -=non_fp_fraction*cpiBranchPredictor
      optimizedfunction["optimizations"].append(dict(optimization="NonFP", timegain=non_fp_time))
      optimizedfunction["originalfunction"]       = function
  
    return optimizedfunction
  
  
  #==================================================================================
  #			Vectorization module
  #==================================================================================
  def VectorizationModule(function):
    optimizedfunction             = copy.deepcopy(function)
    instruction_count             = function["instruction_count"]
    nonidle_elapsed_time          = function["nonidle_elapsed_time"]
    fp_addsub                     = asohelper.get_fp_addsub(function)
    fp_muldiv                     = asohelper.get_fp_muldiv(function)
    cpiBase                       = function["cpiBase"]
    cpiMem                        = function["cpiMem"]
    cpiBranchPredictor            = function["cpiBranchPredictor"]
    cpiOther                      = nonidle_elapsed_time - cpiBase - cpiMem - cpiBranchPredictor
    fp_instructions               = fp_addsub + fp_muldiv
    non_fp_instructions           = instruction_count - fp_instructions
  
    #add instructions
    addpd = function["addpd"]     #packed double precision
    addsd = function["addsd"]     #double precision
    addss = function["addss"]     #single precision
    addps = function["addps"]     #packed single precision
  
    #sub instructions
    subpd = function["subpd"]     #packed double precision
    subsd = function["subsd"]     #double precision
    subss = function["subss"]     #single precision
    subps = function["subps"]     #packed single precision
  
    #mul instructions
    mulpd = function["mulpd"]     #packed double precision
    mulsd = function["mulsd"]     #double precision
    mulss = function["mulss"]     #single precision
    mulps = function["mulps"]     #packed single precision
  
    #div instructions
    divpd = function["divpd"]     #packed double precision
    divsd = function["divsd"]     #double precision
    divss = function["divss"]     #single precision
    divps = function["divps"]     #packed single precision
  
    #check if number of instructions is correct
    assert instruction_count == non_fp_instructions+addpd+addsd+addss+subpd+subsd+subss+subps+mulpd+mulsd+mulss+mulps+divpd+divsd+divss+divps
  
    if instruction_count > 0:
      #if we only have addsub or muldiv operations
      if fp_addsub == 0 or fp_muldiv == 0:
        n_ports = 1 #only one issue port can be used per fp-instruction
      else:
        n_ports = 2 #the two issue ports can be used simultaneously
  
      newtime = 0
      #already vectorized and double precision
      #cpibase part used by packed double precision:
      cpiBasePD   = ((addpd + subpd + mulpd + divpd) / instruction_count) *cpiBase
      newtime     += cpiBasePD/n_ports
      #vectorization: SD -> PD
      #cpibase part used by non-vectorized double precision
      cpiBaseSD   = ((addsd + subsd + mulsd + divsd) / instruction_count) *cpiBase
      newtime     += cpiBaseSD / (2*n_ports)
      #already vectorized and single precision
      #cpibase part used by packed single precision:
      cpiBasePS   = ((addps + subps + mulps + divps) / instruction_count) *cpiBase
      newtime     += cpiBasePS / n_ports
      #vectorization: SS -> PS
      #cpibase part used by non-vectorized single precision
      cpiBaseSS   = ((addss + subss + mulss + divss) / instruction_count) *cpiBase
      newtime     += cpiBaseSS / (4*n_ports)
      #these non_fp instructions can't be vectorized
      #cpibase part used by non-floating point instructions:
      cpiBaseNonFP = (non_fp_instructions / instruction_count)            *cpiBase
      newtime     += cpiBaseNonFP
      new_cpiBase = newtime
      #add the branch, mem and other component
      newtime     += cpiMem + cpiBranchPredictor + cpiOther
  
      #the sum of the cpiBase components should be the total cpiBase component
      total = cpiBasePD + cpiBaseSD + cpiBasePS + cpiBaseSS + cpiBaseNonFP
      assert round(cpiBase) == round(total), "%s != %s" % (round(cpiBase), round(total),)
  
      time_gain = nonidle_elapsed_time - newtime
      if time_gain > 0:
        #changing properties of function
        optimizedfunction["time_won_back"]        += time_gain
        optimizedfunction["nonidle_elapsed_time"] -= time_gain
        optimizedfunction["cpiBase"]              = new_cpiBase
        optimizedfunction["optimizations"].append(dict(optimization="Vectorization", timegain=time_gain))
        optimizedfunction["originalfunction"]     = function
  
      assert time_gain >= 0, "we should not gain negative time"
  
      if fp_instructions == 0:
        #if we don't have any floating point instruction, the time_gain should be zero
        assert time_gain == 0, "%s != %s" % (time_gain, 0,)
  
    return optimizedfunction
  
  #==================================================================================
  #			Memory Plugin
  #==================================================================================
  def MemoryModule(function):
    optimizedfunction                             = copy.deepcopy(function)
    cpiMem                                        = function["cpiMem"]
    #changing properties of function
    #we remove the memory component
    optimizedfunction["cpiMem"]                   = 0
    optimizedfunction["time_won_back"]            += cpiMem
    optimizedfunction["nonidle_elapsed_time"]     -= cpiMem
    optimizedfunction["bits_used"]                = 0
    optimizedfunction["bits_total"]               = 0
    optimizedfunction["optimizations"].append(dict(optimization="Memory", timegain=cpiMem))
    optimizedfunction["originalfunction"]         = function
    return optimizedfunction
  
  #==================================================================================
  #			Thread Level Parallellism
  #==================================================================================
  #assumptions:
          #parallelization to the number of cores in the simulated system
          #5% of the time cannot be parallelized
  def TLPModule(function, config):
    optimizedfunction             = copy.deepcopy(function)
    n_cores                       = float(config['general/total_cores'])
    current_time                  = function["nonidle_elapsed_time"]
    #Amdahl's Law
    new_time                      = ((0.95 * current_time) / n_cores) + 0.05 * current_time
    time_gain                     = current_time - new_time
    if time_gain > 0:
      #change properties of function
      optimizedfunction["time_won_back"]          += time_gain
      optimizedfunction["nonidle_elapsed_time"]   -= time_gain
      optimizedfunction["cpiBase"]                -= time_gain
      optimizedfunction["optimizations"].append(dict(optimization="TLP", timegain=time_gain))
      optimizedfunction["originalfunction"]       = function
    return optimizedfunction
  
  #==================================================================================
  #			Memory Level Parallellism
  #==================================================================================
  #FUTURE WORK
  def MLPModule(function, optimizationlist):
    return
  
  #==================================================================================
  #			Prefetcher module
  #==================================================================================
  #FUTURE WORK
  def PrefetcherModule():
    return
  
  #==================================================================================
  #			Percent Of Data Utilized Plugin
  #==================================================================================
  def PercentOfDataUtilizedModule(function):
    optimizedfunction             = copy.deepcopy(function)
    bits_used                     = function["bits_used"]
    bits_total                    = function["bits_total"]
    cpiMem                        = function["cpiMem"]
    fractionofdatautilized        = 1
    if bits_total > 0:
      fractionofdatautilized      = bits_used / bits_total
    new_cpiMem    = fractionofdatautilized*cpiMem
    time_gain     = cpiMem - new_cpiMem
    if time_gain > 0:
      #change properties of function
      optimizedfunction["time_won_back"]          += time_gain
      optimizedfunction["nonidle_elapsed_time"]   -= time_gain
      optimizedfunction["bits_total"]             = bits_used
      optimizedfunction["optimizations"].append(dict(optimization="PercentOfDataUtilized", timegain=time_gain, detail={'bits_used':bits_used,'bits_total':bits_total}))
      optimizedfunction["originalfunction"]       = function
    return optimizedfunction
  
  
  
  