1b1994897Sopenharmony_ci#!/usr/bin/env ruby
2b1994897Sopenharmony_ci# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3b1994897Sopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License");
4b1994897Sopenharmony_ci# you may not use this file except in compliance with the License.
5b1994897Sopenharmony_ci# You may obtain a copy of the License at
6b1994897Sopenharmony_ci#
7b1994897Sopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0
8b1994897Sopenharmony_ci#
9b1994897Sopenharmony_ci# Unless required by applicable law or agreed to in writing, software
10b1994897Sopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS,
11b1994897Sopenharmony_ci# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12b1994897Sopenharmony_ci# See the License for the specific language governing permissions and
13b1994897Sopenharmony_ci# limitations under the License.
14b1994897Sopenharmony_ci
15b1994897Sopenharmony_cirequire 'optparse'
16b1994897Sopenharmony_cirequire 'ostruct'
17b1994897Sopenharmony_cirequire 'logger'
18b1994897Sopenharmony_cirequire 'fileutils'
19b1994897Sopenharmony_cirequire 'open3'
20b1994897Sopenharmony_ci
21b1994897Sopenharmony_cioptions = OpenStruct.new
22b1994897Sopenharmony_ciOptionParser.new do |opts|
23b1994897Sopenharmony_ci  opts.banner = 'Usage: checker.rb [options] TEST_FILE'
24b1994897Sopenharmony_ci
25b1994897Sopenharmony_ci  opts.on('--run-prefix=PREFIX', 'Prefix that will be inserted before panda run command') do |v|
26b1994897Sopenharmony_ci    options.run_prefix = v
27b1994897Sopenharmony_ci  end
28b1994897Sopenharmony_ci  opts.on('--source=FILE', 'Path to source file')
29b1994897Sopenharmony_ci  opts.on('--test-file=FILE', 'Path to test file') do |v|
30b1994897Sopenharmony_ci    options.test_file = v
31b1994897Sopenharmony_ci  end
32b1994897Sopenharmony_ci  opts.on('--panda=PANDA', 'Path to panda')
33b1994897Sopenharmony_ci  opts.on('--paoc=PAOC', 'Path to paoc') do |v|
34b1994897Sopenharmony_ci    options.paoc = v
35b1994897Sopenharmony_ci  end
36b1994897Sopenharmony_ci  opts.on('--panda-options=OPTIONS', 'Default options for panda run') do |v|
37b1994897Sopenharmony_ci    options.panda_options = v
38b1994897Sopenharmony_ci  end
39b1994897Sopenharmony_ci  opts.on('--paoc-options=OPTIONS', 'Default options for paoc run') do |v|
40b1994897Sopenharmony_ci    options.paoc_options = v
41b1994897Sopenharmony_ci  end
42b1994897Sopenharmony_ci  opts.on('--command-token=STRING', 'String that is recognized as command start') do |v|
43b1994897Sopenharmony_ci    options.command_token = v
44b1994897Sopenharmony_ci  end
45b1994897Sopenharmony_ci  opts.on('--release', 'Run in release mode. EVENT, INST and other will not be checked')
46b1994897Sopenharmony_ci  opts.on('-v', '--verbose', 'Verbose logging')
47b1994897Sopenharmony_ci  opts.on('--arch=ARCHITECTURE', 'Architecture of system where start panda')
48b1994897Sopenharmony_ci  opts.on("--keep-data", "Do not remove generated data from disk") { |v| options.keep_data = true }
49b1994897Sopenharmony_ciend.parse!(into: options)
50b1994897Sopenharmony_ci
51b1994897Sopenharmony_ci$LOG_LEVEL = options.verbose ? Logger::DEBUG : Logger::ERROR
52b1994897Sopenharmony_ci$curr_cmd = nil
53b1994897Sopenharmony_ci
54b1994897Sopenharmony_cidef log
55b1994897Sopenharmony_ci  @log ||= Logger.new($stdout, level: $LOG_LEVEL)
56b1994897Sopenharmony_ciend
57b1994897Sopenharmony_ci
58b1994897Sopenharmony_cidef raise_error(msg)
59b1994897Sopenharmony_ci  log.error "Test failed: #{@name}"
60b1994897Sopenharmony_ci  log.error msg
61b1994897Sopenharmony_ci  log.error "Command to reproduce: #{$curr_cmd}"
62b1994897Sopenharmony_ci  raise msg
63b1994897Sopenharmony_ciend
64b1994897Sopenharmony_ci
65b1994897Sopenharmony_cidef match_str(match)
66b1994897Sopenharmony_ci  match.is_a?(Regexp) ? "/#{match.source}/" : match
67b1994897Sopenharmony_ciend
68b1994897Sopenharmony_ci
69b1994897Sopenharmony_cidef contains?(str, match)
70b1994897Sopenharmony_ci  return str =~ match if match.is_a? Regexp
71b1994897Sopenharmony_ci
72b1994897Sopenharmony_ci  raise_error "Wrong type for search: #{match.class}" unless match.is_a? String
73b1994897Sopenharmony_ci  str.include? match
74b1994897Sopenharmony_ciend
75b1994897Sopenharmony_ci
76b1994897Sopenharmony_ci# Provides methods to search lines in a given array
77b1994897Sopenharmony_ciclass SearchScope
78b1994897Sopenharmony_ci
79b1994897Sopenharmony_ci  attr_reader :lines
80b1994897Sopenharmony_ci
81b1994897Sopenharmony_ci  def initialize(lines, name)
82b1994897Sopenharmony_ci    @lines = lines
83b1994897Sopenharmony_ci    @name = name
84b1994897Sopenharmony_ci    @current_index = 0
85b1994897Sopenharmony_ci  end
86b1994897Sopenharmony_ci
87b1994897Sopenharmony_ci  def self.from_file(fname, name)
88b1994897Sopenharmony_ci    SearchScope.new(File.readlines(fname), name)
89b1994897Sopenharmony_ci  end
90b1994897Sopenharmony_ci
91b1994897Sopenharmony_ci  def find(match)
92b1994897Sopenharmony_ci    return if match.nil?
93b1994897Sopenharmony_ci
94b1994897Sopenharmony_ci    @current_index = @lines.index { |line| contains?(line, match) }
95b1994897Sopenharmony_ci    raise_error "#{@name} not found: #{match_str(match)}" if @current_index.nil?
96b1994897Sopenharmony_ci    @current_index += 1
97b1994897Sopenharmony_ci  end
98b1994897Sopenharmony_ci
99b1994897Sopenharmony_ci  def find_next(match)
100b1994897Sopenharmony_ci    return if match.nil?
101b1994897Sopenharmony_ci
102b1994897Sopenharmony_ci    index = @lines.drop(@current_index).index { |line| contains?(line, match) }
103b1994897Sopenharmony_ci    raise_error "#{@name} not found: #{match_str(match)}" if index.nil?
104b1994897Sopenharmony_ci    @current_index += index + 1
105b1994897Sopenharmony_ci  end
106b1994897Sopenharmony_ci
107b1994897Sopenharmony_ci  def find_not(match)
108b1994897Sopenharmony_ci    return if match.nil?
109b1994897Sopenharmony_ci
110b1994897Sopenharmony_ci    @lines.each do |line|
111b1994897Sopenharmony_ci      raise_error "#{@name} should not occur: #{match_str(match)}" if contains?(line, match)
112b1994897Sopenharmony_ci    end
113b1994897Sopenharmony_ci  end
114b1994897Sopenharmony_ci
115b1994897Sopenharmony_ci  def find_next_not(match)
116b1994897Sopenharmony_ci    return if match.nil?
117b1994897Sopenharmony_ci
118b1994897Sopenharmony_ci    @lines.drop(@current_index).each do |line|
119b1994897Sopenharmony_ci      raise_error "#{@name} should not occur: #{match_str(match)}" if contains?(line, match)
120b1994897Sopenharmony_ci    end
121b1994897Sopenharmony_ci  end
122b1994897Sopenharmony_ci
123b1994897Sopenharmony_ci  def to_s
124b1994897Sopenharmony_ci    "Scope '#{@name}', current=#{@current_index}\n#{@lines.join}"
125b1994897Sopenharmony_ci  end
126b1994897Sopenharmony_ciend
127b1994897Sopenharmony_ci
128b1994897Sopenharmony_ciclass Checker
129b1994897Sopenharmony_ci  attr_reader :name
130b1994897Sopenharmony_ci
131b1994897Sopenharmony_ci  def initialize(options, name)
132b1994897Sopenharmony_ci    @name = name
133b1994897Sopenharmony_ci    @lines = []
134b1994897Sopenharmony_ci    @code = ""
135b1994897Sopenharmony_ci    @cwd = "#{Dir.getwd}/#{name.gsub(/[ -:()]/, '_')}"
136b1994897Sopenharmony_ci    @options = options
137b1994897Sopenharmony_ci    @args = ''
138b1994897Sopenharmony_ci    @ir_files = []
139b1994897Sopenharmony_ci    @architecture = options.arch
140b1994897Sopenharmony_ci    @aot_file = ''
141b1994897Sopenharmony_ci
142b1994897Sopenharmony_ci    # Events scope for 'events.csv'
143b1994897Sopenharmony_ci    @events_scope = nil
144b1994897Sopenharmony_ci    # IR scope for IR dumps files 'ir_dump/*.ir'
145b1994897Sopenharmony_ci    @ir_scope = nil
146b1994897Sopenharmony_ci
147b1994897Sopenharmony_ci    # Disassembly file lines, that were read from 'disasm.txt'
148b1994897Sopenharmony_ci    @disasm_lines = nil
149b1994897Sopenharmony_ci    # Currently processing disasm method
150b1994897Sopenharmony_ci    @disasm_method_scope = nil
151b1994897Sopenharmony_ci    # Current search scope
152b1994897Sopenharmony_ci    @disasm_scope = nil
153b1994897Sopenharmony_ci
154b1994897Sopenharmony_ci    Dir.mkdir(@cwd) unless File.exists?(@cwd)
155b1994897Sopenharmony_ci    clear_data
156b1994897Sopenharmony_ci  end
157b1994897Sopenharmony_ci
158b1994897Sopenharmony_ci  def append_line(line)
159b1994897Sopenharmony_ci    @code << line
160b1994897Sopenharmony_ci  end
161b1994897Sopenharmony_ci
162b1994897Sopenharmony_ci  def RUN(**args)
163b1994897Sopenharmony_ci    expected_result = 0
164b1994897Sopenharmony_ci    aborted_sig = 0
165b1994897Sopenharmony_ci    entry = '_GLOBAL::main'
166b1994897Sopenharmony_ci    env = ''
167b1994897Sopenharmony_ci    args.each do |name, value|
168b1994897Sopenharmony_ci      if name == :force_jit and value
169b1994897Sopenharmony_ci        @args << '--compiler-hotness-threshold=0 --no-async-jit=true --compiler-enable-jit=true '
170b1994897Sopenharmony_ci      elsif name == :options
171b1994897Sopenharmony_ci        @args << value
172b1994897Sopenharmony_ci      elsif name == :entry
173b1994897Sopenharmony_ci        entry = value
174b1994897Sopenharmony_ci      elsif name == :result
175b1994897Sopenharmony_ci        expected_result = value
176b1994897Sopenharmony_ci      elsif name == :abort
177b1994897Sopenharmony_ci        aborted_sig = value
178b1994897Sopenharmony_ci      elsif name == :env
179b1994897Sopenharmony_ci        env = value
180b1994897Sopenharmony_ci      end
181b1994897Sopenharmony_ci    end
182b1994897Sopenharmony_ci
183b1994897Sopenharmony_ci    clear_data
184b1994897Sopenharmony_ci    aot_arg = @aot_file.empty? ? '' : "--aot-file #{@aot_file}"
185b1994897Sopenharmony_ci
186b1994897Sopenharmony_ci    $curr_cmd = "#{env} #{@options.run_prefix} #{@options.panda} --compiler-ignore-failures=false #{@options.panda_options} \
187b1994897Sopenharmony_ci                #{aot_arg} #{@args} --events-output=csv --compiler-dump --compiler-disasm-dump:single-file #{@options.test_file} #{entry}"
188b1994897Sopenharmony_ci    log.debug "Panda command: #{$curr_cmd}"
189b1994897Sopenharmony_ci
190b1994897Sopenharmony_ci    output, status = Open3.capture2e($curr_cmd.to_s, chdir: @cwd.to_s)
191b1994897Sopenharmony_ci    if status.signaled?
192b1994897Sopenharmony_ci      if status.termsig != aborted_sig
193b1994897Sopenharmony_ci        puts output
194b1994897Sopenharmony_ci        log.error "panda aborted with signal #{status.termsig}, but expected #{aborted_sig}"
195b1994897Sopenharmony_ci        raise_error "Test '#{@name}' failed"
196b1994897Sopenharmony_ci      end
197b1994897Sopenharmony_ci    elsif status.exitstatus != expected_result
198b1994897Sopenharmony_ci      puts output
199b1994897Sopenharmony_ci      log.error "panda returns code #{status.exitstatus}, but expected #{expected_result}"
200b1994897Sopenharmony_ci      raise_error "Test '#{@name}' failed"
201b1994897Sopenharmony_ci    end
202b1994897Sopenharmony_ci    log.debug output
203b1994897Sopenharmony_ci
204b1994897Sopenharmony_ci    @events_scope = SearchScope.from_file("#{@cwd}/events.csv", 'Events')
205b1994897Sopenharmony_ci    @ir_files = Dir['ir_dump/*.ir']
206b1994897Sopenharmony_ci  end
207b1994897Sopenharmony_ci
208b1994897Sopenharmony_ci  def RUN_PAOC(**args)
209b1994897Sopenharmony_ci    @aot_file = "#{Dir.getwd}/#{File.basename(@options.test_file, File.extname(@options.test_file))}.an"
210b1994897Sopenharmony_ci
211b1994897Sopenharmony_ci    inputs = @options.test_file
212b1994897Sopenharmony_ci    aot_output_option = '--paoc-output'
213b1994897Sopenharmony_ci    output = @aot_file
214b1994897Sopenharmony_ci    options = ''
215b1994897Sopenharmony_ci    env = ''
216b1994897Sopenharmony_ci    aborted_sig = 0
217b1994897Sopenharmony_ci
218b1994897Sopenharmony_ci    args.each do |name, value|
219b1994897Sopenharmony_ci      case name
220b1994897Sopenharmony_ci      when :options
221b1994897Sopenharmony_ci        options = value
222b1994897Sopenharmony_ci      when :boot
223b1994897Sopenharmony_ci        aot_output_option = '--paoc-boot-output'
224b1994897Sopenharmony_ci      when :env
225b1994897Sopenharmony_ci        env = value
226b1994897Sopenharmony_ci      when :inputs
227b1994897Sopenharmony_ci        inputs = value
228b1994897Sopenharmony_ci      when :abort
229b1994897Sopenharmony_ci        aborted_sig = value
230b1994897Sopenharmony_ci      when :output
231b1994897Sopenharmony_ci        output = value
232b1994897Sopenharmony_ci      end
233b1994897Sopenharmony_ci    end
234b1994897Sopenharmony_ci
235b1994897Sopenharmony_ci    paoc_args = "--paoc-panda-files #{inputs} --events-output=csv --compiler-dump #{options} #{aot_output_option} #{output}"
236b1994897Sopenharmony_ci
237b1994897Sopenharmony_ci    clear_data
238b1994897Sopenharmony_ci
239b1994897Sopenharmony_ci    $curr_cmd = "#{env} #{@options.run_prefix} #{@options.paoc} --compiler-ignore-failures=false --compiler-disasm-dump:single-file #{@options.paoc_options} #{paoc_args}"
240b1994897Sopenharmony_ci    log.debug "Paoc command: #{$curr_cmd}"
241b1994897Sopenharmony_ci
242b1994897Sopenharmony_ci    output, status = Open3.capture2e($curr_cmd.to_s, chdir: @cwd.to_s)
243b1994897Sopenharmony_ci    if status.signaled?
244b1994897Sopenharmony_ci      if status.termsig != aborted_sig
245b1994897Sopenharmony_ci        puts output
246b1994897Sopenharmony_ci        log.error "panda aborted with signal #{status.termsig}, but expected #{aborted_sig}"
247b1994897Sopenharmony_ci        raise_error "Test '#{@name}' failed"
248b1994897Sopenharmony_ci      end
249b1994897Sopenharmony_ci    elsif status.exitstatus != 0
250b1994897Sopenharmony_ci      puts output
251b1994897Sopenharmony_ci      log.error "paoc failed: #{status.exitstatus}"
252b1994897Sopenharmony_ci      raise_error "Test '#{@name}' failed"
253b1994897Sopenharmony_ci    end
254b1994897Sopenharmony_ci    log.debug output
255b1994897Sopenharmony_ci
256b1994897Sopenharmony_ci    @events_scope = SearchScope.from_file("#{@cwd}/events.csv", 'Events')
257b1994897Sopenharmony_ci    @ir_files = Dir['ir_dump/*.ir']
258b1994897Sopenharmony_ci  end
259b1994897Sopenharmony_ci
260b1994897Sopenharmony_ci  def EVENT(match)
261b1994897Sopenharmony_ci    return if @options.release
262b1994897Sopenharmony_ci
263b1994897Sopenharmony_ci    @events_scope.find(match)
264b1994897Sopenharmony_ci  end
265b1994897Sopenharmony_ci
266b1994897Sopenharmony_ci  def EVENT_NEXT(match)
267b1994897Sopenharmony_ci    return if @options.release
268b1994897Sopenharmony_ci
269b1994897Sopenharmony_ci    @events_scope.find_next(match)
270b1994897Sopenharmony_ci  end
271b1994897Sopenharmony_ci
272b1994897Sopenharmony_ci  def EVENT_COUNT(match)
273b1994897Sopenharmony_ci    return 0 if @options.release
274b1994897Sopenharmony_ci
275b1994897Sopenharmony_ci    @events_scope.lines.count { |event| contains?(event, match) }
276b1994897Sopenharmony_ci  end
277b1994897Sopenharmony_ci
278b1994897Sopenharmony_ci  def EVENT_NOT(match)
279b1994897Sopenharmony_ci    return if @options.release
280b1994897Sopenharmony_ci
281b1994897Sopenharmony_ci    @events_scope.find_not(match)
282b1994897Sopenharmony_ci  end
283b1994897Sopenharmony_ci
284b1994897Sopenharmony_ci  def EVENT_NEXT_NOT(match)
285b1994897Sopenharmony_ci    return if @options.release
286b1994897Sopenharmony_ci
287b1994897Sopenharmony_ci    @events_scope.find_next_not(match)
288b1994897Sopenharmony_ci  end
289b1994897Sopenharmony_ci
290b1994897Sopenharmony_ci  def EVENTS_COUNT(match, count)
291b1994897Sopenharmony_ci    return if @options.release
292b1994897Sopenharmony_ci
293b1994897Sopenharmony_ci    res = @events_scope.lines.count { |event| contains?(event, match) }
294b1994897Sopenharmony_ci    raise_error "Events count missmatch for #{match}, expected: #{count}, real: #{res}" unless res == count
295b1994897Sopenharmony_ci  end
296b1994897Sopenharmony_ci
297b1994897Sopenharmony_ci  def TRUE(condition)
298b1994897Sopenharmony_ci    return if @options.release
299b1994897Sopenharmony_ci
300b1994897Sopenharmony_ci    raise_error "Not true condition: \"#{condition}\"" unless condition
301b1994897Sopenharmony_ci  end
302b1994897Sopenharmony_ci
303b1994897Sopenharmony_ci  class SkipException < StandardError
304b1994897Sopenharmony_ci  end
305b1994897Sopenharmony_ci
306b1994897Sopenharmony_ci  def SKIP_IF(condition)
307b1994897Sopenharmony_ci    return if @options.release
308b1994897Sopenharmony_ci    raise SkipException if condition
309b1994897Sopenharmony_ci  end
310b1994897Sopenharmony_ci
311b1994897Sopenharmony_ci  def IR_COUNT(match)
312b1994897Sopenharmony_ci    return 0 if @options.release
313b1994897Sopenharmony_ci
314b1994897Sopenharmony_ci    @ir_scope.lines.count { |inst| contains?(inst, match) }
315b1994897Sopenharmony_ci  end
316b1994897Sopenharmony_ci
317b1994897Sopenharmony_ci  def BLOCK_COUNT
318b1994897Sopenharmony_ci    IR_COUNT('BB ')
319b1994897Sopenharmony_ci  end
320b1994897Sopenharmony_ci
321b1994897Sopenharmony_ci  def INST(match)
322b1994897Sopenharmony_ci    return if @options.release
323b1994897Sopenharmony_ci
324b1994897Sopenharmony_ci    @ir_scope.find(match)
325b1994897Sopenharmony_ci  end
326b1994897Sopenharmony_ci
327b1994897Sopenharmony_ci  def INST_NEXT(match)
328b1994897Sopenharmony_ci    return if @options.release
329b1994897Sopenharmony_ci
330b1994897Sopenharmony_ci    @ir_scope.find_next(match)
331b1994897Sopenharmony_ci  end
332b1994897Sopenharmony_ci
333b1994897Sopenharmony_ci  def INST_NOT(match)
334b1994897Sopenharmony_ci    return if @options.release
335b1994897Sopenharmony_ci
336b1994897Sopenharmony_ci    @ir_scope.find_not(match)
337b1994897Sopenharmony_ci  end
338b1994897Sopenharmony_ci
339b1994897Sopenharmony_ci  def INST_NEXT_NOT(match)
340b1994897Sopenharmony_ci    return if @options.release
341b1994897Sopenharmony_ci
342b1994897Sopenharmony_ci    @ir_scope.find_next_not(match)
343b1994897Sopenharmony_ci  end
344b1994897Sopenharmony_ci
345b1994897Sopenharmony_ci  def INST_COUNT(match, count)
346b1994897Sopenharmony_ci    return if @options.release
347b1994897Sopenharmony_ci
348b1994897Sopenharmony_ci    real_count = IR_COUNT(match)
349b1994897Sopenharmony_ci    raise_error "IR_COUNT mismatch: expected=#{count}, real=#{real_count}" unless real_count == count
350b1994897Sopenharmony_ci  end
351b1994897Sopenharmony_ci
352b1994897Sopenharmony_ci  module SearchState
353b1994897Sopenharmony_ci    NONE = 0
354b1994897Sopenharmony_ci    SEARCH_BODY = 1
355b1994897Sopenharmony_ci    SEARCH_END = 2
356b1994897Sopenharmony_ci  end
357b1994897Sopenharmony_ci
358b1994897Sopenharmony_ci  def ASM_METHOD(match)
359b1994897Sopenharmony_ci    ensure_disasm
360b1994897Sopenharmony_ci    state = SearchState::NONE
361b1994897Sopenharmony_ci    start_index = nil
362b1994897Sopenharmony_ci    end_index = -1
363b1994897Sopenharmony_ci    @disasm_lines.each_with_index do |line, index|
364b1994897Sopenharmony_ci      case state
365b1994897Sopenharmony_ci      when SearchState::NONE
366b1994897Sopenharmony_ci        if line.start_with?('METHOD_INFO:') && contains?(@disasm_lines[index + 1].split(':', 2)[1].strip, match)
367b1994897Sopenharmony_ci          state = SearchState::SEARCH_BODY
368b1994897Sopenharmony_ci        end
369b1994897Sopenharmony_ci      when SearchState::SEARCH_BODY
370b1994897Sopenharmony_ci        if line.start_with?('DISASSEMBLY')
371b1994897Sopenharmony_ci          start_index = index + 1
372b1994897Sopenharmony_ci          state = SearchState::SEARCH_END
373b1994897Sopenharmony_ci        end
374b1994897Sopenharmony_ci      when SearchState::SEARCH_END
375b1994897Sopenharmony_ci        if line.start_with?('METHOD_INFO:')
376b1994897Sopenharmony_ci          end_index = index - 1
377b1994897Sopenharmony_ci          break
378b1994897Sopenharmony_ci        end
379b1994897Sopenharmony_ci      end
380b1994897Sopenharmony_ci    end
381b1994897Sopenharmony_ci    raise "Method not found: #{match_str(match)}" if start_index.nil?
382b1994897Sopenharmony_ci
383b1994897Sopenharmony_ci    @disasm_method_scope = SearchScope.new(@disasm_lines[start_index..end_index], "Method: #{match_str(match)}")
384b1994897Sopenharmony_ci    @disasm_scope = @disasm_method_scope
385b1994897Sopenharmony_ci  end
386b1994897Sopenharmony_ci
387b1994897Sopenharmony_ci  def ASM_INST(match)
388b1994897Sopenharmony_ci    ensure_disasm
389b1994897Sopenharmony_ci    state = SearchState::NONE
390b1994897Sopenharmony_ci    start_index = nil
391b1994897Sopenharmony_ci    end_index = -1
392b1994897Sopenharmony_ci    prefix = nil
393b1994897Sopenharmony_ci    @disasm_method_scope.lines.each_with_index do |line, index|
394b1994897Sopenharmony_ci      case state
395b1994897Sopenharmony_ci      when SearchState::NONE
396b1994897Sopenharmony_ci        if contains?(line, match)
397b1994897Sopenharmony_ci          prefix = line.sub(/#.*/, '#').gsub("\n", '')
398b1994897Sopenharmony_ci          start_index = index + 1
399b1994897Sopenharmony_ci          state = SearchState::SEARCH_END
400b1994897Sopenharmony_ci        end
401b1994897Sopenharmony_ci      when SearchState::SEARCH_END
402b1994897Sopenharmony_ci        if line.start_with?(prefix)
403b1994897Sopenharmony_ci          end_index = index - 1
404b1994897Sopenharmony_ci          break
405b1994897Sopenharmony_ci        end
406b1994897Sopenharmony_ci      end
407b1994897Sopenharmony_ci    end
408b1994897Sopenharmony_ci    raise "Can not find asm instruction: #{match}" if start_index.nil?
409b1994897Sopenharmony_ci
410b1994897Sopenharmony_ci    @disasm_scope = SearchScope.new(@disasm_method_scope.lines[start_index..end_index], "Inst: #{match_str(match)}")
411b1994897Sopenharmony_ci  end
412b1994897Sopenharmony_ci
413b1994897Sopenharmony_ci  def ASM_RESET
414b1994897Sopenharmony_ci    @disasm_scope = @disasm_method_scope
415b1994897Sopenharmony_ci  end
416b1994897Sopenharmony_ci
417b1994897Sopenharmony_ci  def ASM(**kwargs)
418b1994897Sopenharmony_ci    ensure_disasm
419b1994897Sopenharmony_ci    @disasm_scope.find(select_asm(kwargs))
420b1994897Sopenharmony_ci  end
421b1994897Sopenharmony_ci
422b1994897Sopenharmony_ci  def ASM_NEXT(**kwargs)
423b1994897Sopenharmony_ci    ensure_disasm
424b1994897Sopenharmony_ci    @disasm_scope.find_next(select_asm(kwargs))
425b1994897Sopenharmony_ci  end
426b1994897Sopenharmony_ci
427b1994897Sopenharmony_ci  def ASM_NOT(**kwargs)
428b1994897Sopenharmony_ci    ensure_disasm
429b1994897Sopenharmony_ci    @disasm_scope.find_not(select_asm(kwargs))
430b1994897Sopenharmony_ci  end
431b1994897Sopenharmony_ci
432b1994897Sopenharmony_ci  def ASM_NEXT_NOT(**kwargs)
433b1994897Sopenharmony_ci    ensure_disasm
434b1994897Sopenharmony_ci    @disasm_scope.find_next_not(select_asm(kwargs))
435b1994897Sopenharmony_ci  end
436b1994897Sopenharmony_ci
437b1994897Sopenharmony_ci  def select_asm(kwargs)
438b1994897Sopenharmony_ci    kwargs[@options.arch.to_sym]
439b1994897Sopenharmony_ci  end
440b1994897Sopenharmony_ci
441b1994897Sopenharmony_ci  def ensure_disasm
442b1994897Sopenharmony_ci    @disasm_lines ||= File.readlines("#{@cwd}/disasm.txt")
443b1994897Sopenharmony_ci  end
444b1994897Sopenharmony_ci
445b1994897Sopenharmony_ci  def METHOD(method)
446b1994897Sopenharmony_ci    return if @options.release
447b1994897Sopenharmony_ci
448b1994897Sopenharmony_ci    @ir_files = Dir["#{@cwd}/ir_dump/*#{method.sub('::', '_')}*.ir"]
449b1994897Sopenharmony_ci    @ir_files.sort!
450b1994897Sopenharmony_ci    raise_error "IR dumps not found for method: #{method.sub('::', '_')}" if @ir_files.empty?
451b1994897Sopenharmony_ci    @current_method = method
452b1994897Sopenharmony_ci  end
453b1994897Sopenharmony_ci
454b1994897Sopenharmony_ci  def PASS_AFTER(pass)
455b1994897Sopenharmony_ci    return if @options.release
456b1994897Sopenharmony_ci
457b1994897Sopenharmony_ci    fname = @ir_files.detect { |x| File.basename(x).include? pass }
458b1994897Sopenharmony_ci    raise_error "IR file not found for pass: #{pass}" unless fname
459b1994897Sopenharmony_ci    @ir_scope = SearchScope.from_file(fname, 'IR')
460b1994897Sopenharmony_ci  end
461b1994897Sopenharmony_ci
462b1994897Sopenharmony_ci  def PASS_BEFORE(pass)
463b1994897Sopenharmony_ci    return if @options.release
464b1994897Sopenharmony_ci
465b1994897Sopenharmony_ci    index = @ir_files.index { |x| File.basename(x).include? pass }
466b1994897Sopenharmony_ci    raise_error "IR file not found for pass: #{pass}" unless index
467b1994897Sopenharmony_ci    @ir_scope = SearchScope.from_file(@ir_files[index - 1], 'IR')
468b1994897Sopenharmony_ci  end
469b1994897Sopenharmony_ci
470b1994897Sopenharmony_ci  def run
471b1994897Sopenharmony_ci    log.info "Running \"#{@name}\""
472b1994897Sopenharmony_ci    begin
473b1994897Sopenharmony_ci      self.instance_eval @code
474b1994897Sopenharmony_ci    rescue SkipException
475b1994897Sopenharmony_ci      log.info "Skipped: \"#{@name}\""
476b1994897Sopenharmony_ci    else
477b1994897Sopenharmony_ci      log.info "Success: \"#{@name}\""
478b1994897Sopenharmony_ci    end
479b1994897Sopenharmony_ci    clear_data
480b1994897Sopenharmony_ci  end
481b1994897Sopenharmony_ci
482b1994897Sopenharmony_ci  def clear_data
483b1994897Sopenharmony_ci   if !@options.keep_data
484b1994897Sopenharmony_ci      FileUtils.rm_rf("#{@cwd}/ir_dump")
485b1994897Sopenharmony_ci      FileUtils.rm_rf("#{@cwd}/events.csv")
486b1994897Sopenharmony_ci      FileUtils.rm_rf("#{@cwd}/disasm.txt")
487b1994897Sopenharmony_ci   end
488b1994897Sopenharmony_ci  end
489b1994897Sopenharmony_ciend
490b1994897Sopenharmony_ci
491b1994897Sopenharmony_cidef read_checks(options)
492b1994897Sopenharmony_ci  checks = []
493b1994897Sopenharmony_ci  check = nil
494b1994897Sopenharmony_ci  checker_start = "#{options.command_token} CHECKER"
495b1994897Sopenharmony_ci  File.readlines(options.source).each do |line|
496b1994897Sopenharmony_ci    if check
497b1994897Sopenharmony_ci      unless line.start_with? options.command_token
498b1994897Sopenharmony_ci        check = nil
499b1994897Sopenharmony_ci        next
500b1994897Sopenharmony_ci      end
501b1994897Sopenharmony_ci      check.append_line(line[options.command_token.size..-1])
502b1994897Sopenharmony_ci    else
503b1994897Sopenharmony_ci      next unless line.start_with? checker_start
504b1994897Sopenharmony_ci
505b1994897Sopenharmony_ci      name = line.split(' ', 3)[2].strip
506b1994897Sopenharmony_ci      raise "Checker with name '#{name}'' already exists" if checks.any? { |x| x.name == name }
507b1994897Sopenharmony_ci
508b1994897Sopenharmony_ci      check = Checker.new(options, name)
509b1994897Sopenharmony_ci      checks << check
510b1994897Sopenharmony_ci    end
511b1994897Sopenharmony_ci  end
512b1994897Sopenharmony_ci  checks
513b1994897Sopenharmony_ciend
514b1994897Sopenharmony_ci
515b1994897Sopenharmony_cidef main(options)
516b1994897Sopenharmony_ci  read_checks(options).each(&:run)
517b1994897Sopenharmony_ci  0
518b1994897Sopenharmony_ciend
519b1994897Sopenharmony_ci
520b1994897Sopenharmony_ciif __FILE__ == $PROGRAM_NAME
521b1994897Sopenharmony_ci  main(options)
522b1994897Sopenharmony_ciend
523b1994897Sopenharmony_ci
524b1994897Sopenharmony_ci# Somehow ruby resolves `Checker` name to another class in a Testing scope, so make this global
525b1994897Sopenharmony_ci# variable to refer to it from unit tests. I believe there is more proper way to do it, but I
526b1994897Sopenharmony_ci# didn't find it at first glance.
527b1994897Sopenharmony_ci$CheckerForTest = Checker