Class: Adarwin::Engine

Inherits:
Common
  • Object
show all
Defined in:
lib/adarwin/engine.rb

Overview

This is the main ‘engine’ for the A-darwin algorithmic species extraction tool. It contains methods to parse the command-line arguments, to run the pre-processor, to insert the annotations, and to pretty print the final output. TODO: Add a syntax check by a normal compiler first (e.g. gcc)

Instance Method Summary collapse

Constructor Details

#initializeEngine

Initializes the engine and processes the command line arguments. This method uses the ‘trollop’ gem to parse the arguments and to create a nicely formatted help menu. This method additionally initializes a result- hash and reads the contents of the source file from disk.

Command-line usage:

adarwin --application <input> [OPTIONS]

Options:

      --application, -a <s>:   Input application file
--no-memory-annotations, -m:   Disable the printing of memory annotations
  --mem-remove-spurious, -s:   Memcopy optimisation: remove spurious copies
  --mem-copyin-to-front, -f:   Memcopy optimisation: move copyins to front
  --mem-copyout-to-back, -b:   Memcopy optimisation: move copyouts to back
    --mem-to-outer-loop, -l:   Memcopy optimisation: move copies to outer loops
  --only-alg-number, -o <i>:   Only generate code for the x-th species (99 -> all) (default: 99)
              --version, -v:   Print version and exit
                 --help, -h:   Show this message


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/adarwin/engine.rb', line 30

def initialize
  @result = {:original_code            => [],
             :species_code             => []}
  
  # Parse the command line options using the 'trollop' gem.
  @options = Trollop::options do
    version 'A-darwin, part of Bones version '+File.read(ADARWIN_DIR+'/VERSION').strip+' (c) 2013 Cedric Nugteren, Eindhoven University of Technology'
    banner  NL+'A-darwin is an algorithmic species extraction tool. ' +
            'For more information, see the README.rdoc file or visit the Bones/A-darwin website at http://parse.ele.tue.nl/bones/.' + NL + NL +
            'Usage:' + NL +
            '    adarwin --application <input> [OPTIONS]' + NL +
            'using the following flags:'
    opt :application,           'Input application file',                                           :short => 'a', :type => String
    opt :no_memory_annotations, 'Disable the printing of memory annotations',                       :short => 'm', :default => false
    opt :mem_remove_spurious,   'Memcopy optimisation: remove spurious copies',                     :short => 'r', :default => false
    opt :mem_copyin_to_front,   'Memcopy optimisation: move copyins to front',                      :short => 'f', :default => false
    opt :mem_copyout_to_back,   'Memcopy optimisation: move copyouts to back',                      :short => 'b', :default => false
    opt :mem_to_outer_loop,     'Memcopy optimisation: move copies to outer loops',                 :short => 'l', :default => false
    opt :fusion,                'Type of kernel fusion to perform (0 -> disable)',                  :short => 'k', :type => Integer, :default => 0
    opt :print_arc,             'Print array reference characterisations (ARC) instead of species', :short => 'c', :default => false
    opt :silent,                'Become silent (no message printing)',                              :short => 's', :default => false
    opt :only_alg_number,       'Only generate code for the x-th species (99 -> all)',              :short => 'o', :type => Integer, :default => 99
  end
  Trollop::die 'no input file supplied (use: --application)'              if !@options[:application_given]
  Trollop::die 'input file "'+@options[:application]+'" does not exist'   if !File.exists?(@options[:application])
  @options[:name] = @options[:application].split('/').last.split('.').first
  @options[:no_memory_annotations] = true if @options[:print_arc]
  
  # Obtain the source code from file
  @source = File.open(@options[:application],'r'){|f| f.read}
  @basename = File.basename(@options[:application],'.c')
end

Instance Method Details

#get_children(parent) ⇒ Object

Method to obtain the children of a nest



205
206
207
208
209
210
211
212
213
214
215
# File 'lib/adarwin/engine.rb', line 205

