Always access Thread state and flags as 32-bit location.

Rewrite access to Thread's state and flags to use 32-bit
atomic operations. Avoid `volatile` accesses that prevent
compiler optimizations.

Change `ThreadState` and `ThreadFlag` to `enum class`es.

Golem results for art-opt-cc (higher is better):
linux-ia32                       before after
NativeDowncallStaticNormal       28.162 35.323 (+25.43%)
NativeDowncallStaticNormal6      26.447 32.951 (+24.59%)
NativeDowncallStaticNormalRefs6
NativeDowncallVirtualNormal      27.972 35.027 (+25.22%)
NativeDowncallVirtualNormal6     26.096 32.131 (+23.13%)
NativeDowncallVirtualNormalRefs6 25.922 31.873 (+22.95%)
linux-x64                        before after
NativeDowncallStaticNormal       26.987 34.380 (+27.40%)
NativeDowncallStaticNormal6      25.424 31.096 (+22.31%)
NativeDowncallStaticNormalRefs6  25.086 30.602 (+21.99%)
NativeDowncallVirtualNormal      26.812 33.234 (+23.95%)
NativeDowncallVirtualNormal6     25.086 30.617 (+22.05%)
NativeDowncallVirtualNormalRefs6 25.086 30.602 (+21.99%)
linux-armv7                      before after
NativeDowncallStaticNormal       7.2394 7.9523 (+9.848%)
NativeDowncallStaticNormal6      6.8527 7.4888 (+9.283%)
NativeDowncallStaticNormalRefs6  6.3976 6.9444 (+8.547%)
NativeDowncallVirtualNormal      7.2081 7.9130 (+9.779%)
NativeDowncallVirtualNormal6     6.8527 7.4888 (+9.283%)
NativeDowncallVirtualNormalRefs6 6.3168 6.8527 (+8.483%)
linux-armv8                      before after
NativeDowncallStaticNormal       7.0389 7.5973 (+7.933%)
NativeDowncallStaticNormal6      6.8527 7.3783 (+7.670%)
NativeDowncallStaticNormalRefs6  6.2924 6.8226 (+8.427%)
NativeDowncallVirtualNormal      6.8527 7.3783 (+7.670%)
NativeDowncallVirtualNormal6     6.5604 7.0423 (+7.344%)
NativeDowncallVirtualNormalRefs6 6.1408 6.5329 (+6.386%)

Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Test: run-gtests.sh
Test: testrunner.py --target --optimizing --interpreter
Bug: 172332525
Bug: 143299880
Change-Id: Ib55d457ad8f5d9e1159b681dfd279d1f9cfb2af7
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
index cca4485..f9ebe40 100644
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -183,7 +183,7 @@
 static jobject CreateAdbConnectionThread(art::Thread* thr) {
   JNIEnv* env = thr->GetJniEnv();
   // Move to native state to talk with the jnienv api.
-  art::ScopedThreadStateChange stsc(thr, art::kNative);
+  art::ScopedThreadStateChange stsc(thr, art::ThreadState::kNative);
   ScopedLocalRef<jstring> thr_name(env, env->NewStringUTF(kAdbConnectionThreadName));
   ScopedLocalRef<jobject> thr_group(
       env,
@@ -528,9 +528,9 @@
 void AdbConnectionState::RunPollLoop(art::Thread* self) {
   DCHECK(IsDebuggingPossible() || art::Runtime::Current()->IsProfileableFromShell());
   CHECK_NE(agent_name_, "");
-  CHECK_EQ(self->GetState(), art::kNative);
+  CHECK_EQ(self->GetState(), art::ThreadState::kNative);
   art::Locks::mutator_lock_->AssertNotHeld(self);
-  self->SetState(art::kWaitingInMainDebuggerLoop);
+  self->SetState(art::ThreadState::kWaitingInMainDebuggerLoop);
   // shutting_down_ set by StopDebuggerThreads
   while (!shutting_down_) {
     // First, connect to adbd if we haven't already.
diff --git a/cmdline/cmdline.h b/cmdline/cmdline.h
index 5821496..b8ca7d0 100644
--- a/cmdline/cmdline.h
+++ b/cmdline/cmdline.h
@@ -127,7 +127,7 @@
 
   // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start,
   // give it away now and then switch to a more manageable ScopedObjectAccess.
-  Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+  Thread::Current()->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
   return Runtime::Current();
 }
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 2f96d44..d63ae17 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -536,9 +536,9 @@
 static void expectValidThreadState() {
   // Normal JNI always transitions to "Native". Other JNIs stay in the "Runnable" state.
   if (IsCurrentJniNormal()) {
-    EXPECT_EQ(kNative, Thread::Current()->GetState());
+    EXPECT_EQ(ThreadState::kNative, Thread::Current()->GetState());
   } else {
-    EXPECT_EQ(kRunnable, Thread::Current()->GetState());
+    EXPECT_EQ(ThreadState::kRunnable, Thread::Current()->GetState());
   }
 }
 
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 2cf2571..bcb5ac5 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -1994,7 +1994,8 @@
   UseScratchRegisterScope temps(codegen_->GetVIXLAssembler());
   Register temp = temps.AcquireW();
 
-  __ Ldrh(temp, MemOperand(tr, Thread::ThreadFlagsOffset<kArm64PointerSize>().SizeValue()));
+  __ Ldr(temp, MemOperand(tr, Thread::ThreadFlagsOffset<kArm64PointerSize>().SizeValue()));
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
   if (successor == nullptr) {
     __ Cbnz(temp, slow_path->GetEntryLabel());
     __ Bind(slow_path->GetReturnLabel());
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 62c285d..aa06c5a 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -7181,7 +7181,8 @@
   UseScratchRegisterScope temps(GetVIXLAssembler());
   vixl32::Register temp = temps.Acquire();
   GetAssembler()->LoadFromOffset(
-      kLoadUnsignedHalfword, temp, tr, Thread::ThreadFlagsOffset<kArmPointerSize>().Int32Value());
+      kLoadWord, temp, tr, Thread::ThreadFlagsOffset<kArmPointerSize>().Int32Value());
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
   if (successor == nullptr) {
     __ CompareAndBranchIfNonZero(temp, slow_path->GetEntryLabel());
     __ Bind(slow_path->GetReturnLabel());
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 11c15d6..758a471 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -6699,7 +6699,8 @@
     DCHECK_EQ(slow_path->GetSuccessor(), successor);
   }
 
-  __ fs()->cmpw(Address::Absolute(Thread::ThreadFlagsOffset<kX86PointerSize>().Int32Value()),
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+  __ fs()->cmpl(Address::Absolute(Thread::ThreadFlagsOffset<kX86PointerSize>().Int32Value()),
                 Immediate(0));
   if (successor == nullptr) {
     __ j(kNotEqual, slow_path->GetEntryLabel());
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index e601b40..c402e83 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -6016,7 +6016,8 @@
     DCHECK_EQ(slow_path->GetSuccessor(), successor);
   }
 
-  __ gs()->cmpw(Address::Absolute(Thread::ThreadFlagsOffset<kX86_64PointerSize>().Int32Value(),
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+  __ gs()->cmpl(Address::Absolute(Thread::ThreadFlagsOffset<kX86_64PointerSize>().Int32Value(),
                                   /* no_rip= */ true),
                 Immediate(0));
   if (successor == nullptr) {
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 16abf9d..6d7a953 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1043,7 +1043,7 @@
     // All signature polymorphic methods are native.
     DCHECK(method == nullptr || !method->IsSignaturePolymorphic());
     // Go to native so that we don't block GC during compilation.
-    ScopedThreadSuspension sts(soa.Self(), kNative);
+    ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative);
     // Try to compile a fully intrinsified implementation.
     if (method != nullptr && UNLIKELY(method->IsIntrinsic())) {
       DCHECK(compiler_options.IsBootImage());
@@ -1159,7 +1159,7 @@
           compiling_class);
       CodeVectorAllocator code_allocator(&allocator);
       // Go to native so that we don't block GC during compilation.
-      ScopedThreadSuspension sts(soa.Self(), kNative);
+      ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative);
       std::unique_ptr<CodeGenerator> codegen(
           TryCompileIntrinsic(&allocator,
                               &arena_stack,
@@ -1328,7 +1328,7 @@
         compiling_class);
 
     // Go to native so that we don't block GC during compilation.
-    ScopedThreadSuspension sts(self, kNative);
+    ScopedThreadSuspension sts(self, ThreadState::kNative);
     codegen.reset(
         TryCompile(&allocator,
                    &arena_stack,
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
index bd8aa083..2b3c2dd 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
@@ -1053,11 +1053,12 @@
 void ArmVIXLJNIMacroAssembler::SuspendCheck(JNIMacroLabel* label) {
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
   vixl32::Register scratch = temps.Acquire();
-  asm_.LoadFromOffset(kLoadUnsignedHalfword,
+  asm_.LoadFromOffset(kLoadWord,
                       scratch,
                       tr,
                       Thread::ThreadFlagsOffset<kArmPointerSize>().Int32Value());
 
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
   ___ Cmp(scratch, 0);
   ___ BPreferNear(ne, ArmVIXLJNIMacroLabel::Cast(label)->AsArm());
   // TODO: think about using CBNZ here.
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
index 561cbbd..e2d29fd 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
@@ -892,7 +892,8 @@
 void Arm64JNIMacroAssembler::SuspendCheck(JNIMacroLabel* label) {
   UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
   Register scratch = temps.AcquireW();
-  ___ Ldrh(scratch, MEM_OP(reg_x(TR), Thread::ThreadFlagsOffset<kArm64PointerSize>().Int32Value()));
+  ___ Ldr(scratch, MEM_OP(reg_x(TR), Thread::ThreadFlagsOffset<kArm64PointerSize>().Int32Value()));
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
   ___ Cbnz(scratch, Arm64JNIMacroLabel::Cast(label)->AsArm64());
 }
 
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.cc b/compiler/utils/x86/jni_macro_assembler_x86.cc
index 7dff279..904cca4 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.cc
+++ b/compiler/utils/x86/jni_macro_assembler_x86.cc
@@ -590,7 +590,8 @@
 }
 
 void X86JNIMacroAssembler::SuspendCheck(JNIMacroLabel* label) {
-  __ fs()->cmpw(Address::Absolute(Thread::ThreadFlagsOffset<kX86PointerSize>()), Immediate(0));
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+  __ fs()->cmpl(Address::Absolute(Thread::ThreadFlagsOffset<kX86PointerSize>()), Immediate(0));
   __ j(kNotEqual, X86JNIMacroLabel::Cast(label)->AsX86());
 }
 
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
index 2da1b47..2fb2797 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
@@ -672,7 +672,8 @@
 }
 
 void X86_64JNIMacroAssembler::SuspendCheck(JNIMacroLabel* label) {
-  __ gs()->cmpw(Address::Absolute(Thread::ThreadFlagsOffset<kX86_64PointerSize>(), true),
+  static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+  __ gs()->cmpl(Address::Absolute(Thread::ThreadFlagsOffset<kX86_64PointerSize>(), true),
                 Immediate(0));
   __ j(kNotEqual, X86_64JNIMacroLabel::Cast(label)->AsX86_64());
 }
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index fe46552..217afa9 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -2680,7 +2680,7 @@
 
     // Runtime::Create acquired the mutator_lock_ that is normally given away when we
     // Runtime::Start, give it away now so that we don't starve GC.
-    self->TransitionFromRunnableToSuspended(kNative);
+    self->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
     WatchDog::SetRuntime(runtime_.get());
 
diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc
index 5429430..6939b0c 100644
--- a/dex2oat/driver/compiler_driver.cc
+++ b/dex2oat/driver/compiler_driver.cc
@@ -1426,7 +1426,7 @@
 
     // Ensure we're suspended while we're blocked waiting for the other threads to finish (worker
     // thread destructor's called below perform join).
-    CHECK_NE(self->GetState(), kRunnable);
+    CHECK_NE(self->GetState(), ThreadState::kRunnable);
 
     // Wait for all the worker threads to finish.
     thread_pool_->Wait(self, true, false);
@@ -2537,7 +2537,7 @@
     }
 
     // Go to native so that we don't block GC during compilation.
-    ScopedThreadSuspension sts(soa.Self(), kNative);
+    ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative);
 
     // Compile direct and virtual methods.
     int64_t previous_method_idx = -1;
diff --git a/dex2oat/linker/image_write_read_test.cc b/dex2oat/linker/image_write_read_test.cc
index 2966f19..3e3dac1 100644
--- a/dex2oat/linker/image_write_read_test.cc
+++ b/dex2oat/linker/image_write_read_test.cc
@@ -80,7 +80,7 @@
   runtime_.reset(Runtime::Current());
   // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start,
   // give it away now and then switch to a more managable ScopedObjectAccess.
-  Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+  Thread::Current()->TransitionFromRunnableToSuspended(ThreadState::kNative);
   ScopedObjectAccess soa(Thread::Current());
   ASSERT_TRUE(runtime_.get() != nullptr);
   class_linker_ = runtime_->GetClassLinker();
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 99e8286..0cc2cdb 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -284,7 +284,7 @@
     }
     // Runtime::Create acquired the mutator_lock_ that is normally given away when we
     // Runtime::Start. Give it away now.
-    Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+    Thread::Current()->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
     return true;
   }
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 365d1e8..7e21f64 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -1831,7 +1831,7 @@
       // Since FlushAllocStack() above resets the (active) allocation
       // stack. Need to revoke the thread-local allocation stacks that
       // point into it.
-      ScopedThreadSuspension sts(self, kNative);
+      ScopedThreadSuspension sts(self, ThreadState::kNative);
       ScopedSuspendAll ssa(__FUNCTION__);
       heap->RevokeAllThreadLocalAllocationStacks(self);
     }
diff --git a/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
index 9b514af..a9fa0fc6c 100644
--- a/openjdkjvm/OpenjdkJvm.cc
+++ b/openjdkjvm/OpenjdkJvm.cc
@@ -367,7 +367,8 @@
                          jobject java_lock, jlong millis) {
   art::ScopedFastNativeObjectAccess soa(env);
   art::ObjPtr<art::mirror::Object> lock = soa.Decode<art::mirror::Object>(java_lock);
-  art::Monitor::Wait(art::Thread::Current(), lock.Ptr(), millis, 0, true, art::kSleeping);
+  art::Monitor::Wait(
+      art::Thread::Current(), lock.Ptr(), millis, 0, true, art::ThreadState::kSleeping);
 }
 
 JNIEXPORT jobject JVM_CurrentThread(JNIEnv* env, jclass unused ATTRIBUTE_UNUSED) {
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index cf28a71..f30a21d 100644
--- a/openjdkjvmti/deopt_manager.cc
+++ b/openjdkjvmti/deopt_manager.cc
@@ -219,14 +219,14 @@
 
 void DeoptManager::RemoveDeoptimizeAllMethods() {
   art::Thread* self = art::Thread::Current();
-  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
   deoptimization_status_lock_.ExclusiveLock(self);
   RemoveDeoptimizeAllMethodsLocked(self);
 }
 
 void DeoptManager::AddDeoptimizeAllMethods() {
   art::Thread* self = art::Thread::Current();
-  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
   deoptimization_status_lock_.ExclusiveLock(self);
   AddDeoptimizeAllMethodsLocked(self);
 }
@@ -240,7 +240,7 @@
   method = method->GetCanonicalMethod();
   bool is_default = method->IsDefault();
 
-  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
   deoptimization_status_lock_.ExclusiveLock(self);
   {
     breakpoint_status_lock_.ExclusiveLock(self);
@@ -280,7 +280,7 @@
   method = method->GetCanonicalMethod();
   bool is_default = method->IsDefault();
 
-  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
   // Ideally we should do a ScopedSuspendAll right here to get the full mutator_lock_ that we might
   // need but since that is very heavy we will instead just use a condition variable to make sure we
   // don't race with ourselves.
@@ -452,7 +452,7 @@
 
 void DeoptManager::RemoveDeoptimizationRequester() {
   art::Thread* self = art::Thread::Current();
-  art::ScopedThreadStateChange sts(self, art::kSuspended);
+  art::ScopedThreadStateChange sts(self, art::ThreadState::kSuspended);
   deoptimization_status_lock_.ExclusiveLock(self);
   DCHECK_GT(deopter_count_, 0u) << "Removing deoptimization requester without any being present";
   deopter_count_--;
@@ -468,7 +468,7 @@
 
 void DeoptManager::AddDeoptimizationRequester() {
   art::Thread* self = art::Thread::Current();
-  art::ScopedThreadStateChange stsc(self, art::kSuspended);
+  art::ScopedThreadStateChange stsc(self, art::ThreadState::kSuspended);
   deoptimization_status_lock_.ExclusiveLock(self);
   deopter_count_++;
   if (deopter_count_ == 1) {
@@ -487,7 +487,7 @@
 void DeoptManager::DeoptimizeThread(art::Thread* target) {
   // We might or might not be running on the target thread (self) so get Thread::Current
   // directly.
-  art::ScopedThreadSuspension sts(art::Thread::Current(), art::kSuspended);
+  art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kSuspended);
   art::gc::ScopedGCCriticalSection sgccs(art::Thread::Current(),
                                          art::gc::GcCause::kGcCauseDebugger,
                                          art::gc::CollectorType::kCollectorTypeDebugger);
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index 924a0d8..3345883 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -368,7 +368,7 @@
       heap->IncrementDisableMovingGC(self);
     }
     {
-      art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+      art::ScopedThreadSuspension sts(self, art::ThreadState::kWaitingForVisitObjects);
       art::ScopedSuspendAll ssa("FixupTempClass");
 
       art::mirror::Class* input = temp_klass.Get();
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index bd9d2dd..2a1d442 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -1403,7 +1403,7 @@
   {
     art::ScopedObjectAccess soa(self);      // Now we know we have the shared lock.
     art::jni::ScopedEnableSuspendAllJniIdQueries sjni;  // make sure we can get JNI ids.
-    art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kWaitingForVisitObjects);
     art::ScopedSuspendAll ssa("FollowReferences");
 
     art::ObjPtr<art::mirror::Class> class_filter = klass == nullptr
diff --git a/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc
index 2ca5057..f244cc1 100644
--- a/openjdkjvmti/ti_monitor.cc
+++ b/openjdkjvmti/ti_monitor.cc
@@ -376,39 +376,42 @@
       switch (target_thread->GetState()) {
         // These three we are actually currently waiting on a monitor and have sent the appropriate
         // events (if anyone is listening).
-        case art::kBlocked:
-        case art::kTimedWaiting:
-        case art::kWaiting: {
+        case art::ThreadState::kBlocked:
+        case art::ThreadState::kTimedWaiting:
+        case art::ThreadState::kWaiting: {
           out_ = art::GcRoot<art::mirror::Object>(art::Monitor::GetContendedMonitor(target_thread));
           return;
         }
-        case art::kTerminated:
-        case art::kRunnable:
-        case art::kSleeping:
-        case art::kWaitingForLockInflation:
-        case art::kWaitingForTaskProcessor:
-        case art::kWaitingForGcToComplete:
-        case art::kWaitingForCheckPointsToRun:
-        case art::kWaitingPerformingGc:
-        case art::kWaitingForDebuggerSend:
-        case art::kWaitingForDebuggerToAttach:
-        case art::kWaitingInMainDebuggerLoop:
-        case art::kWaitingForDebuggerSuspension:
-        case art::kWaitingForJniOnLoad:
-        case art::kWaitingForSignalCatcherOutput:
-        case art::kWaitingInMainSignalCatcherLoop:
-        case art::kWaitingForDeoptimization:
-        case art::kWaitingForMethodTracingStart:
-        case art::kWaitingForVisitObjects:
-        case art::kWaitingForGetObjectsAllocated:
-        case art::kWaitingWeakGcRootRead:
-        case art::kWaitingForGcThreadFlip:
-        case art::kNativeForAbort:
-        case art::kStarting:
-        case art::kNative:
-        case art::kSuspended: {
+        case art::ThreadState::kTerminated:
+        case art::ThreadState::kRunnable:
+        case art::ThreadState::kSleeping:
+        case art::ThreadState::kWaitingForLockInflation:
+        case art::ThreadState::kWaitingForTaskProcessor:
+        case art::ThreadState::kWaitingForGcToComplete:
+        case art::ThreadState::kWaitingForCheckPointsToRun:
+        case art::ThreadState::kWaitingPerformingGc:
+        case art::ThreadState::kWaitingForDebuggerSend:
+        case art::ThreadState::kWaitingForDebuggerToAttach:
+        case art::ThreadState::kWaitingInMainDebuggerLoop:
+        case art::ThreadState::kWaitingForDebuggerSuspension:
+        case art::ThreadState::kWaitingForJniOnLoad:
+        case art::ThreadState::kWaitingForSignalCatcherOutput:
+        case art::ThreadState::kWaitingInMainSignalCatcherLoop:
+        case art::ThreadState::kWaitingForDeoptimization:
+        case art::ThreadState::kWaitingForMethodTracingStart:
+        case art::ThreadState::kWaitingForVisitObjects:
+        case art::ThreadState::kWaitingForGetObjectsAllocated:
+        case art::ThreadState::kWaitingWeakGcRootRead:
+        case art::ThreadState::kWaitingForGcThreadFlip:
+        case art::ThreadState::kNativeForAbort:
+        case art::ThreadState::kStarting:
+        case art::ThreadState::kNative:
+        case art::ThreadState::kSuspended: {
           // We aren't currently (explicitly) waiting for a monitor so just return null.
           return;
+        case art::ThreadState::kObsoleteRunnable:
+          LOG(FATAL) << "UNREACHABLE";  // Obsolete value.
+          UNREACHABLE();
         }
       }
     }
diff --git a/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc
index 344ae88..eb1140d 100644
--- a/openjdkjvmti/ti_object.cc
+++ b/openjdkjvmti/ti_object.cc
@@ -91,7 +91,7 @@
   std::vector<jthread> notify_wait;
   {
     art::ScopedObjectAccess soa(self);      // Now we know we have the shared lock.
-    art::ScopedThreadSuspension sts(self, art::kNative);
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
     art::ScopedSuspendAll ssa("GetObjectMonitorUsage", /*long_suspend=*/false);
     art::ObjPtr<art::mirror::Object> target(self->DecodeJObject(obj));
     // This gets the list of threads trying to lock or wait on the monitor.
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index a9a6ee8..f31759e 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -174,7 +174,7 @@
 
 static void WaitForSystemDaemonStart(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
   {
-    art::ScopedThreadStateChange strc(self, art::kNative);
+    art::ScopedThreadStateChange strc(self, art::ThreadState::kNative);
     JNIEnv* jni = self->GetJniEnv();
     jni->CallStaticVoidMethod(art::WellKnownClasses::java_lang_Daemons,
                               art::WellKnownClasses::java_lang_Daemons_waitForDaemonStart);
@@ -487,6 +487,7 @@
       jvmti_state |= (JVMTI_THREAD_STATE_WAITING |
                       JVMTI_THREAD_STATE_WAITING_INDEFINITELY);
       break;
+    case art::ThreadState::kObsoleteRunnable:  // Obsolete value.
     case art::ThreadState::kStarting:
     case art::ThreadState::kTerminated:
       // We only call this if we are alive so we shouldn't see either of these states.
@@ -539,6 +540,9 @@
     case art::ThreadState::kWaitingForGcThreadFlip:
     case art::ThreadState::kNativeForAbort:
       return JVMTI_JAVA_LANG_THREAD_STATE_WAITING;
+
+    case art::ThreadState::kObsoleteRunnable:
+      break;  // Obsolete value.
   }
   LOG(FATAL) << "Unreachable";
   UNREACHABLE();
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 9dda50c..5b1a476 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -319,7 +319,7 @@
 
   if (kIsDebugBuild) {
     self->AssertThreadSuspensionIsAllowable();
-    CHECK_EQ(kRunnable, self->GetState());
+    CHECK_EQ(ThreadState::kRunnable, self->GetState());
     CHECK_STREQ(GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty(), shorty);
   }
 
diff --git a/runtime/cha.cc b/runtime/cha.cc
index 392b35c..03025dc 100644
--- a/runtime/cha.cc
+++ b/runtime/cha.cc
@@ -249,7 +249,7 @@
 
   void WaitForThreadsToRunThroughCheckpoint(size_t threads_running_checkpoint) {
     Thread* self = Thread::Current();
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     barrier_.Increment(self, threads_running_checkpoint);
   }
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 45562c4..5410bb0 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -2550,7 +2550,7 @@
     }
     {
       // Handle wrapper deals with klass moving.
-      ScopedThreadSuspension sts(self, kSuspended);
+      ScopedThreadSuspension sts(self, ThreadState::kSuspended);
       if (index < kNumYieldIterations) {
         sched_yield();
       } else {
@@ -2935,7 +2935,7 @@
             soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get()));
         ScopedLocalRef<jobject> result(soa.Env(), nullptr);
         {
-          ScopedThreadStateChange tsc(self, kNative);
+          ScopedThreadStateChange tsc(self, ThreadState::kNative);
           ScopedLocalRef<jobject> class_name_object(
               soa.Env(), soa.Env()->NewStringUTF(class_name_string.c_str()));
           if (class_name_object.get() == nullptr) {
@@ -3228,7 +3228,7 @@
     // We must be in the kRunnable state to prevent instrumentation from
     // suspending all threads to update entrypoints while we are doing it
     // for this class.
-    DCHECK_EQ(self->GetState(), kRunnable);
+    DCHECK_EQ(self->GetState(), ThreadState::kRunnable);
     Runtime::Current()->GetInstrumentation()->InstallStubsForClass(h_new_class.Get());
   }
 
@@ -7439,7 +7439,7 @@
       if (methods != old_methods && old_methods != nullptr) {
         // Need to make sure the GC is not running since it could be scanning the methods we are
         // about to overwrite.
-        ScopedThreadStateChange tsc(self_, kSuspended);
+        ScopedThreadStateChange tsc(self_, ThreadState::kSuspended);
         gc::ScopedGCCriticalSection gcs(self_,
                                         gc::kGcCauseClassLinker,
                                         gc::kCollectorTypeClassLinker);
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 3f643e8..f949759 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -130,7 +130,7 @@
 
   // Runtime::Create acquired the mutator_lock_ that is normally given away when we
   // Runtime::Start, give it away now and then switch to a more managable ScopedObjectAccess.
-  Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+  Thread::Current()->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
   // Get the boot class path from the runtime so it can be used in tests.
   boot_class_path_ = class_linker_->GetBootClassPath();
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 2da3e41..af36531 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -268,7 +268,7 @@
   VLOG(jdwp) << "Broadcasting DDM " << (connect ? "connect" : "disconnect") << "...";
 
   Thread* self = Thread::Current();
-  if (self->GetState() != kRunnable) {
+  if (self->GetState() != ThreadState::kRunnable) {
     LOG(ERROR) << "DDM broadcast in thread state " << self->GetState();
     /* try anyway? */
   }
@@ -747,7 +747,7 @@
         context.SetChunkOverhead(0);
         // Need to acquire the mutator lock before the heap bitmap lock with exclusive access since
         // RosAlloc's internal logic doesn't know to release and reacquire the heap bitmap lock.
-        ScopedThreadSuspension sts(self, kSuspended);
+        ScopedThreadSuspension sts(self, ThreadState::kSuspended);
         ScopedSuspendAll ssa(__FUNCTION__);
         ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
         space->AsRosAllocSpace()->Walk(HeapChunkContext::HeapChunkJavaCallback, &context);
@@ -759,7 +759,7 @@
       } else if (space->IsRegionSpace()) {
         heap->IncrementDisableMovingGC(self);
         {
-          ScopedThreadSuspension sts(self, kSuspended);
+          ScopedThreadSuspension sts(self, ThreadState::kSuspended);
           ScopedSuspendAll ssa(__FUNCTION__);
           ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
           context.SetChunkOverhead(0);
diff --git a/runtime/entrypoints/quick/quick_jni_entrypoints.cc b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
index 9507213..4fa37e5 100644
--- a/runtime/entrypoints/quick/quick_jni_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
@@ -66,7 +66,7 @@
   }
 
   // Transition out of runnable.
-  self->TransitionFromRunnableToSuspended(kNative);
+  self->TransitionFromRunnableToSuspended(ThreadState::kNative);
 }
 
 // TODO: NO_THREAD_SAFETY_ANALYSIS due to different control paths depending on fast JNI.
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc
index 66fb3c7..f8bd213 100644
--- a/runtime/fault_handler.cc
+++ b/runtime/fault_handler.cc
@@ -309,7 +309,7 @@
   }
 
   ThreadState state = thread->GetState();
-  if (state != kRunnable) {
+  if (state != ThreadState::kRunnable) {
     VLOG(signals) << "not runnable";
     return false;
   }
diff --git a/runtime/gc/accounting/mod_union_table_test.cc b/runtime/gc/accounting/mod_union_table_test.cc
index 1cd719d..e42682a 100644
--- a/runtime/gc/accounting/mod_union_table_test.cc
+++ b/runtime/gc/accounting/mod_union_table_test.cc
@@ -188,7 +188,7 @@
       "other space", 128 * KB, 4 * MB, 4 * MB, /*can_move_objects=*/ false));
   ASSERT_TRUE(other_space.get() != nullptr);
   {
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     ScopedSuspendAll ssa("Add image space");
     heap->AddSpace(other_space.get());
   }
@@ -260,7 +260,7 @@
   std::ostringstream oss2;
   table->Dump(oss2);
   // Remove the space we added so it doesn't persist to the next test.
-  ScopedThreadSuspension sts(self, kSuspended);
+  ScopedThreadSuspension sts(self, ThreadState::kSuspended);
   ScopedSuspendAll ssa("Add image space");
   heap->RemoveSpace(other_space.get());
 }
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index af02da8..3a60191 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -266,7 +266,9 @@
   void Run(Thread* thread) override NO_THREAD_SAFETY_ANALYSIS {
     // Note: self is not necessarily equal to thread since thread may be suspended.
     Thread* self = Thread::Current();
-    DCHECK(thread == self || thread->IsSuspended() || thread->GetState() == kWaitingPerformingGc)
+    DCHECK(thread == self ||
+           thread->IsSuspended() ||
+           thread->GetState() == ThreadState::kWaitingPerformingGc)
         << thread->GetState() << " thread " << thread << " self " << self;
     // Switch to the read barrier entrypoints.
     thread->SetReadBarrierEntrypoints();
@@ -307,7 +309,7 @@
   if (barrier_count == 0) {
     return;
   }
-  ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
   gc_barrier_->Increment(self, barrier_count);
 }
 
@@ -462,7 +464,9 @@
   void Run(Thread* thread) override REQUIRES_SHARED(Locks::mutator_lock_) {
     // Note: self is not necessarily equal to thread since thread may be suspended.
     Thread* self = Thread::Current();
-    CHECK(thread == self || thread->IsSuspended() || thread->GetState() == kWaitingPerformingGc)
+    CHECK(thread == self ||
+          thread->IsSuspended() ||
+          thread->GetState() == ThreadState::kWaitingPerformingGc)
         << thread->GetState() << " thread " << thread << " self " << self;
     thread->SetIsGcMarkingAndUpdateEntrypoints(true);
     if (use_tlab_ && thread->HasTlab()) {
@@ -768,7 +772,7 @@
       &thread_flip_visitor, &flip_callback, this, GetHeap()->GetGcPauseListener());
 
   {
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     gc_barrier_->Increment(self, barrier_count);
   }
   is_asserting_to_space_invariant_ = true;
@@ -980,7 +984,9 @@
   void Run(Thread* thread) override NO_THREAD_SAFETY_ANALYSIS {
     // Note: self is not necessarily equal to thread since thread may be suspended.
     Thread* const self = Thread::Current();
-    CHECK(thread == self || thread->IsSuspended() || thread->GetState() == kWaitingPerformingGc)
+    CHECK(thread == self ||
+          thread->IsSuspended() ||
+          thread->GetState() == ThreadState::kWaitingPerformingGc)
         << thread->GetState() << " thread " << thread << " self " << self;
     // Revoke thread local mark stacks.
     {
@@ -1047,7 +1053,7 @@
   }
   Locks::mutator_lock_->SharedUnlock(self);
   {
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     gc_barrier_->Increment(self, barrier_count);
   }
   Locks::mutator_lock_->SharedLock(self);
@@ -1718,7 +1724,9 @@
   void Run(Thread* thread) override NO_THREAD_SAFETY_ANALYSIS {
     // Note: self is not necessarily equal to thread since thread may be suspended.
     Thread* self = Thread::Current();
-    DCHECK(thread == self || thread->IsSuspended() || thread->GetState() == kWaitingPerformingGc)
+    DCHECK(thread == self ||
+           thread->IsSuspended() ||
+           thread->GetState() == ThreadState::kWaitingPerformingGc)
         << thread->GetState() << " thread " << thread << " self " << self;
     // Disable the thread-local is_gc_marking flag.
     // Note a thread that has just started right before this checkpoint may have already this flag
@@ -1771,7 +1779,7 @@
   // Release locks then wait for all mutator threads to pass the barrier.
   Locks::mutator_lock_->SharedUnlock(self);
   {
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     gc_barrier_->Increment(self, barrier_count);
   }
   Locks::mutator_lock_->SharedLock(self);
@@ -2075,7 +2083,7 @@
     }
     Locks::mutator_lock_->SharedUnlock(self);
     {
-      ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+      ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
       gc_barrier_->Increment(self, barrier_count);
     }
     Locks::mutator_lock_->SharedLock(self);
diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index ebac36d..e3cd1ee 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -1163,7 +1163,9 @@
     ScopedTrace trace("Marking thread roots");
     // Note: self is not necessarily equal to thread since thread may be suspended.
     Thread* const self = Thread::Current();
-    CHECK(thread == self || thread->IsSuspended() || thread->GetState() == kWaitingPerformingGc)
+    CHECK(thread == self ||
+          thread->IsSuspended() ||
+          thread->GetState() == ThreadState::kWaitingPerformingGc)
         << thread->GetState() << " thread " << thread << " self " << self;
     thread->VisitRoots(this, kVisitRootFlagAllRoots);
     if (revoke_ros_alloc_thread_local_buffers_at_checkpoint_) {
@@ -1197,7 +1199,7 @@
   Locks::heap_bitmap_lock_->ExclusiveUnlock(self);
   Locks::mutator_lock_->SharedUnlock(self);
   {
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     gc_barrier_->Increment(self, barrier_count);
   }
   Locks::mutator_lock_->SharedLock(self);
diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc
index d191031..0848f34 100644
--- a/runtime/gc/collector/semi_space.cc
+++ b/runtime/gc/collector/semi_space.cc
@@ -156,13 +156,13 @@
     Locks::mutator_lock_->AssertExclusiveHeld(self_);
     // Store the stack traces into the runtime fault string in case we Get a heap corruption
     // related crash later.
-    ThreadState old_state = self_->SetStateUnsafe(kRunnable);
+    ThreadState old_state = self_->SetStateUnsafe(ThreadState::kRunnable);
     std::ostringstream oss;
     Runtime* runtime = Runtime::Current();
     runtime->GetThreadList()->DumpForSigQuit(oss);
     runtime->GetThreadList()->DumpNativeStacks(oss);
     runtime->SetFaultMessage(oss.str());
-    CHECK_EQ(self_->SetStateUnsafe(old_state), kRunnable);
+    CHECK_EQ(self_->SetStateUnsafe(old_state), ThreadState::kRunnable);
   }
   // Revoke the thread local buffers since the GC may allocate into a RosAllocSpace and this helps
   // to prevent fragmentation.
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 50cfc6e..9017f30 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -57,7 +57,7 @@
     CheckPreconditionsForAllocObject(klass, byte_count);
     // Since allocation can cause a GC which will need to SuspendAll, make sure all allocations are
     // done in the runnable state where suspension is expected.
-    CHECK_EQ(self->GetState(), kRunnable);
+    CHECK_EQ(self->GetState(), ThreadState::kRunnable);
     self->AssertThreadSuspensionIsAllowable();
     self->AssertNoPendingException();
     // Make sure to preserve klass.
diff --git a/runtime/gc/heap-visit-objects-inl.h b/runtime/gc/heap-visit-objects-inl.h
index b6ccb277..e20d981 100644
--- a/runtime/gc/heap-visit-objects-inl.h
+++ b/runtime/gc/heap-visit-objects-inl.h
@@ -50,7 +50,7 @@
     // IncrementDisableMovingGC() and threads are suspended.
     IncrementDisableMovingGC(self);
     {
-      ScopedThreadSuspension sts(self, kWaitingForVisitObjects);
+      ScopedThreadSuspension sts(self, ThreadState::kWaitingForVisitObjects);
       ScopedSuspendAll ssa(__FUNCTION__);
       VisitObjectsInternalRegionSpace(visitor);
       VisitObjectsInternal(visitor);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index cb2a648..100754a 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -942,7 +942,7 @@
 void Heap::IncrementDisableMovingGC(Thread* self) {
   // Need to do this holding the lock to prevent races where the GC is about to run / running when
   // we attempt to disable it.
-  ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcToComplete);
   MutexLock mu(self, *gc_complete_lock_);
   ++disable_moving_gc_count_;
   if (IsMovingGc(collector_type_running_)) {
@@ -966,7 +966,7 @@
     // counter. The global counter is incremented only once for a thread for the outermost enter.
     return;
   }
-  ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcThreadFlip);
   MutexLock mu(self, *thread_flip_lock_);
   thread_flip_cond_->CheckSafeToWait(self);
   bool has_waited = false;
@@ -1013,7 +1013,7 @@
   // Supposed to be called by GC. Set thread_flip_running_ to be true. If disable_thread_flip_count_
   // > 0, block. Otherwise, go ahead.
   CHECK(kUseReadBarrier);
-  ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcThreadFlip);
   MutexLock mu(self, *thread_flip_lock_);
   thread_flip_cond_->CheckSafeToWait(self);
   bool has_waited = false;
@@ -1559,7 +1559,7 @@
   // TODO: May also want to look for entirely empty pages maintained by SmallIrtAllocator.
   Barrier barrier(0);
   TrimIndirectReferenceTableClosure closure(&barrier);
-  ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
   size_t barrier_count = Runtime::Current()->GetThreadList()->RunCheckpoint(&closure);
   if (barrier_count != 0) {
     barrier.Increment(self, barrier_count);
@@ -1569,7 +1569,7 @@
 void Heap::StartGC(Thread* self, GcCause cause, CollectorType collector_type) {
   // Need to do this before acquiring the locks since we don't want to get suspended while
   // holding any locks.
-  ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcToComplete);
   MutexLock mu(self, *gc_complete_lock_);
   // Ensure there is only one GC at a time.
   WaitForGcToCompleteLocked(cause, self);
@@ -2000,7 +2000,7 @@
 
 size_t Heap::GetObjectsAllocated() const {
   Thread* const self = Thread::Current();
-  ScopedThreadStateChange tsc(self, kWaitingForGetObjectsAllocated);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGetObjectsAllocated);
   // Prevent GC running during GetObjectsAllocated since we may get a checkpoint request that tells
   // us to suspend while we are doing SuspendAll. b/35232978
   gc::ScopedGCCriticalSection gcs(Thread::Current(),
@@ -2085,10 +2085,10 @@
   // Inc requested homogeneous space compaction.
   count_requested_homogeneous_space_compaction_++;
   // Store performed homogeneous space compaction at a new request arrival.
-  ScopedThreadStateChange tsc(self, kWaitingPerformingGc);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingPerformingGc);
   Locks::mutator_lock_->AssertNotHeld(self);
   {
-    ScopedThreadStateChange tsc2(self, kWaitingForGcToComplete);
+    ScopedThreadStateChange tsc2(self, ThreadState::kWaitingForGcToComplete);
     MutexLock mu(self, *gc_complete_lock_);
     // Ensure there is only one GC at a time.
     WaitForGcToCompleteLocked(kGcCauseHomogeneousSpaceCompact, self);
@@ -2641,7 +2641,7 @@
       // here is full GC.
     }
   }
-  ScopedThreadStateChange tsc(self, kWaitingPerformingGc);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingPerformingGc);
   Locks::mutator_lock_->AssertNotHeld(self);
   if (self->IsHandlingStackOverflow()) {
     // If we are throwing a stack overflow error we probably don't have enough remaining stack
@@ -2653,7 +2653,7 @@
   bool compacting_gc;
   {
     gc_complete_lock_->AssertNotHeld(self);
-    ScopedThreadStateChange tsc2(self, kWaitingForGcToComplete);
+    ScopedThreadStateChange tsc2(self, ThreadState::kWaitingForGcToComplete);
     MutexLock mu(self, *gc_complete_lock_);
     // Ensure there is only one GC at a time.
     WaitForGcToCompleteLocked(gc_cause, self);
@@ -3437,7 +3437,7 @@
   // reachable objects.
   if (verify_pre_sweeping_heap_) {
     TimingLogger::ScopedTiming t2("(Paused)PostSweepingVerifyHeapReferences", timings);
-    CHECK_NE(self->GetState(), kRunnable);
+    CHECK_NE(self->GetState(), ThreadState::kRunnable);
     {
       WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
       // Swapping bound bitmaps does nothing.
@@ -3501,7 +3501,7 @@
 }
 
 collector::GcType Heap::WaitForGcToComplete(GcCause cause, Thread* self) {
-  ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcToComplete);
   MutexLock mu(self, *gc_complete_lock_);
   return WaitForGcToCompleteLocked(cause, self);
 }
diff --git a/runtime/gc/space/dlmalloc_space.cc b/runtime/gc/space/dlmalloc_space.cc
index 7564c89..4f7cc71 100644
--- a/runtime/gc/space/dlmalloc_space.cc
+++ b/runtime/gc/space/dlmalloc_space.cc
@@ -367,7 +367,7 @@
   // lock, temporarily release the shared access to the mutator
   // lock here by transitioning to the suspended state.
   Locks::mutator_lock_->AssertSharedHeld(self);
-  ScopedThreadSuspension sts(self, kSuspended);
+  ScopedThreadSuspension sts(self, ThreadState::kSuspended);
   Walk(MSpaceChunkCallback, &max_contiguous_allocation);
   if (failed_alloc_bytes > max_contiguous_allocation) {
     os << "; failed due to fragmentation (largest possible contiguous allocation "
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index fefba27..fe387e2 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1008,7 +1008,7 @@
         if (use_parallel) {
           ScopedTrace trace("Waiting for workers");
           // Go to native since we don't want to suspend while holding the mutator lock.
-          ScopedThreadSuspension sts(Thread::Current(), kNative);
+          ScopedThreadSuspension sts(Thread::Current(), ThreadState::kNative);
           pool->Wait(self, true, false);
         }
         const uint64_t time = NanoTime() - start;
diff --git a/runtime/gc/space/rosalloc_space.cc b/runtime/gc/space/rosalloc_space.cc
index fc9cad0..80430bd 100644
--- a/runtime/gc/space/rosalloc_space.cc
+++ b/runtime/gc/space/rosalloc_space.cc
@@ -396,7 +396,7 @@
     // The mutators are not suspended yet and we have a shared access
     // to the mutator lock. Temporarily release the shared access by
     // transitioning to the suspend state, and suspend the mutators.
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     InspectAllRosAllocWithSuspendAll(callback, arg, do_null_callback_at_end);
   } else {
     // The mutators are not suspended yet. Suspend the mutators.
diff --git a/runtime/gc/space/space_create_test.cc b/runtime/gc/space/space_create_test.cc
index 4849d6c..25bc12e 100644
--- a/runtime/gc/space/space_create_test.cc
+++ b/runtime/gc/space/space_create_test.cc
@@ -168,7 +168,7 @@
   gc::Heap* heap = Runtime::Current()->GetHeap();
   space::Space* old_space = space;
   {
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     ScopedSuspendAll ssa("Add image space");
     heap->RemoveSpace(old_space);
   }
diff --git a/runtime/gc/space/space_test.h b/runtime/gc/space/space_test.h
index e40ee50..4b01e83 100644
--- a/runtime/gc/space/space_test.h
+++ b/runtime/gc/space/space_test.h
@@ -46,7 +46,7 @@
       heap->RevokeAllThreadLocalBuffers();
     }
     {
-      ScopedThreadStateChange sts(Thread::Current(), kSuspended);
+      ScopedThreadStateChange sts(Thread::Current(), ThreadState::kSuspended);
       ScopedSuspendAll ssa("Add image space");
       heap->AddSpace(space);
     }
@@ -237,7 +237,7 @@
   size_t free_increment = 96;
   while (true) {
     {
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       // Give the space a haircut.
       space->Trim();
     }
diff --git a/runtime/gc/task_processor.cc b/runtime/gc/task_processor.cc
index 64e8322..494cf2b 100644
--- a/runtime/gc/task_processor.cc
+++ b/runtime/gc/task_processor.cc
@@ -39,14 +39,14 @@
 }
 
 void TaskProcessor::AddTask(Thread* self, HeapTask* task) {
-  ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForTaskProcessor);
   MutexLock mu(self, lock_);
   tasks_.insert(task);
   cond_.Signal(self);
 }
 
 HeapTask* TaskProcessor::GetTask(Thread* self) {
-  ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingForTaskProcessor);
   MutexLock mu(self, lock_);
   while (true) {
     if (tasks_.empty()) {
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
index d35847e..cfca51e 100644
--- a/runtime/instrumentation_test.cc
+++ b/runtime/instrumentation_test.cc
@@ -187,7 +187,7 @@
   void CheckConfigureStubs(const char* key, Instrumentation::InstrumentationLevel level) {
     ScopedObjectAccess soa(Thread::Current());
     instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
-    ScopedThreadSuspension sts(soa.Self(), kSuspended);
+    ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(soa.Self(),
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
@@ -216,7 +216,7 @@
     instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
     TestInstrumentationListener listener;
     {
-      ScopedThreadSuspension sts(soa.Self(), kSuspended);
+      ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended);
       ScopedSuspendAll ssa("Add instrumentation listener");
       instr->AddListener(&listener, instrumentation_event);
     }
@@ -240,7 +240,7 @@
 
     listener.Reset();
     {
-      ScopedThreadSuspension sts(soa.Self(), kSuspended);
+      ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended);
       ScopedSuspendAll ssa("Remove instrumentation listener");
       instr->RemoveListener(&listener, instrumentation_event);
     }
@@ -263,7 +263,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
@@ -279,7 +279,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
@@ -294,7 +294,7 @@
         REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
@@ -309,7 +309,7 @@
         REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
@@ -324,7 +324,7 @@
         REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
@@ -336,7 +336,7 @@
         REQUIRES_SHARED(Locks::mutator_lock_) {
     Runtime* runtime = Runtime::Current();
     instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     gc::ScopedGCCriticalSection gcs(self,
                                     gc::kGcCauseInstrumentation,
                                     gc::kCollectorTypeInstrumentation);
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index 6af0455..25c45cb 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -187,7 +187,7 @@
 void InternTable::WaitUntilAccessible(Thread* self) {
   Locks::intern_table_lock_->ExclusiveUnlock(self);
   {
-    ScopedThreadSuspension sts(self, kWaitingWeakGcRootRead);
+    ScopedThreadSuspension sts(self, ThreadState::kWaitingWeakGcRootRead);
     MutexLock mu(self, *Locks::intern_table_lock_);
     while ((!kUseReadBarrier && weak_root_state_ == gc::kWeakRootStateNoReadsOrWrites) ||
            (kUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 299892e..0cce09e 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -61,7 +61,7 @@
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
       jobject jresult;
       {
-        ScopedThreadStateChange tsc(self, kNative);
+        ScopedThreadStateChange tsc(self, ThreadState::kNative);
         jresult = fn(soa.Env(), klass.get());
       }
       result->SetL(soa.Decode<mirror::Object>(jresult));
@@ -70,28 +70,28 @@
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       fn(soa.Env(), klass.get());
     } else if (shorty == "Z") {
       using fntype = jboolean(JNIEnv*, jclass);
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetZ(fn(soa.Env(), klass.get()));
     } else if (shorty == "BI") {
       using fntype = jbyte(JNIEnv*, jclass, jint);
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetB(fn(soa.Env(), klass.get(), args[0]));
     } else if (shorty == "II") {
       using fntype = jint(JNIEnv*, jclass, jint);
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetI(fn(soa.Env(), klass.get(), args[0]));
     } else if (shorty == "LL") {
       using fntype = jobject(JNIEnv*, jclass, jobject);
@@ -102,7 +102,7 @@
                                    soa.AddLocalReference<jobject>(ObjArg(args[0])));
       jobject jresult;
       {
-        ScopedThreadStateChange tsc(self, kNative);
+        ScopedThreadStateChange tsc(self, ThreadState::kNative);
         jresult = fn(soa.Env(), klass.get(), arg0.get());
       }
       result->SetL(soa.Decode<mirror::Object>(jresult));
@@ -111,7 +111,7 @@
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetI(fn(soa.Env(), klass.get(), args[0], args[1]));
     } else if (shorty == "ILI") {
       using fntype = jint(JNIEnv*, jclass, jobject, jint);
@@ -121,7 +121,7 @@
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
       ScopedLocalRef<jobject> arg0(soa.Env(),
                                    soa.AddLocalReference<jobject>(ObjArg(args[0])));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetI(fn(soa.Env(), klass.get(), arg0.get(), args[1]));
     } else if (shorty == "SIZ") {
       using fntype = jshort(JNIEnv*, jclass, jint, jboolean);
@@ -129,14 +129,14 @@
           reinterpret_cast<fntype*>(const_cast<void*>(method->GetEntryPointFromJni()));
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetS(fn(soa.Env(), klass.get(), args[0], args[1]));
     } else if (shorty == "VIZ") {
       using fntype = void(JNIEnv*, jclass, jint, jboolean);
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jclass> klass(soa.Env(),
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       fn(soa.Env(), klass.get(), args[0], args[1]);
     } else if (shorty == "ZLL") {
       using fntype = jboolean(JNIEnv*, jclass, jobject, jobject);
@@ -147,7 +147,7 @@
                                    soa.AddLocalReference<jobject>(ObjArg(args[0])));
       ScopedLocalRef<jobject> arg1(soa.Env(),
                                    soa.AddLocalReference<jobject>(ObjArg(args[1])));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetZ(fn(soa.Env(), klass.get(), arg0.get(), arg1.get()));
     } else if (shorty == "ZILL") {
       using fntype = jboolean(JNIEnv*, jclass, jint, jobject, jobject);
@@ -158,7 +158,7 @@
                                    soa.AddLocalReference<jobject>(ObjArg(args[1])));
       ScopedLocalRef<jobject> arg2(soa.Env(),
                                    soa.AddLocalReference<jobject>(ObjArg(args[2])));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetZ(fn(soa.Env(), klass.get(), args[0], arg1.get(), arg2.get()));
     } else if (shorty == "VILII") {
       using fntype = void(JNIEnv*, jclass, jint, jobject, jint, jint);
@@ -167,7 +167,7 @@
                                    soa.AddLocalReference<jclass>(method->GetDeclaringClass()));
       ScopedLocalRef<jobject> arg1(soa.Env(),
                                    soa.AddLocalReference<jobject>(ObjArg(args[1])));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       fn(soa.Env(), klass.get(), args[0], arg1.get(), args[2], args[3]);
     } else if (shorty == "VLILII") {
       using fntype = void(JNIEnv*, jclass, jobject, jint, jobject, jint, jint);
@@ -178,7 +178,7 @@
                                    soa.AddLocalReference<jobject>(ObjArg(args[0])));
       ScopedLocalRef<jobject> arg2(soa.Env(),
                                    soa.AddLocalReference<jobject>(ObjArg(args[2])));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       fn(soa.Env(), klass.get(), arg0.get(), args[1], arg2.get(), args[3], args[4]);
     } else {
       LOG(FATAL) << "Do something with static native method: " << method->PrettyMethod()
@@ -192,7 +192,7 @@
                                    soa.AddLocalReference<jobject>(receiver));
       jobject jresult;
       {
-        ScopedThreadStateChange tsc(self, kNative);
+        ScopedThreadStateChange tsc(self, ThreadState::kNative);
         jresult = fn(soa.Env(), rcvr.get());
       }
       result->SetL(soa.Decode<mirror::Object>(jresult));
@@ -201,7 +201,7 @@
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jobject> rcvr(soa.Env(),
                                    soa.AddLocalReference<jobject>(receiver));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       fn(soa.Env(), rcvr.get());
     } else if (shorty == "LL") {
       using fntype = jobject(JNIEnv*, jobject, jobject);
@@ -212,17 +212,17 @@
                                    soa.AddLocalReference<jobject>(ObjArg(args[0])));
       jobject jresult;
       {
-        ScopedThreadStateChange tsc(self, kNative);
+        ScopedThreadStateChange tsc(self, ThreadState::kNative);
         jresult = fn(soa.Env(), rcvr.get(), arg0.get());
       }
       result->SetL(soa.Decode<mirror::Object>(jresult));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
     } else if (shorty == "III") {
       using fntype = jint(JNIEnv*, jobject, jint, jint);
       fntype* const fn = reinterpret_cast<fntype*>(method->GetEntryPointFromJni());
       ScopedLocalRef<jobject> rcvr(soa.Env(),
                                    soa.AddLocalReference<jobject>(receiver));
-      ScopedThreadStateChange tsc(self, kNative);
+      ScopedThreadStateChange tsc(self, ThreadState::kNative);
       result->SetI(fn(soa.Env(), rcvr.get(), args[0], args[1]));
     } else {
       LOG(FATAL) << "Do something with native method: " << method->PrettyMethod()
diff --git a/runtime/interpreter/mterp/arm64ng/main.S b/runtime/interpreter/mterp/arm64ng/main.S
index ffd1b13..53792c0 100644
--- a/runtime/interpreter/mterp/arm64ng/main.S
+++ b/runtime/interpreter/mterp/arm64ng/main.S
@@ -309,8 +309,8 @@
     add x2, x2, #-1
     strh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
     // Otherwise, do a suspend check.
-    ldr x0, [xSELF, #THREAD_FLAGS_OFFSET]
-    ands x0, x0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
+    ldr w0, [xSELF, #THREAD_FLAGS_OFFSET]
+    tst w0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
     b.eq 1b
     EXPORT_PC
     bl    art_quick_test_suspend
@@ -435,8 +435,8 @@
     cbz w2, 2f
     add x2, x2, #-1
     strh w2, [x0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
-    ldr x0, [xSELF, #THREAD_FLAGS_OFFSET]
-    tst x0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
+    ldr w0, [xSELF, #THREAD_FLAGS_OFFSET]
+    tst w0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
     b.ne 3f
 1:
     FETCH_INST
diff --git a/runtime/interpreter/mterp/armng/main.S b/runtime/interpreter/mterp/armng/main.S
index e21c64f..e26d922 100644
--- a/runtime/interpreter/mterp/armng/main.S
+++ b/runtime/interpreter/mterp/armng/main.S
@@ -318,7 +318,7 @@
     strh r2, [r0, #ART_METHOD_HOTNESS_COUNT_OFFSET]
     // Otherwise, do a suspend check.
     ldr r0, [rSELF, #THREAD_FLAGS_OFFSET]
-    ands r0, r0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
+    tst r0, #THREAD_SUSPEND_OR_CHECKPOINT_REQUEST
     beq 1b
     EXPORT_PC
     bl    art_quick_test_suspend
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 3fcb10a..5cf08f9 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -568,7 +568,7 @@
   if (IsWeakAccessEnabled(self)) {
     return;
   }
-  ScopedThreadSuspension sts(self, kWaitingWeakGcRootRead);
+  ScopedThreadSuspension sts(self, ThreadState::kWaitingWeakGcRootRead);
   MutexLock mu(self, *Locks::jit_lock_);
   while (!IsWeakAccessEnabled(self)) {
     inline_cache_cond_.Wait(self);
@@ -625,7 +625,7 @@
   while (collection_in_progress_) {
     Locks::jit_lock_->Unlock(self);
     {
-      ScopedThreadSuspension sts(self, kSuspended);
+      ScopedThreadSuspension sts(self, ThreadState::kSuspended);
       MutexLock mu(self, *Locks::jit_lock_);
       WaitForPotentialCollectionToComplete(self);
     }
@@ -943,7 +943,7 @@
   while (true) {
     bool at_max_capacity = false;
     {
-      ScopedThreadSuspension sts(self, kSuspended);
+      ScopedThreadSuspension sts(self, ThreadState::kSuspended);
       MutexLock mu(self, *Locks::jit_lock_);
       WaitForPotentialCollectionToComplete(self);
       ScopedCodeCacheWrite ccw(*region);
@@ -1069,7 +1069,7 @@
   threads_running_checkpoint = Runtime::Current()->GetThreadList()->RunCheckpoint(&closure);
   // Now that we have run our checkpoint, move to a suspended state and wait
   // for other threads to run the checkpoint.
-  ScopedThreadSuspension sts(self, kSuspended);
+  ScopedThreadSuspension sts(self, ThreadState::kSuspended);
   if (threads_running_checkpoint != 0) {
     barrier.Increment(self, threads_running_checkpoint);
   }
@@ -1100,7 +1100,7 @@
   ScopedTrace trace(__FUNCTION__);
   // Wait for an existing collection, or let everyone know we are starting one.
   {
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     MutexLock mu(self, *Locks::jit_lock_);
     if (!garbage_collect_code_) {
       private_region_.IncreaseCodeCacheCapacity();
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index a5cb81b..cea654f 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -788,7 +788,7 @@
     // Release the mutator lock. We shall need to re-acquire the lock for a moment to
     // destroy the `VariableSizedHandleScope` inside the `helper` which shall be
     // conveniently handled by destroying `sts`, then `helper` and then `soa`.
-    ScopedThreadSuspension sts(self, kNative);
+    ScopedThreadSuspension sts(self, ThreadState::kNative);
     // Get back to the previous thread priority. We shall not increase the priority
     // for the short time we need to re-acquire mutator lock for `helper` destructor.
     sdp.reset();
diff --git a/runtime/jni/java_vm_ext.cc b/runtime/jni/java_vm_ext.cc
index 053afc9..dd45c32 100644
--- a/runtime/jni/java_vm_ext.cc
+++ b/runtime/jni/java_vm_ext.cc
@@ -285,7 +285,7 @@
     const char* shorty = m->GetShorty();
     {
       // Go to suspended since dlsym may block for a long time if other threads are using dlopen.
-      ScopedThreadSuspension sts(self, kNative);
+      ScopedThreadSuspension sts(self, ThreadState::kNative);
       void* native_code = FindNativeMethodInternal(self,
                                                    declaring_class_loader_allocator,
                                                    shorty,
@@ -353,7 +353,7 @@
         }
       }
     }
-    ScopedThreadSuspension sts(self, kNative);
+    ScopedThreadSuspension sts(self, ThreadState::kNative);
     // Do this without holding the jni libraries lock to prevent possible deadlocks.
     UnloadLibraries(self->GetJniEnv()->GetVm(), unload_libraries);
     for (auto library : unload_libraries) {
@@ -575,7 +575,7 @@
     check_jni_abort_hook_(check_jni_abort_hook_data_, os.str());
   } else {
     // Ensure that we get a native stack trace for this thread.
-    ScopedThreadSuspension sts(self, kNative);
+    ScopedThreadSuspension sts(self, ThreadState::kNative);
     LOG(FATAL) << os.str();
     UNREACHABLE();
   }
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 2476b13..c679fde 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -100,7 +100,7 @@
 }
 
 inline void Object::Wait(Thread* self, int64_t ms, int32_t ns) {
-  Monitor::Wait(self, this, ms, ns, true, kTimedWaiting);
+  Monitor::Wait(self, this, ms, ns, true, ThreadState::kTimedWaiting);
 }
 
 inline uint32_t Object::GetMarkBit() {
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index f2189e1..0cad79b 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -515,7 +515,8 @@
   }
   self->SetMonitorEnterObject(GetObject().Ptr());
   {
-    ScopedThreadSuspension tsc(self, kBlocked);  // Change to blocked and give up mutator_lock_.
+    // Change to blocked and give up mutator_lock_.
+    ScopedThreadSuspension tsc(self, ThreadState::kBlocked);
 
     // Acquire monitor_lock_ without mutator_lock_, expecting to block this time.
     // We already tried spinning above. The shutdown procedure currently assumes we stop
@@ -827,7 +828,9 @@
 void Monitor::Wait(Thread* self, int64_t ms, int32_t ns,
                    bool interruptShouldThrow, ThreadState why) {
   DCHECK(self != nullptr);
-  DCHECK(why == kTimedWaiting || why == kWaiting || why == kSleeping);
+  DCHECK(why == ThreadState::kTimedWaiting ||
+         why == ThreadState::kWaiting ||
+         why == ThreadState::kSleeping);
 
   // Make sure that we hold the lock.
   if (owner_.load(std::memory_order_relaxed) != self) {
@@ -837,8 +840,8 @@
 
   // We need to turn a zero-length timed wait into a regular wait because
   // Object.wait(0, 0) is defined as Object.wait(0), which is defined as Object.wait().
-  if (why == kTimedWaiting && (ms == 0 && ns == 0)) {
-    why = kWaiting;
+  if (why == ThreadState::kTimedWaiting && (ms == 0 && ns == 0)) {
+    why = ThreadState::kWaiting;
   }
 
   // Enforce the timeout range.
@@ -900,10 +903,10 @@
       was_interrupted = true;
     } else {
       // Wait for a notification or a timeout to occur.
-      if (why == kWaiting) {
+      if (why == ThreadState::kWaiting) {
         self->GetWaitConditionVariable()->Wait(self);
       } else {
-        DCHECK(why == kTimedWaiting || why == kSleeping) << why;
+        DCHECK(why == ThreadState::kTimedWaiting || why == ThreadState::kSleeping) << why;
         timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
       }
       was_interrupted = self->IsInterrupted();
@@ -1065,7 +1068,7 @@
     bool timed_out;
     Thread* owner;
     {
-      ScopedThreadSuspension sts(self, kWaitingForLockInflation);
+      ScopedThreadSuspension sts(self, ThreadState::kWaitingForLockInflation);
       owner = thread_list->SuspendThreadByThreadId(owner_thread_id,
                                                    SuspendReason::kInternal,
                                                    &timed_out);
@@ -1388,9 +1391,9 @@
   ThreadState state = thread->GetState();
 
   switch (state) {
-    case kWaiting:
-    case kTimedWaiting:
-    case kSleeping:
+    case ThreadState::kWaiting:
+    case ThreadState::kTimedWaiting:
+    case ThreadState::kSleeping:
     {
       Thread* self = Thread::Current();
       MutexLock mu(self, *thread->GetWaitMutex());
@@ -1401,8 +1404,8 @@
     }
     break;
 
-    case kBlocked:
-    case kWaitingForLockInflation:
+    case ThreadState::kBlocked:
+    case ThreadState::kWaitingForLockInflation:
     {
       ObjPtr<mirror::Object> lock_object = thread->GetMonitorEnterObject();
       if (lock_object != nullptr) {
diff --git a/runtime/monitor_objects_stack_visitor.cc b/runtime/monitor_objects_stack_visitor.cc
index 61f4159..2e75e37 100644
--- a/runtime/monitor_objects_stack_visitor.cc
+++ b/runtime/monitor_objects_stack_visitor.cc
@@ -47,16 +47,16 @@
                                             &monitor_object,
                                             &lock_owner_tid);
     switch (state) {
-      case kWaiting:
-      case kTimedWaiting:
+      case ThreadState::kWaiting:
+      case ThreadState::kTimedWaiting:
         VisitWaitingObject(monitor_object, state);
         break;
-      case kSleeping:
+      case ThreadState::kSleeping:
         VisitSleepingObject(monitor_object);
         break;
 
-      case kBlocked:
-      case kWaitingForLockInflation:
+      case ThreadState::kBlocked:
+      case ThreadState::kWaitingForLockInflation:
         VisitBlockedOnObject(monitor_object, state, lock_owner_tid);
         break;
 
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index 8ddd50f..66008f3 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -265,7 +265,7 @@
   }
 
   // Need to drop the mutator lock to allow barriers.
-  ScopedThreadSuspension sts(soa.Self(), kNative);
+  ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative);
   ThreadPool thread_pool(pool_name, 3);
   thread_pool.AddTask(self, new CreateTask(test, create_sleep, c_millis, c_expected));
   if (interrupt) {
@@ -361,7 +361,7 @@
     // Test failure case.
     thread_pool.AddTask(self, new TryLockTask(obj1));
     thread_pool.StartWorkers(self);
-    ScopedThreadSuspension sts(self, kSuspended);
+    ScopedThreadSuspension sts(self, ThreadState::kSuspended);
     thread_pool.Wait(Thread::Current(), /*do_work=*/false, /*may_hold_locks=*/false);
   }
   // Test that the trylock actually locks the object.
diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc
index e88516e..71078c9 100644
--- a/runtime/native/dalvik_system_VMStack.cc
+++ b/runtime/native/dalvik_system_VMStack.cc
@@ -55,7 +55,7 @@
       return nullptr;
     }
     // Suspend thread to build stack trace.
-    ScopedThreadSuspension sts(soa.Self(), kNative);
+    ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative);
     ThreadList* thread_list = Runtime::Current()->GetThreadList();
     bool timed_out;
     Thread* thread = thread_list->SuspendThreadByPeer(peer,
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index c3b4fe0..570c554 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -71,41 +71,43 @@
   const jint kJavaTerminated = 5;
 
   ScopedObjectAccess soa(env);
-  ThreadState internal_thread_state = (has_been_started ? kTerminated : kStarting);
+  ThreadState internal_thread_state =
+      (has_been_started ? ThreadState::kTerminated : ThreadState::kStarting);
   MutexLock mu(soa.Self(), *Locks::thread_list_lock_);
   Thread* thread = Thread::FromManagedThread(soa, java_thread);
   if (thread != nullptr) {
     internal_thread_state = thread->GetState();
   }
   switch (internal_thread_state) {
-    case kTerminated:                     return kJavaTerminated;
-    case kRunnable:                       return kJavaRunnable;
-    case kTimedWaiting:                   return kJavaTimedWaiting;
-    case kSleeping:                       return kJavaTimedWaiting;
-    case kBlocked:                        return kJavaBlocked;
-    case kWaiting:                        return kJavaWaiting;
-    case kStarting:                       return kJavaNew;
-    case kNative:                         return kJavaRunnable;
-    case kWaitingForTaskProcessor:        return kJavaWaiting;
-    case kWaitingForLockInflation:        return kJavaWaiting;
-    case kWaitingForGcToComplete:         return kJavaWaiting;
-    case kWaitingPerformingGc:            return kJavaWaiting;
-    case kWaitingForCheckPointsToRun:     return kJavaWaiting;
-    case kWaitingForDebuggerSend:         return kJavaWaiting;
-    case kWaitingForDebuggerToAttach:     return kJavaWaiting;
-    case kWaitingInMainDebuggerLoop:      return kJavaWaiting;
-    case kWaitingForDebuggerSuspension:   return kJavaWaiting;
-    case kWaitingForDeoptimization:       return kJavaWaiting;
-    case kWaitingForGetObjectsAllocated:  return kJavaWaiting;
-    case kWaitingForJniOnLoad:            return kJavaWaiting;
-    case kWaitingForSignalCatcherOutput:  return kJavaWaiting;
-    case kWaitingInMainSignalCatcherLoop: return kJavaWaiting;
-    case kWaitingForMethodTracingStart:   return kJavaWaiting;
-    case kWaitingForVisitObjects:         return kJavaWaiting;
-    case kWaitingWeakGcRootRead:          return kJavaRunnable;
-    case kWaitingForGcThreadFlip:         return kJavaWaiting;
-    case kNativeForAbort:                 return kJavaWaiting;
-    case kSuspended:                      return kJavaRunnable;
+    case ThreadState::kTerminated:                     return kJavaTerminated;
+    case ThreadState::kRunnable:                       return kJavaRunnable;
+    case ThreadState::kObsoleteRunnable:               break;  // Obsolete value.
+    case ThreadState::kTimedWaiting:                   return kJavaTimedWaiting;
+    case ThreadState::kSleeping:                       return kJavaTimedWaiting;
+    case ThreadState::kBlocked:                        return kJavaBlocked;
+    case ThreadState::kWaiting:                        return kJavaWaiting;
+    case ThreadState::kStarting:                       return kJavaNew;
+    case ThreadState::kNative:                         return kJavaRunnable;
+    case ThreadState::kWaitingForTaskProcessor:        return kJavaWaiting;
+    case ThreadState::kWaitingForLockInflation:        return kJavaWaiting;
+    case ThreadState::kWaitingForGcToComplete:         return kJavaWaiting;
+    case ThreadState::kWaitingPerformingGc:            return kJavaWaiting;
+    case ThreadState::kWaitingForCheckPointsToRun:     return kJavaWaiting;
+    case ThreadState::kWaitingForDebuggerSend:         return kJavaWaiting;
+    case ThreadState::kWaitingForDebuggerToAttach:     return kJavaWaiting;
+    case ThreadState::kWaitingInMainDebuggerLoop:      return kJavaWaiting;
+    case ThreadState::kWaitingForDebuggerSuspension:   return kJavaWaiting;
+    case ThreadState::kWaitingForDeoptimization:       return kJavaWaiting;
+    case ThreadState::kWaitingForGetObjectsAllocated:  return kJavaWaiting;
+    case ThreadState::kWaitingForJniOnLoad:            return kJavaWaiting;
+    case ThreadState::kWaitingForSignalCatcherOutput:  return kJavaWaiting;
+    case ThreadState::kWaitingInMainSignalCatcherLoop: return kJavaWaiting;
+    case ThreadState::kWaitingForMethodTracingStart:   return kJavaWaiting;
+    case ThreadState::kWaitingForVisitObjects:         return kJavaWaiting;
+    case ThreadState::kWaitingWeakGcRootRead:          return kJavaRunnable;
+    case ThreadState::kWaitingForGcThreadFlip:         return kJavaWaiting;
+    case ThreadState::kNativeForAbort:                 return kJavaWaiting;
+    case ThreadState::kSuspended:                      return kJavaRunnable;
     // Don't add a 'default' here so the compiler can spot incompatible enum changes.
   }
   LOG(ERROR) << "Unexpected thread state: " << internal_thread_state;
@@ -180,7 +182,7 @@
 static void Thread_sleep(JNIEnv* env, jclass, jobject java_lock, jlong ms, jint ns) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::Object> lock = soa.Decode<mirror::Object>(java_lock);
-  Monitor::Wait(Thread::Current(), lock.Ptr(), ms, ns, true, kSleeping);
+  Monitor::Wait(Thread::Current(), lock.Ptr(), ms, ns, true, ThreadState::kSleeping);
 }
 
 /*
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index f74e120..081ec20 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -109,38 +109,40 @@
     TS_WAIT     = 4,  // (in Object.wait())
   };
   switch (state) {
-    case kBlocked:
+    case ThreadState::kBlocked:
       return TS_MONITOR;
-    case kNative:
-    case kRunnable:
-    case kSuspended:
+    case ThreadState::kNative:
+    case ThreadState::kRunnable:
+    case ThreadState::kSuspended:
       return TS_RUNNING;
-    case kSleeping:
+    case ThreadState::kObsoleteRunnable:
+      break;  // Obsolete value.
+    case ThreadState::kSleeping:
       return TS_SLEEPING;
-    case kStarting:
-    case kTerminated:
+    case ThreadState::kStarting:
+    case ThreadState::kTerminated:
       return TS_ZOMBIE;
-    case kTimedWaiting:
-    case kWaitingForTaskProcessor:
-    case kWaitingForLockInflation:
-    case kWaitingForCheckPointsToRun:
-    case kWaitingForDebuggerSend:
-    case kWaitingForDebuggerSuspension:
-    case kWaitingForDebuggerToAttach:
-    case kWaitingForDeoptimization:
-    case kWaitingForGcToComplete:
-    case kWaitingForGetObjectsAllocated:
-    case kWaitingForJniOnLoad:
-    case kWaitingForMethodTracingStart:
-    case kWaitingForSignalCatcherOutput:
-    case kWaitingForVisitObjects:
-    case kWaitingInMainDebuggerLoop:
-    case kWaitingInMainSignalCatcherLoop:
-    case kWaitingPerformingGc:
-    case kWaitingWeakGcRootRead:
-    case kWaitingForGcThreadFlip:
-    case kNativeForAbort:
-    case kWaiting:
+    case ThreadState::kTimedWaiting:
+    case ThreadState::kWaitingForTaskProcessor:
+    case ThreadState::kWaitingForLockInflation:
+    case ThreadState::kWaitingForCheckPointsToRun:
+    case ThreadState::kWaitingForDebuggerSend:
+    case ThreadState::kWaitingForDebuggerSuspension:
+    case ThreadState::kWaitingForDebuggerToAttach:
+    case ThreadState::kWaitingForDeoptimization:
+    case ThreadState::kWaitingForGcToComplete:
+    case ThreadState::kWaitingForGetObjectsAllocated:
+    case ThreadState::kWaitingForJniOnLoad:
+    case ThreadState::kWaitingForMethodTracingStart:
+    case ThreadState::kWaitingForSignalCatcherOutput:
+    case ThreadState::kWaitingForVisitObjects:
+    case ThreadState::kWaitingInMainDebuggerLoop:
+    case ThreadState::kWaitingInMainSignalCatcherLoop:
+    case ThreadState::kWaitingPerformingGc:
+    case ThreadState::kWaitingWeakGcRootRead:
+    case ThreadState::kWaitingForGcThreadFlip:
+    case ThreadState::kNativeForAbort:
+    case ThreadState::kWaiting:
       return TS_WAIT;
       // Don't add a 'default' here so the compiler can spot incompatible enum changes.
   }
diff --git a/runtime/native/scoped_fast_native_object_access-inl.h b/runtime/native/scoped_fast_native_object_access-inl.h
index 20ff76e..0b8ad11 100644
--- a/runtime/native/scoped_fast_native_object_access-inl.h
+++ b/runtime/native/scoped_fast_native_object_access-inl.h
@@ -29,7 +29,7 @@
   Locks::mutator_lock_->AssertSharedHeld(Self());
   DCHECK((*Self()->GetManagedStack()->GetTopQuickFrame())->IsFastNative());
   // Don't work with raw objects in non-runnable states.
-  DCHECK_EQ(Self()->GetState(), kRunnable);
+  DCHECK_EQ(Self()->GetState(), ThreadState::kRunnable);
 }
 
 }  // namespace art
diff --git a/runtime/oat.h b/runtime/oat.h
index 0b6bf7d..8a97cdd 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
-  // Last oat version changed reason: JNI: Rewrite locking for synchronized methods.
-  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '1', '3', '\0' } };
+  // Last oat version changed reason: Always access thread state and flags as a 32-bit location.
+  static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '1', '4', '\0' } };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 7166dd1..dfa4518 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -263,7 +263,7 @@
             // Add image space has a race condition since other threads could be reading from the
             // spaces array.
             {
-              ScopedThreadSuspension sts(self, kSuspended);
+              ScopedThreadSuspension sts(self, ThreadState::kSuspended);
               gc::ScopedGCCriticalSection gcs(self,
                                               gc::kGcCauseAddRemoveAppImageSpace,
                                               gc::kCollectorTypeAddRemoveAppImageSpace);
@@ -290,7 +290,7 @@
               LOG(INFO) << "Failed to add image file " << temp_error_msg;
               dex_files.clear();
               {
-                ScopedThreadSuspension sts(self, kSuspended);
+                ScopedThreadSuspension sts(self, ThreadState::kSuspended);
                 gc::ScopedGCCriticalSection gcs(self,
                                                 gc::kGcCauseAddRemoveAppImageSpace,
                                                 gc::kCollectorTypeAddRemoveAppImageSpace);
diff --git a/runtime/object_lock.cc b/runtime/object_lock.cc
index 744bc42..9e02484 100644
--- a/runtime/object_lock.cc
+++ b/runtime/object_lock.cc
@@ -35,7 +35,7 @@
 
 template <typename T>
 void ObjectLock<T>::WaitIgnoringInterrupts() {
-  Monitor::Wait(self_, obj_.Get(), 0, 0, false, kWaiting);
+  Monitor::Wait(self_, obj_.Get(), 0, 0, false, ThreadState::kWaiting);
 }
 
 template <typename T>
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index f642bcb..a7290a2 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -879,7 +879,7 @@
   }
 
   ScopedObjectAccessUnchecked soa(Thread::Current());
-  DCHECK_EQ(soa.Self()->GetState(), kRunnable);
+  DCHECK_EQ(soa.Self()->GetState(), ThreadState::kRunnable);
 
   ArgArray arg_array(shorty, 2);
   JValue result;
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 0f6b521..bbc6492 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -654,7 +654,7 @@
   {
     // Ensure that we don't have multiple threads trying to abort at once,
     // which would result in significantly worse diagnostics.
-    ScopedThreadStateChange tsc(Thread::Current(), kNativeForAbort);
+    ScopedThreadStateChange tsc(Thread::Current(), ThreadState::kNativeForAbort);
     Locks::abort_lock_->ExclusiveLock(Thread::Current());
   }
 
@@ -718,7 +718,7 @@
 
 void Runtime::CallExitHook(jint status) {
   if (exit_ != nullptr) {
-    ScopedThreadStateChange tsc(Thread::Current(), kNative);
+    ScopedThreadStateChange tsc(Thread::Current(), ThreadState::kNative);
     exit_(status);
     LOG(WARNING) << "Exit hook returned instead of exiting!";
   }
@@ -883,7 +883,7 @@
   // Restore main thread state to kNative as expected by native code.
   Thread* self = Thread::Current();
 
-  self->TransitionFromRunnableToSuspended(kNative);
+  self->TransitionFromRunnableToSuspended(ThreadState::kNative);
 
   started_ = true;
 
@@ -990,7 +990,7 @@
   finished_starting_ = true;
 
   if (trace_config_.get() != nullptr && trace_config_->trace_file != "") {
-    ScopedThreadStateChange tsc(self, kWaitingForMethodTracingStart);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForMethodTracingStart);
     Trace::Start(trace_config_->trace_file.c_str(),
                  static_cast<int>(trace_config_->trace_file_size),
                  0,
@@ -1195,7 +1195,7 @@
   Thread* self = Thread::Current();
 
   // Must be in the kNative state for calling native methods.
-  CHECK_EQ(self->GetState(), kNative);
+  CHECK_EQ(self->GetState(), ThreadState::kNative);
 
   JNIEnv* env = self->GetJniEnv();
   env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
@@ -2086,7 +2086,7 @@
   JNIEnv* env = self->GetJniEnv();
 
   // Must be in the kNative state for calling native methods (JNI_OnLoad code).
-  CHECK_EQ(self->GetState(), kNative);
+  CHECK_EQ(self->GetState(), ThreadState::kNative);
 
   // Set up the native methods provided by the runtime itself.
   RegisterRuntimeNativeMethods(env);
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index e49deae..7619750 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -55,7 +55,7 @@
 
     Thread* self = Thread::Current();
     ScopedObjectAccess soa(self);
-    ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
+    ScopedThreadSuspension sts(self, ThreadState::kWaitingForDebuggerToAttach);
     ScopedSuspendAll ssa("RuntimeCallbacksTest SetUp");
     AddListener();
   }
@@ -64,7 +64,7 @@
     {
       Thread* self = Thread::Current();
       ScopedObjectAccess soa(self);
-      ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
+      ScopedThreadSuspension sts(self, ThreadState::kWaitingForDebuggerToAttach);
       ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown");
       RemoveListener();
     }
@@ -523,7 +523,7 @@
           /*ms=*/0,
           /*ns=*/0,
           /*interruptShouldThrow=*/false,
-          /*why=*/kWaiting);
+          /*why=*/ThreadState::kWaiting);
     }
   }
   ASSERT_TRUE(cb_.saw_wait_start_);
diff --git a/runtime/scoped_thread_state_change-inl.h b/runtime/scoped_thread_state_change-inl.h
index 04be224..d601952 100644
--- a/runtime/scoped_thread_state_change-inl.h
+++ b/runtime/scoped_thread_state_change-inl.h
@@ -34,7 +34,7 @@
     : self_(self), thread_state_(new_thread_state), expected_has_no_thread_(false) {
   if (UNLIKELY(self_ == nullptr)) {
     // Value chosen arbitrarily and won't be used in the destructor since thread_ == null.
-    old_thread_state_ = kTerminated;
+    old_thread_state_ = ThreadState::kTerminated;
     Runtime* runtime = Runtime::Current();
     CHECK(runtime == nullptr || !runtime->IsStarted() || runtime->IsShuttingDown(self_));
   } else {
@@ -43,9 +43,9 @@
     // in the suspend count (this will be handled in the runnable transitions).
     old_thread_state_ = self->GetState();
     if (old_thread_state_ != new_thread_state) {
-      if (new_thread_state == kRunnable) {
+      if (new_thread_state == ThreadState::kRunnable) {
         self_->TransitionFromSuspendedToRunnable();
-      } else if (old_thread_state_ == kRunnable) {
+      } else if (old_thread_state_ == ThreadState::kRunnable) {
         self_->TransitionFromRunnableToSuspended(new_thread_state);
       } else {
         // A suspended transition to another effectively suspended transition, ok to use Unsafe.
@@ -60,9 +60,9 @@
     ScopedThreadChangeDestructorCheck();
   } else {
     if (old_thread_state_ != thread_state_) {
-      if (old_thread_state_ == kRunnable) {
+      if (old_thread_state_ == ThreadState::kRunnable) {
         self_->TransitionFromSuspendedToRunnable();
-      } else if (thread_state_ == kRunnable) {
+      } else if (thread_state_ == ThreadState::kRunnable) {
         self_->TransitionFromRunnableToSuspended(old_thread_state_);
       } else {
         // A suspended transition to another effectively suspended transition, ok to use Unsafe.
@@ -90,7 +90,7 @@
 }
 
 inline bool ScopedObjectAccessAlreadyRunnable::IsRunnable() const {
-  return self_->GetState() == kRunnable;
+  return self_->GetState() == ThreadState::kRunnable;
 }
 
 inline ScopedObjectAccessAlreadyRunnable::ScopedObjectAccessAlreadyRunnable(JNIEnv* env)
@@ -102,13 +102,13 @@
       vm_(env_ != nullptr ? env_->GetVm() : nullptr) {}
 
 inline ScopedObjectAccessUnchecked::ScopedObjectAccessUnchecked(JNIEnv* env)
-    : ScopedObjectAccessAlreadyRunnable(env), tsc_(Self(), kRunnable) {
+    : ScopedObjectAccessAlreadyRunnable(env), tsc_(Self(), ThreadState::kRunnable) {
   Self()->VerifyStack();
   Locks::mutator_lock_->AssertSharedHeld(Self());
 }
 
 inline ScopedObjectAccessUnchecked::ScopedObjectAccessUnchecked(Thread* self)
-    : ScopedObjectAccessAlreadyRunnable(self), tsc_(self, kRunnable) {
+    : ScopedObjectAccessAlreadyRunnable(self), tsc_(self, ThreadState::kRunnable) {
   Self()->VerifyStack();
   Locks::mutator_lock_->AssertSharedHeld(Self());
 }
diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h
index 08cb5b4..7416d18 100644
--- a/runtime/scoped_thread_state_change.h
+++ b/runtime/scoped_thread_state_change.h
@@ -55,12 +55,12 @@
   ScopedThreadStateChange() {}
 
   Thread* const self_ = nullptr;
-  const ThreadState thread_state_ = kTerminated;
+  const ThreadState thread_state_ = ThreadState::kTerminated;
 
  private:
   void ScopedThreadChangeDestructorCheck();
 
-  ThreadState old_thread_state_ = kTerminated;
+  ThreadState old_thread_state_ = ThreadState::kTerminated;
   const bool expected_has_no_thread_ = true;
 
   friend class ScopedObjectAccessUnchecked;
diff --git a/runtime/signal_catcher.cc b/runtime/signal_catcher.cc
index f76a0d0..70cebaf 100644
--- a/runtime/signal_catcher.cc
+++ b/runtime/signal_catcher.cc
@@ -104,7 +104,7 @@
 }
 
 void SignalCatcher::Output(const std::string& s) {
-  ScopedThreadStateChange tsc(Thread::Current(), kWaitingForSignalCatcherOutput);
+  ScopedThreadStateChange tsc(Thread::Current(), ThreadState::kWaitingForSignalCatcherOutput);
   palette_status_t status = PaletteWriteCrashThreadStacks(s.data(), s.size());
   if (status == PALETTE_STATUS_OK) {
     LOG(INFO) << "Wrote stack traces to tombstoned";
@@ -149,7 +149,7 @@
 }
 
 int SignalCatcher::WaitForSignal(Thread* self, SignalSet& signals) {
-  ScopedThreadStateChange tsc(self, kWaitingInMainSignalCatcherLoop);
+  ScopedThreadStateChange tsc(self, ThreadState::kWaitingInMainSignalCatcherLoop);
 
   // Signals for sigwait() must be blocked but not ignored.  We
   // block signals like SIGQUIT for all threads, so the condition
@@ -177,7 +177,7 @@
                                      !runtime->IsAotCompiler()));
 
   Thread* self = Thread::Current();
-  DCHECK_NE(self->GetState(), kRunnable);
+  DCHECK_NE(self->GetState(), ThreadState::kRunnable);
   {
     MutexLock mu(self, signal_catcher->lock_);
     signal_catcher->thread_ = self;
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index f5bf5fb..3ac1292 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -52,11 +52,12 @@
 inline void Thread::CheckSuspend() {
   DCHECK_EQ(Thread::Current(), this);
   for (;;) {
-    if (ReadFlag(kCheckpointRequest)) {
+    StateAndFlags state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    if (state_and_flags.IsFlagSet(ThreadFlag::kCheckpointRequest)) {
       RunCheckpointFunction();
-    } else if (ReadFlag(kSuspendRequest)) {
+    } else if (state_and_flags.IsFlagSet(ThreadFlag::kSuspendRequest)) {
       FullSuspendCheck();
-    } else if (ReadFlag(kEmptyCheckpointRequest)) {
+    } else if (state_and_flags.IsFlagSet(ThreadFlag::kEmptyCheckpointRequest)) {
       RunEmptyCheckpoint();
     } else {
       break;
@@ -68,7 +69,7 @@
   Thread* self = Thread::Current();
   DCHECK_EQ(self, this);
   for (;;) {
-    if (ReadFlag(kEmptyCheckpointRequest)) {
+    if (ReadFlag(ThreadFlag::kEmptyCheckpointRequest)) {
       RunEmptyCheckpoint();
       // Check we hold only an expected mutex when accessing weak ref.
       if (kIsDebugBuild) {
@@ -92,7 +93,7 @@
 inline void Thread::CheckEmptyCheckpointFromMutex() {
   DCHECK_EQ(Thread::Current(), this);
   for (;;) {
-    if (ReadFlag(kEmptyCheckpointRequest)) {
+    if (ReadFlag(ThreadFlag::kEmptyCheckpointRequest)) {
       RunEmptyCheckpoint();
     } else {
       break;
@@ -103,21 +104,29 @@
 inline ThreadState Thread::SetState(ThreadState new_state) {
   // Should only be used to change between suspended states.
   // Cannot use this code to change into or from Runnable as changing to Runnable should
-  // fail if old_state_and_flags.suspend_request is true and changing from Runnable might
+  // fail if the `ThreadFlag::kSuspendRequest` is set and changing from Runnable might
   // miss passing an active suspend barrier.
-  DCHECK_NE(new_state, kRunnable);
+  DCHECK_NE(new_state, ThreadState::kRunnable);
   if (kIsDebugBuild && this != Thread::Current()) {
     std::string name;
     GetThreadName(name);
     LOG(FATAL) << "Thread \"" << name << "\"(" << this << " != Thread::Current()="
                << Thread::Current() << ") changing state to " << new_state;
   }
-  union StateAndFlags old_state_and_flags;
-  old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-  CHECK_NE(old_state_and_flags.as_struct.state, kRunnable) << new_state << " " << *this << " "
-      << *Thread::Current();
-  tls32_.state_and_flags.as_struct.state = new_state;
-  return static_cast<ThreadState>(old_state_and_flags.as_struct.state);
+
+  while (true) {
+    StateAndFlags old_state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    CHECK_NE(old_state_and_flags.GetState(), ThreadState::kRunnable)
+        << new_state << " " << *this << " " << *Thread::Current();
+    StateAndFlags new_state_and_flags = old_state_and_flags;
+    new_state_and_flags.SetState(new_state);
+    bool done =
+        tls32_.state_and_flags.CompareAndSetWeakRelaxed(old_state_and_flags.GetValue(),
+                                                        new_state_and_flags.GetValue());
+    if (done) {
+      return static_cast<ThreadState>(old_state_and_flags.GetState());
+    }
+  }
 }
 
 inline bool Thread::IsThreadSuspensionAllowable() const {
@@ -182,31 +191,29 @@
 }
 
 inline void Thread::TransitionToSuspendedAndRunCheckpoints(ThreadState new_state) {
-  DCHECK_NE(new_state, kRunnable);
-  DCHECK_EQ(GetState(), kRunnable);
-  union StateAndFlags old_state_and_flags;
-  union StateAndFlags new_state_and_flags;
+  DCHECK_NE(new_state, ThreadState::kRunnable);
   while (true) {
-    old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-    if (UNLIKELY((old_state_and_flags.as_struct.flags & kCheckpointRequest) != 0)) {
+    StateAndFlags old_state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    DCHECK_EQ(old_state_and_flags.GetState(), ThreadState::kRunnable);
+    if (UNLIKELY(old_state_and_flags.IsFlagSet(ThreadFlag::kCheckpointRequest))) {
       RunCheckpointFunction();
       continue;
     }
-    if (UNLIKELY((old_state_and_flags.as_struct.flags & kEmptyCheckpointRequest) != 0)) {
+    if (UNLIKELY(old_state_and_flags.IsFlagSet(ThreadFlag::kEmptyCheckpointRequest))) {
       RunEmptyCheckpoint();
       continue;
     }
     // Change the state but keep the current flags (kCheckpointRequest is clear).
-    DCHECK_EQ((old_state_and_flags.as_struct.flags & kCheckpointRequest), 0);
-    DCHECK_EQ((old_state_and_flags.as_struct.flags & kEmptyCheckpointRequest), 0);
-    new_state_and_flags.as_struct.flags = old_state_and_flags.as_struct.flags;
-    new_state_and_flags.as_struct.state = new_state;
+    DCHECK(!old_state_and_flags.IsFlagSet(ThreadFlag::kCheckpointRequest));
+    DCHECK(!old_state_and_flags.IsFlagSet(ThreadFlag::kEmptyCheckpointRequest));
+    StateAndFlags new_state_and_flags = old_state_and_flags;
+    new_state_and_flags.SetState(new_state);
 
     // CAS the value, ensuring that prior memory operations are visible to any thread
     // that observes that we are suspended.
     bool done =
-        tls32_.state_and_flags.as_atomic_int.CompareAndSetWeakRelease(old_state_and_flags.as_int,
-                                                                        new_state_and_flags.as_int);
+        tls32_.state_and_flags.CompareAndSetWeakRelease(old_state_and_flags.GetValue(),
+                                                        new_state_and_flags.GetValue());
     if (LIKELY(done)) {
       break;
     }
@@ -215,11 +222,12 @@
 
 inline void Thread::PassActiveSuspendBarriers() {
   while (true) {
-    uint16_t current_flags = tls32_.state_and_flags.as_struct.flags;
-    if (LIKELY((current_flags &
-                (kCheckpointRequest | kEmptyCheckpointRequest | kActiveSuspendBarrier)) == 0)) {
+    StateAndFlags state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    if (LIKELY(!state_and_flags.IsFlagSet(ThreadFlag::kCheckpointRequest) &&
+               !state_and_flags.IsFlagSet(ThreadFlag::kEmptyCheckpointRequest) &&
+               !state_and_flags.IsFlagSet(ThreadFlag::kActiveSuspendBarrier))) {
       break;
-    } else if ((current_flags & kActiveSuspendBarrier) != 0) {
+    } else if (state_and_flags.IsFlagSet(ThreadFlag::kActiveSuspendBarrier)) {
       PassActiveSuspendBarriers(this);
     } else {
       // Impossible
@@ -241,38 +249,33 @@
 }
 
 inline ThreadState Thread::TransitionFromSuspendedToRunnable() {
-  union StateAndFlags old_state_and_flags;
-  old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-  int16_t old_state = old_state_and_flags.as_struct.state;
-  DCHECK_NE(static_cast<ThreadState>(old_state), kRunnable);
-  do {
+  StateAndFlags old_state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+  ThreadState old_state = old_state_and_flags.GetState();
+  DCHECK_NE(old_state, ThreadState::kRunnable);
+  while (true) {
     GetMutatorLock()->AssertNotHeld(this);  // Otherwise we starve GC.
-    old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-    DCHECK_EQ(old_state_and_flags.as_struct.state, old_state);
-    if (LIKELY(old_state_and_flags.as_struct.flags == 0)) {
-      // Optimize for the return from native code case - this is the fast path.
-      // Atomically change from suspended to runnable if no suspend request pending.
-      union StateAndFlags new_state_and_flags;
-      new_state_and_flags.as_int = old_state_and_flags.as_int;
-      new_state_and_flags.as_struct.state = kRunnable;
-
+    // Optimize for the return from native code case - this is the fast path.
+    // Atomically change from suspended to runnable if no suspend request pending.
+    StateAndFlags new_state_and_flags = old_state_and_flags;
+    new_state_and_flags.SetState(ThreadState::kRunnable);
+    static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+    if (LIKELY(new_state_and_flags.GetValue() == 0u)) {  // No flags set?
       // CAS the value with a memory barrier.
-      if (LIKELY(tls32_.state_and_flags.as_atomic_int.CompareAndSetWeakAcquire(
-                                                 old_state_and_flags.as_int,
-                                                 new_state_and_flags.as_int))) {
+      if (LIKELY(tls32_.state_and_flags.CompareAndSetWeakAcquire(old_state_and_flags.GetValue(),
+                                                                 new_state_and_flags.GetValue()))) {
         // Mark the acquisition of a share of the mutator lock.
         GetMutatorLock()->TransitionFromSuspendedToRunnable(this);
         break;
       }
-    } else if ((old_state_and_flags.as_struct.flags & kActiveSuspendBarrier) != 0) {
+    } else if (old_state_and_flags.IsFlagSet(ThreadFlag::kActiveSuspendBarrier)) {
       PassActiveSuspendBarriers(this);
-    } else if ((old_state_and_flags.as_struct.flags &
-                (kCheckpointRequest | kEmptyCheckpointRequest)) != 0) {
+    } else if (UNLIKELY(old_state_and_flags.IsFlagSet(ThreadFlag::kCheckpointRequest) ||
+                        old_state_and_flags.IsFlagSet(ThreadFlag::kEmptyCheckpointRequest))) {
       // Impossible
       LOG(FATAL) << "Transitioning to runnable with checkpoint flag, "
-                 << " flags=" << old_state_and_flags.as_struct.flags
-                 << " state=" << old_state_and_flags.as_struct.state;
-    } else if ((old_state_and_flags.as_struct.flags & kSuspendRequest) != 0) {
+                 << " flags=" << new_state_and_flags.GetValue()  // State set to kRunnable = 0.
+                 << " state=" << old_state_and_flags.GetState();
+    } else if (old_state_and_flags.IsFlagSet(ThreadFlag::kSuspendRequest)) {
       // Wait while our suspend count is non-zero.
 
       // We pass null to the MutexLock as we may be in a situation where the
@@ -286,17 +289,22 @@
       }
       MutexLock mu(thread_to_pass, *Locks::thread_suspend_count_lock_);
       ScopedTransitioningToRunnable scoped_transitioning_to_runnable(this);
-      old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-      DCHECK_EQ(old_state_and_flags.as_struct.state, old_state);
-      while ((old_state_and_flags.as_struct.flags & kSuspendRequest) != 0) {
+      // Reload state and flags after locking the mutex.
+      old_state_and_flags.SetValue(tls32_.state_and_flags.load(std::memory_order_relaxed));
+      DCHECK_EQ(old_state, old_state_and_flags.GetState());
+      while (old_state_and_flags.IsFlagSet(ThreadFlag::kSuspendRequest)) {
         // Re-check when Thread::resume_cond_ is notified.
         Thread::resume_cond_->Wait(thread_to_pass);
-        old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-        DCHECK_EQ(old_state_and_flags.as_struct.state, old_state);
+        // Reload state and flags after waiting.
+        old_state_and_flags.SetValue(tls32_.state_and_flags.load(std::memory_order_relaxed));
+        DCHECK_EQ(old_state, old_state_and_flags.GetState());
       }
       DCHECK_EQ(GetSuspendCount(), 0);
     }
-  } while (true);
+    // Reload state and flags.
+    old_state_and_flags.SetValue(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    DCHECK_EQ(old_state, old_state_and_flags.GetState());
+  }
   // Run the flip function, if set.
   Closure* flip_func = GetFlipFunction();
   if (flip_func != nullptr) {
@@ -344,7 +352,7 @@
   if (kIsDebugBuild) {
     // Note: self is not necessarily equal to this thread since thread may be suspended.
     Thread* self = Thread::Current();
-    DCHECK(this == self || IsSuspended() || GetState() == kWaitingPerformingGc)
+    DCHECK(this == self || IsSuspended() || GetState() == ThreadState::kWaitingPerformingGc)
         << GetState() << " thread " << this << " self " << self;
   }
   tlsPtr_.thread_local_alloc_stack_end = nullptr;
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 46aa38e..c7ce0de 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1003,8 +1003,8 @@
 
   self->InitStringEntryPoints();
 
-  CHECK_NE(self->GetState(), kRunnable);
-  self->SetState(kNative);
+  CHECK_NE(self->GetState(), ThreadState::kRunnable);
+  self->SetState(ThreadState::kNative);
 
   // Run the action that is acting on the peer.
   if (!peer_action(self)) {
@@ -1444,7 +1444,7 @@
     return false;
   }
 
-  uint16_t flags = kSuspendRequest;
+  uint32_t flags = enum_cast<uint32_t>(ThreadFlag::kSuspendRequest);
   if (delta > 0 && suspend_barrier != nullptr) {
     uint32_t available_barrier = kMaxSuspendBarriers;
     for (uint32_t i = 0; i < kMaxSuspendBarriers; ++i) {
@@ -1458,7 +1458,7 @@
       return false;
     }
     tlsPtr_.active_suspend_barriers[available_barrier] = suspend_barrier;
-    flags |= kActiveSuspendBarrier;
+    flags |= enum_cast<uint32_t>(ThreadFlag::kActiveSuspendBarrier);
   }
 
   tls32_.suspend_count += delta;
@@ -1471,10 +1471,10 @@
   }
 
   if (tls32_.suspend_count == 0) {
-    AtomicClearFlag(kSuspendRequest);
+    AtomicClearFlag(ThreadFlag::kSuspendRequest);
   } else {
     // Two bits might be set simultaneously.
-    tls32_.state_and_flags.as_atomic_int.fetch_or(flags, std::memory_order_seq_cst);
+    tls32_.state_and_flags.fetch_or(flags, std::memory_order_seq_cst);
     TriggerSuspend();
   }
   return true;
@@ -1488,7 +1488,7 @@
   AtomicInteger* pass_barriers[kMaxSuspendBarriers];
   {
     MutexLock mu(self, *Locks::thread_suspend_count_lock_);
-    if (!ReadFlag(kActiveSuspendBarrier)) {
+    if (!ReadFlag(ThreadFlag::kActiveSuspendBarrier)) {
       // quick exit test: the barriers have already been claimed - this is
       // possible as there may be a race to claim and it doesn't matter
       // who wins.
@@ -1503,7 +1503,7 @@
       pass_barriers[i] = tlsPtr_.active_suspend_barriers[i];
       tlsPtr_.active_suspend_barriers[i] = nullptr;
     }
-    AtomicClearFlag(kActiveSuspendBarrier);
+    AtomicClearFlag(ThreadFlag::kActiveSuspendBarrier);
   }
 
   uint32_t barrier_count = 0;
@@ -1530,7 +1530,7 @@
 }
 
 void Thread::ClearSuspendBarrier(AtomicInteger* target) {
-  CHECK(ReadFlag(kActiveSuspendBarrier));
+  CHECK(ReadFlag(ThreadFlag::kActiveSuspendBarrier));
   bool clear_flag = true;
   for (uint32_t i = 0; i < kMaxSuspendBarriers; ++i) {
     AtomicInteger* ptr = tlsPtr_.active_suspend_barriers[i];
@@ -1541,7 +1541,7 @@
     }
   }
   if (LIKELY(clear_flag)) {
-    AtomicClearFlag(kActiveSuspendBarrier);
+    AtomicClearFlag(ThreadFlag::kActiveSuspendBarrier);
   }
 }
 
@@ -1559,7 +1559,7 @@
     } else {
       // No overflow checkpoints. Clear the kCheckpointRequest flag
       tlsPtr_.checkpoint_function = nullptr;
-      AtomicClearFlag(kCheckpointRequest);
+      AtomicClearFlag(ThreadFlag::kCheckpointRequest);
     }
   }
   // Outside the lock, run the checkpoint function.
@@ -1570,24 +1570,22 @@
 
 void Thread::RunEmptyCheckpoint() {
   DCHECK_EQ(Thread::Current(), this);
-  AtomicClearFlag(kEmptyCheckpointRequest);
+  AtomicClearFlag(ThreadFlag::kEmptyCheckpointRequest);
   Runtime::Current()->GetThreadList()->EmptyCheckpointBarrier()->Pass(this);
 }
 
 bool Thread::RequestCheckpoint(Closure* function) {
-  union StateAndFlags old_state_and_flags;
-  old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-  if (old_state_and_flags.as_struct.state != kRunnable) {
+  StateAndFlags old_state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+  if (old_state_and_flags.GetState() != ThreadState::kRunnable) {
     return false;  // Fail, thread is suspended and so can't run a checkpoint.
   }
 
   // We must be runnable to request a checkpoint.
-  DCHECK_EQ(old_state_and_flags.as_struct.state, kRunnable);
-  union StateAndFlags new_state_and_flags;
-  new_state_and_flags.as_int = old_state_and_flags.as_int;
-  new_state_and_flags.as_struct.flags |= kCheckpointRequest;
-  bool success = tls32_.state_and_flags.as_atomic_int.CompareAndSetStrongSequentiallyConsistent(
-      old_state_and_flags.as_int, new_state_and_flags.as_int);
+  DCHECK_EQ(old_state_and_flags.GetState(), ThreadState::kRunnable);
+  StateAndFlags new_state_and_flags = old_state_and_flags;
+  new_state_and_flags.SetFlag(ThreadFlag::kCheckpointRequest);
+  bool success = tls32_.state_and_flags.CompareAndSetStrongSequentiallyConsistent(
+      old_state_and_flags.GetValue(), new_state_and_flags.GetValue());
   if (success) {
     // Succeeded setting checkpoint flag, now insert the actual checkpoint.
     if (tlsPtr_.checkpoint_function == nullptr) {
@@ -1595,28 +1593,26 @@
     } else {
       checkpoint_overflow_.push_back(function);
     }
-    CHECK_EQ(ReadFlag(kCheckpointRequest), true);
+    CHECK(ReadFlag(ThreadFlag::kCheckpointRequest));
     TriggerSuspend();
   }
   return success;
 }
 
 bool Thread::RequestEmptyCheckpoint() {
-  union StateAndFlags old_state_and_flags;
-  old_state_and_flags.as_int = tls32_.state_and_flags.as_int;
-  if (old_state_and_flags.as_struct.state != kRunnable) {
+  StateAndFlags old_state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+  if (old_state_and_flags.GetState() != ThreadState::kRunnable) {
     // If it's not runnable, we don't need to do anything because it won't be in the middle of a
     // heap access (eg. the read barrier).
     return false;
   }
 
   // We must be runnable to request a checkpoint.
-  DCHECK_EQ(old_state_and_flags.as_struct.state, kRunnable);
-  union StateAndFlags new_state_and_flags;
-  new_state_and_flags.as_int = old_state_and_flags.as_int;
-  new_state_and_flags.as_struct.flags |= kEmptyCheckpointRequest;
-  bool success = tls32_.state_and_flags.as_atomic_int.CompareAndSetStrongSequentiallyConsistent(
-      old_state_and_flags.as_int, new_state_and_flags.as_int);
+  DCHECK_EQ(old_state_and_flags.GetState(), ThreadState::kRunnable);
+  StateAndFlags new_state_and_flags = old_state_and_flags;
+  new_state_and_flags.SetFlag(ThreadFlag::kEmptyCheckpointRequest);
+  bool success = tls32_.state_and_flags.CompareAndSetStrongSequentiallyConsistent(
+      old_state_and_flags.GetValue(), new_state_and_flags.GetValue());
   if (success) {
     TriggerSuspend();
   }
@@ -1776,7 +1772,7 @@
   VLOG(threads) << this << " self-suspending";
   // Make thread appear suspended to other threads, release mutator_lock_.
   // Transition to suspended and back to runnable, re-acquire share on mutator_lock_.
-  ScopedThreadSuspension(this, kSuspended);  // NOLINT
+  ScopedThreadSuspension(this, ThreadState::kSuspended);  // NOLINT
   VLOG(threads) << this << " self-reviving";
 }
 
@@ -1879,10 +1875,15 @@
 
   if (thread != nullptr) {
     auto suspend_log_fn = [&]() REQUIRES(Locks::thread_suspend_count_lock_) {
+      StateAndFlags state_and_flags(
+          thread->tls32_.state_and_flags.load(std::memory_order_relaxed));
+      static_assert(
+          static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+      state_and_flags.SetState(ThreadState::kRunnable);  // Clear state bits.
       os << "  | group=\"" << group_name << "\""
          << " sCount=" << thread->tls32_.suspend_count
          << " ucsCount=" << thread->tls32_.user_code_suspend_count
-         << " flags=" << thread->tls32_.state_and_flags.as_struct.flags
+         << " flags=" << state_and_flags.GetValue()
          << " obj=" << reinterpret_cast<void*>(thread->tlsPtr_.opeer)
          << " self=" << reinterpret_cast<const void*>(thread) << "\n";
     };
@@ -2058,11 +2059,11 @@
       REQUIRES_SHARED(Locks::mutator_lock_) {
     const char* msg;
     switch (state) {
-      case kBlocked:
+      case ThreadState::kBlocked:
         msg = "  - waiting to lock ";
         break;
 
-      case kWaitingForLockInflation:
+      case ThreadState::kWaitingForLockInflation:
         msg = "  - waiting for lock inflation of ";
         break;
 
@@ -2116,12 +2117,14 @@
   ThreadState state = thread->GetState();
 
   // In native code somewhere in the VM (one of the kWaitingFor* states)? That's interesting.
-  if (state > kWaiting && state < kStarting) {
+  if (state > ThreadState::kWaiting && state < ThreadState::kStarting) {
     return true;
   }
 
   // In an Object.wait variant or Thread.sleep? That's not interesting.
-  if (state == kTimedWaiting || state == kSleeping || state == kWaiting) {
+  if (state == ThreadState::kTimedWaiting ||
+      state == ThreadState::kSleeping ||
+      state == ThreadState::kWaiting) {
     return false;
   }
 
@@ -2305,8 +2308,10 @@
 
   static_assert((sizeof(Thread) % 4) == 0U,
                 "art::Thread has a size which is not a multiple of 4.");
-  tls32_.state_and_flags.as_struct.flags = 0;
-  tls32_.state_and_flags.as_struct.state = kNative;
+  DCHECK_EQ(tls32_.state_and_flags.load(std::memory_order_relaxed), 0u);
+  StateAndFlags state_and_flags(0u);
+  state_and_flags.SetState(ThreadState::kNative);
+  tls32_.state_and_flags.store(state_and_flags.GetValue(), std::memory_order_relaxed);
   tls32_.interrupted.store(false, std::memory_order_relaxed);
   // Initialize with no permit; if the java Thread was unparked before being
   // started, it will unpark itself before calling into java code.
@@ -2462,9 +2467,9 @@
     delete tlsPtr_.jni_env;
     tlsPtr_.jni_env = nullptr;
   }
-  CHECK_NE(GetState(), kRunnable);
-  CHECK(!ReadFlag(kCheckpointRequest));
-  CHECK(!ReadFlag(kEmptyCheckpointRequest));
+  CHECK_NE(GetState(), ThreadState::kRunnable);
+  CHECK(!ReadFlag(ThreadFlag::kCheckpointRequest));
+  CHECK(!ReadFlag(ThreadFlag::kEmptyCheckpointRequest));
   CHECK(tlsPtr_.checkpoint_function == nullptr);
   CHECK_EQ(checkpoint_overflow_.size(), 0u);
   CHECK(tlsPtr_.flip_function == nullptr);
@@ -2476,7 +2481,7 @@
       "Not all deoptimized frames have been consumed by the debugger.";
 
   // We may be deleting a still born thread.
-  SetStateUnsafe(kTerminated);
+  SetStateUnsafe(ThreadState::kTerminated);
 
   delete wait_cond_;
   delete wait_mutex_;
@@ -2503,7 +2508,7 @@
     return;
   }
   ScopedLocalRef<jobject> peer(tlsPtr_.jni_env, soa.AddLocalReference<jobject>(tlsPtr_.opeer));
-  ScopedThreadStateChange tsc(this, kNative);
+  ScopedThreadStateChange tsc(this, ThreadState::kNative);
 
   // Get and clear the exception.
   ScopedLocalRef<jthrowable> exception(tlsPtr_.jni_env, tlsPtr_.jni_env->ExceptionOccurred());
@@ -2526,7 +2531,7 @@
   if (ogroup != nullptr) {
     ScopedLocalRef<jobject> group(soa.Env(), soa.AddLocalReference<jobject>(ogroup));
     ScopedLocalRef<jobject> peer(soa.Env(), soa.AddLocalReference<jobject>(tlsPtr_.opeer));
-    ScopedThreadStateChange tsc(soa.Self(), kNative);
+    ScopedThreadStateChange tsc(soa.Self(), ThreadState::kNative);
     tlsPtr_.jni_env->CallVoidMethod(group.get(),
                                     WellKnownClasses::java_lang_ThreadGroup_removeThread,
                                     peer.get());
@@ -4470,7 +4475,7 @@
 
 std::string Thread::StateAndFlagsAsHexString() const {
   std::stringstream result_stream;
-  result_stream << std::hex << tls32_.state_and_flags.as_atomic_int.load();
+  result_stream << std::hex << tls32_.state_and_flags.load(std::memory_order_relaxed);
   return result_stream.str();
 }
 
diff --git a/runtime/thread.h b/runtime/thread.h
index 9478980..f1dd7b8 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -26,6 +26,8 @@
 #include <string>
 
 #include "base/atomic.h"
+#include "base/bit_field.h"
+#include "base/bit_utils.h"
 #include "base/enums.h"
 #include "base/locks.h"
 #include "base/macros.h"
@@ -118,12 +120,21 @@
   kMaxThreadPriority = 10,
 };
 
-enum ThreadFlag {
-  kSuspendRequest   = 1,  // If set implies that suspend_count_ > 0 and the Thread should enter the
-                          // safepoint handler.
-  kCheckpointRequest = 2,  // Request that the thread do some checkpoint work and then continue.
-  kEmptyCheckpointRequest = 4,  // Request that the thread do empty checkpoint and then continue.
-  kActiveSuspendBarrier = 8,  // Register that at least 1 suspend barrier needs to be passed.
+enum class ThreadFlag : uint32_t {
+  // If set, implies that suspend_count_ > 0 and the Thread should enter the safepoint handler.
+  kSuspendRequest = 1u << 0,
+
+  // Request that the thread do some checkpoint work and then continue.
+  kCheckpointRequest = 1u << 1,
+
+  // Request that the thread do empty checkpoint and then continue.
+  kEmptyCheckpointRequest = 1u << 2,
+
+  // Register that at least 1 suspend barrier needs to be passed.
+  kActiveSuspendBarrier = 1u << 3,
+
+  // Indicates the last flag. Used for checking that the flags do not overlap thread state.
+  kLastFlag = kActiveSuspendBarrier
 };
 
 enum class StackedShadowFrameType {
@@ -236,9 +247,8 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   ThreadState GetState() const {
-    DCHECK_GE(tls32_.state_and_flags.as_struct.state, kTerminated);
-    DCHECK_LE(tls32_.state_and_flags.as_struct.state, kSuspended);
-    return static_cast<ThreadState>(tls32_.state_and_flags.as_struct.state);
+    StateAndFlags state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    return state_and_flags.GetState();
   }
 
   ThreadState SetState(ThreadState new_state);
@@ -253,10 +263,9 @@
   }
 
   bool IsSuspended() const {
-    union StateAndFlags state_and_flags;
-    state_and_flags.as_int = tls32_.state_and_flags.as_atomic_int.load(std::memory_order_relaxed);
-    return state_and_flags.as_struct.state != kRunnable &&
-        (state_and_flags.as_struct.flags & kSuspendRequest) != 0;
+    StateAndFlags state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    return state_and_flags.GetState() != ThreadState::kRunnable &&
+           state_and_flags.IsFlagSet(ThreadFlag::kSuspendRequest);
   }
 
   void DecrDefineClassCount() {
@@ -1097,19 +1106,23 @@
       REQUIRES(Locks::thread_suspend_count_lock_);
 
   bool ReadFlag(ThreadFlag flag) const {
-    return (tls32_.state_and_flags.as_struct.flags & flag) != 0;
+    StateAndFlags state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    return state_and_flags.IsFlagSet(flag);
   }
 
   bool TestAllFlags() const {
-    return (tls32_.state_and_flags.as_struct.flags != 0);
+    StateAndFlags state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    static_assert(static_cast<std::underlying_type_t<ThreadState>>(ThreadState::kRunnable) == 0u);
+    state_and_flags.SetState(ThreadState::kRunnable);  // Clear state bits.
+    return state_and_flags.GetValue() != 0u;
   }
 
   void AtomicSetFlag(ThreadFlag flag) {
-    tls32_.state_and_flags.as_atomic_int.fetch_or(flag, std::memory_order_seq_cst);
+    tls32_.state_and_flags.fetch_or(enum_cast<uint32_t>(flag), std::memory_order_seq_cst);
   }
 
   void AtomicClearFlag(ThreadFlag flag) {
-    tls32_.state_and_flags.as_atomic_int.fetch_and(-1 ^ flag, std::memory_order_seq_cst);
+    tls32_.state_and_flags.fetch_and(~enum_cast<uint32_t>(flag), std::memory_order_seq_cst);
   }
 
   void ResetQuickAllocEntryPointsForThread();
@@ -1330,10 +1343,15 @@
                        jint thread_priority)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Avoid use, callers should use SetState. Used only by SignalCatcher::HandleSigQuit and, ~Thread
+  // Avoid use, callers should use SetState.
+  // Used only by `Thread` destructor and stack trace collection in semi-space GC (currently
+  // disabled by `kStoreStackTraces = false`).
   ThreadState SetStateUnsafe(ThreadState new_state) {
-    ThreadState old_state = GetState();
-    if (old_state == kRunnable && new_state != kRunnable) {
+    StateAndFlags old_state_and_flags(tls32_.state_and_flags.load(std::memory_order_relaxed));
+    ThreadState old_state = old_state_and_flags.GetState();
+    if (old_state == new_state) {
+      // Nothing to do.
+    } else if (old_state == ThreadState::kRunnable) {
       // Need to run pending checkpoint and suspend barriers. Run checkpoints in runnable state in
       // case they need to use a ScopedObjectAccess. If we are holding the mutator lock and a SOA
       // attempts to TransitionFromSuspendedToRunnable, it results in a deadlock.
@@ -1341,7 +1359,18 @@
       // Since we transitioned to a suspended state, check the pass barrier requests.
       PassActiveSuspendBarriers();
     } else {
-      tls32_.state_and_flags.as_struct.state = new_state;
+      while (true) {
+        StateAndFlags new_state_and_flags = old_state_and_flags;
+        new_state_and_flags.SetState(new_state);
+        if (LIKELY(tls32_.state_and_flags.CompareAndSetWeakAcquire(
+                                              old_state_and_flags.GetValue(),
+                                              new_state_and_flags.GetValue()))) {
+          break;
+        }
+        // Reload state and flags.
+        old_state_and_flags.SetValue(tls32_.state_and_flags.load(std::memory_order_relaxed));
+        DCHECK_EQ(old_state, old_state_and_flags.GetState());
+      }
     }
     return old_state;
   }
@@ -1440,29 +1469,61 @@
 
   void ReleaseLongJumpContextInternal();
 
-  // 32 bits of atomically changed state and flags. Keeping as 32 bits allows and atomic CAS to
-  // change from being Suspended to Runnable without a suspend request occurring.
-  union PACKED(4) StateAndFlags {
-    StateAndFlags() {}
-    struct PACKED(4) {
-      // Bitfield of flag values. Must be changed atomically so that flag values aren't lost. See
-      // ThreadFlag for bit field meanings.
-      volatile uint16_t flags;
-      // Holds the ThreadState. May be changed non-atomically between Suspended (ie not Runnable)
-      // transitions. Changing to Runnable requires that the suspend_request be part of the atomic
-      // operation. If a thread is suspended and a suspend_request is present, a thread may not
-      // change to Runnable as a GC or other operation is in progress.
-      volatile uint16_t state;
-    } as_struct;
-    AtomicInteger as_atomic_int;
-    volatile int32_t as_int;
+  // Helper class for manipulating the 32 bits of atomically changed state and flags.
+  class StateAndFlags {
+   public:
+    explicit StateAndFlags(uint32_t value) :value_(value) {}
+
+    uint32_t GetValue() const {
+      return value_;
+    }
+
+    void SetValue(uint32_t value) {
+      value_ = value;
+    }
+
+    bool IsFlagSet(ThreadFlag flag) const {
+      return (value_ & enum_cast<uint32_t>(flag)) != 0u;
+    }
+
+    void SetFlag(ThreadFlag flag) {
+      value_ |= enum_cast<uint32_t>(flag);
+    }
+
+    void ClearFlag(ThreadFlag flag) {
+      value_ &= ~enum_cast<uint32_t>(flag);
+    }
+
+    ThreadState GetState() const {
+      ThreadState state = ThreadStateField::Decode(value_);
+      ValidateThreadState(state);
+      return state;
+    }
+
+    void SetState(ThreadState state) {
+      ValidateThreadState(state);
+      value_ = ThreadStateField::Update(state, value_);
+    }
 
    private:
-    // gcc does not handle struct with volatile member assignments correctly.
-    // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47409
-    DISALLOW_COPY_AND_ASSIGN(StateAndFlags);
+    static void ValidateThreadState(ThreadState state) {
+      if (kIsDebugBuild && state != ThreadState::kRunnable) {
+        CHECK_GE(state, ThreadState::kTerminated);
+        CHECK_LE(state, ThreadState::kSuspended);
+        CHECK_NE(state, ThreadState::kObsoleteRunnable);
+      }
+    }
+
+    // The value holds thread flags and thread state.
+    uint32_t value_;
+
+    static constexpr size_t kThreadStateBitSize = BitSizeOf<std::underlying_type_t<ThreadState>>();
+    static constexpr size_t kThreadStatePosition = BitSizeOf<uint32_t>() - kThreadStateBitSize;
+    using ThreadStateField = BitField<ThreadState, kThreadStatePosition, kThreadStateBitSize>;
+    static_assert(
+        WhichPowerOf2(enum_cast<uint32_t>(ThreadFlag::kLastFlag)) < kThreadStatePosition);
   };
-  static_assert(sizeof(StateAndFlags) == sizeof(int32_t), "Weird state_and_flags size");
+  static_assert(sizeof(StateAndFlags) == sizeof(uint32_t), "Unexpected StateAndFlags size");
 
   // Format state and flags as a hex string. For diagnostic output.
   std::string StateAndFlagsAsHexString() const;
@@ -1502,7 +1563,8 @@
     using bool32_t = uint32_t;
 
     explicit tls_32bit_sized_values(bool is_daemon)
-        : suspend_count(0),
+        : state_and_flags(0u),
+          suspend_count(0),
           thin_lock_thread_id(0),
           tid(0),
           daemon(is_daemon),
@@ -1518,9 +1580,13 @@
           make_visibly_initialized_counter(0),
           define_class_counter(0) {}
 
-    union StateAndFlags state_and_flags;
-    static_assert(sizeof(union StateAndFlags) == sizeof(int32_t),
-                  "Size of state_and_flags and int32 are different");
+    // The state and flags field must be changed atomically so that flag values aren't lost.
+    // See `StateAndFlags` for bit assignments of `ThreadFlag` and `ThreadState` values.
+    // Keeping the state and flags together allows an atomic CAS to change from being
+    // Suspended to Runnable without a suspend request occurring.
+    Atomic<uint32_t> state_and_flags;
+    static_assert(sizeof(state_and_flags) == sizeof(uint32_t),
+                  "Size of state_and_flags and uint32 are different");
 
     // A non-zero value is used to tell the current thread to enter a safe point
     // at the next poll.
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 8a48300..f50dba8 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -222,7 +222,7 @@
 
   void WaitForThreadsToRunThroughCheckpoint(size_t threads_running_checkpoint) {
     Thread* self = Thread::Current();
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     bool timed_out = barrier_.Increment(self, threads_running_checkpoint, kDumpWaitTimeout);
     if (timed_out) {
       // Avoid a recursive abort.
@@ -338,7 +338,7 @@
             break;
           } else {
             // The thread is probably suspended, try to make sure that it stays suspended.
-            if (thread->GetState() == kRunnable) {
+            if (thread->GetState() == ThreadState::kRunnable) {
               // Spurious fail, try again.
               continue;
             }
@@ -419,7 +419,7 @@
             }
             break;
           }
-          if (thread->GetState() != kRunnable) {
+          if (thread->GetState() != ThreadState::kRunnable) {
             // It's seen suspended, we are done because it must not be in the middle of a mutator
             // heap access.
             break;
@@ -434,7 +434,7 @@
   Runtime::Current()->GetHeap()->GetReferenceProcessor()->BroadcastForSlowPath(self);
   Runtime::Current()->BroadcastForNewSystemWeaks(/*broadcast_for_checkpoint=*/true);
   {
-    ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
+    ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
     uint64_t total_wait_time = 0;
     bool first_iter = true;
     while (true) {
@@ -481,7 +481,7 @@
                 std::find(runnable_thread_ids.begin(), runnable_thread_ids.end(), tid) !=
                 runnable_thread_ids.end();
             if (is_in_runnable_thread_ids &&
-                thread->ReadFlag(kEmptyCheckpointRequest)) {
+                thread->ReadFlag(ThreadFlag::kEmptyCheckpointRequest)) {
               // Found a runnable thread that hasn't responded to the empty checkpoint request.
               // Assume it's stuck and safe to dump its stack.
               thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT),
@@ -514,7 +514,7 @@
   Locks::mutator_lock_->AssertNotHeld(self);
   Locks::thread_list_lock_->AssertNotHeld(self);
   Locks::thread_suspend_count_lock_->AssertNotHeld(self);
-  CHECK_NE(self->GetState(), kRunnable);
+  CHECK_NE(self->GetState(), ThreadState::kRunnable);
 
   collector->GetHeap()->ThreadFlipBegin(self);  // Sync with JNI critical calls.
 
@@ -557,7 +557,7 @@
       // runnable (both cases waiting inside Thread::TransitionFromSuspendedToRunnable), or waiting
       // for the thread flip to end at the JNI critical section entry (kWaitingForGcThreadFlip),
       ThreadState state = thread->GetState();
-      if ((state == kWaitingForGcThreadFlip || thread->IsTransitioningToRunnable()) &&
+      if ((state == ThreadState::kWaitingForGcThreadFlip || thread->IsTransitioningToRunnable()) &&
           thread->GetSuspendCount() == 1) {
         // The thread will resume right after the broadcast.
         bool updated = thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal);
@@ -667,7 +667,7 @@
   Locks::thread_list_lock_->AssertNotHeld(self);
   Locks::thread_suspend_count_lock_->AssertNotHeld(self);
   if (kDebugLocking && self != nullptr) {
-    CHECK_NE(self->GetState(), kRunnable);
+    CHECK_NE(self->GetState(), ThreadState::kRunnable);
   }
 
   // First request that all threads suspend, then wait for them to suspend before
@@ -1201,7 +1201,7 @@
     {
       MutexLock mu(self, *Locks::thread_list_lock_);
       for (const auto& thread : list_) {
-        if (thread != self && thread->GetState() == kRunnable) {
+        if (thread != self && thread->GetState() == ThreadState::kRunnable) {
           if (!have_complained) {
             LOG(WARNING) << "daemon thread not yet suspended: " << *thread;
             have_complained = true;
@@ -1236,7 +1236,7 @@
     // touching deallocated memory, but it also prevents mutexes from getting released. Thus we
     // only do this once we're reasonably sure that no system mutexes are still held.
     for (const auto& thread : list_) {
-      DCHECK(thread == self || !all_suspended || thread->GetState() != kRunnable);
+      DCHECK(thread == self || !all_suspended || thread->GetState() != ThreadState::kRunnable);
       // In the !all_suspended case, the target is probably sleeping.
       thread->GetJniEnv()->SetRuntimeDeleted();
       // Possibly contended Mutex acquisitions are unsafe after this.
@@ -1291,7 +1291,7 @@
 
 void ThreadList::Unregister(Thread* self) {
   DCHECK_EQ(self, Thread::Current());
-  CHECK_NE(self->GetState(), kRunnable);
+  CHECK_NE(self->GetState(), ThreadState::kRunnable);
   Locks::mutator_lock_->AssertNotHeld(self);
 
   VLOG(threads) << "ThreadList::Unregister() " << *self;
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index 69b5b5d..e03df27 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -25,12 +25,18 @@
 // When we refer to "a suspended state", or when function names mention "ToSuspended" or
 // "FromSuspended", we mean any state other than kRunnable, i.e. any state in which the thread is
 // guaranteed not to access the Java heap. The kSuspended state is merely one of these.
-enum ThreadState {
+enum class ThreadState : uint8_t {
+  // `kRunnable` was previously 67 but it is now set to 0 so that we do not need to extract
+  // flags from the thread's `state_and_flags` to check for any flag being set while Runnable.
+  // Note: All atomic accesses for a location should use the same data size,
+  // so the incorrect old approach of reading just 16 bits has been rewritten.
+
   //                                   Java
   //                                   Thread.State   JDWP state
   kTerminated = 66,                 // TERMINATED     TS_ZOMBIE    Thread.run has returned, but Thread* still around
-  kRunnable,                        // RUNNABLE       TS_RUNNING   runnable
-  kTimedWaiting,                    // TIMED_WAITING  TS_WAIT      in Object.wait() with a timeout
+  kRunnable = 0,                    // RUNNABLE       TS_RUNNING   runnable
+  kObsoleteRunnable = 67,           // ---            ---          obsolete value
+  kTimedWaiting = 68,               // TIMED_WAITING  TS_WAIT      in Object.wait() with a timeout
   kSleeping,                        // TIMED_WAITING  TS_SLEEPING  in Thread.sleep()
   kBlocked,                         // BLOCKED        TS_MONITOR   blocked on a monitor
   kWaiting,                         // WAITING        TS_WAIT      in Object.wait()
diff --git a/test/566-polymorphic-inlining/polymorphic_inline.cc b/test/566-polymorphic-inlining/polymorphic_inline.cc
index 34ec3ff..02ba04c 100644
--- a/test/566-polymorphic-inlining/polymorphic_inline.cc
+++ b/test/566-polymorphic-inlining/polymorphic_inline.cc
@@ -42,7 +42,7 @@
       header = OatQuickMethodHeader::FromEntryPoint(pc);
       break;
     } else {
-      ScopedThreadSuspension sts(soa.Self(), kSuspended);
+      ScopedThreadSuspension sts(soa.Self(), ThreadState::kSuspended);
       // Sleep to yield to the compiler thread.
       usleep(1000);
     }
diff --git a/test/597-deopt-new-string/deopt.cc b/test/597-deopt-new-string/deopt.cc
index fe229e4..572be28 100644
--- a/test/597-deopt-new-string/deopt.cc
+++ b/test/597-deopt-new-string/deopt.cc
@@ -30,7 +30,7 @@
     JNIEnv* env,
     jclass cls ATTRIBUTE_UNUSED) {
   ScopedObjectAccess soa(env);
-  ScopedThreadSuspension sts(Thread::Current(), kWaitingForDeoptimization);
+  ScopedThreadSuspension sts(Thread::Current(), ThreadState::kWaitingForDeoptimization);
   gc::ScopedGCCriticalSection gcs(Thread::Current(),
                                   gc::kGcCauseInstrumentation,
                                   gc::kCollectorTypeInstrumentation);
@@ -49,7 +49,7 @@
     JNIEnv* env,
     jclass cls ATTRIBUTE_UNUSED) {
   ScopedObjectAccess soa(env);
-  ScopedThreadSuspension sts(Thread::Current(), kWaitingForDeoptimization);
+  ScopedThreadSuspension sts(Thread::Current(), ThreadState::kWaitingForDeoptimization);
   gc::ScopedGCCriticalSection gcs(Thread::Current(),
                                   gc::kGcCauseInstrumentation,
                                   gc::kCollectorTypeInstrumentation);
diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java
index 5a66fbb..d4d3923 100644
--- a/test/706-checker-scheduler/src/Main.java
+++ b/test/706-checker-scheduler/src/Main.java
@@ -605,7 +605,7 @@
   /// CHECK:     subs
   /// CHECK:     add
   /// CHECK:     adds
-  /// CHECK:     ldrh
+  /// CHECK:     ldr
   /// CHECK:     cmp
   /// CHECK:     beq
 
@@ -613,7 +613,7 @@
   /// CHECK:     sub
   /// CHECK:     add
   /// CHECK:     add
-  /// CHECK:     ldrh
+  /// CHECK:     ldr
   /// CHECK:     cbz
   private static void testCrossItersDependencies() {
     int[] data = {1, 2, 3, 0};
diff --git a/tools/cpp-define-generator/thread.def b/tools/cpp-define-generator/thread.def
index d472cc4..fff5755 100644
--- a/tools/cpp-define-generator/thread.def
+++ b/tools/cpp-define-generator/thread.def
@@ -22,9 +22,9 @@
 ASM_DEFINE(THREAD_CARD_TABLE_OFFSET,
            art::Thread::CardTableOffset<art::kRuntimePointerSize>().Int32Value())
 ASM_DEFINE(THREAD_CHECKPOINT_REQUEST,
-           art::kCheckpointRequest)
+           static_cast<uint32_t>(art::ThreadFlag::kCheckpointRequest))
 ASM_DEFINE(THREAD_EMPTY_CHECKPOINT_REQUEST,
-           art::kEmptyCheckpointRequest)
+           static_cast<uint32_t>(art::ThreadFlag::kEmptyCheckpointRequest))
 ASM_DEFINE(THREAD_EXCEPTION_OFFSET,
            art::Thread::ExceptionOffset<art::kRuntimePointerSize>().Int32Value())
 ASM_DEFINE(THREAD_FLAGS_OFFSET,
@@ -56,9 +56,11 @@
 ASM_DEFINE(THREAD_SELF_OFFSET,
            art::Thread::SelfOffset<art::kRuntimePointerSize>().Int32Value())
 ASM_DEFINE(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST,
-           art::kSuspendRequest | art::kCheckpointRequest | art::kEmptyCheckpointRequest)
+           static_cast<uint32_t>(art::ThreadFlag::kSuspendRequest) |
+               static_cast<uint32_t>(art::ThreadFlag::kCheckpointRequest) |
+               static_cast<uint32_t>(art::ThreadFlag::kEmptyCheckpointRequest))
 ASM_DEFINE(THREAD_SUSPEND_REQUEST,
-           art::kSuspendRequest)
+           static_cast<uint32_t>(art::ThreadFlag::kSuspendRequest))
 ASM_DEFINE(THREAD_TOP_QUICK_FRAME_OFFSET,
            art::Thread::TopOfManagedStackOffset<art::kRuntimePointerSize>().Int32Value())
 ASM_DEFINE(THREAD_ALLOC_OBJECT_ENTRYPOINT_OFFSET,