diff --git a/nptl/Makefile b/nptl/Makefile
index 63976ec..9beb879 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -138,6 +138,12 @@ libpthread-routines = nptl-init vars events version pt-interp \
 #		      pthread_setgid pthread_setegid pthread_setregid \
 #		      pthread_setresgid
 
+ifeq ($(build-infinity),yes)
+infinity-routines = infinity-map_lwp2thr
+
+libpthread-routines += $(infinity-routines)
+endif
+
 libpthread-shared-only-routines = version pt-interp pt-allocrtsig \
 				  unwind-forcedunwind
 libpthread-static-only-routines = pthread_atfork
@@ -413,6 +419,20 @@ ifneq ($(have-cxx-thread_local),yes)
 tests-unsupported += tst-thread_local1
 endif
 
+tests += tst-infinity
+ifeq ($(build-infinity),yes)
+infinity-tests = $(addprefix tst-,$(addsuffix .py,$(infinity-routines)))
+
+$(objpfx)tst-infinity.out: $(objpfx)libpthread.so$(libpthread.so-version) \
+			   $(infinity-tests)
+	$(I8X) -i $< -I$(common-objpfx) $(infinity-tests) > $@; \
+	$(evaluate-test)
+
+tests-special += $(objpfx)tst-infinity.out
+else
+tests-unsupported += tst-infinity
+endif
+
 include ../Rules
 
 ifeq (yes,$(build-shared))
