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



216
217
218
219
220
221
222
223
224
225
226
# File 'lib/adarwin/engine.rb', line 216

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.



274
275
276
277
278
279
280
281
282
283
# File 'lib/adarwin/engine.rb', line 274

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.



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
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/adarwin/engine.rb', line 231

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.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/adarwin/engine.rb', line 171

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]))
					@id += 1
				end
			end
		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
# 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)
	
	# Process every SCoP, one by one
	@id = 0
	@result[:species_code] = preprocessor.target_code
	preprocessor.scop_code.each do |scop_code|
		process_scop(scop_code)
	end
end

#process_scop(scop_code) ⇒ Object



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
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/adarwin/engine.rb', line 94

def process_scop(scop_code)
	# Create an AST of the SCoP (using CAST) and save a backup
	scop_ast = C::Block.parse('{'+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 = []
	populate_nests(scop_ast)
	
	# return if no loop nests are found in the code
	return unless @nests.length > 0
	
	# 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].gsub!(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.



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

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.



159
160
161
162
163
164
165
166
# File 'lib/adarwin/engine.rb', line 159

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