1# plugin interpreter_handlers 2# Copyright (c) 2021-2025 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 15include_relative '../../plugins/ets/irtoc_scripts/common.irt' 16 17macro(:lookup_field_by_name) do |v, id| 18 res := cache_entry(id, false, false, :ptr, nil).ptr 19 res_u64 := Bitcast(res).SrcType("DataType::POINTER").u64 20 method_flag = AndI(res_u64).Imm(0x1).u64 21 If(res, 0).NE.Likely { 22 If(method_flag, 0x1).NE.Likely { 23 field_class := LoadI(res).Imm(Constants::FIELD_CLASS_OFFSET).ref 24 If(field_class, v).EQ.Likely { 25 field_0 := res 26 Goto(:Exit_0) 27 } 28 } 29 } 30 field_1 := call_runtime("LookupFieldByNameEntrypoint", get_cache_entry_ptr(), v, id, get_method_ptr(), %pc).ptr 31 Label(:Exit_0) 32 Phi(field_0.ptr, field_1.ptr).ptr 33end 34 35['short', 'long', 'obj'].each do |flavor| 36 macro(:"lookup_getter_by_name_#{flavor}") do |v, id| 37 res := cache_entry(id, false, false, :ptr, nil) 38 res_u64 := Bitcast(res).SrcType("DataType::POINTER").u64 39 method_flag = AndI(res_u64).Imm(0x1).u64 40 If(res, 0).NE.Likely { 41 If(method_flag, 0x1).EQ.Likely { 42 method_class := LoadI(res).Imm(Constants::METHOD_CLASS_OFFSET).ref 43 If(method_class, v).EQ.Likely { 44 method_0 := res 45 Goto(:Exit_1) 46 } 47 } 48 } 49 method_1 := call_runtime("LookupGetterByName#{flavor.capitalize}Entrypoint", get_cache_entry_ptr(), v, id, get_method_ptr(), %pc).ptr 50 Label(:Exit_1) 51 Phi(method_0, method_1).ptr 52 end 53end 54 55['short', 'long', 'obj'].each do |flavor| 56 macro(:"lookup_setter_by_name_#{flavor}") do |v, id| 57 res := cache_entry(id, false, false, :ptr, nil) 58 res_u64 := Bitcast(res).SrcType("DataType::POINTER").u64 59 method_flag = AndI(res_u64).Imm(0x1).u64 60 If(res, 0).NE.Likely { 61 If(method_flag, 0x1).EQ.Likely { 62 method_class := LoadI(res).Imm(Constants::METHOD_CLASS_OFFSET).ref 63 If(method_class, v).EQ.Likely { 64 method_0 := res 65 Goto(:Exit_1) 66 } 67 } 68 } 69 method_1 := call_runtime("LookupSetterByName#{flavor.capitalize}Entrypoint", get_cache_entry_ptr(), v, id, get_method_ptr(), %pc).ptr 70 Label(:Exit_1) 71 Phi(method_0, method_1).ptr 72 end 73end 74 75macro(:lookup_method_by_name) do |v, id| 76 res := cache_entry(id, false, false, :ptr, nil) 77 res_u64 := Bitcast(res).SrcType("DataType::POINTER").u64 78 method_flag = AndI(res_u64).Imm(0x1).u64 79 If(res, 0).NE.Likely { 80 If(method_flag, 0x1).EQ.Likely { 81 method_class := LoadI(res).Imm(Constants::METHOD_CLASS_OFFSET).ref 82 If(method_class, v).EQ.Likely { 83 method_0 := res 84 Goto(:Exit_1) 85 } 86 } 87 } 88 method_1 := call_runtime("LookupMethodByNameEntrypoint", get_cache_entry_ptr(), v, id, get_method_ptr(), %pc).ptr 89 Label(:Exit_1) 90 Phi(method_0, method_1).ptr 91end 92 93['static', 'virt'].each do |dispatch| 94 ['short', 'long', 'range'].each do |flavor| 95 macro(:"handle_ets_launch_#{dispatch}_#{flavor}") do |v, id| 96 # TODO(mbolshov): verify method 97 method_ptr = get_callee(id, dispatch == 'virt', false, v) 98 verify(method_ptr, false) 99 set_acc_object(call_runtime("LaunchFromInterpreter#{flavor.capitalize}", method_ptr, %frame, pc).ptr) 100 If(acc, 0).EQ { 101 move_to_exception 102 } 103 end 104 end 105end 106 107macro(:handle_ets_ldnullvalue) do 108 set_acc_object(ets_nullvalue).ref 109end 110 111macro(:handle_ets_movnullvalue) do |vd| 112 set_object(vd, ets_nullvalue).ref 113end 114 115macro(:handle_ets_isnullvalue) do 116 set_acc_primitive(btou32(Compare(acc.ref, ets_nullvalue).b)) 117end 118 119macro(:"load_field_short") do |field, klass| 120 acc_field := acc 121 offset := LoadI(field).Imm(Constants::FIELD_OFFSET_OFFSET).u32 122 field_access_flags := LoadI(field).Imm(Constants::FIELD_ACCESS_FLAGS_OFFSET).u32 123 field_type_id := ShrI(AndI(field_access_flags).Imm("ACC_TYPE").u32).Imm("ACC_TYPE_SHIFT").u32 124 If(field_type_id, 0x7).LT { 125 [[0x2, "u8"], [0x3, "i8"], [0x4, "u8"], [0x5, "i16"], [0x6, "u16"]].each do |typeid, field_type| 126 If(field_type_id, typeid).EQ { 127 store_type = field_type[0] + "32" 128 value_0 := Load(klass, offset).send(:"#{field_type}") 129 acc_fast_value_0 := send(:"#{field_type}to#{store_type}", value_0.send(:"#{field_type}")) 130 } 131 acc_field := Phi(acc_field.u64, acc_fast_value_0.u64).u64 132 end 133 acc_fast_0 := acc_field 134 } Else { 135 value_1 := Load(klass, offset).u32 136 acc_fast_1 := u32tou64(value_1.u32) 137 } 138 Phi(acc_fast_0.u64, acc_fast_1.u64).u64 139end 140 141macro(:"load_field_long") do |field, klass| 142 offset := LoadI(field).Imm(Constants::FIELD_OFFSET_OFFSET).u32 143 Load(klass, offset).u64 144end 145 146macro(:"load_field_obj") do |field, klass| 147 offset := LoadI(field).Imm(Constants::FIELD_OFFSET_OFFSET).u32 148 Load(klass, offset).ref 149end 150 151['short', 'long', 'obj'].each do |flavor| 152 macro(:"handle_ets_ldobj_name_#{flavor}") do |v, id, size| 153 klass := vreg_value(v).ref 154 If(klass, 0).EQ.Unlikely { 155 call_runtime("ThrowNullPointerExceptionFromInterpreter").void 156 move_to_exception 157 } 158 field := lookup_field_by_name(klass, id) 159 if flavor != 'obj' 160 acc := acc.u64 161 else 162 acc := acc.ref 163 end 164 If(field, 0).NE.Likely { 165 acc_fast := send(:"load_field_#{flavor}", field, klass) 166 } Else { 167 method := send(:"lookup_getter_by_name_#{flavor}", klass, id) 168 If(method, 0).EQ.Unlikely { 169 call_runtime("ThrowEtsExceptionNoSuchGetterEntrypoint", klass, id, get_method_ptr()).void 170 move_to_exception 171 } 172 method_flags := LoadI(method).Imm(Constants::METHOD_ACCESS_FLAGS_OFFSET).u32 173 nargs := u32toword(LoadI(method).Imm(Constants::METHOD_NUM_ARGS_OFFSET).u32) 174 generic_call(id, size, false, method, nargs, lambda do |new_frame, num_vregs, _, new_moffset| 175 copy_reg(new_frame, num_vregs, v, new_moffset) 176 end) 177 dec_pc := SubI(pc).Imm(size).ptr 178 } 179 if flavor != 'obj' 180 set_acc_primitive(Phi(acc_fast.u64, acc.u64).u64) 181 else 182 set_acc_object(Phi(acc_fast.ref, acc.ref).ref) 183 end 184 frame := Phi(%frame, frame).ptr 185 if Options.arm64? 186 moffset := Phi(%moffset, moffset).word 187 method_ptr := Phi(%method_ptr, method_ptr).ptr 188 end 189 pc := Phi(%pc, dec_pc).ptr 190 end 191end 192 193macro(:"store_field_short") do |field, klass| 194 acc_field := acc 195 offset := LoadI(field).Imm(Constants::FIELD_OFFSET_OFFSET).u32 196 field_access_flags := LoadI(field).Imm(Constants::FIELD_ACCESS_FLAGS_OFFSET).u32 197 field_type_id := ShrI(AndI(field_access_flags).Imm("ACC_TYPE").u32).Imm("ACC_TYPE_SHIFT").u32 198 If(field_type_id, 0x7).LT { 199 [[0x2, "u8"], [0x3, "i8"], [0x4, "u8"], [0x5, "i16"], [0x6, "u16"]].each do |typeid, field_type| 200 If(field_type_id, typeid).EQ { 201 acc_type = field_type[0] + "32" 202 Store(klass, offset, acc_field.send(:"#{acc_type}")).send(:"#{field_type}") 203 } 204 end 205 } Else { 206 Store(klass, offset, acc_field.u32).u32 207 } 208end 209 210macro(:"store_field_long") do |field, klass| 211 offset := LoadI(field).Imm(Constants::FIELD_OFFSET_OFFSET).u32 212 Store(klass, offset, acc.u64).u64 213end 214 215macro(:"store_field_obj") do |field, klass| 216 offset := LoadI(field).Imm(Constants::FIELD_OFFSET_OFFSET).u32 217 Store(klass, offset, acc).SetNeedBarrier(true).ref 218end 219 220['short', 'long', 'obj'].each do |flavor| 221 macro(:"handle_ets_stobj_name_#{flavor}") do |v, id, size| 222 klass := vreg_value(v).ref 223 If(klass, 0).EQ.Unlikely { 224 call_runtime("ThrowNullPointerExceptionFromInterpreter").void 225 move_to_exception 226 } 227 field := lookup_field_by_name(klass, id) 228 If(field, 0).NE.Likely { 229 send(:"store_field_#{flavor}", field, klass) 230 Goto(:Exit_) 231 } Else { 232 method := send(:"lookup_setter_by_name_#{flavor}", klass, id) 233 If(method, 0).EQ.Unlikely { 234 call_runtime("ThrowEtsExceptionNoSuchSetterEntrypoint", klass, id, get_method_ptr()).void 235 move_to_exception 236 } 237 method_flags := LoadI(method).Imm(Constants::METHOD_ACCESS_FLAGS_OFFSET).u32 238 nargs := u32toword(LoadI(method).Imm(Constants::METHOD_NUM_ARGS_OFFSET).u32) 239 generic_call(id, size, false, method, nargs, lambda do |new_frame, num_vregs, _, new_moffset| 240 copy_reg(new_frame, num_vregs, v, new_moffset) 241 copy_acc_to_reg(new_frame, frame_vreg_ptr(new_frame, AddI(num_vregs).Imm(1).word), new_moffset) 242 end) 243 dec_pc := SubI(pc).Imm(size).ptr 244 } 245 Label(:Exit_) 246 if flavor == 'short' 247 acc := Phi(%acc, %acc, acc).u32 248 elsif flavor == 'long' 249 acc := Phi(%acc, %acc, acc).u64 250 elsif flavor == 'obj' 251 acc := Phi(%acc, %acc, acc).ref 252 end 253 acc_tag := Phi(%acc_tag, %acc_tag, acc_tag).u64 254 frame := Phi(%frame, %frame, frame).ptr 255 if Options.arm64? 256 moffset := Phi(%moffset, %moffset, moffset).word 257 method_ptr := Phi(%method_ptr, %method_ptr, method_ptr).ptr 258 end 259 pc := Phi(%pc, %pc, dec_pc).ptr 260 end 261end 262 263macro(:is_null) do |obj| 264 Compare(obj, 0).b 265end 266 267macro(:is_nullvalue) do |obj| 268 Compare(obj, ets_nullvalue).b 269end 270 271macro(:is_nullish) do |obj| 272 Or(is_null(obj).b, is_nullvalue(obj).b).b 273end 274 275macro(:is_string_cls) do |obj| 276 obj_flags := LoadI(obj).Imm(Constants::BASE_CLASS_FLAGS_OFFSET).u32 277 Compare(AndI(obj_flags).Imm("ark::Class::STRING_CLASS").u32, 0).NE.b 278end 279 280macro(:is_valuetyped_cls) do |obj| 281 call_runtime("IsClassValueTypedEntrypoint", obj).b 282end 283 284macro(:handle_ets_typeof) do |obj| 285 set_acc_object(call_runtime("EtsGetTypeofEntrypoint", %tr, obj).ref) 286end 287 288macro(:handle_ets_istrue) do |v| 289 set_acc_primitive(btou8(call_runtime("EtsIstrueEntrypoint", %tr, v).b)) 290end 291 292['', '_strict'].each do |suffix| 293 macro(:"handle_ets_equals#{suffix}") do |v1, v2| 294 If(Compare(v1, v2).b, 0).NE.Unlikely { 295 result_0 := 1 296 } Else { 297 v1isnullish = is_nullish(v1) 298 v2isnullish = is_nullish(v2) 299 If(Or(v1isnullish, v2isnullish).b, 0).NE { 300 if suffix.empty? 301 result_1 := And(v1isnullish, v2isnullish).b 302 else 303 result_1 := 0 304 end 305 } Else { 306 v1cls := load_class(v1) 307 v2cls := load_class(v2) 308 If(And(is_valuetyped_cls(v1cls), is_valuetyped_cls(v2cls)).b, 0).EQ.Likely { 309 result_2 := 0 310 } Else { 311 result_3 := call_runtime("CompareETSValueTypedEntrypoint", %tr, v1, v2).b 312 } 313 } 314 } 315 result := Phi(result_0, result_1, result_2, result_3).b 316 set_acc_primitive(btou8(result)) 317 end 318end 319 320macro(:"handle_ets_call_name_short") do |v1, v2, id, size| 321 klass := vreg_value(v1).ref 322 If(klass, 0).EQ.Unlikely { 323 call_runtime("ThrowNullPointerExceptionFromInterpreter").void 324 move_to_exception 325 } 326 method := lookup_method_by_name(klass, id) 327 If(method, 0).EQ.Unlikely { 328 call_runtime("ThrowEtsExceptionNoSuchMethodEntrypoint", klass, id, get_method_ptr()).void 329 move_to_exception 330 } 331 method_flags := LoadI(method).Imm(Constants::METHOD_ACCESS_FLAGS_OFFSET).u32 332 copy_lambda := lambda { |new_frame, num_vregs, _, new_moffset| 333 copy_reg(new_frame, num_vregs, v1, new_moffset) 334 copy_reg(new_frame, AddI(num_vregs).Imm(1).word, v2, new_moffset) 335 } 336 generic_call(id, size, false, method, 2, copy_lambda) 337end 338 339macro(:"handle_ets_call_name_long") do |v1, v2, v3, v4, id, size| 340 klass := vreg_value(v1).ref 341 If(klass, 0).EQ.Unlikely { 342 call_runtime("ThrowNullPointerExceptionFromInterpreter").void 343 move_to_exception 344 } 345 method := lookup_method_by_name(klass, id) 346 If(method, 0).EQ.Unlikely { 347 call_runtime("ThrowEtsExceptionNoSuchMethodEntrypoint", klass, id, get_method_ptr()).void 348 move_to_exception 349 } 350 method_flags := LoadI(method).Imm(Constants::METHOD_ACCESS_FLAGS_OFFSET).u32 351 copy_lambda := lambda { |new_frame, num_vregs, _, new_moffset| 352 copy_reg(new_frame, num_vregs, v1, new_moffset) 353 copy_reg(new_frame, AddI(num_vregs).Imm(1).word, v2, new_moffset) 354 copy_reg(new_frame, AddI(num_vregs).Imm(2).word, v3, new_moffset) 355 copy_reg(new_frame, AddI(num_vregs).Imm(3).word, v4, new_moffset) 356 } 357 generic_call(id, size, false, method, 4, copy_lambda) 358end 359 360macro(:"handle_ets_call_name_range") do |v, id, size| 361 klass := vreg_value(v).ref 362 If(klass, 0).EQ.Unlikely { 363 call_runtime("ThrowNullPointerExceptionFromInterpreter").void 364 move_to_exception 365 } 366 method := lookup_method_by_name(klass, id) 367 If(method, 0).EQ.Unlikely { 368 call_runtime("ThrowEtsExceptionNoSuchMethodEntrypoint", klass, id, get_method_ptr()).void 369 move_to_exception 370 } 371 method_flags := LoadI(method).Imm(Constants::METHOD_ACCESS_FLAGS_OFFSET).u32 372 copy_lambda := lambda { |new_frame, num_vregs, num_args, new_moffset| 373 dst_ptr_0 := frame_vreg_ptr(new_frame, num_vregs) 374 src_ptr_0 := vreg_ptr(v) 375 i0 := 0 376 Label(:Head) # TODO(mbolshov): use While loops when they are ready 377 i := Phi(i0, i1).word 378 If(i, num_args).EQ.Unlikely do 379 Goto(:Exit) 380 end 381 offset := Mul(i, Constants::VREGISTER_SIZE).word 382 dst_ptr := Add(dst_ptr_0, offset).ptr 383 src_ptr := Add(src_ptr_0, offset).ptr 384 set_value(dst_ptr, get_value(src_ptr).i64) 385 set_tag_frame(new_frame, dst_ptr, get_tag(src_ptr), new_moffset) 386 i1 := Add(i, 1).word 387 Goto(:Head) 388 Label(:Exit) 389 } 390 generic_call(id, size, false, method, nil, copy_lambda) 391end 392