1/**
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16<%
17require 'yaml'
18
19Instruction.class_eval do
20  def get_input_idx(index, kind)
21    res = 0
22    index += 1 if has_dst?
23    acc_and_operands.each_with_index do |op, i|
24      break if i == index
25      res += 1 if op.send(kind)
26    end
27    res
28  end
29
30  def inputs
31    @inputs ||= acc_and_operands.select { |x| !x.dst? }
32  end
33
34  def has_dst?
35    !acc_and_operands.empty? && acc_and_operands.first.dst?
36  end
37
38  def get_input_string(index)
39    op = inputs[index]
40    return "GetDefinitionAcc()" if op.acc?
41    return "FindOrCreateConstant(instruction->GetImm<#{get_format}, #{get_input_idx(index, :imm?)}>())" if op.imm?
42    return "GetDefinition(instruction->GetId<#{get_format}, #{get_input_idx(index, :id?)}>().GetOffset())" if op.id?
43    raise "Invalid operand" unless op.reg?
44    return "GetDefinition(instruction->GetVReg<#{get_format}, #{get_input_idx(index, :reg?)}>())"
45  end
46
47  def get_format
48    return "BytecodeInstruction::Format::#{format.pretty.upcase}"
49  end
50
51end
52
53def get_type(type)
54  @type_map ||= {
55    'u1' => 'DataType::BOOL',
56    'i8' => 'DataType::INT8',
57    'i16' => 'DataType::INT16',
58    'i32' => 'DataType::INT32',
59    'i64' => 'DataType::INT64',
60    'u8' => 'DataType::UINT8',
61    'u16' => 'DataType::UINT16',
62    'u32' => 'DataType::UINT32',
63    'u64' => 'DataType::UINT64',
64    'b32' => 'DataType::UINT32',
65    'b64' => 'DataType::UINT64',
66    'f32' => 'DataType::FLOAT32',
67    'f64' => 'DataType::FLOAT64',
68    'ref' => 'DataType::REFERENCE',
69    'any' => 'DataType::ANY',
70    'i8[]' => 'DataType::INT8',
71    'i16[]' => 'DataType::INT16',
72    'i32[]' => 'DataType::INT32',
73    'i64[]' => 'DataType::INT64',
74    'u8[]' => 'DataType::UINT8',
75    'u16[]' => 'DataType::UINT16',
76    'u32[]' => 'DataType::UINT32',
77    'u64[]' => 'DataType::UINT64',
78    'b32[]' => 'DataType::UINT32',
79    'b64[]' => 'DataType::UINT64',
80    'f32[]' => 'DataType::FLOAT32',
81    'f64[]' => 'DataType::FLOAT64',
82    'ref[]' => 'DataType::REFERENCE',
83    'none' => 'DataType::NO_TYPE'}
84  return 'DataType::ANY' if @type_map[type].nil?
85  #  raise "Unknown type #{type}" if @type_map[type].nil?
86  return @type_map[type]
87end
88
89def get_template_by_inst(inst)
90  if (inst.namespace == "ecmascript")
91    return "ecma"
92  else
93    return get_template(inst.stripped_mnemonic)
94  end
95end
96
97def get_template(mn)
98  @tmpl_map ||= {
99    /ldarr/ => "ldarr",
100    /starr$/ => "starr",
101    /^return.*/ => "return",
102    /^[uf]?cmp/ => "cmp",
103    /^[ifu][813264]{1,2}to[ifu][813264]{1,2}$/ => "cast",
104    /^j(eq|ne|lt|gt|le|ge|stricteq|nstricteq)(z|null|undefined)?$/ => "if",
105    /^jmp.*/ => "jump",
106    /(fdiv|fmod|add|sub|mul|and|or|xor|ashr|shr|shl|neg|not)[2i]?$/ => "binop",
107    /^(div|mod)u?[2i]?$/ => "binop_z",
108    /^inci$/ => "inci",
109    /^movi?$/ => "mov",
110    /^fmovi?$/ => "fmovi",
111    /^sta$/ => "sta",
112    /^ldai?$/ => "lda",
113    /^fldai?$/ => "fldai",
114    /^lenarr$/ => "lenarr",
115    /^newarr$/ => "newarr",
116    /^call/ => "call",
117    /^newobj/ => "newobj",
118    /^initobj/ => "initobj",
119    /^ldobj/ => "ldobj",
120    /^stobj/ => "stobj",
121    /^ldstatic/ => "ldstatic",
122    /^ststatic/ => "ststatic",
123    /^isinstance/ => "isinstance",
124    /^checkcast/ => "checkcast",
125    /^throw/ => "throw",
126    /^monitor/ => "monitor",
127    /^nop/ => "nop",
128    /^builtin/ => "builtin",
129    /^ecma/ => "ecma",
130    /^$/ => "unimplemented"
131  }
132  res = @tmpl_map.select { |k, v| mn.match k }
133  raise "Template not found or multiple match: #{mn}" unless res.size == 1
134  return res.first[1]
135end
136
137def template(name, inst, indent, context = {})
138  @inst_yaml ||= YAML.load_file(File.join(File.dirname(__FILE__), '../ir_builder/inst_templates.yaml'))
139  raise "Template '#{name}' not found in templates file" unless @inst_yaml['templates'].key? name
140  indent + ERB.new(@inst_yaml['templates'][name], nil, '%-').result(binding).gsub("\n", "\n#{indent}")
141end
142
143def get_cc(inst)
144  return 'ConditionCode::CC_EQ' if inst.opcode.start_with? 'jeq'
145  return 'ConditionCode::CC_NE' if inst.opcode.start_with? 'jne'
146  return 'ConditionCode::CC_LT' if inst.opcode.start_with? 'jlt'
147  return 'ConditionCode::CC_GT' if inst.opcode.start_with? 'jgt'
148  return 'ConditionCode::CC_LE' if inst.opcode.start_with? 'jle'
149  return 'ConditionCode::CC_GE' if inst.opcode.start_with? 'jge'
150  return 'ConditionCode::CC_EQ' if inst.opcode.start_with? 'jstricteq'
151  return 'ConditionCode::CC_NE' if inst.opcode.start_with? 'jnstricteq'
152  raise 'get_cc: wrong opcode #{inst.opcode}'
153end
154
155%>
156
157#include "bytecode_instruction-inl.h"
158#include "optimizer/ir_builder/inst_builder-inl.h"
159
160namespace panda::compiler {
161
162// NOLINTNEXTLINE(readability-function-size)
163void InstBuilder::BuildInstruction(const BytecodeInstruction* instruction) {
164    switch(instruction->GetOpcode()) {
165% Panda::instructions.each_with_index do |inst, idx|
166%   tmpl = inst.mnemonic.include?('polymorphic') ? 'unimplemented' : get_template_by_inst(inst)
167    // NOLINTNEXTLINE(bugprone-branch-clone)
168    case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
169%   if tmpl == 'unimplemented'
170       // Not implemented
171       failed_ = true;
172%   else
173<%= template(tmpl, inst, ' ' * 8) %>
174%   end
175        break;
176    }
177% end
178    }
179}
180
181// NOLINTNEXTLINE(readability-function-size)
182int64_t InstBuilder::GetInstructionJumpOffset(const BytecodeInstruction* inst) {
183    switch(inst->GetOpcode()) {
184% Panda::instructions.each_with_index do |inst, idx|
185    // NOLINTNEXTLINE(bugprone-branch-clone)
186    case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>:
187%   if inst.jump?
188        return inst->GetImm<BytecodeInstruction::Format::<%= inst.format.pretty.upcase %>, 0, true>();
189%   else
190        return INVALID_OFFSET;
191%   end
192% end
193    }
194    return INVALID_OFFSET;
195}
196
197// TODO(vpukhov): Move this logic from core into plugins/ecmascript
198// Currently we support two strategies for building IR from ecma.* instructions:
199//   1) Each ecma.* instruction is translated to a corresponding intrinsic call.
200//      This is used for bytecode optimizer and slow paths during compiling
201//      to native code (in both JIT and AOT modes).
202//   2) Semantics of each ecma.* instruction is taken from the corresponding
203//      IRtoC handler and is inlined into compiled function.
204//      This is used only for native compilation (again, both JIT and AOT).
205// InstBuilder::BuildEcma selects proper strategy and calls relevant builder.
206
207// NOLINTNEXTLINE(readability-function-size)
208void InstBuilder::BuildEcma([[maybe_unused]] const BytecodeInstruction* bc_inst)
209{
210#ifdef ENABLE_BYTECODE_OPT
211    switch (bc_inst->GetOpcode()) {
212% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
213        case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
214%   if inst.compilable? && inst.inlinable?
215            // +compilable, +inlinable: ecma.* -> intrinsics for BCO, inline IRtoC otherwise:
216            if (GetGraph()->IsBytecodeOptimizer()) {
217                BuildEcmaAsIntrinsics(bc_inst);
218            } else {
219                BuildEcmaAsIntrinsics<true>(bc_inst);
220            }
221%   elsif inst.compilable?
222            // +compilable, -inlinable: ecma.* -> intrinsics for all scenarios:
223            BuildEcmaAsIntrinsics(bc_inst);
224%   else
225%      abort "isa.yaml inconsistency: #{inst.opcode.upcase} is not compilable, but inlinable" if inst.inlinable?
226            // -compilable, -inlinable: ecma.* -> intrinsics for BCO, fail IR builder otherwise:
227            if (GetGraph()->IsBytecodeOptimizer()) {
228                BuildEcmaAsIntrinsics(bc_inst);
229            } else {
230                failed_ = true;
231            }
232%   end
233            break;
234        }
235% end
236        default: {
237            failed_ = true;
238            LOG(ERROR, COMPILER) << "Unknown ecma.* opcode: " << static_cast<int>(bc_inst->GetOpcode());
239            return;
240        }
241    }
242#endif
243}
244
245template <bool with_speculative>
246void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bc_inst) // NOLINT(readability-function-size)
247{
248    switch (bc_inst->GetOpcode()) {
249% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
250%   opc = inst.opcode.upcase
251%   name = opc
252%     acc_read = inst.acc.include?("in")
253%     acc_write = inst.acc.include?("out")
254%     ret_type = acc_write ? "compiler::DataType::ANY" : "compiler::DataType::VOID"
255%     iname = inst.intrinsic_name ? inst.intrinsic_name : opc
256%     intrinsic_id = "compiler::RuntimeInterface::IntrinsicId::" + iname
257%     intrinsic_external_js_id = "compiler::RuntimeInterface::IntrinsicId::" + opc
258        case BytecodeInstruction::Opcode::<%= opc %>: {
259            #ifdef ARK_INTRINSIC_SET
260            auto intrinsic_id = <%= intrinsic_id %>;
261            #else
262            auto intrinsic_id = <%= intrinsic_external_js_id %>;
263            #endif
264            auto inst = GetGraph()->CreateInstIntrinsic(<%= ret_type %>, GetPc(bc_inst->GetAddress()), intrinsic_id);
265%     if inst.throwing?
266            inst->SetFlag(inst_flags::CAN_THROW);
267%     end
268%     if inst.exceptions.include?('x_throw') || inst.properties.include?('return')
269            inst->SetFlag(inst_flags::CF);
270            inst->SetFlag(inst_flags::TERMINATOR);
271%     end
272%     params_arr = inst.operands
273%     format = "BytecodeInstruction::Format::" + inst.format.pretty.upcase
274%     range_should_exclude_last = (name.include? "NEWOBJRANGE") || (name.include? "SUPERCALLTHISRANGE") || (name.include? "SUPERCALLARROWRANGE") || (name.include? "CALLRANGE")
275%     is_range_call = (name.include? "CALLTHISRANGE") || (name.include? "CREATEOBJECTWITHEXCLUDEDKEYS") || range_should_exclude_last
276%     need_newtarget = (name.include? "SUPERCALLSPREAD") || (name.include? "SUPERCALL")
277%     num_vregs = params_arr.select{|b| b.reg?}.length
278%     num_imms = params_arr.select{|b| b.imm?}.length
279%     num_ids = params_arr.select{|b| b.id?}.length
280%     num_inputs = acc_read ? num_vregs + 2 : num_vregs + 1
281%     if range_should_exclude_last
282%       num_inputs = num_inputs - 1
283%     end
284%     if need_newtarget
285%       num_inputs = num_inputs + 1
286%     end
287%     has_ic_slot = inst.properties.include?('ic_slot') || inst.properties.include?('jit_ic_slot')
288%     if is_range_call
289            size_t args_count = <%= num_inputs %>U + static_cast<size_t>(bc_inst->GetImm<<%= format %>, <%= has_ic_slot ? 1 : 0 %>, false>());
290%     else
291            size_t args_count {<%= num_inputs %>U};
292%     end
293%     if need_newtarget
294            if (GetGraph()->IsBytecodeOptimizer()) {
295                --args_count;
296            }
297%     end
298            inst->ReserveInputs(args_count);
299            inst->AllocateInputTypes(GetGraph()->GetAllocator(), args_count);
300            // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
301            if constexpr (with_speculative) {
302                inst->SetInlined(true);
303            }
304
305%     imm_index = 0
306%     vreg_index = 0
307%     id16_index = 0
308            auto inst_save_state = CreateSaveState(Opcode::SaveState, GetPc(bc_inst->GetAddress()));
309            AddInstruction(inst_save_state);
310%     params_arr.each do |param|
311%       if param.imm?
312            auto imm<%= imm_index %> = static_cast<uint32_t>(bc_inst->GetImm<<%= format %>, <%= imm_index %>, false>());
313            inst->AddImm(GetGraph()->GetAllocator(), imm<%= imm_index %>);
314%           imm_index = imm_index + 1
315%       elsif param.reg?
316            {
317                auto input = GetDefinition(bc_inst->GetVReg<<%= format %>, <%= vreg_index %>>());
318                inst->AppendInput(input);
319                inst->AddInputType(DataType::ANY);
320            }
321%           vreg_index = vreg_index + 1
322%       elsif param.id?
323%         if inst.properties.include?("method_id")
324            auto m_idx<%= id16_index %> = bc_inst->template GetId<<%= format %>, <%= id16_index %>>().AsRawValue();
325            m_idx<%= id16_index %> = GetRuntime()->ResolveOffsetByIndex(GetGraph()->GetMethod(), m_idx<%= id16_index %>);
326            inst->AddImm(GetGraph()->GetAllocator(), m_idx<%= id16_index %>);
327%         elsif inst.properties.include?("string_id")
328            auto string_id<%= id16_index %> = bc_inst->template GetId<<%= format %>, <%= id16_index %>>().AsRawValue();
329            string_id<%= id16_index %> = GetRuntime()->ResolveOffsetByIndex(GetGraph()->GetMethod(), string_id<%= id16_index %>);
330            inst->AddImm(GetGraph()->GetAllocator(), string_id<%= id16_index %>);
331%         elsif inst.properties.include?("literalarray_id")
332            auto literalarray_id<%= id16_index %> = bc_inst->template GetId<<%= format %>, <%= id16_index %>>().AsRawValue();
333            literalarray_id<%= id16_index %> = GetRuntime()->ResolveOffsetByIndex(GetGraph()->GetMethod(), literalarray_id<%= id16_index %>);
334            inst->AddImm(GetGraph()->GetAllocator(), literalarray_id<%= id16_index %>);
335%         end
336%         id16_index = id16_index + 1
337%       end
338%     end
339%     if is_range_call
340%           range_reg_idx = (name.include? "CREATEOBJECTWITHEXCLUDEDKEYS") ? 1 : 0
341%           imm_arg_num = has_ic_slot ? 'imm1' : 'imm0'
342%           num_actual_vregs = range_should_exclude_last ? imm_arg_num : imm_arg_num + " + 1"
343            size_t start_reg = bc_inst->GetVReg<<%= format %>, <%= range_reg_idx %>>();
344            for (uint32_t i = 1; i < <%= num_actual_vregs %>; ++i) {
345                auto input = GetDefinition(start_reg + i);
346                inst->AppendInput(input);
347                inst->AddInputType(DataType::ANY);
348            }
349%     end
350%     if acc_read
351            {
352                auto input = GetDefinitionAcc();
353                inst->AppendInput(input);
354                inst->AddInputType(DataType::ANY);
355                inst->SetFlag(compiler::inst_flags::ACC_READ);
356            }
357%     end
358            inst->AppendInput(inst_save_state);
359            inst->AddInputType(DataType::NO_TYPE);
360            AddInstruction(inst);
361%     if acc_write
362            UpdateDefinitionAcc(inst);
363            inst->SetFlag(compiler::inst_flags::ACC_WRITE);
364%     end
365            break;
366        }
367% end
368        default:
369            failed_ = true;
370            LOG(ERROR,COMPILER) << "unknown Ecma opcode!" << static_cast<int>(bc_inst->GetOpcode());
371            return;
372  }
373}
374
375std::string IntrinsicInst::GetIntrinsicOpcodeName() const
376{
377    switch(GetIntrinsicId()) {
378% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
379        case compiler::RuntimeInterface::IntrinsicId::<%= inst.opcode.upcase %>: {
380            return "<%= inst.mnemonic%>";
381        }
382% end
383        default: {
384            return "";
385        }
386    }
387}
388}  // namespace panda::compiler
389