These opcodes are not yet fully specified, and *will* change before they become shippable.
Do not write production code against experimental opcodes.
--
Implement partial interpreter support for new dex instructions box/unbox-lambda.
* box-lambda will take a closure and convert it into an Object
* unbox-lambda will take an Object and convert it to a closure
(Currently does not implement object identity or variable capture).
All new opcodes are disabled by default, use runtime option -Xexperimental-lambdas to enable them.
Change-Id: I3c15ccf8a26ccecd1d35808a8c1b4149220f6019
Instruction::UNUSED_F5,
Instruction::CREATE_LAMBDA,
Instruction::UNUSED_F7,
- Instruction::UNUSED_F8,
- Instruction::UNUSED_F9,
+ Instruction::BOX_LAMBDA,
+ Instruction::UNBOX_LAMBDA,
Instruction::UNUSED_FA,
Instruction::UNUSED_FB,
Instruction::UNUSED_FC,
static const int kUnsupportedLambdaOpcodes[] = {
Instruction::INVOKE_LAMBDA,
Instruction::CREATE_LAMBDA,
+ Instruction::BOX_LAMBDA,
+ Instruction::UNBOX_LAMBDA,
};
// Unsupported opcodes. Null can be used when everything is supported. Size of the lists is
/* TODO(iam): get rid of the unused 'false' column */ \
V(0xF6, CREATE_LAMBDA, "create-lambda", k21c, false_UNUSED, kMethodRef, kContinue | kThrow | kExperimental, kVerifyRegA | kVerifyRegBMethod) \
V(0xF7, UNUSED_F7, "unused-f7", k10x, false, kUnknown, 0, kVerifyError) \
- V(0xF8, UNUSED_F8, "unused-f8", k10x, false, kUnknown, 0, kVerifyError) \
- V(0xF9, UNUSED_F9, "unused-f9", k10x, false, kUnknown, 0, kVerifyError) \
+ V(0xF8, BOX_LAMBDA, "box-lambda", k22x, true, kNone, kContinue | kExperimental, kVerifyRegA | kVerifyRegB) \
+ V(0xF9, UNBOX_LAMBDA, "unbox-lambda", k22c, true, kTypeRef, kContinue | kThrow | kExperimental, kVerifyRegA | kVerifyRegB | kVerifyRegCType) \
V(0xFA, UNUSED_FA, "unused-fa", k10x, false, kUnknown, 0, kVerifyError) \
V(0xFB, UNUSED_FB, "unused-fb", k10x, false, kUnknown, 0, kVerifyError) \
V(0xFC, UNUSED_FC, "unused-fc", k10x, false, kUnknown, 0, kVerifyError) \
#include "entrypoints/entrypoint_utils-inl.h"
#include "handle_scope-inl.h"
#include "mirror/class-inl.h"
+#include "mirror/method.h"
#include "mirror/object-inl.h"
#include "mirror/object_array-inl.h"
#include "mirror/string-inl.h"
return success;
}
+// Write out the 'ArtMethod*' into vreg and vreg+1
+static inline void WriteLambdaClosureIntoVRegs(ShadowFrame& shadow_frame,
+ const ArtMethod& called_method,
+ 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)
+ >> 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);
+
+ shadow_frame.SetVReg(vreg, called_method_lo);
+ shadow_frame.SetVReg(vreg + 1, called_method_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)
return false;
}
- // 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)
- >> BitSizeOf<uint32_t>());
- // Use uint64_t instead of uintptr_t to allow shifting past the max on 32-bit.
+ WriteLambdaClosureIntoVRegs(shadow_frame, *called_method, vregA);
+ return true;
+}
+
+// Reads out the 'ArtMethod*' stored inside of vreg and vreg+1
+//
+// 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)
+ SHARED_LOCKS_REQUIRED(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.
+ uint32_t vc_value_lo = shadow_frame.GetVReg(vreg);
+ uint32_t vc_value_hi = shadow_frame.GetVReg(vreg + 1);
+
+ uint64_t vc_value_ptr = (static_cast<uint64_t>(vc_value_hi) << BitSizeOf<uint32_t>())
+ | vc_value_lo;
+
+ // 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);
- DCHECK_NE(called_method_lo | called_method_hi, 0u);
+ // Guard against the user passing a null closure, which is odd but (sadly) semantically valid.
+ if (UNLIKELY(called_method == nullptr)) {
+ ThrowNullPointerExceptionFromInterpreter();
+ return nullptr;
+ } else if (UNLIKELY(!IsValidLambdaTargetOrThrow(called_method))) {
+ return nullptr;
+ }
- shadow_frame.SetVReg(vregA, called_method_lo);
- shadow_frame.SetVReg(vregA + 1, called_method_hi);
- return true;
+ return called_method;
}
template<bool do_access_check>
* - 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);
- // TODO(iam): Introduce a closure abstraction that will contain the captured variables
- // instead of just an ArtMethod. We also should only need to use 1 register instead of 2.
- uint32_t vc_value_lo = shadow_frame.GetVReg(vC);
- uint32_t vc_value_hi = shadow_frame.GetVReg(vC + 1);
-
- uint64_t vc_value_ptr = (static_cast<uint64_t>(vc_value_hi) << BitSizeOf<uint32_t>())
- | vc_value_lo;
-
- // 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);
-
- // Guard against the user passing a null closure, which is odd but (sadly) semantically valid.
+ // Failed lambda target runtime check, an exception was raised.
if (UNLIKELY(called_method == nullptr)) {
- ThrowNullPointerExceptionFromInterpreter();
- result->SetJ(0);
- return false;
- }
-
- if (UNLIKELY(!IsValidLambdaTargetOrThrow(called_method))) {
CHECK(self->IsExceptionPending());
result->SetJ(0);
return false;
- } else {
- return DoLambdaCall<false, do_access_check>(called_method, self, shadow_frame, inst, inst_data,
- result);
}
+
+ // 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.
return 3;
}
+template <bool _do_check>
+static inline bool DoBoxLambda(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
+ uint16_t inst_data) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+ /*
+ * box-lambda vA, vB /// opcode 0xf8, format 22x
+ * - vA is the target register where the Object representation of the closure will be stored into
+ * - vB is a closure (made by create-lambda)
+ * (also reads vB + 1)
+ */
+ uint32_t vreg_target_object = inst->VRegA_22x(inst_data);
+ uint32_t vreg_source_closure = inst->VRegB_22x();
+
+ ArtMethod* const closure_method = ReadLambdaClosureFromVRegsOrThrow(shadow_frame,
+ vreg_source_closure);
+
+ // Failed lambda target runtime check, an exception was raised.
+ if (UNLIKELY(closure_method == nullptr)) {
+ CHECK(self->IsExceptionPending());
+ return false;
+ }
+
+ // Convert the ArtMethod into a java.lang.reflect.Method 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.
+
+ // 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
+ // TODO: Repeated boxing should return the same object reference
+ mirror::Method* method_as_object =
+ mirror::Method::CreateFromArtMethod(self, closure_method);
+
+ if (UNLIKELY(method_as_object == nullptr)) {
+ // Most likely an OOM has occurred.
+ CHECK(self->IsExceptionPending());
+ return false;
+ }
+
+ shadow_frame.SetVRegReference(vreg_target_object, method_as_object);
+ return true;
+}
+
+template <bool _do_check> SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
+static inline bool DoUnboxLambda(Thread* self ATTRIBUTE_UNUSED,
+ ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data) {
+ /*
+ * unbox-lambda vA, vB, [type id] /// opcode 0xf9, format 22c
+ * - vA is the target register where the closure will be written into
+ * (also writes vA + 1)
+ * - vB is the Object representation of the closure (made by box-lambda)
+ */
+ uint32_t vreg_target_closure = inst->VRegA_22c(inst_data);
+ uint32_t vreg_source_object = inst->VRegB_22c();
+
+ // Raise NullPointerException if object is null
+ mirror::Object* boxed_closure_object = shadow_frame.GetVRegReference(vreg_source_object);
+ if (UNLIKELY(boxed_closure_object == nullptr)) {
+ ThrowNullPointerExceptionFromInterpreter();
+ return false;
+ }
+
+ // 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());
+ 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.
+
+ // 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);
+
+ ArtMethod* unboxed_closure = boxed_closure_as_method->GetArtMethod();
+ DCHECK(unboxed_closure != nullptr);
+
+ WriteLambdaClosureIntoVRegs(shadow_frame, *unboxed_closure, vreg_target_closure);
+ return true;
+}
+
uint32_t FindNextInstructionFollowingException(Thread* self, ShadowFrame& shadow_frame,
uint32_t dex_pc, const instrumentation::Instrumentation* instrumentation)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
EXPLICIT_DO_INVOKE_LAMBDA_DECL(true); // invoke-lambda
#undef EXPLICIT_DO_INVOKE_LAMBDA_DECL
+// Explicitly instantiate all DoBoxLambda functions.
+#define EXPLICIT_DO_BOX_LAMBDA_DECL(_do_check) \
+template SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) \
+bool DoBoxLambda<_do_check>(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst, \
+ uint16_t inst_data);
+
+EXPLICIT_DO_BOX_LAMBDA_DECL(false); // box-lambda
+EXPLICIT_DO_BOX_LAMBDA_DECL(true); // box-lambda
+#undef EXPLICIT_DO_BOX_LAMBDA_DECL
+
+// Explicitly instantiate all DoUnBoxLambda functions.
+#define EXPLICIT_DO_UNBOX_LAMBDA_DECL(_do_check) \
+template SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) \
+bool DoUnboxLambda<_do_check>(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst, \
+ uint16_t inst_data);
+
+EXPLICIT_DO_UNBOX_LAMBDA_DECL(false); // unbox-lambda
+EXPLICIT_DO_UNBOX_LAMBDA_DECL(true); // unbox-lambda
+#undef EXPLICIT_DO_BOX_LAMBDA_DECL
+
} // namespace interpreter
} // namespace art
}
HANDLE_EXPERIMENTAL_INSTRUCTION_END();
+ HANDLE_EXPERIMENTAL_INSTRUCTION_START(BOX_LAMBDA) {
+ bool success = DoBoxLambda<do_access_check>(self, shadow_frame, inst, inst_data);
+ POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2);
+ }
+ HANDLE_EXPERIMENTAL_INSTRUCTION_END();
+
+ HANDLE_EXPERIMENTAL_INSTRUCTION_START(UNBOX_LAMBDA) {
+ bool success = DoUnboxLambda<do_access_check>(self, shadow_frame, inst, inst_data);
+ POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, 2);
+ }
+ HANDLE_EXPERIMENTAL_INSTRUCTION_END();
+
HANDLE_INSTRUCTION_START(UNUSED_3E)
UnexpectedOpcode(inst, shadow_frame);
HANDLE_INSTRUCTION_END();
UnexpectedOpcode(inst, shadow_frame);
HANDLE_INSTRUCTION_END();
- HANDLE_INSTRUCTION_START(UNUSED_F8)
- UnexpectedOpcode(inst, shadow_frame);
- HANDLE_INSTRUCTION_END();
-
- HANDLE_INSTRUCTION_START(UNUSED_F9)
- UnexpectedOpcode(inst, shadow_frame);
- HANDLE_INSTRUCTION_END();
-
HANDLE_INSTRUCTION_START(UNUSED_FA)
UnexpectedOpcode(inst, shadow_frame);
HANDLE_INSTRUCTION_END();
}
case Instruction::UNUSED_F4:
case Instruction::UNUSED_F5:
- case Instruction::UNUSED_F7 ... Instruction::UNUSED_F9: {
+ case Instruction::UNUSED_F7: {
if (!IsExperimentalInstructionEnabled(inst)) {
UnexpectedOpcode(inst, shadow_frame);
}
CHECK(false); // TODO(iam): Implement opcodes for lambdas
break;
}
+ case Instruction::BOX_LAMBDA: {
+ if (!IsExperimentalInstructionEnabled(inst)) {
+ UnexpectedOpcode(inst, shadow_frame);
+ }
+
+ PREAMBLE();
+ bool success = DoBoxLambda<do_access_check>(self, shadow_frame, inst, inst_data);
+ POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
+ break;
+ }
+ case Instruction::UNBOX_LAMBDA: {
+ if (!IsExperimentalInstructionEnabled(inst)) {
+ UnexpectedOpcode(inst, shadow_frame);
+ }
+
+ PREAMBLE();
+ bool success = DoUnboxLambda<do_access_check>(self, shadow_frame, inst, inst_data);
+ POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
+ break;
+ }
case Instruction::UNUSED_3E ... Instruction::UNUSED_43:
case Instruction::UNUSED_FA ... Instruction::UNUSED_FF:
case Instruction::UNUSED_79:
break;
}
- case 0xf4:
- case 0xf5:
- case 0xf7 ... 0xf9: {
+ case Instruction::UNUSED_F4:
+ case Instruction::UNUSED_F5:
+ case Instruction::UNUSED_F7: {
DCHECK(false); // TODO(iam): Implement opcodes for lambdas
- FALLTHROUGH_INTENDED; // Conservatively fail verification on release builds.
+ // Conservatively fail verification on release builds.
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_);
+ break;
+ }
+
+ case Instruction::BOX_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
+ // appropriate verification errors at runtime.
+ Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement box-lambda verification
+ break;
+ }
+
+ case Instruction::UNBOX_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
+ // appropriate verification errors at runtime.
+ Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement unbox-lambda verification
+ break;
}
/* These should never appear during verification. */
Hello world! (0-args, no closure)
ABCD Hello world! (4-args, no closure)
Caught NPE
+(BoxUnbox) Hello boxing world! (0-args, no closure)
+(BoxUnbox) Caught NPE for unbox-lambda
+(BoxUnbox) Caught NPE for box-lambda
+(BoxUnbox) Caught ClassCastException for unbox-lambda
--- /dev/null
+#
+# 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 LBoxUnbox;
+.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 2
+ # Trivial 0-arg hello world
+ create-lambda v0, LBoxUnbox;->doHelloWorld(Ljava/lang/reflect/ArtMethod;)V
+ # TODO: create-lambda should not write to both v0 and v1
+ invoke-lambda v0, {}
+
+ invoke-static {}, LBoxUnbox;->testFailures()V
+ invoke-static {}, LBoxUnbox;->testFailures2()V
+ invoke-static {}, LBoxUnbox;->testFailures3()V
+
+ 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
+
+ const-string v0, "(BoxUnbox) Hello boxing world! (0-args, no closure)"
+
+ sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
+ invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)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
+ unbox-lambda v2, v0, Ljava/lang/reflect/ArtMethod;
+ # attempting to unbox a null lambda will throw NPE
+:end
+ return-void
+
+:handler
+ const-string v2, "(BoxUnbox) Caught NPE for unbox-lambda"
+ 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
+
+# Test exceptions are thrown as expected when used opcodes incorrectly
+.method private static testFailures2()V
+ .registers 4 # 0 parameters, 4 locals
+
+ const v0, 0 # v0 = null
+ const v1, 0 # v1 = null
+:start
+ box-lambda v2, v0 # attempting to box a null lambda will throw NPE
+:end
+ return-void
+
+ # TODO: refactor testFailures using a goto
+
+:handler
+ const-string v2, "(BoxUnbox) Caught NPE for box-lambda"
+ 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
+
+# Test exceptions are thrown as expected when used opcodes incorrectly
+.method private static testFailures3()V
+ .registers 4 # 0 parameters, 4 locals
+
+ const-string v0, "This is not a boxed lambda"
+:start
+ # TODO: use \FunctionalType; here instead
+ unbox-lambda v2, v0, Ljava/lang/reflect/ArtMethod;
+ # can't use a string, expects a lambda object here. throws ClassCastException.
+:end
+ return-void
+
+ # TODO: refactor testFailures using a goto
+
+:handler
+ const-string v2, "(BoxUnbox) Caught ClassCastException for unbox-lambda"
+ 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/ClassCastException; {:start .. :end} :handler
+.end method
invoke-static {}, LSanityCheck;->run()I
invoke-static {}, LTrivialHelloWorld;->run()V
+ invoke-static {}, LBoxUnbox;->run()V
# TODO: add tests when verification fails