/** * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ <% require 'yaml' Instruction.class_eval do def get_input_idx(index, kind) res = 0 index += 1 if has_dst? acc_and_operands.each_with_index do |op, i| break if i == index res += 1 if op.send(kind) end res end def inputs @inputs ||= acc_and_operands.select { |x| !x.dst? } end def has_dst? !acc_and_operands.empty? && acc_and_operands.first.dst? end def get_input_string(index) op = inputs[index] return "GetDefinitionAcc()" if op.acc? return "FindOrCreateConstant(instruction->GetImm<#{get_format}, #{get_input_idx(index, :imm?)}>())" if op.imm? return "GetDefinition(instruction->GetId<#{get_format}, #{get_input_idx(index, :id?)}>().GetOffset())" if op.id? raise "Invalid operand" unless op.reg? return "GetDefinition(instruction->GetVReg<#{get_format}, #{get_input_idx(index, :reg?)}>())" end def get_format return "BytecodeInstruction::Format::#{format.pretty.upcase}" end end def get_type(type) @type_map ||= { 'u1' => 'DataType::BOOL', 'i8' => 'DataType::INT8', 'i16' => 'DataType::INT16', 'i32' => 'DataType::INT32', 'i64' => 'DataType::INT64', 'u8' => 'DataType::UINT8', 'u16' => 'DataType::UINT16', 'u32' => 'DataType::UINT32', 'u64' => 'DataType::UINT64', 'b32' => 'DataType::UINT32', 'b64' => 'DataType::UINT64', 'f32' => 'DataType::FLOAT32', 'f64' => 'DataType::FLOAT64', 'ref' => 'DataType::REFERENCE', 'any' => 'DataType::ANY', 'i8[]' => 'DataType::INT8', 'i16[]' => 'DataType::INT16', 'i32[]' => 'DataType::INT32', 'i64[]' => 'DataType::INT64', 'u8[]' => 'DataType::UINT8', 'u16[]' => 'DataType::UINT16', 'u32[]' => 'DataType::UINT32', 'u64[]' => 'DataType::UINT64', 'b32[]' => 'DataType::UINT32', 'b64[]' => 'DataType::UINT64', 'f32[]' => 'DataType::FLOAT32', 'f64[]' => 'DataType::FLOAT64', 'ref[]' => 'DataType::REFERENCE', 'none' => 'DataType::NO_TYPE'} return 'DataType::ANY' if @type_map[type].nil? # raise "Unknown type #{type}" if @type_map[type].nil? return @type_map[type] end def get_template_by_inst(inst) if (inst.namespace == "ecmascript") return "ecma" else return get_template(inst.stripped_mnemonic) end end def get_template(mn) @tmpl_map ||= { /ldarr/ => "ldarr", /starr$/ => "starr", /^return.*/ => "return", /^[uf]?cmp/ => "cmp", /^[ifu][813264]{1,2}to[ifu][813264]{1,2}$/ => "cast", /^j(eq|ne|lt|gt|le|ge|stricteq|nstricteq)(z|null|undefined)?$/ => "if", /^jmp.*/ => "jump", /(fdiv|fmod|add|sub|mul|and|or|xor|ashr|shr|shl|neg|not)[2i]?$/ => "binop", /^(div|mod)u?[2i]?$/ => "binop_z", /^inci$/ => "inci", /^movi?$/ => "mov", /^fmovi?$/ => "fmovi", /^sta$/ => "sta", /^ldai?$/ => "lda", /^fldai?$/ => "fldai", /^lenarr$/ => "lenarr", /^newarr$/ => "newarr", /^call/ => "call", /^newobj/ => "newobj", /^initobj/ => "initobj", /^ldobj/ => "ldobj", /^stobj/ => "stobj", /^ldstatic/ => "ldstatic", /^ststatic/ => "ststatic", /^isinstance/ => "isinstance", /^checkcast/ => "checkcast", /^throw/ => "throw", /^monitor/ => "monitor", /^nop/ => "nop", /^builtin/ => "builtin", /^ecma/ => "ecma", /^$/ => "unimplemented" } res = @tmpl_map.select { |k, v| mn.match k } raise "Template not found or multiple match: #{mn}" unless res.size == 1 return res.first[1] end def template(name, inst, indent, context = {}) @inst_yaml ||= YAML.load_file(File.join(File.dirname(__FILE__), '../ir_builder/inst_templates.yaml')) raise "Template '#{name}' not found in templates file" unless @inst_yaml['templates'].key? name indent + ERB.new(@inst_yaml['templates'][name], nil, '%-').result(binding).gsub("\n", "\n#{indent}") end def get_cc(inst) return 'ConditionCode::CC_EQ' if inst.opcode.start_with? 'jeq' return 'ConditionCode::CC_NE' if inst.opcode.start_with? 'jne' return 'ConditionCode::CC_LT' if inst.opcode.start_with? 'jlt' return 'ConditionCode::CC_GT' if inst.opcode.start_with? 'jgt' return 'ConditionCode::CC_LE' if inst.opcode.start_with? 'jle' return 'ConditionCode::CC_GE' if inst.opcode.start_with? 'jge' return 'ConditionCode::CC_EQ' if inst.opcode.start_with? 'jstricteq' return 'ConditionCode::CC_NE' if inst.opcode.start_with? 'jnstricteq' raise 'get_cc: wrong opcode #{inst.opcode}' end %> #include "compiler_logger.h" #include "optimizer/ir_builder/inst_builder.h" #include "optimizer/ir_builder/ir_builder.h" #include "optimizer/ir/inst.h" #include "bytecode_instruction.h" #include "bytecode_instruction-inl.h" #include "optimizer/ir_builder/inst_builder-inl.h" namespace panda::compiler { // NOLINTNEXTLINE(readability-function-size) void InstBuilder::BuildInstruction(const BytecodeInstruction* instruction) { switch(instruction->GetOpcode()) { % Panda::instructions.each_with_index do |inst, idx| % tmpl = inst.mnemonic.include?('polymorphic') ? 'unimplemented' : get_template_by_inst(inst) // NOLINTNEXTLINE(bugprone-branch-clone) case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: { % if tmpl == 'unimplemented' // Not implemented failed_ = true; % else <%= template(tmpl, inst, ' ' * 8) %> % end break; } % end } } // NOLINTNEXTLINE(readability-function-size) int64_t InstBuilder::GetInstructionJumpOffset(const BytecodeInstruction* inst) { switch(inst->GetOpcode()) { % Panda::instructions.each_with_index do |inst, idx| // NOLINTNEXTLINE(bugprone-branch-clone) case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: % if inst.jump? return inst->GetImm, 0>(); % else return INVALID_OFFSET; % end % end } return INVALID_OFFSET; } // TODO(vpukhov): Move this logic from core into plugins/ecmascript // Currently we support two strategies for building IR from ecma.* instructions: // 1) Each ecma.* instruction is translated to a corresponding intrinsic call. // This is used for bytecode optimizer and slow paths during compiling // to native code (in both JIT and AOT modes). // 2) Semantics of each ecma.* instruction is taken from the corresponding // IRtoC handler and is inlined into compiled function. // This is used only for native compilation (again, both JIT and AOT). // InstBuilder::BuildEcma selects proper strategy and calls relevant builder. // NOLINTNEXTLINE(readability-function-size) void InstBuilder::BuildEcma([[maybe_unused]] const BytecodeInstruction* bc_inst) { #ifdef ENABLE_BYTECODE_OPT switch (bc_inst->GetOpcode()) { % Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst| case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: { % if inst.compilable? && inst.inlinable? // +compilable, +inlinable: ecma.* -> intrinsics for BCO, inline IRtoC otherwise: if (GetGraph()->IsBytecodeOptimizer()) { BuildEcmaAsIntrinsics(bc_inst); } else { BuildEcmaAsIntrinsics(bc_inst); } % elsif inst.compilable? // +compilable, -inlinable: ecma.* -> intrinsics for all scenarios: BuildEcmaAsIntrinsics(bc_inst); % else % abort "isa.yaml inconsistency: #{inst.opcode.upcase} is not compilable, but inlinable" if inst.inlinable? // -compilable, -inlinable: ecma.* -> intrinsics for BCO, fail IR builder otherwise: if (GetGraph()->IsBytecodeOptimizer()) { BuildEcmaAsIntrinsics(bc_inst); } else { failed_ = true; } % end break; } % end default: { failed_ = true; LOG(ERROR, COMPILER) << "Unknown ecma.* opcode: " << static_cast(bc_inst->GetOpcode()); return; } } #endif } template void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // NOLINT(readability-function-size) { switch (bc_inst->GetOpcode()) { % Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst| % opc = inst.opcode.upcase % name = opc % acc_read = inst.acc.include?("in") % acc_write = inst.acc.include?("out") % ret_type = acc_write ? "compiler::DataType::ANY" : "compiler::DataType::VOID" % iname = inst.intrinsic_name ? inst.intrinsic_name : opc % intrinsic_id = "compiler::RuntimeInterface::IntrinsicId::" + iname % intrinsic_external_js_id = "compiler::RuntimeInterface::IntrinsicId::" + opc case BytecodeInstruction::Opcode::<%= opc %>: { #ifdef ARK_INTRINSIC_SET auto intrinsic_id = <%= intrinsic_id %>; #else auto intrinsic_id = <%= intrinsic_external_js_id %>; #endif auto inst = GetGraph()->CreateInstIntrinsic(<%= ret_type %>, GetPc(bc_inst->GetAddress()), intrinsic_id); % if inst.throwing? inst->SetFlag(inst_flags::CAN_THROW); % end % if inst.exceptions.include?('x_throw') || inst.properties.include?('return') inst->SetFlag(inst_flags::CF); inst->SetFlag(inst_flags::TERMINATOR); % end % params_arr = inst.operands % format = "BytecodeInstruction::Format::" + inst.format.pretty.upcase % range_should_exclude_last = (name.include? "NEWOBJRANGE") || (name.include? "SUPERCALLTHISRANGE") || (name.include? "SUPERCALLARROWRANGE") || (name.include? "CALLRANGE") % is_range_call = (name.include? "CALLTHISRANGE") || (name.include? "CREATEOBJECTWITHEXCLUDEDKEYS") || range_should_exclude_last % need_newtarget = (name.include? "SUPERCALLSPREAD") || (name.include? "SUPERCALL") % num_vregs = params_arr.select{|b| b.reg?}.length % num_imms = params_arr.select{|b| b.imm?}.length % num_ids = params_arr.select{|b| b.id?}.length % num_inputs = acc_read ? num_vregs + 2 : num_vregs + 1 % if range_should_exclude_last % num_inputs = num_inputs - 1 % end % if need_newtarget % num_inputs = num_inputs + 1 % end % has_ic_slot = inst.properties.include?('ic_slot') || inst.properties.include?('jit_ic_slot') % if is_range_call size_t args_count = <%= num_inputs %>U + static_cast(bc_inst->GetImm<<%= format %>, <%= has_ic_slot ? 1 : 0 %>>()); % else size_t args_count {<%= num_inputs %>U}; % end % if need_newtarget if (GetGraph()->IsBytecodeOptimizer()) { --args_count; } % end inst->ReserveInputs(args_count); inst->AllocateInputTypes(GetGraph()->GetAllocator(), args_count); // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon) if constexpr (with_speculative) { inst->SetInlined(true); } % imm_index = 0 % vreg_index = 0 % id16_index = 0 auto inst_save_state = CreateSaveState(Opcode::SaveState, GetPc(bc_inst->GetAddress())); AddInstruction(inst_save_state); % params_arr.each do |param| % if param.imm? auto imm<%= imm_index %> = static_cast(bc_inst->GetImm<<%= format %>, <%= imm_index %>>()); inst->AddImm(GetGraph()->GetAllocator(), imm<%= imm_index %>); % imm_index = imm_index + 1 % elsif param.reg? { auto input = GetDefinition(bc_inst->GetVReg<<%= format %>, <%= vreg_index %>>()); inst->AppendInput(input); inst->AddInputType(DataType::ANY); } % vreg_index = vreg_index + 1 % elsif param.id? % if inst.properties.include?("method_id") auto m_idx<%= id16_index %> = bc_inst->template GetId<<%= format %>, <%= id16_index %>>().AsRawValue(); m_idx<%= id16_index %> = GetRuntime()->ResolveOffsetByIndex(GetGraph()->GetMethod(), m_idx<%= id16_index %>); inst->AddImm(GetGraph()->GetAllocator(), m_idx<%= id16_index %>); % elsif inst.properties.include?("string_id") auto string_id<%= id16_index %> = bc_inst->template GetId<<%= format %>, <%= id16_index %>>().AsRawValue(); string_id<%= id16_index %> = GetRuntime()->ResolveOffsetByIndex(GetGraph()->GetMethod(), string_id<%= id16_index %>); inst->AddImm(GetGraph()->GetAllocator(), string_id<%= id16_index %>); % elsif inst.properties.include?("literalarray_id") auto literalarray_id<%= id16_index %> = bc_inst->template GetId<<%= format %>, <%= id16_index %>>().AsRawValue(); literalarray_id<%= id16_index %> = GetRuntime()->ResolveOffsetByIndex(GetGraph()->GetMethod(), literalarray_id<%= id16_index %>); inst->AddImm(GetGraph()->GetAllocator(), literalarray_id<%= id16_index %>); % end % id16_index = id16_index + 1 % end % end % if is_range_call % range_reg_idx = (name.include? "CREATEOBJECTWITHEXCLUDEDKEYS") ? 1 : 0 % imm_arg_num = has_ic_slot ? 'imm1' : 'imm0' % num_actual_vregs = range_should_exclude_last ? imm_arg_num : imm_arg_num + " + 1" size_t start_reg = bc_inst->GetVReg<<%= format %>, <%= range_reg_idx %>>(); for (uint32_t i = 1; i < <%= num_actual_vregs %>; ++i) { auto input = GetDefinition(start_reg + i); inst->AppendInput(input); inst->AddInputType(DataType::ANY); } % end % if acc_read { auto input = GetDefinitionAcc(); inst->AppendInput(input); inst->AddInputType(DataType::ANY); inst->SetFlag(compiler::inst_flags::ACC_READ); } % end inst->AppendInput(inst_save_state); inst->AddInputType(DataType::NO_TYPE); AddInstruction(inst); % if acc_write UpdateDefinitionAcc(inst); inst->SetFlag(compiler::inst_flags::ACC_WRITE); % end break; } % end default: failed_ = true; LOG(ERROR,COMPILER) << "unknown Ecma opcode!" << static_cast(bc_inst->GetOpcode()); return; } } std::string IntrinsicInst::GetIntrinsicOpcodeName() const { switch(GetIntrinsicId()) { % Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst| case compiler::RuntimeInterface::IntrinsicId::<%= inst.opcode.upcase %>: { return "<%= inst.mnemonic%>"; } % end default: { return ""; } } } } // namespace panda::compiler