diff --git a/nptl/infinity-lookup_th_unique_cta.i8 b/nptl/infinity-lookup_th_unique_cta.i8
new file mode 100644
index 0000000..2c9879b
--- /dev/null
+++ b/nptl/infinity-lookup_th_unique_cta.i8
@@ -0,0 +1,39 @@
+/* Which thread is running on an LWP?
+   Copyright (C) 2003-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+
+/* Given an lwpid_t identifying an LWP, return TD_OK and the
+   descriptor of the thread running on it, or a non-TD_OK
+   td_err_e code indicating the reason for failure and an
+   undefined value that must be ignored.  The caller must
+   ensure that __pthread_initialize_minimal has gotten far
+   enough; see the comments in infinity_map_lwp2thr.i8 for
+   specifics.  */
+
+define MODULE_NAME::__lookup_th_unique returns td_err_e, pthread_t
+	argument lwpid_t lwpid
+
+	load I8_TS_CTA_VALUE
+	call procservice::get_thread_area
+	bne PS_OK, get_thread_area_failed
+	load TD_OK
+	return
+
+get_thread_area_failed:
+	load TD_ERR
+	return
diff --git a/nptl/infinity-lookup_th_unique_reg.i8 b/nptl/infinity-lookup_th_unique_reg.i8
new file mode 100644
index 0000000..399308e
--- /dev/null
+++ b/nptl/infinity-lookup_th_unique_reg.i8
@@ -0,0 +1,41 @@
+/* Which thread is running on an LWP?
+   Copyright (C) 2003-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+
+/* Given an lwpid_t identifying an LWP, return TD_OK and the
+   descriptor of the thread running on it, or a non-TD_OK
+   td_err_e code indicating the reason for failure and an
+   undefined value that must be ignored.  The caller must
+   ensure that __pthread_initialize_minimal has gotten far
+   enough; see the comments in infinity_map_lwp2thr.i8 for
+   specifics.  */
+
+define MODULE_NAME::__lookup_th_unique returns td_err_e, pthread_t
+	argument lwpid_t lwpid
+
+	load I8_TS_REG_OFFSET
+	call procservice::get_register
+	cast 1, ptr
+	bne PS_OK, get_register_failed
+	add I8_TS_REG_BIAS
+	load TD_OK
+	return
+
+get_register_failed:
+	load TD_ERR
+	return
diff --git a/nptl/infinity-lookup_th_unique_rta.i8 b/nptl/infinity-lookup_th_unique_rta.i8
new file mode 100644
index 0000000..c1d0ecc
--- /dev/null
+++ b/nptl/infinity-lookup_th_unique_rta.i8
@@ -0,0 +1,50 @@
+/* Which thread is running on an LWP?
+   Copyright (C) 2003-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+
+/* Given an lwpid_t identifying an LWP, return TD_OK and the
+   descriptor of the thread running on it, or a non-TD_OK
+   td_err_e code indicating the reason for failure and an
+   undefined value that must be ignored.  The caller must
+   ensure that __pthread_initialize_minimal has gotten far
+   enough; see the comments in infinity_map_lwp2thr.i8 for
+   specifics.  */
+
+define MODULE_NAME::__lookup_th_unique returns td_err_e, pthread_t
+	argument lwpid_t lwpid
+
+	load lwpid
+	load I8_TS_RTA_OFFSET
+	call procservice::get_register
+	bne PS_OK, get_register_failed
+
+	shr I8_TS_RTA_SCALE
+	call procservice::get_thread_area
+	bne PS_OK, get_thread_area_failed
+
+	load TD_OK
+	return
+
+get_register_failed:
+	cast 0, ptr
+	load TD_ERR
+	return
+
+get_thread_area_failed:
+	load TD_ERR
+	return
diff --git a/nptl/infinity-map_lwp2thr.i8 b/nptl/infinity-map_lwp2thr.i8
new file mode 100644
index 0000000..4b33d72
--- /dev/null
+++ b/nptl/infinity-map_lwp2thr.i8
@@ -0,0 +1,71 @@
+/* Which thread is running on an LWP?
+   Copyright (C) 2003-2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include "infinity-nptl.i8"
+#include "infinity-thread_self.h"
+
+#if I8_THREAD_SELF == I8_TS_CONST_THREAD_AREA
+# include "infinity-lookup_th_unique_cta.i8"
+#endif
+#if I8_THREAD_SELF == I8_TS_REGISTER
+# include "infinity-lookup_th_unique_reg.i8"
+#endif
+#if I8_THREAD_SELF == I8_TS_REGISTER_THREAD_AREA
+# include "infinity-lookup_th_unique_rta.i8"
+#endif
+
+/* Given an lwpid_t identifying an LWP, return TD_OK and the
+   descriptor of the thread running on it, or a non-TD_OK
+   td_err_e code indicating the reason for failure and an
+   undefined value that must be ignored.  Thread descriptors
+   are opaque pointers and should not be dereferenced outside
+   of this library.  */
+
+define MODULE_NAME::map_lwp2thr returns td_err_e, pthread_t
+	argument lwpid_t lwpid
+
+  /* We cannot rely on thread registers and such information at all
+     before __pthread_initialize_minimal has gotten far enough: they
+     sometimes contain garbage left by the kernel at exec that would
+     confuse us.  If it looks like initialization is incomplete we
+     fake a special descriptor of NULL to indicate the initial thread.
+     Other routines in this library recognise this special descriptor
+     and act accordingly.  */
+	deref LIST_T_NEXT_OFFSET(__stack_user), ptr
+	beq NULL, libpthread_uninitialized
+	call __lookup_th_unique
+	return
+
+libpthread_uninitialized:
+
+  /* Load the pointer we'll return beneath lwpid.  I8C can't yet
+     hoist, but it'll optimize the branch away if we do the hoist
+     ourselves.  */
+	load NULL
+	swap
+
+	call procservice::getpid
+	beq is_main_thread
+
+not_main_thread:
+	load TD_ERR
+	return
+
+is_main_thread:
+	load TD_OK
+	return
diff --git a/nptl/tst-infinity-map_lwp2thr.py b/nptl/tst-infinity-map_lwp2thr.py
new file mode 100644
index 0000000..37989be
--- /dev/null
+++ b/nptl/tst-infinity-map_lwp2thr.py
@@ -0,0 +1,152 @@
+from i8c.runtime import TestCase
+import struct
+
+TestCase.import_builtin_constants()
+TestCase.import_constants_from("infinity-nptl-constants.h")
+TestCase.import_constants_from("infinity-nptl_db-constants.h")
+
+class TestMapLwp2Thr(TestCase):
+    TESTFUNC = "libpthread::map_lwp2thr(i)ip"
+    MAIN_PID = 30000
+
+    def setUp(self):
+        # Set up the test address space for the __stack_user.next
+        # dereference.
+        with self.memory.builder() as mem:
+            stack_user = mem.alloc("__stack_user")
+            if self.STACK_USER_SETUP:
+                stack_user_next = mem.alloc()
+            else:
+                stack_user_next = NULL
+            stack_user.store_ptr(LIST_T_NEXT_OFFSET, stack_user_next)
+        # Initialize flags so we can see what was called.
+        self.ps_get_register_called = False
+        self.ps_get_thread_area_called = False
+
+    def call_procservice_getpid(self):
+        """Implementation of procservice::getpid."""
+        return self.MAIN_PID
+
+    def call_procservice_get_register(self, lwpid, offset):
+        """Implementation of procservice::get_register."""
+        self.assertFalse(self.ps_get_register_called)
+        result = getattr(self, "PS_GETREG_RESULT", None)
+        if result is None:
+            self.fail("unexpected ps_get_register")
+        self.assertEqual(lwpid, self.lwpid)
+        self.assertNotEqual(offset, self.lwpid)
+        self.assertGreaterEqual(offset, 0)
+        # We can't really say much about offset.  It's an offset into
+        # a prgregset_t structure, so it's probably not huge and it's
+        # probably aligned to the machine's wordsize.
+        self.assertLess(offset, 128 * 8) # =128 64-bit registers (IA-64)
+        bytes_per_word, check = divmod(self.i8ctx.wordsize, 8)
+        self.assertNotEqual(bytes_per_word, 0)
+        self.assertEqual(check, 0)
+        self.assertEqual(offset % bytes_per_word, 0)
+        self.ps_get_register_called = True
+        return result
+
+    def call_procservice_get_thread_area(self, lwpid, idx):
+        """Implementation of procservice::get_thread_area."""
+        self.assertFalse(self.ps_get_thread_area_called)
+        result = getattr(self, "PS_GET_TA_RESULT", None)
+        if result is None:
+            self.fail("unexpected ps_get_thread_area")
+        self.assertEqual(lwpid, self.lwpid)
+        self.assertNotEqual(idx, self.lwpid)
+        self.ps_get_thread_area_called = True
+        return result
+
+    def check_I8_TS_CONST_THREAD_AREA_result(self, result):
+        # The result is whatever ps_get_thread_area returned.
+        self.assertTrue(self.ps_get_thread_area_called)
+        self.assertEqual(result[0], TD_OK)
+        self.assertNotEqual(result[1], 0)
+        self.assertEqual(result[1], self.PS_GET_TA_RESULT[1])
+
+    def check_I8_TS_REGISTER_result(self, result):
+        # The result is what ps_get_register returned with some
+        # bias added.  We'll assume the bias is fairly small.
+        self.assertTrue(self.ps_get_register_called)
+        self.assertEqual(result[0], TD_OK)
+        self.assertNotEqual(result[1], 0)
+        bias = result[1] - self.PS_GETREG_RESULT[1]
+        self.assertLess(abs(bias), 0x10000)
+
+    def check_I8_TS_REGISTER_THREAD_AREA_result(self, result):
+        # The result is whatever ps_get_thread_area returned.
+        self.assertTrue(self.ps_get_register_called)
+        self.assertTrue(self.ps_get_thread_area_called)
+        self.assertEqual(result[0], TD_OK)
+        self.assertNotEqual(result[1], 0)
+        self.assertEqual(result[1], self.PS_GET_TA_RESULT[1])
+
+class TestMapLwp2Thr_uninit(TestMapLwp2Thr):
+    STACK_USER_SETUP = False
+
+    def test_uninit(self):
+        """Test map_lwp2thr with NPTL uninitialized"""
+        result = self.i8ctx.call(self.TESTFUNC, self.MAIN_PID)
+        self.assertEqual(len(result), 2)
+        self.assertEqual(result[0], TD_OK)
+        self.assertEqual(result[1], NULL)
+
+class TestMapLwp2Thr_uninit_wrongpid(TestMapLwp2Thr):
+    STACK_USER_SETUP = False
+
+    def test_uninit_wrongpid(self):
+        """Test map_lwp2thr with NPTL uninitialized and lwpid != main pid"""
+        result = self.i8ctx.call(self.TESTFUNC, self.MAIN_PID + 1)
+        self.assertEqual(len(result), 2)
+        self.assertEqual(result[0], TD_ERR)
+
+class TestMapLwp2Thr_getreg_fail(TestMapLwp2Thr):
+    STACK_USER_SETUP = True
+    PS_GETREG_RESULT = PS_ERR, 0x23ff00fa
+    PS_GET_TA_RESULT = PS_OK, 0x89ab1234
+
+    def test_ps_getreg_fail(self):
+        """Check map_lwp2thr handles ps_get_register failures"""
+        self.lwpid = self.MAIN_PID + 1
+        result = self.i8ctx.call(self.TESTFUNC, self.lwpid)
+        self.assertEqual(len(result), 2)
+        if self.ps_get_register_called:
+            self.assertEqual(result[0], TD_ERR)
+        else:
+            # This failure isn't a problem for this platform.
+            self.check_I8_TS_CONST_THREAD_AREA_result(result)
+
+class TestMapLwp2Thr_gta_fail(TestMapLwp2Thr):
+    STACK_USER_SETUP = True
+    PS_GETREG_RESULT = PS_OK, 0x23ff00fa
+    PS_GET_TA_RESULT = PS_ERR, 0x89ab1234
+
+    def test_ps_gta_fail(self):
+        """Check map_lwp2thr handles ps_get_thread_area failures"""
+        self.lwpid = self.MAIN_PID + 1
+        result = self.i8ctx.call(self.TESTFUNC, self.lwpid)
+        self.assertEqual(len(result), 2)
+        if self.ps_get_thread_area_called:
+            self.assertEqual(result[0], TD_ERR)
+        else:
+            # This failure isn't a problem for this platform.
+            self.check_I8_TS_REGISTER_result(result)
+
+class TestMapLwp2Thr_mainpath(TestMapLwp2Thr):
+    STACK_USER_SETUP = True
+    PS_GETREG_RESULT = PS_OK, 0x23ff00fa
+    PS_GET_TA_RESULT = PS_OK, 0x89ab1234
+
+    def test_mainpath(self):
+        """Test the main path through map_lwp2thr"""
+        self.lwpid = self.MAIN_PID + 1
+        result = self.i8ctx.call(self.TESTFUNC, self.lwpid)
+        self.assertEqual(len(result), 2)
+        if self.ps_get_thread_area_called:
+            if self.ps_get_register_called:
+                self.check_I8_TS_REGISTER_THREAD_AREA_result(result)
+            else:
+                self.check_I8_TS_CONST_THREAD_AREA_result(result)
+        else:
+            self.check_I8_TS_REGISTER_result(result)