def get_children(parent)
  children = []
  @nests.map do |nest|
    if parent.depth+1 == nest.depth
      if parent.level == nest.level.reverse.drop(1).reverse
        children << nest
      end
    end
  end
  return children
end

#insert_copies(scop_ast) ⇒ Object

Iterate over the loop nests and insert the memory copy annotations into the original AST.



263
264
265
266
267
268
269
270
271
272
# File 'lib/adarwin/engine.rb', line 263

def insert_copies(scop_ast)
  @nests.each do |nest|
    if nest.has_copyins?
      nest.code.insert_prev(C::StringLiteral.parse(nest.print_copyins))
    end
    if nest.has_copyouts?
      nest.code.insert_next(C::StringLiteral.parse(nest.print_copyouts))
    end
  end
end

#insert_species(scop_ast) ⇒ Object

This method iterates over the loop nests and inserts the species into the original AST. It also inserts the synchronisation barries when needed, and only if the user is interested in the memory copy annotations.



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/adarwin/engine.rb', line 220

def insert_species(scop_ast)
  
  # Find out where the synchronisation statements are needed
  sync_needed = []
  @nests.each do |nest|
    sync_needed << nest.copyins.map{ |c| c.get_sync_id }
    sync_needed << nest.copyouts.map{ |c| c.get_sync_id }
  end
  sync_needed = sync_needed.flatten.uniq
  
  # Insert the annotations into the code
  sync = 0
  @nests.each do |nest|
    sync = 2*nest.id
    
    # Insert the pre-kernel synchronisation barrier
    if sync_needed.include?(sync) && !@options[:no_memory_annotations]
      nest.code.insert_prev(C::StringLiteral.parse(PRAGMA_DELIMITER_START+PRAGMA_SPECIES+' sync '+(sync).to_s+PRAGMA_DELIMITER_END))
    end
    
    # Insert the pre-kernel species (start of species)
    if nest.has_species?
      to_print = (@options[:print_arc]) ? nest.print_arc_start : nest.print_species_start
      nest.code.insert_prev(C::StringLiteral.parse(to_print))
    end
    
    # Insert the post-kernel synchronisation barrier
    if sync_needed.include?(sync+1) && !@options[:no_memory_annotations]
      node = (nest.code.next && nest.code.next.string? && nest.code.next.val =~ /pragma species copyout/) ? nest.code.next : nest.code
      node.insert_next(C::StringLiteral.parse(PRAGMA_DELIMITER_START+PRAGMA_SPECIES+' sync '+(sync+1).to_s+PRAGMA_DELIMITER_END))
    end
    
    # Insert the post-kernel species (end of species)
    if nest.has_species?
      to_print = (@options[:print_arc]) ? nest.print_arc_end : nest.print_species_end
      location = nest.code
      location.insert_next(C::StringLiteral.parse(to_print))
    end
  end
end

#populate_nests(ast, level = []) ⇒ Object

This method populates the Nest datastructure (recursively). It is the main method to process the loop nests and fine the species information. It is called recursively.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/adarwin/engine.rb', line 160

def populate_nests(ast,level=[])
  
  # Only proceed if it is a loop
  if ast.block?
    
    # Create the new loop nests for the current depth level
    ast.stmts.each_with_index do |nest,index|
      new_level = level.clone.push(index)
      
      # Push the loop nest, but only if it is not disabled by options
      if @options[:only_alg_number].to_i == 99 || @options[:only_alg_number].to_i == (@id+1)
        
        # Only continue if the nest is an actual loop nest
        if nest.for_statement?
          @nests.push(Nest.new(new_level,nest,@id,@basename,!@options[:silent]))
        end
      end
      @id += 1
    end
    
    # Proceed to the next depth level.
    # TODO: Make it an option to only investigate the outer most level(s).
    ast.stmts.each_with_index do |nest,index|
      new_level = level.clone.push(index)
      if nest.stmt # && new_level == 0
        populate_nests(nest.stmt,new_level)
      end
    end
  end
end

#processObject

Method to process a file and to output target code. This method calls all the other methods, it is the main engine.

