• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env ruby
2
3# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16require_relative 'options'
17# Parse options in advance, since it may be required during scripts loading
18Options.parse
19
20require_relative 'function'
21require_relative 'cpp_function'
22require_relative 'instructions_data'
23require_relative 'output'
24require_relative 'ir_generator'
25require_relative 'isa'
26require 'logger'
27require 'optparse'
28require 'tempfile'
29require 'yaml'
30
31FILE_BEGIN = %{
32// THIS FILE WAS GENERATED FOR #{Options.arch.upcase}
33
34#include "irtoc/backend/compilation.h"
35#include "compiler/optimizer/ir/ir_constructor.h"
36#include "compiler/optimizer/code_generator/relocations.h"
37#include "utils/expected.h"
38#include "asm_defines.h"
39#include "cross_values.h"
40#include "runtime/include/managed_thread.h"
41#include "runtime/mem/gc/g1/g1-allocator.h"
42
43#ifndef __clang_analyzer__
44
45using namespace panda::compiler;
46
47namespace panda::irtoc \{
48// NOLINTBEGIN(readability-identifier-naming)
49
50}
51
52FILE_END = %{
53// NOLINTEND(readability-identifier-naming)
54\} // namespace panda::irtoc
55
56#endif  // __clang_analyzer__
57}
58
59class Irtoc
60
61  attr_reader :functions, :cpp_functions
62  attr_writer :compile_for_llvm
63
64  def initialize
65    # List of all compiled functions.
66    @functions = []
67    # Currently processed script file.
68    @current_script = nil
69    # Macros, that are defined for the current script. Since each macros is just a method in Function class, we need
70    # to remove all these methods after script is done. Because further scripts should not see macros, defined in
71    # previous one.
72    @macros = []
73    # Macros that also generate corresponding IR Builder methods
74    @builder_macros = []
75    @compile_for_llvm = false
76  end
77
78  (0..31).each { |i| define_method("r#{i}") { i } }
79  { :rax => 0,
80    :rcx => 1,
81    :rdx => 2,
82    :rbx => 11,
83    :rsp => 10,
84    :rbp => 9,
85    :rsi => 6,
86    :rdi => 7}.each { |name, value| define_method(name) { value } }
87
88  def function(name, **kwargs, &block)
89    func = Function.new name, **kwargs, compile_for_llvm: @compile_for_llvm, &block
90    @functions << func unless func.mode.nil?
91    func.compile
92  end
93
94  def cpp_function(name, &block)
95    func = CppFunction.new(name)
96    func.instance_eval(&block)
97    (@cpp_functions ||= []) << func
98  end
99
100  def macro(name, &block)
101    raise "Macro can't start with capital letter" if name.to_s.start_with? /[A-Z]/
102    Function.define_method(name.to_sym, &block)
103    @macros << name
104  end
105
106  def scoped_macro(name, &block)
107    raise "Macro can't start with capital letter" if name.to_s.start_with? /[A-Z]/
108    Function.define_method(name.to_sym) do |*args|
109      @locals.push({})
110      @unresolved.push({})
111      @labels.push({})
112      if args.size != block.arity && block.arity >= 0 || args.size < -(block.arity + 1) && block.arity < 0
113        raise ArgumentError.new("Macro #{name} wrong number of arguments (given #{args.size}, expected #{block.arity})")
114      end
115      res = instance_exec(*args, &block)
116      @locals.pop()
117      @unresolved.pop()
118      @labels.pop()
119      res
120    end
121    @macros << name
122  end
123
124  def remove_macros
125    @macros.each do |name|
126      Function.remove_method name
127    end
128    @macros.clear
129  end
130
131  def include_relative(filename)
132    fixed_filename = "#{File.basename(filename)}.fixed"
133    filename = "#{File.dirname(@current_script)}/#{filename}"
134    preprocess(filename, fixed_filename)
135    self.instance_eval File.open(fixed_filename).read, filename
136  end
137
138  def preprocess_line(line)
139    line.gsub(/(\w+) *:= *([^>])/, 'let :\1, \2').gsub(/\} *Else/, "}; Else").gsub(/%(\w+)/, 'LiveIn(regmap[:\1])')
140  end
141
142  def read_plugin(filename)
143    return "" if Options.plugins.empty?
144    output = ""
145    Options.plugins[filename].each do |full_filename|
146      File.open(full_filename).readlines.each do |line|
147        output += preprocess_line(line)
148      end
149    end
150    output
151  end
152
153  def preprocess(filename, output_filename)
154    File.open(output_filename, "w") do | file|
155      File.open(filename).readlines.each do |line|
156        line_matches = line.match(/include_plugin '(.*)'/)
157        if line_matches
158          filename = line_matches.captures[0]
159          file.puts read_plugin(filename)
160        else
161          file.puts preprocess_line(line)
162        end
163      end
164    end
165  end
166
167  def run(input_file)
168    @current_script = input_file
169    fixed_filename = "#{File.basename(input_file)}.fixed"
170    preprocess(input_file, fixed_filename)
171    begin
172      data = File.open(fixed_filename).read
173      self.instance_eval data, fixed_filename
174    rescue SyntaxError => e
175      puts "========== Begin #{fixed_filename} =========="
176      puts data
177      puts "========== End #{fixed_filename} =========="
178      raise
179    end
180  end
181end
182
183def main
184  abort "YAML description file is not specified" unless Options.instructions_yaml
185  abort "ISA YAML file is not specified" unless Options.isa
186  abort "ISAPI file is not specified" unless Options.isapi
187
188  InstructionsData.setup Options.instructions_yaml
189  Function.setup
190  IRInstruction.setup
191  ISA.setup Options.isa, Options.isapi
192
193  require Options.commonapi
194
195  functions = []
196  cpp_functions = []
197  llvm_functions = []
198
199  Options.input_files.each do |input_file|
200    irtoc = Irtoc.new
201    irtoc.run(input_file)
202    functions += irtoc.functions
203    cpp_functions += irtoc.cpp_functions if irtoc.cpp_functions
204  end
205  # Compile each function for LLVM
206  Options.input_files.each do |input_file|
207    irtoc = Irtoc.new
208    irtoc.compile_for_llvm = true
209    irtoc.run(input_file)
210    llvm_functions += irtoc.functions
211  end
212
213  if Options.ir_api == 'ir-constructor'
214    raise 'Provide one --output file to generate intepreter handler IR for stock compiler. Or provide two --output files delimited by \':\' to generate IR for stock compiler (the first file), and llvm compiler (the second file)' unless Options.output_files.size <= 2
215    Output.setup Options.output_files[0]
216    Output.println(FILE_BEGIN)
217    functions.reject(&:enable_builder).each do |function|
218      function.emit_ir ""
219    end
220    Output.println(FILE_END)
221    if Options.output_files.size == 2
222      Output.setup Options.output_files[1]
223      interp_llvm_functions = llvm_functions.reject(&:enable_builder).select do |f|
224        f.mode.include?(:Interpreter) || f.mode.include?(:InterpreterEntry)
225      end
226      unless interp_llvm_functions.empty?
227        Output.println(FILE_BEGIN)
228        interp_llvm_functions.each do |function|
229          function.emit_ir '_LLVM'
230        end
231        Output.println(FILE_END)
232      end
233    end
234  elsif Options.ir_api == 'ir-builder'
235    builder_functions = functions.select(&:enable_builder)
236    Output.setup Options.output_files[0]
237    Output.println('#include "optimizer/ir/graph.h"')
238    Output.println('#include "optimizer/ir/basicblock.h"')
239    Output.println('#include "optimizer/ir/inst.h"')
240    Output.println('#include "asm_defines.h"')
241    Output.println('namespace panda::compiler {')
242    Output.println('// NOLINTBEGIN(readability-identifier-naming)')
243    builder_functions.each(&:generate_builder)
244    Output.println('// NOLINTEND(readability-identifier-naming)')
245    Output.println('} // namespace panda::compiler')
246  elsif Options.ir_api == 'ir-inline'
247    Output.setup Options.output_files[0]
248    Output << '#include "optimizer/ir/graph.h"'
249    Output << '#include "optimizer/ir/basicblock.h"'
250    Output << '#include "optimizer/ir/inst.h"'
251    Output << "#include \"asm_defines.h\"\n"
252    Output << "namespace panda::compiler {\n"
253    Output << "// NOLINTBEGIN(readability-identifier-naming)\n"
254    cpp_functions.each { |func| func.generate_ir(GeneratorIrInline.new) }
255    Output << "// NOLINTEND(readability-identifier-naming)\n"
256    Output << '}  // namespace panda::compiler'
257  else
258    abort 'Should be unreachable: Unknown IR API'
259  end
260
261  validation = Hash[functions.select(&:validation).map { |f| [f.name, f.validation] } ]
262  File.open("validation.yaml", "w") do |file|
263    file.write(validation.to_yaml)
264  end
265end
266
267main
268