class Adarwin::Engine

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)

Public Class Methods

new() click to toggle source

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

Public Instance Methods

get_children(parent) click to toggle source

Method to obtain the children of a nest

    # File lib/adarwin/engine.rb
216 def get_children(parent)
217         children = []
218         @nests.map do |nest|
219                 if parent.depth+1 == nest.depth
220                         if parent.level == nest.level.reverse.drop(1).reverse
221                                 children << nest
222                         end
223                 end
224         end
225         return children
226 end
insert_copies(scop_ast) click to toggle source

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

    # File lib/adarwin/engine.rb
274 def insert_copies(scop_ast)
275         @nests.each do |nest|
276                 if nest.has_copyins?
277                         nest.code.insert_prev(C::StringLiteral.parse(nest.print_copyins))
278                 end
279                 if nest.has_copyouts?
280                         nest.code.insert_next(C::StringLiteral.parse(nest.print_copyouts))
281                 end
282         end
283 end
insert_species(scop_ast) click to toggle source

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.

    # File lib/adarwin/engine.rb
231 def insert_species(scop_ast)
232         
233         # Find out where the synchronisation statements are needed
234         sync_needed = []
235         @nests.each do |nest|
236                 sync_needed << nest.copyins.map{ |c| c.get_sync_id }
237                 sync_needed << nest.copyouts.map{ |c| c.get_sync_id }
238         end
239         sync_needed = sync_needed.flatten.uniq
240         
241         # Insert the annotations into the code
242         sync = 0
243         @nests.each do |nest|
244                 sync = 2*nest.id
245                 
246                 # Insert the pre-kernel synchronisation barrier
247                 if sync_needed.include?(sync) && !@options[:no_memory_annotations]
248                         nest.code.insert_prev(C::StringLiteral.parse(PRAGMA_DELIMITER_START+PRAGMA_SPECIES+' sync '+(sync).to_s+PRAGMA_DELIMITER_END))
249                 end
250                 
251                 # Insert the pre-kernel species (start of species)
252                 if nest.has_species?
253                         to_print = (@options[:print_arc]) ? nest.print_arc_start : nest.print_species_start
254                         nest.code.insert_prev(C::StringLiteral.parse(to_print))
255                 end
256                 
257                 # Insert the post-kernel synchronisation barrier
258                 if sync_needed.include?(sync+1) && !@options[:no_memory_annotations]
259                         node = (nest.code.next && nest.code.next.string? && nest.code.next.val =~ /pragma species copyout/) ? nest.code.next : nest.code
260                         node.insert_next(C::StringLiteral.parse(PRAGMA_DELIMITER_START+PRAGMA_SPECIES+' sync '+(sync+1).to_s+PRAGMA_DELIMITER_END))
261                 end
262                 
263                 # Insert the post-kernel species (end of species)
264                 if nest.has_species?
265                         to_print = (@options[:print_arc]) ? nest.print_arc_end : nest.print_species_end
266                         location = nest.code
267                         location.insert_next(C::StringLiteral.parse(to_print))
268                 end
269         end
270 end
populate_nests(ast,level=[]) click to toggle source

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.

    # File lib/adarwin/engine.rb
171 def populate_nests(ast,level=[])
172         
173         # Only proceed if it is a loop
174         if ast.block?
175                 
176                 # Create the new loop nests for the current depth level
177                 ast.stmts.each_with_index do |nest,index|
178                         new_level = level.clone.push(index)
179                         
180                         # Push the loop nest, but only if it is not disabled by options
181                         if @options[:only_alg_number].to_i == 99 || @options[:only_alg_number].to_i == (@id+1)
182                                 
183                                 # Only continue if the nest is an actual loop nest
184                                 if nest.for_statement?
185                                         @nests.push(Nest.new(new_level,nest,@id,@basename,!@options[:silent]))
186                                         @id += 1
187                                 end
188                         end
189                 end
190                 
191                 # Proceed to the next depth level.
192                 # TODO: Make it an option to only investigate the outer most level(s).
193                 ast.stmts.each_with_index do |nest,index|
194                         new_level = level.clone.push(index)
195                         if nest.stmt # && new_level == 0
196                                 populate_nests(nest.stmt,new_level)
197                         end
198                 end
199         end
200 end
process() click to toggle source

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.

   # File lib/adarwin/engine.rb