Tasks:

  • Run the preprocessor to obtain algorithm information.

  • Use the ‘CAST’ gem to parse the source into an AST.

  • Call the code generator to perform the real work and produce output.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/adarwin/engine.rb', line 70

def process
  
  # Run the preprocessor
  preprocessor = Adarwin::Preprocessor.new(@source)
  preprocessor.process
  @result[:header_code] = preprocessor.header_code
  
  # Set-up the CAST gem to include certain types
  # FIXME: What about other (user-defined?) types?
  parser = C::Parser.new
  parser.type_names << 'FILE'
  parser.type_names << 'size_t'
  
  # Parse the original source code into AST form (using CAST)
  original_ast = parser.parse(preprocessor.parsed_code)
  
  # Create an AST of the SCoP (using CAST) and save a backup
  scop_ast = C::Block.parse('{'+preprocessor.scop_code+'}')
  original_scop_ast = scop_ast.clone
  
  # Process the scop to identify the loop nests of interest and to find the
  # corresponding species. This is the method performing most of the work.
  @nests = []
  @id = 0
  populate_nests(scop_ast)
  
  # Remove inner-loop (nested) species. This removes all species that are
  # found within another species. For completeness, this might be desired in
  # some cases.
  # TODO: Make this an option
  @nests.each do |nest|
    if nest.has_species?
      remove_inner_species(get_children(nest))
    end
  end
  @nests.delete_if{ |n| n.removed }
  
  # Iterate over the nests/statements to optimize the copies. Currently, 
  # this will only look at loop nests with a depth of 1. Re-call the memory
  # copy optimisations method every time a change is made.
  # TODO: Investigate what the depth should be.
  basenests = @nests.select{ |n| n.depth == 1 }
  recursive_copy_optimisations(basenests,@options)
  
  # Kernel fusion is enabled (1,2,3,4) or disabled (0)
  if @options[:fusion] > 0
    # Test if fusion is legal and perform the actual transformation
    kernel_fusion(@nests, @options[:fusion])
  end
  
  # Delete the to-be-removed code (because of fusion)
  @nests.each do |nest|
    if nest.removed
      scop_ast.remove_once(nest.code)
    end
  end
  @nests.delete_if{ |n| n.removed }
  
  # Insert the species and memory copy annotations into the original code.
  # Don't do this if the user specified that he is not interested in the
  # memory copy annotations.
  insert_copies(scop_ast) unless @options[:no_memory_annotations]
  insert_species(scop_ast)
  
  # Create the modified SCoP and remove the quotes from the pragma's
  # FIXME: This is a hack for now, this has conflicts with strings in code
  modified_scop = INDENT+SCOP_START+NL+scop_ast.to_s+NL+INDENT+SCOP_END+NL
  modified_scop = modified_scop.gsub(PRAGMA_DELIMITER_START,'')
  modified_scop = modified_scop.gsub(PRAGMA_DELIMITER_END,'')
  
  # Print the result SCoP
  puts modified_scop if !@options[:silent]
  
  # Store the result
  @result[:species_code] = preprocessor.target_code.gsub(preprocessor.scop_code,modified_scop)
end

#remove_inner_species(nests) ⇒ Object

This method removes all species in the current loop nest (called recursively). It assumes these species should be removed.



193
194
195
196
197
198
199
200
201
202
# File 'lib/adarwin/engine.rb', line 193

def remove_inner_species(nests)
  nests.each do |nest|
    nest.copyins = []
    nest.copyouts = []
    nest.species = ''
    nest.removed = true
    children = get_children(nest)
    remove_inner_species(children) if children
  end
end

#write_outputObject

This method writes the output code to a file.



148
149
150
151
152
153
154
155
# File 'lib/adarwin/engine.rb', line 148

def write_output
  
  # Populate the species file
  # TODO: The filename is fixed, make this an optional argument
  File.open(File.join(@options[:application].split('.').first+'_species'+'.c'),'w') do |target|
    target.puts @result[:species_code]
  end
end