diff options
author | 2015-09-27 19:19:06 -0700 | |
---|---|---|
committer | 2015-09-27 19:19:23 -0700 | |
commit | 6918bf13eb855b3aa8ccdddda2d27ae8c60cec56 (patch) | |
tree | 907f504a4d004ac1e44b73c3984e365e889407e8 | |
parent | 446ca43e8f876dcc1ee90fcf432d6392c44a3f60 (diff) |
Revert "Revert "lambda: Experimental support for capture-variable and liberate-variable""
This reverts commit 7bbb80ab52c203e44d2ded2c947b3b03b4b31ec4.
Change-Id: If806ce5c6c5e96fdb2c3761dee096f74e7e5b001
27 files changed, 1230 insertions, 172 deletions
diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc index b5ecf9c418..1cd742abac 100644 --- a/compiler/dex/quick/quick_compiler.cc +++ b/compiler/dex/quick/quick_compiler.cc @@ -391,9 +391,9 @@ static int kAllOpcodes[] = { Instruction::IGET_SHORT_QUICK, Instruction::INVOKE_LAMBDA, Instruction::UNUSED_F4, - Instruction::UNUSED_F5, + Instruction::CAPTURE_VARIABLE, Instruction::CREATE_LAMBDA, - Instruction::UNUSED_F7, + Instruction::LIBERATE_VARIABLE, Instruction::BOX_LAMBDA, Instruction::UNBOX_LAMBDA, Instruction::UNUSED_FA, diff --git a/runtime/Android.mk b/runtime/Android.mk index 995a1d5c0d..059c4cdd57 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -104,6 +104,7 @@ LIBART_COMMON_SRC_FILES := \ lambda/box_table.cc \ lambda/closure.cc \ lambda/closure_builder.cc \ + lambda/leaking_allocator.cc \ jni_internal.cc \ jobject_comparator.cc \ linear_alloc.cc \ diff --git a/runtime/dex_instruction-inl.h b/runtime/dex_instruction-inl.h index 7344d13805..e160a103d9 100644 --- a/runtime/dex_instruction-inl.h +++ b/runtime/dex_instruction-inl.h @@ -454,8 +454,8 @@ inline bool Instruction::HasVarArgs25x() const { return FormatOf(Opcode()) == k25x; } -// Copies all of the parameter registers into the arg array. Check the length with VRegB_25x()+1. -inline void Instruction::GetAllArgs25x(uint32_t arg[kMaxVarArgRegs]) const { +// Copies all of the parameter registers into the arg array. Check the length with VRegB_25x()+2. +inline void Instruction::GetAllArgs25x(uint32_t (&arg)[kMaxVarArgRegs25x]) const { DCHECK_EQ(FormatOf(Opcode()), k25x); /* @@ -500,19 +500,21 @@ inline void Instruction::GetAllArgs25x(uint32_t arg[kMaxVarArgRegs]) const { */ switch (count) { case 4: - arg[4] = (Fetch16(0) >> 8) & 0x0f; // vG + arg[5] = (Fetch16(0) >> 8) & 0x0f; // vG FALLTHROUGH_INTENDED; case 3: - arg[3] = (reg_list >> 12) & 0x0f; // vF + arg[4] = (reg_list >> 12) & 0x0f; // vF FALLTHROUGH_INTENDED; case 2: - arg[2] = (reg_list >> 8) & 0x0f; // vE + arg[3] = (reg_list >> 8) & 0x0f; // vE FALLTHROUGH_INTENDED; case 1: - arg[1] = (reg_list >> 4) & 0x0f; // vD + arg[2] = (reg_list >> 4) & 0x0f; // vD FALLTHROUGH_INTENDED; default: // case 0 + // The required lambda 'this' is actually a pair, but the pair is implicit. arg[0] = VRegC_25x(); // vC + arg[1] = arg[0] + 1; // vC + 1 break; } } diff --git a/runtime/dex_instruction.cc b/runtime/dex_instruction.cc index fc4df1475a..5250b0d79b 100644 --- a/runtime/dex_instruction.cc +++ b/runtime/dex_instruction.cc @@ -322,10 +322,10 @@ std::string Instruction::DumpString(const DexFile* file) const { } case k25x: { if (Opcode() == INVOKE_LAMBDA) { - uint32_t arg[kMaxVarArgRegs]; + uint32_t arg[kMaxVarArgRegs25x]; GetAllArgs25x(arg); const size_t num_extra_var_args = VRegB_25x(); - DCHECK_LE(num_extra_var_args + 1, kMaxVarArgRegs); + DCHECK_LE(num_extra_var_args + 2, arraysize(arg)); // invoke-lambda vC, {vD, vE, vF, vG} os << opcode << " v" << arg[0] << ", {"; @@ -333,7 +333,7 @@ std::string Instruction::DumpString(const DexFile* file) const { if (i != 0) { os << ", "; } - os << "v" << arg[i+1]; + os << "v" << arg[i+2]; // Don't print the pair of vC registers. Pair is implicit. } os << "}"; break; diff --git a/runtime/dex_instruction.h b/runtime/dex_instruction.h index df2d3799ab..48a12e53af 100644 --- a/runtime/dex_instruction.h +++ b/runtime/dex_instruction.h @@ -180,9 +180,11 @@ class Instruction { kVerifyVarArgRangeNonZero = 0x100000, kVerifyRuntimeOnly = 0x200000, kVerifyError = 0x400000, + kVerifyRegCString = 0x800000, }; static constexpr uint32_t kMaxVarArgRegs = 5; + static constexpr uint32_t kMaxVarArgRegs25x = 6; // lambdas are 2 registers. // Returns the size (in 2 byte code units) of this instruction. size_t SizeInCodeUnits() const { @@ -408,7 +410,7 @@ class Instruction { void GetVarArgs(uint32_t args[kMaxVarArgRegs]) const { return GetVarArgs(args, Fetch16(0)); } - void GetAllArgs25x(uint32_t args[kMaxVarArgRegs]) const; + void GetAllArgs25x(uint32_t (&args)[kMaxVarArgRegs25x]) const; // Returns the opcode field of the instruction. The given "inst_data" parameter must be the first // 16 bits of instruction. @@ -536,7 +538,7 @@ class Instruction { int GetVerifyTypeArgumentC() const { return (kInstructionVerifyFlags[Opcode()] & (kVerifyRegC | kVerifyRegCField | - kVerifyRegCNewArray | kVerifyRegCType | kVerifyRegCWide)); + kVerifyRegCNewArray | kVerifyRegCType | kVerifyRegCWide | kVerifyRegCString)); } int GetVerifyExtraFlags() const { diff --git a/runtime/dex_instruction_list.h b/runtime/dex_instruction_list.h index a176772a84..9d7e0c4409 100644 --- a/runtime/dex_instruction_list.h +++ b/runtime/dex_instruction_list.h @@ -263,10 +263,10 @@ V(0xF2, IGET_SHORT_QUICK, "iget-short-quick", k22c, true, kIndexFieldOffset, kContinue | kThrow | kLoad | kRegCFieldOrConstant, kVerifyRegA | kVerifyRegB | kVerifyRuntimeOnly) \ V(0xF3, INVOKE_LAMBDA, "invoke-lambda", k25x, false, kIndexNone, kContinue | kThrow | kInvoke | kExperimental, kVerifyRegC /*TODO: | kVerifyVarArg*/) \ V(0xF4, UNUSED_F4, "unused-f4", k10x, false, kIndexUnknown, 0, kVerifyError) \ - V(0xF5, UNUSED_F5, "unused-f5", k10x, false, kIndexUnknown, 0, kVerifyError) \ + V(0xF5, CAPTURE_VARIABLE, "capture-variable", k21c, false, kIndexStringRef, kExperimental, kVerifyRegA | kVerifyRegBString) \ /* TODO(iam): get rid of the unused 'false' column */ \ V(0xF6, CREATE_LAMBDA, "create-lambda", k21c, false_UNUSED, kIndexMethodRef, kContinue | kThrow | kExperimental, kVerifyRegA | kVerifyRegBMethod) \ - V(0xF7, UNUSED_F7, "unused-f7", k10x, false, kIndexUnknown, 0, kVerifyError) \ + V(0xF7, LIBERATE_VARIABLE, "liberate-variable", k22c, false, kIndexStringRef, kExperimental, kVerifyRegA | kVerifyRegB | kVerifyRegCString) \ V(0xF8, BOX_LAMBDA, "box-lambda", k22x, true, kIndexNone, kContinue | kExperimental, kVerifyRegA | kVerifyRegB) \ V(0xF9, UNBOX_LAMBDA, "unbox-lambda", k22c, true, kIndexTypeRef, kContinue | kThrow | kExperimental, kVerifyRegA | kVerifyRegB | kVerifyRegCType) \ V(0xFA, UNUSED_FA, "unused-fa", k10x, false, kIndexUnknown, 0, kVerifyError) \ diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 5fbd687452..2de12dcec2 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -27,6 +27,9 @@ namespace art { namespace interpreter { +// All lambda closures have to be a consecutive pair of virtual registers. +static constexpr size_t kLambdaVirtualRegisterWidth = 2; + void ThrowNullPointerExceptionFromInterpreter() { ThrowNullPointerExceptionFromDexPC(); } @@ -483,13 +486,16 @@ void AbortTransactionV(Thread* self, const char* fmt, va_list args) { } // Separate declaration is required solely for the attributes. -template<bool is_range, bool do_assignability_check> SHARED_REQUIRES(Locks::mutator_lock_) +template <bool is_range, + bool do_assignability_check, + size_t kVarArgMax> + SHARED_REQUIRES(Locks::mutator_lock_) static inline bool DoCallCommon(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame, JValue* result, uint16_t number_of_inputs, - uint32_t arg[Instruction::kMaxVarArgRegs], + uint32_t (&arg)[kVarArgMax], uint32_t vregC) ALWAYS_INLINE; SHARED_REQUIRES(Locks::mutator_lock_) @@ -509,13 +515,15 @@ static inline bool NeedsInterpreter(Thread* self, ShadowFrame* new_shadow_frame) Dbg::IsForcedInterpreterNeededForCalling(self, target); } -template<bool is_range, bool do_assignability_check> +template <bool is_range, + bool do_assignability_check, + size_t kVarArgMax> static inline bool DoCallCommon(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame, JValue* result, uint16_t number_of_inputs, - uint32_t arg[Instruction::kMaxVarArgRegs], + uint32_t (&arg)[kVarArgMax], uint32_t vregC) { bool string_init = false; // Replace calls to String.<init> with equivalent StringFactory call. @@ -560,10 +568,10 @@ static inline bool DoCallCommon(ArtMethod* called_method, number_of_inputs--; // Rewrite the var-args, dropping the 0th argument ("this") - for (uint32_t i = 1; i < Instruction::kMaxVarArgRegs; ++i) { + for (uint32_t i = 1; i < arraysize(arg); ++i) { arg[i - 1] = arg[i]; } - arg[Instruction::kMaxVarArgRegs - 1] = 0; + arg[arraysize(arg) - 1] = 0; // Rewrite the non-var-arg case vregC++; // Skips the 0th vreg in the range ("this"). @@ -669,7 +677,7 @@ static inline bool DoCallCommon(ArtMethod* called_method, AssignRegister(new_shadow_frame, shadow_frame, dest_reg, src_reg); } } else { - DCHECK_LE(number_of_inputs, Instruction::kMaxVarArgRegs); + DCHECK_LE(number_of_inputs, arraysize(arg)); for (; arg_index < number_of_inputs; ++arg_index) { AssignRegister(new_shadow_frame, shadow_frame, first_dest_reg + arg_index, arg[arg_index]); @@ -736,12 +744,13 @@ bool DoLambdaCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_fr const Instruction* inst, uint16_t inst_data, JValue* result) { const uint4_t num_additional_registers = inst->VRegB_25x(); // Argument word count. - const uint16_t number_of_inputs = num_additional_registers + 1; - // The first input register is always present and is not encoded in the count. + const uint16_t number_of_inputs = num_additional_registers + kLambdaVirtualRegisterWidth; + // The lambda closure register is always present and is not encoded in the count. + // Furthermore, the lambda closure register is always wide, so it counts as 2 inputs. // TODO: find a cleaner way to separate non-range and range information without duplicating // code. - uint32_t arg[Instruction::kMaxVarArgRegs]; // only used in invoke-XXX. + uint32_t arg[Instruction::kMaxVarArgRegs25x]; // only used in invoke-XXX. uint32_t vregC = 0; // only used in invoke-XXX-range. if (is_range) { vregC = inst->VRegC_3rc(); @@ -767,7 +776,7 @@ bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame, // TODO: find a cleaner way to separate non-range and range information without duplicating // code. - uint32_t arg[Instruction::kMaxVarArgRegs]; // only used in invoke-XXX. + uint32_t arg[Instruction::kMaxVarArgRegs] = {}; // only used in invoke-XXX. uint32_t vregC = 0; if (is_range) { vregC = inst->VRegC_3rc(); diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 7398778d15..f57bddbb4f 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -34,7 +34,12 @@ #include "dex_instruction-inl.h" #include "entrypoints/entrypoint_utils-inl.h" #include "handle_scope-inl.h" +#include "lambda/art_lambda_method.h" #include "lambda/box_table.h" +#include "lambda/closure.h" +#include "lambda/closure_builder-inl.h" +#include "lambda/leaking_allocator.h" +#include "lambda/shorty_field_type.h" #include "mirror/class-inl.h" #include "mirror/method.h" #include "mirror/object-inl.h" @@ -133,32 +138,44 @@ static inline bool IsValidLambdaTargetOrThrow(ArtMethod* called_method) return success; } -// Write out the 'ArtMethod*' into vreg and vreg+1 +// Write out the 'Closure*' into vreg and vreg+1, as if it was a jlong. static inline void WriteLambdaClosureIntoVRegs(ShadowFrame& shadow_frame, - const ArtMethod& called_method, + const lambda::Closure* lambda_closure, uint32_t vreg) { // Split the method into a lo and hi 32 bits so we can encode them into 2 virtual registers. - uint32_t called_method_lo = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&called_method)); - uint32_t called_method_hi = static_cast<uint32_t>(reinterpret_cast<uint64_t>(&called_method) + uint32_t closure_lo = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(lambda_closure)); + uint32_t closure_hi = static_cast<uint32_t>(reinterpret_cast<uint64_t>(lambda_closure) >> BitSizeOf<uint32_t>()); // Use uint64_t instead of uintptr_t to allow shifting past the max on 32-bit. static_assert(sizeof(uint64_t) >= sizeof(uintptr_t), "Impossible"); - DCHECK_NE(called_method_lo | called_method_hi, 0u); + DCHECK_NE(closure_lo | closure_hi, 0u); - shadow_frame.SetVReg(vreg, called_method_lo); - shadow_frame.SetVReg(vreg + 1, called_method_hi); + shadow_frame.SetVReg(vreg, closure_lo); + shadow_frame.SetVReg(vreg + 1, closure_hi); } // Handles create-lambda instructions. // Returns true on success, otherwise throws an exception and returns false. // (Exceptions are thrown by creating a new exception and then being put in the thread TLS) // +// The closure must be allocated big enough to hold the data, and should not be +// pre-initialized. It is initialized with the actual captured variables as a side-effect, +// although this should be unimportant to the caller since this function also handles storing it to +// the ShadowFrame. +// // As a work-in-progress implementation, this shoves the ArtMethod object corresponding // to the target dex method index into the target register vA and vA + 1. template<bool do_access_check> -static inline bool DoCreateLambda(Thread* self, ShadowFrame& shadow_frame, - const Instruction* inst) { +static inline bool DoCreateLambda(Thread* self, + const Instruction* inst, + /*inout*/ShadowFrame& shadow_frame, + /*inout*/lambda::ClosureBuilder* closure_builder, + /*inout*/lambda::Closure* uninitialized_closure) { + DCHECK(closure_builder != nullptr); + DCHECK(uninitialized_closure != nullptr); + DCHECK_ALIGNED(uninitialized_closure, alignof(lambda::Closure)); + /* * create-lambda is opcode 0x21c * - vA is the target register where the closure will be stored into @@ -171,16 +188,69 @@ static inline bool DoCreateLambda(Thread* self, ShadowFrame& shadow_frame, ArtMethod* const called_method = FindMethodFromCode<kStatic, do_access_check>( method_idx, &receiver, sf_method, self); - uint32_t vregA = inst->VRegA_21c(); + uint32_t vreg_dest_closure = inst->VRegA_21c(); if (UNLIKELY(!IsValidLambdaTargetOrThrow(called_method))) { CHECK(self->IsExceptionPending()); - shadow_frame.SetVReg(vregA, 0u); - shadow_frame.SetVReg(vregA + 1, 0u); + shadow_frame.SetVReg(vreg_dest_closure, 0u); + shadow_frame.SetVReg(vreg_dest_closure + 1, 0u); return false; } - WriteLambdaClosureIntoVRegs(shadow_frame, *called_method, vregA); + lambda::ArtLambdaMethod* initialized_lambda_method; + // Initialize the ArtLambdaMethod with the right data. + { + lambda::ArtLambdaMethod* uninitialized_lambda_method = + reinterpret_cast<lambda::ArtLambdaMethod*>( + lambda::LeakingAllocator::AllocateMemory(self, sizeof(lambda::ArtLambdaMethod))); + + std::string captured_variables_shorty = closure_builder->GetCapturedVariableShortyTypes(); + std::string captured_variables_long_type_desc; + + // Synthesize a long type descriptor from the short one. + for (char shorty : captured_variables_shorty) { + lambda::ShortyFieldType shorty_field_type(shorty); + if (shorty_field_type.IsObject()) { + // Not the true type, but good enough until we implement verifier support. + captured_variables_long_type_desc += "Ljava/lang/Object;"; + UNIMPLEMENTED(FATAL) << "create-lambda with an object captured variable"; + } else if (shorty_field_type.IsLambda()) { + // Not the true type, but good enough until we implement verifier support. + captured_variables_long_type_desc += "Ljava/lang/Runnable;"; + UNIMPLEMENTED(FATAL) << "create-lambda with a lambda captured variable"; + } else { + // The primitive types have the same length shorty or not, so this is always correct. + DCHECK(shorty_field_type.IsPrimitive()); + captured_variables_long_type_desc += shorty_field_type; + } + } + + // Copy strings to dynamically allocated storage. This leaks, but that's ok. Fix it later. + // TODO: Strings need to come from the DexFile, so they won't need their own allocations. + char* captured_variables_type_desc = lambda::LeakingAllocator::MakeFlexibleInstance<char>( + self, + captured_variables_long_type_desc.size() + 1); + strcpy(captured_variables_type_desc, captured_variables_long_type_desc.c_str()); + char* captured_variables_shorty_copy = lambda::LeakingAllocator::MakeFlexibleInstance<char>( + self, + captured_variables_shorty.size() + 1); + strcpy(captured_variables_shorty_copy, captured_variables_shorty.c_str()); + + new (uninitialized_lambda_method) lambda::ArtLambdaMethod(called_method, + captured_variables_type_desc, + captured_variables_shorty_copy, + true); // innate lambda + initialized_lambda_method = uninitialized_lambda_method; + } + + // Write all the closure captured variables and the closure header into the closure. + lambda::Closure* initialized_closure; + { + initialized_closure = + closure_builder->CreateInPlace(uninitialized_closure, initialized_lambda_method); + } + + WriteLambdaClosureIntoVRegs(/*inout*/shadow_frame, initialized_closure, vreg_dest_closure); return true; } @@ -189,13 +259,11 @@ static inline bool DoCreateLambda(Thread* self, ShadowFrame& shadow_frame, // Validates that the art method points to a valid lambda function, otherwise throws // an exception and returns null. // (Exceptions are thrown by creating a new exception and then being put in the thread TLS) -static inline ArtMethod* ReadLambdaClosureFromVRegsOrThrow(ShadowFrame& shadow_frame, - uint32_t vreg) +static inline lambda::Closure* ReadLambdaClosureFromVRegsOrThrow(ShadowFrame& shadow_frame, + uint32_t vreg) SHARED_REQUIRES(Locks::mutator_lock_) { - // TODO(iam): Introduce a closure abstraction that will contain the captured variables - // instead of just an ArtMethod. - // This is temporarily using 2 vregs because a native ArtMethod can be up to 64-bit, - // but once proper variable capture is implemented it will only use 1 vreg. + // Lambda closures take up a consecutive pair of 2 virtual registers. + // On 32-bit the high bits are always 0. uint32_t vc_value_lo = shadow_frame.GetVReg(vreg); uint32_t vc_value_hi = shadow_frame.GetVReg(vreg + 1); @@ -204,17 +272,285 @@ static inline ArtMethod* ReadLambdaClosureFromVRegsOrThrow(ShadowFrame& shadow_f // Use uint64_t instead of uintptr_t to allow left-shifting past the max on 32-bit. static_assert(sizeof(uint64_t) >= sizeof(uintptr_t), "Impossible"); - ArtMethod* const called_method = reinterpret_cast<ArtMethod* const>(vc_value_ptr); + lambda::Closure* const lambda_closure = reinterpret_cast<lambda::Closure*>(vc_value_ptr); + DCHECK_ALIGNED(lambda_closure, alignof(lambda::Closure)); // Guard against the user passing a null closure, which is odd but (sadly) semantically valid. - if (UNLIKELY(called_method == nullptr)) { + if (UNLIKELY(lambda_closure == nullptr)) { ThrowNullPointerExceptionFromInterpreter(); return nullptr; - } else if (UNLIKELY(!IsValidLambdaTargetOrThrow(called_method))) { + } else if (UNLIKELY(!IsValidLambdaTargetOrThrow(lambda_closure->GetTargetMethod()))) { + // Sanity check against data corruption. return nullptr; } - return called_method; + return lambda_closure; +} + +// Forward declaration for lock annotations. See below for documentation. +template <bool do_access_check> +static inline const char* GetStringDataByDexStringIndexOrThrow(ShadowFrame& shadow_frame, + uint32_t string_idx) + SHARED_REQUIRES(Locks::mutator_lock_); + +// Find the c-string data corresponding to a dex file's string index. +// Otherwise, returns null if not found and throws a VerifyError. +// +// Note that with do_access_check=false, we never return null because the verifier +// must guard against invalid string indices. +// (Exceptions are thrown by creating a new exception and then being put in the thread TLS) +template <bool do_access_check> +static inline const char* GetStringDataByDexStringIndexOrThrow(ShadowFrame& shadow_frame, + uint32_t string_idx) { + ArtMethod* method = shadow_frame.GetMethod(); + const DexFile* dex_file = method->GetDexFile(); + + mirror::Class* declaring_class = method->GetDeclaringClass(); + if (!do_access_check) { + // MethodVerifier refuses methods with string_idx out of bounds. + DCHECK_LT(string_idx, declaring_class->GetDexCache()->NumStrings()); + } else { + // Access checks enabled: perform string index bounds ourselves. + if (string_idx >= dex_file->GetHeader().string_ids_size_) { + ThrowVerifyError(declaring_class, "String index '%" PRIu32 "' out of bounds", + string_idx); + return nullptr; + } + } + + const char* type_string = dex_file->StringDataByIdx(string_idx); + + if (UNLIKELY(type_string == nullptr)) { + CHECK_EQ(false, do_access_check) + << " verifier should've caught invalid string index " << string_idx; + CHECK_EQ(true, do_access_check) + << " string idx size check should've caught invalid string index " << string_idx; + } + + return type_string; +} + +// Handles capture-variable instructions. +// Returns true on success, otherwise throws an exception and returns false. +// (Exceptions are thrown by creating a new exception and then being put in the thread TLS) +template<bool do_access_check> +static inline bool DoCaptureVariable(Thread* self, + const Instruction* inst, + /*inout*/ShadowFrame& shadow_frame, + /*inout*/lambda::ClosureBuilder* closure_builder) { + DCHECK(closure_builder != nullptr); + using lambda::ShortyFieldType; + /* + * capture-variable is opcode 0xf6, fmt 0x21c + * - vA is the source register of the variable that will be captured + * - vB is the string ID of the variable's type that will be captured + */ + const uint32_t source_vreg = inst->VRegA_21c(); + const uint32_t string_idx = inst->VRegB_21c(); + // TODO: this should be a proper [type id] instead of a [string ID] pointing to a type. + + const char* type_string = GetStringDataByDexStringIndexOrThrow<do_access_check>(shadow_frame, + string_idx); + if (UNLIKELY(type_string == nullptr)) { + CHECK(self->IsExceptionPending()); + return false; + } + + char type_first_letter = type_string[0]; + ShortyFieldType shorty_type; + if (do_access_check && + UNLIKELY(!ShortyFieldType::MaybeCreate(type_first_letter, /*out*/&shorty_type))) { // NOLINT: [whitespace/comma] [3] + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "capture-variable vB must be a valid type"); + return false; + } else { + // Already verified that the type is valid. + shorty_type = ShortyFieldType(type_first_letter); + } + + const size_t captured_variable_count = closure_builder->GetCaptureCount(); + + // Note: types are specified explicitly so that the closure is packed tightly. + switch (shorty_type) { + case ShortyFieldType::kBoolean: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<bool>(primitive_narrow_value); + break; + } + case ShortyFieldType::kByte: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<int8_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kChar: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<uint16_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kShort: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<int16_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kInt: { + uint32_t primitive_narrow_value = shadow_frame.GetVReg(source_vreg); + closure_builder->CaptureVariablePrimitive<int32_t>(primitive_narrow_value); + break; + } + case ShortyFieldType::kDouble: { + closure_builder->CaptureVariablePrimitive(shadow_frame.GetVRegDouble(source_vreg)); + break; + } + case ShortyFieldType::kFloat: { + closure_builder->CaptureVariablePrimitive(shadow_frame.GetVRegFloat(source_vreg)); + break; + } + case ShortyFieldType::kLambda: { + UNIMPLEMENTED(FATAL) << " capture-variable with type kLambda"; + // TODO: Capturing lambdas recursively will be done at a later time. + UNREACHABLE(); + } + case ShortyFieldType::kLong: { + closure_builder->CaptureVariablePrimitive(shadow_frame.GetVRegLong(source_vreg)); + break; + } + case ShortyFieldType::kObject: { + closure_builder->CaptureVariableObject(shadow_frame.GetVRegReference(source_vreg)); + UNIMPLEMENTED(FATAL) << " capture-variable with type kObject"; + // TODO: finish implementing this. disabled for now since we can't track lambda refs for GC. + UNREACHABLE(); + } + + default: + LOG(FATAL) << "Invalid shorty type value " << shorty_type; + UNREACHABLE(); + } + + DCHECK_EQ(captured_variable_count + 1, closure_builder->GetCaptureCount()); + + return true; +} + +// Handles capture-variable instructions. +// Returns true on success, otherwise throws an exception and returns false. +// (Exceptions are thrown by creating a new exception and then being put in the thread TLS) +template<bool do_access_check> +static inline bool DoLiberateVariable(Thread* self, + const Instruction* inst, + size_t captured_variable_index, + /*inout*/ShadowFrame& shadow_frame) { + using lambda::ShortyFieldType; + /* + * liberate-variable is opcode 0xf7, fmt 0x22c + * - vA is the destination register + * - vB is the register with the lambda closure in it + * - vC is the string ID which needs to be a valid field type descriptor + */ + + const uint32_t dest_vreg = inst->VRegA_22c(); + const uint32_t closure_vreg = inst->VRegB_22c(); + const uint32_t string_idx = inst->VRegC_22c(); + // TODO: this should be a proper [type id] instead of a [string ID] pointing to a type. + + + // Synthesize a long type descriptor from a shorty type descriptor list. + // TODO: Fix the dex encoding to contain the long and short type descriptors. + const char* type_string = GetStringDataByDexStringIndexOrThrow<do_access_check>(shadow_frame, + string_idx); + if (UNLIKELY(do_access_check && type_string == nullptr)) { + CHECK(self->IsExceptionPending()); + shadow_frame.SetVReg(dest_vreg, 0); + return false; + } + + char type_first_letter = type_string[0]; + ShortyFieldType shorty_type; + if (do_access_check && + UNLIKELY(!ShortyFieldType::MaybeCreate(type_first_letter, /*out*/&shorty_type))) { // NOLINT: [whitespace/comma] [3] + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "liberate-variable vC must be a valid type"); + shadow_frame.SetVReg(dest_vreg, 0); + return false; + } else { + // Already verified that the type is valid. + shorty_type = ShortyFieldType(type_first_letter); + } + + // Check for closure being null *after* the type check. + // This way we can access the type info in case we fail later, to know how many vregs to clear. + const lambda::Closure* lambda_closure = + ReadLambdaClosureFromVRegsOrThrow(/*inout*/shadow_frame, closure_vreg); + + // Failed lambda target runtime check, an exception was raised. + if (UNLIKELY(lambda_closure == nullptr)) { + CHECK(self->IsExceptionPending()); + + // Clear the destination vreg(s) to be safe. + shadow_frame.SetVReg(dest_vreg, 0); + if (shorty_type.IsPrimitiveWide() || shorty_type.IsLambda()) { + shadow_frame.SetVReg(dest_vreg + 1, 0); + } + return false; + } + + if (do_access_check && + UNLIKELY(captured_variable_index >= lambda_closure->GetNumberOfCapturedVariables())) { + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "liberate-variable captured variable index %zu out of bounds", + lambda_closure->GetNumberOfCapturedVariables()); + // Clear the destination vreg(s) to be safe. + shadow_frame.SetVReg(dest_vreg, 0); + if (shorty_type.IsPrimitiveWide() || shorty_type.IsLambda()) { + shadow_frame.SetVReg(dest_vreg + 1, 0); + } + return false; + } + + // Verify that the runtime type of the captured-variable matches the requested dex type. + if (do_access_check) { + ShortyFieldType actual_type = lambda_closure->GetCapturedShortyType(captured_variable_index); + if (actual_type != shorty_type) { + ThrowVerifyError(shadow_frame.GetMethod()->GetDeclaringClass(), + "cannot liberate-variable of runtime type '%c' to dex type '%c'", + static_cast<char>(actual_type), + static_cast<char>(shorty_type)); + + shadow_frame.SetVReg(dest_vreg, 0); + if (shorty_type.IsPrimitiveWide() || shorty_type.IsLambda()) { + shadow_frame.SetVReg(dest_vreg + 1, 0); + } + return false; + } + + if (actual_type.IsLambda() || actual_type.IsObject()) { + UNIMPLEMENTED(FATAL) << "liberate-variable type checks needs to " + << "parse full type descriptor for objects and lambdas"; + } + } + + // Unpack the captured variable from the closure into the correct type, then save it to the vreg. + if (shorty_type.IsPrimitiveNarrow()) { + uint32_t primitive_narrow_value = + lambda_closure->GetCapturedPrimitiveNarrow(captured_variable_index); + shadow_frame.SetVReg(dest_vreg, primitive_narrow_value); + } else if (shorty_type.IsPrimitiveWide()) { + uint64_t primitive_wide_value = + lambda_closure->GetCapturedPrimitiveWide(captured_variable_index); + shadow_frame.SetVRegLong(dest_vreg, static_cast<int64_t>(primitive_wide_value)); + } else if (shorty_type.IsObject()) { + mirror::Object* unpacked_object = + lambda_closure->GetCapturedObject(captured_variable_index); + shadow_frame.SetVRegReference(dest_vreg, unpacked_object); + + UNIMPLEMENTED(FATAL) << "liberate-variable cannot unpack objects yet"; + } else if (shorty_type.IsLambda()) { + UNIMPLEMENTED(FATAL) << "liberate-variable cannot unpack lambdas yet"; + } else { + LOG(FATAL) << "unreachable"; + UNREACHABLE(); + } + + return true; } template<bool do_access_check> @@ -229,22 +565,24 @@ static inline bool DoInvokeLambda(Thread* self, ShadowFrame& shadow_frame, const * * - reading var-args for 0x25 gets us vD,vE,vF,vG (but not vB) */ - uint32_t vC = inst->VRegC_25x(); - ArtMethod* const called_method = ReadLambdaClosureFromVRegsOrThrow(shadow_frame, vC); + uint32_t vreg_closure = inst->VRegC_25x(); + const lambda::Closure* lambda_closure = + ReadLambdaClosureFromVRegsOrThrow(shadow_frame, vreg_closure); // Failed lambda target runtime check, an exception was raised. - if (UNLIKELY(called_method == nullptr)) { + if (UNLIKELY(lambda_closure == nullptr)) { CHECK(self->IsExceptionPending()); result->SetJ(0); return false; } + ArtMethod* const called_method = lambda_closure->GetTargetMethod(); // Invoke a non-range lambda return DoLambdaCall<false, do_access_check>(called_method, self, shadow_frame, inst, inst_data, result); } -// Handles invoke-XXX/range instructions. +// Handles invoke-XXX/range instructions (other than invoke-lambda[-range]). // Returns true on success, otherwise throws an exception and returns false. template<InvokeType type, bool is_range, bool do_access_check> static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst, @@ -521,17 +859,17 @@ static inline bool DoBoxLambda(Thread* self, ShadowFrame& shadow_frame, const In uint32_t vreg_target_object = inst->VRegA_22x(inst_data); uint32_t vreg_source_closure = inst->VRegB_22x(); - ArtMethod* closure_method = ReadLambdaClosureFromVRegsOrThrow(shadow_frame, - vreg_source_closure); + lambda::Closure* lambda_closure = ReadLambdaClosureFromVRegsOrThrow(shadow_frame, + vreg_source_closure); // Failed lambda target runtime check, an exception was raised. - if (UNLIKELY(closure_method == nullptr)) { + if (UNLIKELY(lambda_closure == nullptr)) { CHECK(self->IsExceptionPending()); return false; } mirror::Object* closure_as_object = - Runtime::Current()->GetLambdaBoxTable()->BoxLambda(closure_method); + Runtime::Current()->GetLambdaBoxTable()->BoxLambda(lambda_closure); // Failed to box the lambda, an exception was raised. if (UNLIKELY(closure_as_object == nullptr)) { @@ -564,16 +902,16 @@ static inline bool DoUnboxLambda(Thread* self, return false; } - ArtMethod* unboxed_closure = nullptr; + lambda::Closure* unboxed_closure = nullptr; // Raise an exception if unboxing fails. if (!Runtime::Current()->GetLambdaBoxTable()->UnboxLambda(boxed_closure_object, - &unboxed_closure)) { + /*out*/&unboxed_closure)) { CHECK(self->IsExceptionPending()); return false; } DCHECK(unboxed_closure != nullptr); - WriteLambdaClosureIntoVRegs(shadow_frame, *unboxed_closure, vreg_target_closure); + WriteLambdaClosureIntoVRegs(/*inout*/shadow_frame, unboxed_closure, vreg_target_closure); return true; } @@ -650,10 +988,13 @@ EXPLICIT_DO_INVOKE_VIRTUAL_QUICK_TEMPLATE_DECL(true); // invoke-virtual-quick- #undef EXPLICIT_INSTANTIATION_DO_INVOKE_VIRTUAL_QUICK // Explicitly instantiate all DoCreateLambda functions. -#define EXPLICIT_DO_CREATE_LAMBDA_DECL(_do_check) \ -template SHARED_REQUIRES(Locks::mutator_lock_) \ -bool DoCreateLambda<_do_check>(Thread* self, ShadowFrame& shadow_frame, \ - const Instruction* inst) +#define EXPLICIT_DO_CREATE_LAMBDA_DECL(_do_check) \ +template SHARED_REQUIRES(Locks::mutator_lock_) \ +bool DoCreateLambda<_do_check>(Thread* self, \ + const Instruction* inst, \ + /*inout*/ShadowFrame& shadow_frame, \ + /*inout*/lambda::ClosureBuilder* closure_builder, \ + /*inout*/lambda::Closure* uninitialized_closure); EXPLICIT_DO_CREATE_LAMBDA_DECL(false); // create-lambda EXPLICIT_DO_CREATE_LAMBDA_DECL(true); // create-lambda @@ -689,7 +1030,29 @@ EXPLICIT_DO_UNBOX_LAMBDA_DECL(false); // unbox-lambda EXPLICIT_DO_UNBOX_LAMBDA_DECL(true); // unbox-lambda #undef EXPLICIT_DO_BOX_LAMBDA_DECL +// Explicitly instantiate all DoCaptureVariable functions. +#define EXPLICIT_DO_CAPTURE_VARIABLE_DECL(_do_check) \ +template SHARED_REQUIRES(Locks::mutator_lock_) \ +bool DoCaptureVariable<_do_check>(Thread* self, \ + const Instruction* inst, \ + ShadowFrame& shadow_frame, \ + lambda::ClosureBuilder* closure_builder); + +EXPLICIT_DO_CAPTURE_VARIABLE_DECL(false); // capture-variable +EXPLICIT_DO_CAPTURE_VARIABLE_DECL(true); // capture-variable +#undef EXPLICIT_DO_CREATE_LAMBDA_DECL +// Explicitly instantiate all DoLiberateVariable functions. +#define EXPLICIT_DO_LIBERATE_VARIABLE_DECL(_do_check) \ +template SHARED_REQUIRES(Locks::mutator_lock_) \ +bool DoLiberateVariable<_do_check>(Thread* self, \ + const Instruction* inst, \ + size_t captured_variable_index, \ + ShadowFrame& shadow_frame); \ + +EXPLICIT_DO_LIBERATE_VARIABLE_DECL(false); // liberate-variable +EXPLICIT_DO_LIBERATE_VARIABLE_DECL(true); // liberate-variable +#undef EXPLICIT_DO_LIBERATE_LAMBDA_DECL } // namespace interpreter } // namespace art diff --git a/runtime/interpreter/interpreter_goto_table_impl.cc b/runtime/interpreter/interpreter_goto_table_impl.cc index 72e2ba0e7b..9677d79de3 100644 --- a/runtime/interpreter/interpreter_goto_table_impl.cc +++ b/runtime/interpreter/interpreter_goto_table_impl.cc @@ -17,9 +17,13 @@ #if !defined(__clang__) // Clang 3.4 fails to build the goto interpreter implementation. + +#include "base/stl_util.h" // MakeUnique #include "interpreter_common.h" #include "safe_math.h" +#include <memory> // std::unique_ptr + namespace art { namespace interpreter { @@ -179,6 +183,9 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF } } + std::unique_ptr<lambda::ClosureBuilder> lambda_closure_builder; + size_t lambda_captured_variable_index = 0; + // Jump to first instruction. ADVANCE(0); UNREACHABLE_CODE_CHECK(); @@ -2412,7 +2419,20 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF HANDLE_INSTRUCTION_END(); HANDLE_EXPERIMENTAL_INSTRUCTION_START(CREATE_LAMBDA) { - bool success = DoCreateLambda<true>(self, shadow_frame, inst); + if (lambda_closure_builder == nullptr) { + // DoCreateLambda always needs a ClosureBuilder, even if it has 0 captured variables. + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + // TODO: these allocations should not leak, and the lambda method should not be local. + lambda::Closure* lambda_closure = + reinterpret_cast<lambda::Closure*>(alloca(lambda_closure_builder->GetSize())); + bool success = DoCreateLambda<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get(), + /*inout*/lambda_closure); + lambda_closure_builder.reset(nullptr); // reset state of variables captured POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2); } HANDLE_EXPERIMENTAL_INSTRUCTION_END(); @@ -2429,6 +2449,31 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF } HANDLE_EXPERIMENTAL_INSTRUCTION_END(); + HANDLE_EXPERIMENTAL_INSTRUCTION_START(CAPTURE_VARIABLE) { + if (lambda_closure_builder == nullptr) { + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + bool success = DoCaptureVariable<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get()); + + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2); + } + HANDLE_EXPERIMENTAL_INSTRUCTION_END(); + + HANDLE_EXPERIMENTAL_INSTRUCTION_START(LIBERATE_VARIABLE) { + bool success = DoLiberateVariable<do_access_check>(self, + inst, + lambda_captured_variable_index, + /*inout*/shadow_frame); + // Temporarily only allow sequences of 'liberate-variable, liberate-variable, ...' + lambda_captured_variable_index++; + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2); + } + HANDLE_EXPERIMENTAL_INSTRUCTION_END(); + HANDLE_INSTRUCTION_START(UNUSED_3E) UnexpectedOpcode(inst, shadow_frame); HANDLE_INSTRUCTION_END(); @@ -2465,14 +2510,6 @@ JValue ExecuteGotoImpl(Thread* self, const DexFile::CodeItem* code_item, ShadowF UnexpectedOpcode(inst, shadow_frame); HANDLE_INSTRUCTION_END(); - HANDLE_INSTRUCTION_START(UNUSED_F5) - UnexpectedOpcode(inst, shadow_frame); - HANDLE_INSTRUCTION_END(); - - HANDLE_INSTRUCTION_START(UNUSED_F7) - UnexpectedOpcode(inst, shadow_frame); - HANDLE_INSTRUCTION_END(); - HANDLE_INSTRUCTION_START(UNUSED_FA) UnexpectedOpcode(inst, shadow_frame); HANDLE_INSTRUCTION_END(); diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index b5cc11e070..083dfb5267 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -14,9 +14,12 @@ * limitations under the License. */ +#include "base/stl_util.h" // MakeUnique #include "interpreter_common.h" #include "safe_math.h" +#include <memory> // std::unique_ptr + namespace art { namespace interpreter { @@ -82,6 +85,11 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, const uint16_t* const insns = code_item->insns_; const Instruction* inst = Instruction::At(insns + dex_pc); uint16_t inst_data; + + // TODO: collapse capture-variable+create-lambda into one opcode, then we won't need + // to keep this live for the scope of the entire function call. + std::unique_ptr<lambda::ClosureBuilder> lambda_closure_builder; + size_t lambda_captured_variable_index = 0; while (true) { dex_pc = inst->GetDexPc(insns); shadow_frame.SetDexPC(dex_pc); @@ -2235,19 +2243,63 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); break; } + case Instruction::CAPTURE_VARIABLE: { + if (!IsExperimentalInstructionEnabled(inst)) { + UnexpectedOpcode(inst, shadow_frame); + } + + if (lambda_closure_builder == nullptr) { + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + PREAMBLE(); + bool success = DoCaptureVariable<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get()); + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); + break; + } case Instruction::CREATE_LAMBDA: { if (!IsExperimentalInstructionEnabled(inst)) { UnexpectedOpcode(inst, shadow_frame); } PREAMBLE(); - bool success = DoCreateLambda<do_access_check>(self, shadow_frame, inst); + + if (lambda_closure_builder == nullptr) { + // DoCreateLambda always needs a ClosureBuilder, even if it has 0 captured variables. + lambda_closure_builder = MakeUnique<lambda::ClosureBuilder>(); + } + + // TODO: these allocations should not leak, and the lambda method should not be local. + lambda::Closure* lambda_closure = + reinterpret_cast<lambda::Closure*>(alloca(lambda_closure_builder->GetSize())); + bool success = DoCreateLambda<do_access_check>(self, + inst, + /*inout*/shadow_frame, + /*inout*/lambda_closure_builder.get(), + /*inout*/lambda_closure); + lambda_closure_builder.reset(nullptr); // reset state of variables captured + POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); + break; + } + case Instruction::LIBERATE_VARIABLE: { + if (!IsExperimentalInstructionEnabled(inst)) { + UnexpectedOpcode(inst, shadow_frame); + } + + PREAMBLE(); + bool success = DoLiberateVariable<do_access_check>(self, + inst, + lambda_captured_variable_index, + /*inout*/shadow_frame); + // Temporarily only allow sequences of 'liberate-variable, liberate-variable, ...' + lambda_captured_variable_index++; POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx); break; } - case Instruction::UNUSED_F4: - case Instruction::UNUSED_F5: - case Instruction::UNUSED_F7: { + case Instruction::UNUSED_F4: { if (!IsExperimentalInstructionEnabled(inst)) { UnexpectedOpcode(inst, shadow_frame); } diff --git a/runtime/lambda/art_lambda_method.h b/runtime/lambda/art_lambda_method.h index 892d8c6f6b..ea13eb7af6 100644 --- a/runtime/lambda/art_lambda_method.h +++ b/runtime/lambda/art_lambda_method.h @@ -35,7 +35,7 @@ class ArtLambdaMethod { // (Ownership of strings is retained by the caller and the lifetime should exceed this class). ArtLambdaMethod(ArtMethod* target_method, const char* captured_variables_type_descriptor, - const char* captured_variables_shorty_, + const char* captured_variables_shorty, bool innate_lambda = true); // Get the target method for this lambda that would be used by the invoke-lambda dex instruction. diff --git a/runtime/lambda/box_table.cc b/runtime/lambda/box_table.cc index 26575fd995..8eef10bbad 100644 --- a/runtime/lambda/box_table.cc +++ b/runtime/lambda/box_table.cc @@ -18,6 +18,8 @@ #include "base/mutex.h" #include "common_throws.h" #include "gc_root-inl.h" +#include "lambda/closure.h" +#include "lambda/leaking_allocator.h" #include "mirror/method.h" #include "mirror/object-inl.h" #include "thread.h" @@ -26,11 +28,53 @@ namespace art { namespace lambda { +// Temporarily represent the lambda Closure as its raw bytes in an array. +// TODO: Generate a proxy class for the closure when boxing the first time. +using BoxedClosurePointerType = mirror::ByteArray*; + +static mirror::Class* GetBoxedClosureClass() SHARED_REQUIRES(Locks::mutator_lock_) { + return mirror::ByteArray::GetArrayClass(); +} + +namespace { + // Convenience functions to allocating/deleting box table copies of the closures. + struct ClosureAllocator { + // Deletes a Closure that was allocated through ::Allocate. + static void Delete(Closure* ptr) { + delete[] reinterpret_cast<char*>(ptr); + } + + // Returns a well-aligned pointer to a newly allocated Closure on the 'new' heap. + static Closure* Allocate(size_t size) { + DCHECK_GE(size, sizeof(Closure)); + + // TODO: Maybe point to the interior of the boxed closure object after we add proxy support? + Closure* closure = reinterpret_cast<Closure*>(new char[size]); + DCHECK_ALIGNED(closure, alignof(Closure)); + return closure; + } + }; +} // namespace BoxTable::BoxTable() : allow_new_weaks_(true), new_weaks_condition_("lambda box table allowed weaks", *Locks::lambda_table_lock_) {} +BoxTable::~BoxTable() { + // Free all the copies of our closures. + for (auto map_iterator = map_.begin(); map_iterator != map_.end(); ++map_iterator) { + std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; + + Closure* closure = key_value_pair.first; + + // Remove from the map first, so that it doesn't try to access dangling pointer. + map_iterator = map_.Erase(map_iterator); + + // Safe to delete, no dangling pointers. + ClosureAllocator::Delete(closure); + } +} + mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { Thread* self = Thread::Current(); @@ -58,22 +102,29 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { // Release the lambda table lock here, so that thread suspension is allowed. - // Convert the ArtMethod into a java.lang.reflect.Method which will serve + // Convert the Closure into a managed byte[] which will serve // as the temporary 'boxed' version of the lambda. This is good enough // to check all the basic object identities that a boxed lambda must retain. + // It's also good enough to contain all the captured primitive variables. // TODO: Boxing an innate lambda (i.e. made with create-lambda) should make a proxy class // TODO: Boxing a learned lambda (i.e. made with unbox-lambda) should return the original object - mirror::Method* method_as_object = - mirror::Method::CreateFromArtMethod(self, closure); + BoxedClosurePointerType closure_as_array_object = + mirror::ByteArray::Alloc(self, closure->GetSize()); + // There are no thread suspension points after this, so we don't need to put it into a handle. - if (UNLIKELY(method_as_object == nullptr)) { + if (UNLIKELY(closure_as_array_object == nullptr)) { // Most likely an OOM has occurred. CHECK(self->IsExceptionPending()); return nullptr; } + // Write the raw closure data into the byte[]. + closure->CopyTo(closure_as_array_object->GetRawData(sizeof(uint8_t), // component size + 0 /*index*/), // index + closure_as_array_object->GetLength()); + // The method has been successfully boxed into an object, now insert it into the hash map. { MutexLock mu(self, *Locks::lambda_table_lock_); @@ -87,38 +138,56 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { return value.Read(); } - // Otherwise we should insert it into the hash map in this thread. - map_.Insert(std::make_pair(closure, ValueType(method_as_object))); + // Otherwise we need to insert it into the hash map in this thread. + + // Make a copy for the box table to keep, in case the closure gets collected from the stack. + // TODO: GC may need to sweep for roots in the box table's copy of the closure. + Closure* closure_table_copy = ClosureAllocator::Allocate(closure->GetSize()); + closure->CopyTo(closure_table_copy, closure->GetSize()); + + // The closure_table_copy needs to be deleted by us manually when we erase it from the map. + + // Actually insert into the table. + map_.Insert({closure_table_copy, ValueType(closure_as_array_object)}); } - return method_as_object; + return closure_as_array_object; } bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { DCHECK(object != nullptr); *out_closure = nullptr; + Thread* self = Thread::Current(); + // Note that we do not need to access lambda_table_lock_ here // since we don't need to look at the map. mirror::Object* boxed_closure_object = object; - // Raise ClassCastException if object is not instanceof java.lang.reflect.Method - if (UNLIKELY(!boxed_closure_object->InstanceOf(mirror::Method::StaticClass()))) { - ThrowClassCastException(mirror::Method::StaticClass(), boxed_closure_object->GetClass()); + // Raise ClassCastException if object is not instanceof byte[] + if (UNLIKELY(!boxed_closure_object->InstanceOf(GetBoxedClosureClass()))) { + ThrowClassCastException(GetBoxedClosureClass(), boxed_closure_object->GetClass()); return false; } // TODO(iam): We must check that the closure object extends/implements the type - // specified in [type id]. This is not currently implemented since it's always a Method. + // specified in [type id]. This is not currently implemented since it's always a byte[]. // If we got this far, the inputs are valid. - // Write out the java.lang.reflect.Method's embedded ArtMethod* into the vreg target. - mirror::AbstractMethod* boxed_closure_as_method = - down_cast<mirror::AbstractMethod*>(boxed_closure_object); + // Shuffle the byte[] back into a raw closure, then allocate it, copy, and return it. + BoxedClosurePointerType boxed_closure_as_array = + down_cast<BoxedClosurePointerType>(boxed_closure_object); + + const int8_t* unaligned_interior_closure = boxed_closure_as_array->GetData(); - ArtMethod* unboxed_closure = boxed_closure_as_method->GetArtMethod(); - DCHECK(unboxed_closure != nullptr); + // Allocate a copy that can "escape" and copy the closure data into that. + Closure* unboxed_closure = + LeakingAllocator::MakeFlexibleInstance<Closure>(self, boxed_closure_as_array->GetLength()); + // TODO: don't just memcpy the closure, it's unsafe when we add references to the mix. + memcpy(unboxed_closure, unaligned_interior_closure, boxed_closure_as_array->GetLength()); + + DCHECK_EQ(unboxed_closure->GetSize(), static_cast<size_t>(boxed_closure_as_array->GetLength())); *out_closure = unboxed_closure; return true; @@ -127,7 +196,7 @@ bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { BoxTable::ValueType BoxTable::FindBoxedLambda(const ClosureType& closure) const { auto map_iterator = map_.Find(closure); if (map_iterator != map_.end()) { - const std::pair<ClosureType, ValueType>& key_value_pair = *map_iterator; + const std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; const ValueType& value = key_value_pair.second; DCHECK(!value.IsNull()); // Never store null boxes. @@ -157,7 +226,7 @@ void BoxTable::SweepWeakBoxedLambdas(IsMarkedVisitor* visitor) { */ std::vector<ClosureType> remove_list; for (auto map_iterator = map_.begin(); map_iterator != map_.end(); ) { - std::pair<ClosureType, ValueType>& key_value_pair = *map_iterator; + std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; const ValueType& old_value = key_value_pair.second; @@ -166,10 +235,15 @@ void BoxTable::SweepWeakBoxedLambdas(IsMarkedVisitor* visitor) { mirror::Object* new_value = visitor->IsMarked(old_value_raw); if (new_value == nullptr) { - const ClosureType& closure = key_value_pair.first; // The object has been swept away. + const ClosureType& closure = key_value_pair.first; + // Delete the entry from the map. - map_iterator = map_.Erase(map_.Find(closure)); + map_iterator = map_.Erase(map_iterator); + + // Clean up the memory by deleting the closure. + ClosureAllocator::Delete(closure); + } else { // The object has been moved. // Update the map. @@ -208,16 +282,33 @@ void BoxTable::BroadcastForNewWeakBoxedLambdas() { new_weaks_condition_.Broadcast(self); } -bool BoxTable::EqualsFn::operator()(const ClosureType& lhs, const ClosureType& rhs) const { +void BoxTable::EmptyFn::MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const { + item.first = nullptr; + + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + item.second = ValueType(); // Also clear the GC root. +} + +bool BoxTable::EmptyFn::IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const { + return item.first == nullptr; +} + +bool BoxTable::EqualsFn::operator()(const UnorderedMapKeyType& lhs, + const UnorderedMapKeyType& rhs) const { // Nothing needs this right now, but leave this assertion for later when // we need to look at the references inside of the closure. - if (kIsDebugBuild) { - Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); - } + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + + return lhs->ReferenceEquals(rhs); +} + +size_t BoxTable::HashFn::operator()(const UnorderedMapKeyType& key) const { + const lambda::Closure* closure = key; + DCHECK_ALIGNED(closure, alignof(lambda::Closure)); - // TODO: Need rework to use read barriers once closures have references inside of them that can - // move. Until then, it's safe to just compare the data inside of it directly. - return lhs == rhs; + // Need to hold mutator_lock_ before calling into Closure::GetHashCode. + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + return closure->GetHashCode(); } } // namespace lambda diff --git a/runtime/lambda/box_table.h b/runtime/lambda/box_table.h index 9ffda6658f..adb733271e 100644 --- a/runtime/lambda/box_table.h +++ b/runtime/lambda/box_table.h @@ -34,6 +34,7 @@ class Object; // forward declaration } // namespace mirror namespace lambda { +struct Closure; // forward declaration /* * Store a table of boxed lambdas. This is required to maintain object referential equality @@ -44,7 +45,7 @@ namespace lambda { */ class BoxTable FINAL { public: - using ClosureType = art::ArtMethod*; + using ClosureType = art::lambda::Closure*; // Boxes a closure into an object. Returns null and throws an exception on failure. mirror::Object* BoxLambda(const ClosureType& closure) @@ -72,10 +73,9 @@ class BoxTable FINAL { REQUIRES(!Locks::lambda_table_lock_); BoxTable(); - ~BoxTable() = default; + ~BoxTable(); private: - // FIXME: This needs to be a GcRoot. // Explanation: // - After all threads are suspended (exclusive mutator lock), // the concurrent-copying GC can move objects from the "from" space to the "to" space. @@ -97,30 +97,30 @@ class BoxTable FINAL { void BlockUntilWeaksAllowed() SHARED_REQUIRES(Locks::lambda_table_lock_); + // Wrap the Closure into a unique_ptr so that the HashMap can delete its memory automatically. + using UnorderedMapKeyType = ClosureType; + // EmptyFn implementation for art::HashMap struct EmptyFn { - void MakeEmpty(std::pair<ClosureType, ValueType>& item) const { - item.first = nullptr; - } - bool IsEmpty(const std::pair<ClosureType, ValueType>& item) const { - return item.first == nullptr; - } + void MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const + NO_THREAD_SAFETY_ANALYSIS; // SHARED_REQUIRES(Locks::mutator_lock_) + + bool IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const; }; // HashFn implementation for art::HashMap struct HashFn { - size_t operator()(const ClosureType& key) const { - // TODO(iam): Rewrite hash function when ClosureType is no longer an ArtMethod* - return static_cast<size_t>(reinterpret_cast<uintptr_t>(key)); - } + size_t operator()(const UnorderedMapKeyType& key) const + NO_THREAD_SAFETY_ANALYSIS; // SHARED_REQUIRES(Locks::mutator_lock_) }; // EqualsFn implementation for art::HashMap struct EqualsFn { - bool operator()(const ClosureType& lhs, const ClosureType& rhs) const; + bool operator()(const UnorderedMapKeyType& lhs, const UnorderedMapKeyType& rhs) const + NO_THREAD_SAFETY_ANALYSIS; // SHARED_REQUIRES(Locks::mutator_lock_) }; - using UnorderedMap = art::HashMap<ClosureType, + using UnorderedMap = art::HashMap<UnorderedMapKeyType, ValueType, EmptyFn, HashFn, diff --git a/runtime/lambda/closure.cc b/runtime/lambda/closure.cc index 95a17c660c..179e4ee7f2 100644 --- a/runtime/lambda/closure.cc +++ b/runtime/lambda/closure.cc @@ -124,6 +124,55 @@ void Closure::CopyTo(void* target, size_t target_size) const { memcpy(target, this, GetSize()); } +ArtMethod* Closure::GetTargetMethod() const { + return const_cast<ArtMethod*>(lambda_info_->GetArtMethod()); +} + +uint32_t Closure::GetHashCode() const { + // Start with a non-zero constant, a prime number. + uint32_t result = 17; + + // Include the hash with the ArtMethod. + { + uintptr_t method = reinterpret_cast<uintptr_t>(GetTargetMethod()); + result = 31 * result + Low32Bits(method); + if (sizeof(method) == sizeof(uint64_t)) { + result = 31 * result + High32Bits(method); + } + } + + // Include a hash for each captured variable. + for (size_t i = 0; i < GetCapturedVariablesSize(); ++i) { + // TODO: not safe for GC-able values since the address can move and the hash code would change. + uint8_t captured_variable_raw_value; + CopyUnsafeAtOffset<uint8_t>(i, /*out*/&captured_variable_raw_value); // NOLINT: [whitespace/comma] [3] + + result = 31 * result + captured_variable_raw_value; + } + + // TODO: Fix above loop to work for objects and lambdas. + static_assert(kClosureSupportsGarbageCollection == false, + "Need to update above loop to read the hash code from the " + "objects and lambdas recursively"); + + return result; +} + +bool Closure::ReferenceEquals(const Closure* other) const { + DCHECK(other != nullptr); + + // TODO: Need rework to use read barriers once closures have references inside of them that can + // move. Until then, it's safe to just compare the data inside of it directly. + static_assert(kClosureSupportsReferences == false, + "Unsafe to use memcmp in read barrier collector"); + + if (GetSize() != other->GetSize()) { + return false; + } + + return memcmp(this, other, GetSize()); +} + size_t Closure::GetNumberOfCapturedVariables() const { // TODO: refactor into art_lambda_method.h. Parsing should only be required here as a DCHECK. VariableInfo variable_info = diff --git a/runtime/lambda/closure.h b/runtime/lambda/closure.h index 60d117e9e2..31ff1944d2 100644 --- a/runtime/lambda/closure.h +++ b/runtime/lambda/closure.h @@ -49,6 +49,19 @@ struct PACKED(sizeof(ArtLambdaMethod*)) Closure { // The target_size must be at least as large as GetSize(). void CopyTo(void* target, size_t target_size) const; + // Get the target method, i.e. the method that will be dispatched into with invoke-lambda. + ArtMethod* GetTargetMethod() const; + + // Calculates the hash code. Value is recomputed each time. + uint32_t GetHashCode() const SHARED_REQUIRES(Locks::mutator_lock_); + + // Is this the same closure as other? e.g. same target method, same variables captured. + // + // Determines whether the two Closures are interchangeable instances. + // Does *not* call Object#equals recursively. If two Closures compare ReferenceEquals true that + // means that they are interchangeable values (usually for the purpose of boxing/unboxing). + bool ReferenceEquals(const Closure* other) const SHARED_REQUIRES(Locks::mutator_lock_); + // How many variables were captured? size_t GetNumberOfCapturedVariables() const; diff --git a/runtime/lambda/closure_builder-inl.h b/runtime/lambda/closure_builder-inl.h index 41a803baf2..3cec21f3ba 100644 --- a/runtime/lambda/closure_builder-inl.h +++ b/runtime/lambda/closure_builder-inl.h @@ -35,6 +35,8 @@ void ClosureBuilder::CaptureVariablePrimitive(T value) { values_.push_back(value_storage); size_ += sizeof(T); + + shorty_types_ += kShortyType; } } // namespace lambda diff --git a/runtime/lambda/closure_builder.cc b/runtime/lambda/closure_builder.cc index 9c37db8fcc..739e965238 100644 --- a/runtime/lambda/closure_builder.cc +++ b/runtime/lambda/closure_builder.cc @@ -64,6 +64,8 @@ void ClosureBuilder::CaptureVariableObject(mirror::Object* object) { UNIMPLEMENTED(FATAL) << "can't yet safely capture objects with read barrier"; } } + + shorty_types_ += ShortyFieldType::kObject; } void ClosureBuilder::CaptureVariableLambda(Closure* closure) { @@ -78,6 +80,8 @@ void ClosureBuilder::CaptureVariableLambda(Closure* closure) { // A closure may be sized dynamically, so always query it for the true size. size_ += closure->GetSize(); + + shorty_types_ += ShortyFieldType::kLambda; } size_t ClosureBuilder::GetSize() const { @@ -85,9 +89,15 @@ size_t ClosureBuilder::GetSize() const { } size_t ClosureBuilder::GetCaptureCount() const { + DCHECK_EQ(values_.size(), shorty_types_.size()); return values_.size(); } +const std::string& ClosureBuilder::GetCapturedVariableShortyTypes() const { + DCHECK_EQ(values_.size(), shorty_types_.size()); + return shorty_types_; +} + Closure* ClosureBuilder::CreateInPlace(void* memory, ArtLambdaMethod* target_method) const { DCHECK(memory != nullptr); DCHECK(target_method != nullptr); @@ -138,11 +148,14 @@ size_t ClosureBuilder::WriteValues(ArtLambdaMethod* target_method, size_t variables_size) const { size_t total_size = header_size; const char* shorty_types = target_method->GetCapturedVariablesShortyTypeDescriptor(); + DCHECK_STREQ(shorty_types, shorty_types_.c_str()); size_t variables_offset = 0; size_t remaining_size = variables_size; const size_t shorty_count = target_method->GetNumberOfCapturedVariables(); + DCHECK_EQ(shorty_count, GetCaptureCount()); + for (size_t i = 0; i < shorty_count; ++i) { ShortyFieldType shorty{shorty_types[i]}; // NOLINT [readability/braces] [4] diff --git a/runtime/lambda/closure_builder.h b/runtime/lambda/closure_builder.h index 542e12afaa..23eb484529 100644 --- a/runtime/lambda/closure_builder.h +++ b/runtime/lambda/closure_builder.h @@ -40,13 +40,12 @@ class ArtLambdaMethod; // forward declaration // // The mutator lock must be held for the duration of the lifetime of this object, // since it needs to temporarily store heap references into an internal list. -class ClosureBuilder : ValueObject { +class ClosureBuilder { public: using ShortyTypeEnum = decltype(ShortyFieldType::kByte); - // Mark this primitive value to be captured as the specified type. - template <typename T, ShortyTypeEnum kShortyType> + template <typename T, ShortyTypeEnum kShortyType = ShortyFieldTypeSelectEnum<T>::value> void CaptureVariablePrimitive(T value); // Mark this object reference to be captured. @@ -63,6 +62,9 @@ class ClosureBuilder : ValueObject { // Returns how many variables have been captured so far. size_t GetCaptureCount() const; + // Get the list of captured variables' shorty field types. + const std::string& GetCapturedVariableShortyTypes() const; + // Creates a closure in-place and writes out the data into 'memory'. // Memory must be at least 'GetSize' bytes large. // All previously marked data to be captured is now written out. @@ -93,6 +95,7 @@ class ClosureBuilder : ValueObject { size_t size_ = kInitialSize; bool is_dynamic_size_ = false; std::vector<ShortyFieldTypeTraits::MaxType> values_; + std::string shorty_types_; }; } // namespace lambda diff --git a/runtime/lambda/leaking_allocator.cc b/runtime/lambda/leaking_allocator.cc new file mode 100644 index 0000000000..4910732a6c --- /dev/null +++ b/runtime/lambda/leaking_allocator.cc @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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. + */ + +#include "lambda/leaking_allocator.h" +#include "linear_alloc.h" +#include "runtime.h" + +namespace art { +namespace lambda { + +void* LeakingAllocator::AllocateMemory(Thread* self, size_t byte_size) { + // TODO: use GetAllocatorForClassLoader to allocate lambda ArtMethod data. + return Runtime::Current()->GetLinearAlloc()->Alloc(self, byte_size); +} + +} // namespace lambda +} // namespace art diff --git a/runtime/lambda/leaking_allocator.h b/runtime/lambda/leaking_allocator.h new file mode 100644 index 0000000000..c3222d0485 --- /dev/null +++ b/runtime/lambda/leaking_allocator.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * 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. + */ +#ifndef ART_RUNTIME_LAMBDA_LEAKING_ALLOCATOR_H_ +#define ART_RUNTIME_LAMBDA_LEAKING_ALLOCATOR_H_ + +#include <utility> // std::forward + +namespace art { +class Thread; // forward declaration + +namespace lambda { + +// Temporary class to centralize all the leaking allocations. +// Allocations made through this class are never freed, but it is a placeholder +// that means that the calling code needs to be rewritten to properly: +// +// (a) Have a lifetime scoped to some other entity. +// (b) Not be allocated over and over again if it was already allocated once (immutable data). +// +// TODO: do all of the above a/b for each callsite, and delete this class. +class LeakingAllocator { + public: + // Allocate byte_size bytes worth of memory. Never freed. + static void* AllocateMemory(Thread* self, size_t byte_size); + + // Make a new instance of T, flexibly sized, in-place at newly allocated memory. Never freed. + template <typename T, typename... Args> + static T* MakeFlexibleInstance(Thread* self, size_t byte_size, Args&&... args) { + return new (AllocateMemory(self, byte_size)) T(std::forward<Args>(args)...); + } + + // Make a new instance of T in-place at newly allocated memory. Never freed. + template <typename T, typename... Args> + static T* MakeInstance(Thread* self, Args&&... args) { + return new (AllocateMemory(self, sizeof(T))) T(std::forward<Args>(args)...); + } +}; + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_LEAKING_ALLOCATOR_H_ diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 9e1a31a6e2..eed3e22a72 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -1008,6 +1008,9 @@ bool MethodVerifier::VerifyInstruction(const Instruction* inst, uint32_t code_of case Instruction::kVerifyRegCWide: result = result && CheckWideRegisterIndex(inst->VRegC()); break; + case Instruction::kVerifyRegCString: + result = result && CheckStringIndex(inst->VRegC()); + break; } switch (inst->GetVerifyExtraFlags()) { case Instruction::kVerifyArrayData: @@ -3163,6 +3166,13 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement invoke-lambda verification break; } + case Instruction::CAPTURE_VARIABLE: { + // Don't bother verifying, instead the interpreter will take the slow path with access checks. + // If the code would've normally hard-failed, then the interpreter will throw the + // appropriate verification errors at runtime. + Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement capture-variable verification + break; + } case Instruction::CREATE_LAMBDA: { // Don't bother verifying, instead the interpreter will take the slow path with access checks. // If the code would've normally hard-failed, then the interpreter will throw the @@ -3170,10 +3180,15 @@ bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) { Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement create-lambda verification break; } + case Instruction::LIBERATE_VARIABLE: { + // Don't bother verifying, instead the interpreter will take the slow path with access checks. + // If the code would've normally hard-failed, then the interpreter will throw the + // appropriate verification errors at runtime. + Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement liberate-variable verification + break; + } - case Instruction::UNUSED_F4: - case Instruction::UNUSED_F5: - case Instruction::UNUSED_F7: { + case Instruction::UNUSED_F4: { DCHECK(false); // TODO(iam): Implement opcodes for lambdas // Conservatively fail verification on release builds. Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_); diff --git a/test/955-lambda-smali/expected.txt b/test/955-lambda-smali/expected.txt index 36370998f4..16381e4b46 100644 --- a/test/955-lambda-smali/expected.txt +++ b/test/955-lambda-smali/expected.txt @@ -16,3 +16,13 @@ Caught NPE (MoveResult) testF success (MoveResult) testD success (MoveResult) testL success +(CaptureVariables) (0-args, 1 captured variable 'Z'): value is true +(CaptureVariables) (0-args, 1 captured variable 'B'): value is R +(CaptureVariables) (0-args, 1 captured variable 'C'): value is ∂ +(CaptureVariables) (0-args, 1 captured variable 'S'): value is 1000 +(CaptureVariables) (0-args, 1 captured variable 'I'): value is 12345678 +(CaptureVariables) (0-args, 1 captured variable 'J'): value is 3287471278325742 +(CaptureVariables) (0-args, 1 captured variable 'F'): value is Infinity +(CaptureVariables) (0-args, 1 captured variable 'D'): value is -Infinity +(CaptureVariables) (0-args, 8 captured variable 'ZBCSIJFD'): value is true,R,∂,1000,12345678,3287471278325742,Infinity,-Infinity +(CaptureVariables) Caught NPE diff --git a/test/955-lambda-smali/smali/BoxUnbox.smali b/test/955-lambda-smali/smali/BoxUnbox.smali index 108b5fafbc..915de2d55d 100644 --- a/test/955-lambda-smali/smali/BoxUnbox.smali +++ b/test/955-lambda-smali/smali/BoxUnbox.smali @@ -1,4 +1,3 @@ -# # Copyright (C) 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,8 +35,8 @@ .end method #TODO: should use a closure type instead of ArtMethod. -.method public static doHelloWorld(Ljava/lang/reflect/ArtMethod;)V - .registers 3 # 1 parameters, 2 locals +.method public static doHelloWorld(J)V + .registers 4 # 1 wide parameters, 2 locals const-string v0, "(BoxUnbox) Hello boxing world! (0-args, no closure)" @@ -51,9 +50,9 @@ .method private static testBox()V .registers 3 - create-lambda v0, LBoxUnbox;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V + create-lambda v0, LBoxUnbox;->doHelloWorld(J)V box-lambda v2, v0 # v2 = box(v0) - unbox-lambda v0, v2, Ljava/lang/reflect/ArtMethod; # v0 = unbox(v2) + unbox-lambda v0, v2, J # v0 = unbox(v2) invoke-lambda v0, {} return-void @@ -63,7 +62,7 @@ .method private static testBoxEquality()V .registers 6 # 0 parameters, 6 locals - create-lambda v0, LBoxUnbox;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V + create-lambda v0, LBoxUnbox;->doHelloWorld(J)V box-lambda v2, v0 # v2 = box(v0) box-lambda v3, v0 # v3 = box(v0) @@ -95,7 +94,7 @@ const v0, 0 # v0 = null const v1, 0 # v1 = null :start - unbox-lambda v2, v0, Ljava/lang/reflect/ArtMethod; + unbox-lambda v2, v0, J # attempting to unbox a null lambda will throw NPE :end return-void @@ -140,7 +139,7 @@ const-string v0, "This is not a boxed lambda" :start # TODO: use \FunctionalType; here instead - unbox-lambda v2, v0, Ljava/lang/reflect/ArtMethod; + unbox-lambda v2, v0, J # can't use a string, expects a lambda object here. throws ClassCastException. :end return-void diff --git a/test/955-lambda-smali/smali/CaptureVariables.smali b/test/955-lambda-smali/smali/CaptureVariables.smali new file mode 100644 index 0000000000..f18b7ff741 --- /dev/null +++ b/test/955-lambda-smali/smali/CaptureVariables.smali @@ -0,0 +1,311 @@ +# +# Copyright (C) 2015 The Android Open Source Project +# +# 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. +# +.class public LCaptureVariables; +.super Ljava/lang/Object; + +.method public constructor <init>()V +.registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static run()V +.registers 8 + # Test boolean capture + const v2, 1 # v2 = true + capture-variable v2, "Z" + create-lambda v0, LCaptureVariables;->printCapturedVariable_Z(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test byte capture + const v2, 82 # v2 = 82, 'R' + capture-variable v2, "B" + create-lambda v0, LCaptureVariables;->printCapturedVariable_B(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test char capture + const v2, 0x2202 # v2 = 0x2202, '∂' + capture-variable v2, "C" + create-lambda v0, LCaptureVariables;->printCapturedVariable_C(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test short capture + const v2, 1000 # v2 = 1000 + capture-variable v2, "S" + create-lambda v0, LCaptureVariables;->printCapturedVariable_S(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test int capture + const v2, 12345678 + capture-variable v2, "I" + create-lambda v0, LCaptureVariables;->printCapturedVariable_I(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test long capture + const-wide v2, 0x0badf00dc0ffeeL # v2 = 3287471278325742 + capture-variable v2, "J" + create-lambda v0, LCaptureVariables;->printCapturedVariable_J(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test float capture + const v2, infinityf + capture-variable v2, "F" + create-lambda v0, LCaptureVariables;->printCapturedVariable_F(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + # Test double capture + const-wide v2, -infinity + capture-variable v2, "D" + create-lambda v0, LCaptureVariables;->printCapturedVariable_D(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + + #TODO: capture objects and lambdas once we have support for it + + # Test capturing multiple variables + invoke-static {}, LCaptureVariables;->testMultipleCaptures()V + + # Test failures + invoke-static {}, LCaptureVariables;->testFailures()V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_Z(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'Z'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "Z" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Z)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_B(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'B'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "B" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(C)V # no println(B), use char instead. + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_C(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'C'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "C" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(C)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_S(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'S'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "S" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V # no println(S), use int instead + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_I(J)V + .registers 5 # 1 wide parameter, 3 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'I'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "I" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(I)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_J(J)V + .registers 6 # 1 wide parameter, 4 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'J'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "J" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(J)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_F(J)V + .registers 5 # 1 parameter, 4 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'F'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "F" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(F)V + + return-void +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_D(J)V + .registers 6 # 1 wide parameter, 4 locals + + const-string v0, "(CaptureVariables) (0-args, 1 captured variable 'D'): value is " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "D" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(D)V + + return-void +.end method + +# Test capturing more than one variable. +.method private static testMultipleCaptures()V + .registers 4 # 0 parameters, 4 locals + + const v2, 1 # v2 = true + capture-variable v2, "Z" + + const v2, 82 # v2 = 82, 'R' + capture-variable v2, "B" + + const v2, 0x2202 # v2 = 0x2202, '∂' + capture-variable v2, "C" + + const v2, 1000 # v2 = 1000 + capture-variable v2, "S" + + const v2, 12345678 + capture-variable v2, "I" + + const-wide v2, 0x0badf00dc0ffeeL # v2 = 3287471278325742 + capture-variable v2, "J" + + const v2, infinityf + capture-variable v2, "F" + + const-wide v2, -infinity + capture-variable v2, "D" + + create-lambda v0, LCaptureVariables;->printCapturedVariable_ZBCSIJFD(J)V + # TODO: create-lambda should not write to both v0 and v1 + invoke-lambda v0, {} + +.end method + +#TODO: should use a closure type instead of a long +.method public static printCapturedVariable_ZBCSIJFD(J)V + .registers 7 # 1 wide parameter, 5 locals + + const-string v0, "(CaptureVariables) (0-args, 8 captured variable 'ZBCSIJFD'): value is " + const-string v4, "," + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "Z" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(Z)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "B" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(C)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "C" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(C)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "S" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(I)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "I" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(I)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "J" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->print(J)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "F" + invoke-virtual {v1, v2}, Ljava/io/PrintStream;->print(F)V + invoke-virtual {v1, v4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + liberate-variable v2, p0, "D" + invoke-virtual {v1, v2, v3}, Ljava/io/PrintStream;->println(D)V + + return-void +.end method + +# Test exceptions are thrown as expected when used opcodes incorrectly +.method private static testFailures()V + .registers 4 # 0 parameters, 4 locals + + const v0, 0 # v0 = null + const v1, 0 # v1 = null +:start + liberate-variable v0, v2, "Z" # invoking a null lambda shall raise an NPE +:end + return-void + +:handler + const-string v2, "(CaptureVariables) Caught NPE" + sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v3, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + + return-void + + .catch Ljava/lang/NullPointerException; {:start .. :end} :handler +.end method diff --git a/test/955-lambda-smali/smali/Main.smali b/test/955-lambda-smali/smali/Main.smali index 5d2aabb386..9892d6124e 100644 --- a/test/955-lambda-smali/smali/Main.smali +++ b/test/955-lambda-smali/smali/Main.smali @@ -24,6 +24,7 @@ invoke-static {}, LTrivialHelloWorld;->run()V invoke-static {}, LBoxUnbox;->run()V invoke-static {}, LMoveResult;->run()V + invoke-static {}, LCaptureVariables;->run()V # TODO: add tests when verification fails diff --git a/test/955-lambda-smali/smali/MoveResult.smali b/test/955-lambda-smali/smali/MoveResult.smali index 1725da3044..52f7ba363b 100644 --- a/test/955-lambda-smali/smali/MoveResult.smali +++ b/test/955-lambda-smali/smali/MoveResult.smali @@ -41,7 +41,7 @@ .method public static testZ()V .registers 6 - create-lambda v0, LMoveResult;->lambdaZ(Ljava/lang/reflect/ArtMethod;)Z + create-lambda v0, LMoveResult;->lambdaZ(J)Z invoke-lambda v0, {} move-result v2 const v3, 1 @@ -61,7 +61,7 @@ .end method # Lambda target for testZ. Always returns "true". -.method public static lambdaZ(Ljava/lang/reflect/ArtMethod;)Z +.method public static lambdaZ(J)Z .registers 3 const v0, 1 @@ -73,7 +73,7 @@ .method public static testB()V .registers 6 - create-lambda v0, LMoveResult;->lambdaB(Ljava/lang/reflect/ArtMethod;)B + create-lambda v0, LMoveResult;->lambdaB(J)B invoke-lambda v0, {} move-result v2 const v3, 15 @@ -93,7 +93,7 @@ .end method # Lambda target for testB. Always returns "15". -.method public static lambdaB(Ljava/lang/reflect/ArtMethod;)B +.method public static lambdaB(J)B .registers 3 # 1 parameters, 2 locals const v0, 15 @@ -105,7 +105,7 @@ .method public static testS()V .registers 6 - create-lambda v0, LMoveResult;->lambdaS(Ljava/lang/reflect/ArtMethod;)S + create-lambda v0, LMoveResult;->lambdaS(J)S invoke-lambda v0, {} move-result v2 const/16 v3, 31000 @@ -125,7 +125,7 @@ .end method # Lambda target for testS. Always returns "31000". -.method public static lambdaS(Ljava/lang/reflect/ArtMethod;)S +.method public static lambdaS(J)S .registers 3 const/16 v0, 31000 @@ -137,7 +137,7 @@ .method public static testI()V .registers 6 - create-lambda v0, LMoveResult;->lambdaI(Ljava/lang/reflect/ArtMethod;)I + create-lambda v0, LMoveResult;->lambdaI(J)I invoke-lambda v0, {} move-result v2 const v3, 128000 @@ -157,7 +157,7 @@ .end method # Lambda target for testI. Always returns "128000". -.method public static lambdaI(Ljava/lang/reflect/ArtMethod;)I +.method public static lambdaI(J)I .registers 3 const v0, 128000 @@ -167,9 +167,9 @@ # Test that chars are returned correctly via move-result. .method public static testC()V - .registers 6 + .registers 7 - create-lambda v0, LMoveResult;->lambdaC(Ljava/lang/reflect/ArtMethod;)C + create-lambda v0, LMoveResult;->lambdaC(J)C invoke-lambda v0, {} move-result v2 const v3, 65535 @@ -189,7 +189,7 @@ .end method # Lambda target for testC. Always returns "65535". -.method public static lambdaC(Ljava/lang/reflect/ArtMethod;)C +.method public static lambdaC(J)C .registers 3 const v0, 65535 @@ -199,12 +199,12 @@ # Test that longs are returned correctly via move-result. .method public static testJ()V - .registers 8 + .registers 9 - create-lambda v0, LMoveResult;->lambdaJ(Ljava/lang/reflect/ArtMethod;)J + create-lambda v0, LMoveResult;->lambdaJ(J)J invoke-lambda v0, {} move-result v2 - const-wide v4, 0xdeadf00dc0ffee + const-wide v4, 0xdeadf00dc0ffeeL if-ne v4, v2, :is_not_equal const-string v6, "(MoveResult) testJ success" @@ -220,11 +220,11 @@ .end method -# Lambda target for testC. Always returns "0xdeadf00dc0ffee". -.method public static lambdaJ(Ljava/lang/reflect/ArtMethod;)J - .registers 4 +# Lambda target for testC. Always returns "0xdeadf00dc0ffeeL". +.method public static lambdaJ(J)J + .registers 5 - const-wide v0, 0xdeadf00dc0ffee + const-wide v0, 0xdeadf00dc0ffeeL return-wide v0 .end method @@ -233,7 +233,7 @@ .method public static testF()V .registers 6 - create-lambda v0, LMoveResult;->lambdaF(Ljava/lang/reflect/ArtMethod;)F + create-lambda v0, LMoveResult;->lambdaF(J)F invoke-lambda v0, {} move-result v2 const v3, infinityf @@ -253,8 +253,8 @@ .end method # Lambda target for testF. Always returns "infinityf". -.method public static lambdaF(Ljava/lang/reflect/ArtMethod;)F - .registers 3 +.method public static lambdaF(J)F + .registers 4 const v0, infinityf return v0 @@ -265,10 +265,10 @@ .method public static testD()V .registers 8 - create-lambda v0, LMoveResult;->lambdaD(Ljava/lang/reflect/ArtMethod;)D + create-lambda v0, LMoveResult;->lambdaD(J)D invoke-lambda v0, {} move-result-wide v2 - const-wide v4, infinity + const-wide v4, -infinity if-ne v4, v2, :is_not_equal const-string v6, "(MoveResult) testD success" @@ -285,10 +285,10 @@ .end method # Lambda target for testD. Always returns "infinity". -.method public static lambdaD(Ljava/lang/reflect/ArtMethod;)D - .registers 4 +.method public static lambdaD(J)D + .registers 5 - const-wide v0, infinity # 123.456789 + const-wide v0, -infinity return-wide v0 .end method @@ -298,7 +298,7 @@ .method public static testL()V .registers 8 - create-lambda v0, LMoveResult;->lambdaL(Ljava/lang/reflect/ArtMethod;)Ljava/lang/String; + create-lambda v0, LMoveResult;->lambdaL(J)Ljava/lang/String; invoke-lambda v0, {} move-result-object v2 const-string v4, "Interned string" @@ -319,8 +319,8 @@ .end method # Lambda target for testL. Always returns "Interned string" (string). -.method public static lambdaL(Ljava/lang/reflect/ArtMethod;)Ljava/lang/String; - .registers 4 +.method public static lambdaL(J)Ljava/lang/String; + .registers 5 const-string v0, "Interned string" return-object v0 diff --git a/test/955-lambda-smali/smali/TrivialHelloWorld.smali b/test/955-lambda-smali/smali/TrivialHelloWorld.smali index 38ee95ac7e..3444b13a65 100644 --- a/test/955-lambda-smali/smali/TrivialHelloWorld.smali +++ b/test/955-lambda-smali/smali/TrivialHelloWorld.smali @@ -25,12 +25,12 @@ .method public static run()V .registers 8 # Trivial 0-arg hello world - create-lambda v0, LTrivialHelloWorld;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V + create-lambda v0, LTrivialHelloWorld;->doHelloWorld(J)V # TODO: create-lambda should not write to both v0 and v1 invoke-lambda v0, {} # Slightly more interesting 4-arg hello world - create-lambda v2, doHelloWorldArgs(Ljava/lang/reflect/ArtMethod;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + create-lambda v2, doHelloWorldArgs(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V # TODO: create-lambda should not write to both v2 and v3 const-string v4, "A" const-string v5, "B" @@ -43,9 +43,9 @@ return-void .end method -#TODO: should use a closure type instead of ArtMethod. -.method public static doHelloWorld(Ljava/lang/reflect/ArtMethod;)V - .registers 3 # 1 parameters, 2 locals +#TODO: should use a closure type instead of jlong. +.method public static doHelloWorld(J)V + .registers 5 # 1 wide parameters, 3 locals const-string v0, "Hello world! (0-args, no closure)" @@ -55,17 +55,17 @@ return-void .end method -#TODO: should use a closure type instead of ArtMethod. -.method public static doHelloWorldArgs(Ljava/lang/reflect/ArtMethod;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - .registers 7 # 5 parameters, 2 locals +#TODO: should use a closure type instead of jlong. +.method public static doHelloWorldArgs(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + .registers 9 # 1 wide parameter, 4 narrow parameters, 3 locals const-string v0, " Hello world! (4-args, no closure)" sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; - invoke-virtual {v1, p1}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, p2}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, p3}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, p4}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + invoke-virtual {v1, p5}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V |