70 def process
71         
72         # Run the preprocessor
73         preprocessor = Adarwin::Preprocessor.new(@source)
74         preprocessor.process
75         @result[:header_code] = preprocessor.header_code
76         
77         # Set-up the CAST gem to include certain types
78         # FIXME: What about other (user-defined?) types?
79         parser = C::Parser.new
80         parser.type_names << 'FILE'
81         parser.type_names << 'size_t'
82         
83         # Parse the original source code into AST form (using CAST)
84         original_ast = parser.parse(preprocessor.parsed_code)
85         
86         # Process every SCoP, one by one
87         @id = 0
88         @result[:species_code] = preprocessor.target_code
89         preprocessor.scop_code.each do |scop_code|
90                 process_scop(scop_code)
91         end
92 end
process_scop(scop_code) click to toggle source
    # File lib/adarwin/engine.rb
 94 def process_scop(scop_code)
 95         # Create an AST of the SCoP (using CAST) and save a backup
 96         scop_ast = C::Block.parse('{'+scop_code+'}')
 97         original_scop_ast = scop_ast.clone
 98         
 99         # Process the scop to identify the loop nests of interest and to find the
100         # corresponding species. This is the method performing most of the work.
101         @nests = []
102         populate_nests(scop_ast)
103         
104         # return if no loop nests are found in the code
105         return unless @nests.length > 0
106         
107         # Remove inner-loop (nested) species. This removes all species that are
108         # found within another species. For completeness, this might be desired in
109         # some cases.
110         # TODO: Make this an option
111         @nests.each do |nest|
112                 if nest.has_species?
113                         remove_inner_species(get_children(nest))
114                 end
115         end
116         @nests.delete_if{ |n| n.removed }
117         
118         # Iterate over the nests/statements to optimize the copies. Currently,
119         # this will only look at loop nests with a depth of 1. Re-call the memory
120         # copy optimisations method every time a change is made.
121         # TODO: Investigate what the depth should be.
122         basenests = @nests.select{ |n| n.depth == 1 }
123         recursive_copy_optimisations(basenests,@options)
124         
125         # Kernel fusion is enabled (1,2,3,4) or disabled (0)
126         if @options[:fusion] > 0
127                 # Test if fusion is legal and perform the actual transformation
128                 kernel_fusion(@nests, @options[:fusion])
129         end
130         
131         # Delete the to-be-removed code (because of fusion)
132         @nests.each do |nest|
133                 if nest.removed
134                         scop_ast.remove_once(nest.code)
135                 end
136         end
137         @nests.delete_if{ |n| n.removed }
138         
139         # Insert the species and memory copy annotations into the original code.
140         # Don't do this if the user specified that he is not interested in the
141         # memory copy annotations.
142         insert_copies(scop_ast) unless @options[:no_memory_annotations]
143         insert_species(scop_ast)
144         
145         # Create the modified SCoP and remove the quotes from the pragma's
146         # FIXME: This is a hack for now, this has conflicts with strings in code
147         modified_scop = INDENT+SCOP_START+NL+scop_ast.to_s+NL+INDENT+SCOP_END+NL
148         modified_scop = modified_scop.gsub(PRAGMA_DELIMITER_START,'')
149         modified_scop = modified_scop.gsub(PRAGMA_DELIMITER_END,'')
150         
151         # Print the result SCoP
152         puts modified_scop if !@options[:silent]
153         
154         # Store the result
155         @result[:species_code].gsub!(scop_code,modified_scop)
156 end
remove_inner_species(nests) click to toggle source

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

    # File lib/adarwin/engine.rb
204 def remove_inner_species(nests)
205         nests.each do |nest|
206                 nest.copyins = []
207                 nest.copyouts = []
208                 nest.species = ''
209                 nest.removed = true
210                 children = get_children(nest)
211                 remove_inner_species(children) if children
212         end
213 end
write_output() click to toggle source

This method writes the output code to a file.

    # File lib/adarwin/engine.rb
159 def write_output
160         
161         # Populate the species file
162         # TODO: The filename is fixed, make this an optional argument
163         File.open(File.join(@options[:application].rpartition('.').first+'_species'+'.c'),'w') do |target|
164                 target.puts @result[:species_code]
165         end
166 end