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