This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new 8ffa65519 examples/nxflat: Add tests of __thread and thread_local
variables.
8ffa65519 is described below
commit 8ffa65519cfb9b1dc2604d0931badaebedaf3e3c
Author: Marco Casaroli <[email protected]>
AuthorDate: Mon Oct 19 10:02:59 2026 +0200
examples/nxflat: Add tests of __thread and thread_local variables.
tls has a __thread variable without an initial value, one with an
initial value, and a struct whose template holds a string pointer and a
data pointer. The main thread and a second thread each check the
initial values and change them. Each thread must not see the changes
of the other one.
tlsxx does the same with a C++ thread_local object with a constructor,
and with the static thread_local member of a class template. Each
thread must construct its own objects at their first use. The member
of the template is in COMDAT section groups. tlsxx needs
CONFIG_HAVE_CXX.
With a toolchain that emulates TLS, a module imports
__emutls_get_address() from the base code, and the generated symbol
table exports it. The checks of the second thread pass only if the
base code keeps a copy of each variable per thread. The libgcc of such
a toolchain keeps one copy for all threads.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marco Casaroli <[email protected]>
---
examples/nxflat/tests/Makefile | 5 +-
examples/nxflat/tests/tls/.gitignore | 4 +
examples/nxflat/tests/tls/Makefile | 70 +++++++++++++++
examples/nxflat/tests/tls/tls.c | 148 ++++++++++++++++++++++++++++++
examples/nxflat/tests/tlsxx/.gitignore | 4 +
examples/nxflat/tests/tlsxx/Makefile | 70 +++++++++++++++
examples/nxflat/tests/tlsxx/tlsxx.cxx | 158 +++++++++++++++++++++++++++++++++
7 files changed, 458 insertions(+), 1 deletion(-)
diff --git a/examples/nxflat/tests/Makefile b/examples/nxflat/tests/Makefile
index 814eb1801..e3fb0a340 100644
--- a/examples/nxflat/tests/Makefile
+++ b/examples/nxflat/tests/Makefile
@@ -35,7 +35,10 @@ SUBDIRS += longjmp
endif
ifneq ($(CONFIG_DISABLE_PTHREAD),y)
-SUBDIRS += mutex pthread
+SUBDIRS += mutex pthread tls
+ifeq ($(CONFIG_HAVE_CXX),y)
+SUBDIRS += tlsxx
+endif
endif
ifneq ($(CONFIG_ARCH_ADDRENV),y)
diff --git a/examples/nxflat/tests/tls/.gitignore
b/examples/nxflat/tests/tls/.gitignore
new file mode 100644
index 000000000..3d1b8c6d3
--- /dev/null
+++ b/examples/nxflat/tests/tls/.gitignore
@@ -0,0 +1,4 @@
+tls
+*.r1
+*.r2
+*-thunk.S
diff --git a/examples/nxflat/tests/tls/Makefile
b/examples/nxflat/tests/tls/Makefile
new file mode 100644
index 000000000..f507bc219
--- /dev/null
+++ b/examples/nxflat/tests/tls/Makefile
@@ -0,0 +1,70 @@
+############################################################################
+# apps/examples/nxflat/tests/tls/Makefile
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs # Basic make _info
+
+BIN = tls
+
+R1SRCS = $(BIN).c
+R1OBJS = $(R1SRCS:.c=.o)
+
+R2SRC = $(BIN)-thunk.S
+R2OBJ = $(R2SRC:.S=.o)
+
+all: $(BIN)
+
+$(R1OBJS): %.o: %.c
+ @echo "CC: $<"
+ $(Q) $(CC) -c $(CPICFLAGS) $< -o $@
+
+$(R2OBJ): %.o: %.S
+ @echo "AS: $<"
+ $(Q) $(CC) -c $(CPICFLAGS) $< -o $@
+
+$(BIN).r1: $(R1OBJS)
+ @echo "LD: $<"
+ $(Q) $(LD) $(NXFLATLDFLAGS1) -o $@ $^
+
+$(R2SRC): $(BIN).r1
+ @echo "MK: $<"
+ $(Q) $(MKNXFLAT) -o $@ $^
+
+$(BIN).r2: $(R2OBJ)
+ @echo "LD: $<"
+ $(Q) $(LD) $(NXFLATLDFLAGS2) -o $@ $(R1OBJS) $(R2OBJ)
+
+$(BIN): $(BIN).r2
+ @echo "LD: $<"
+ $(Q) $(LDNXFLAT) $(LDNXFLATFLAGS) -o $@ $^
+
+$(ROMFS_DIR)/$(BIN): $(BIN)
+ $(Q) mkdir -p $(ROMFS_DIR)
+ $(Q) install -m 0755 $(BIN) $(ROMFS_DIR)/$(BIN)
+
+install: $(ROMFS_DIR)/$(BIN)
+
+clean:
+ $(call DELFILE, $(BIN))
+ $(call DELFILE, $(R2SRC))
+ $(call DELFILE, *.r1)
+ $(call DELFILE, *.r2)
+ $(call CLEAN)
diff --git a/examples/nxflat/tests/tls/tls.c b/examples/nxflat/tests/tls/tls.c
new file mode 100644
index 000000000..ad1625ea8
--- /dev/null
+++ b/examples/nxflat/tests/tls/tls.c
@@ -0,0 +1,148 @@
+/****************************************************************************
+ * apps/examples/nxflat/tests/tls/tls.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct tls_s
+{
+ const char *name;
+ int *ptr;
+ int n;
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+int g_global = 117;
+
+/* No initial value: no template. An initial value: a template. A struct
+ * with pointers: a template that the loader must relocate.
+ */
+
+__thread int g_zero;
+__thread int g_init = 42;
+__thread struct tls_s g_struct =
+{
+ "tls-string", &g_global, 3
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static int g_errors;
+static int *g_childaddr;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static void check(const char *who, const char *what, int ok)
+{
+ printf("%s: %-34s %s\n", who, what, ok ? "PASS" : "FAIL");
+ if (!ok)
+ {
+ g_errors++;
+ }
+}
+
+static void check_initial(const char *who)
+{
+ printf("%s: g_zero=%d g_init=%d name=%s *ptr=%d n=%d\n", who, g_zero,
+ g_init, g_struct.name, *g_struct.ptr, g_struct.n);
+
+ check(who, "zero-initialized variable", g_zero == 0);
+ check(who, "variable with a template", g_init == 42);
+ check(who, "string pointer in a template",
+ strcmp(g_struct.name, "tls-string") == 0);
+ check(who, "data pointer in a template", g_struct.ptr == &g_global);
+ check(who, "value through that pointer", *g_struct.ptr == 117);
+ check(who, "integer in a struct template", g_struct.n == 3);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/* A function that is used through a pointer must have global scope: see
+ * the pthread test.
+ */
+
+void *child_start_routine(void *arg)
+{
+ check_initial("CHILD");
+
+ g_childaddr = &g_zero;
+ g_zero = 2;
+ g_init = 102;
+ g_struct.n = 302;
+
+ check("CHILD", "own values read back",
+ g_zero == 2 && g_init == 102 && g_struct.n == 302);
+ return NULL;
+}
+
+int main(int argc, char **argv)
+{
+ pthread_t thread;
+ int status;
+
+ check_initial("MAIN");
+
+ g_zero = 1;
+ g_init = 101;
+ g_struct.n = 301;
+
+ status = pthread_create(&thread, NULL, child_start_routine, NULL);
+ if (status != 0)
+ {
+ printf("MAIN: pthread_create() returned %d\n", status);
+ return EXIT_FAILURE;
+ }
+
+ pthread_join(thread, NULL);
+
+ check("MAIN", "values kept after the child ran",
+ g_zero == 1 && g_init == 101 && g_struct.n == 301);
+ check("MAIN", "child has its own copy", g_childaddr != &g_zero);
+
+ if (g_errors != 0)
+ {
+ printf("TLS test FAILED: %d error(s)\n", g_errors);
+ return EXIT_FAILURE;
+ }
+
+ printf("TLS test PASSED\n");
+ return EXIT_SUCCESS;
+}
diff --git a/examples/nxflat/tests/tlsxx/.gitignore
b/examples/nxflat/tests/tlsxx/.gitignore
new file mode 100644
index 000000000..c2e1aac44
--- /dev/null
+++ b/examples/nxflat/tests/tlsxx/.gitignore
@@ -0,0 +1,4 @@
+tlsxx
+*.r1
+*.r2
+*-thunk.S
diff --git a/examples/nxflat/tests/tlsxx/Makefile
b/examples/nxflat/tests/tlsxx/Makefile
new file mode 100644
index 000000000..c7ea7aece
--- /dev/null
+++ b/examples/nxflat/tests/tlsxx/Makefile
@@ -0,0 +1,70 @@
+############################################################################
+# apps/examples/nxflat/tests/tlsxx/Makefile
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs # Basic make _info
+
+BIN = tlsxx
+
+R1SRCS = $(BIN).cxx
+R1OBJS = $(R1SRCS:.cxx=.o)
+
+R2SRC = $(BIN)-thunk.S
+R2OBJ = $(R2SRC:.S=.o)
+
+all: $(BIN)
+
+$(R1OBJS): %.o: %.cxx
+ @echo "CXX: $<"
+ $(Q) $(CXX) -c $(CXXPICFLAGS) $< -o $@
+
+$(R2OBJ): %.o: %.S
+ @echo "AS: $<"
+ $(Q) $(CC) -c $(CPICFLAGS) $< -o $@
+
+$(BIN).r1: $(R1OBJS)
+ @echo "LD: $<"
+ $(Q) $(LD) $(NXFLATLDFLAGS1) -o $@ $^
+
+$(R2SRC): $(BIN).r1
+ @echo "MK: $<"
+ $(Q) $(MKNXFLAT) -o $@ $^
+
+$(BIN).r2: $(R2OBJ)
+ @echo "LD: $<"
+ $(Q) $(LD) $(NXFLATLDFLAGS2) -o $@ $(R1OBJS) $(R2OBJ)
+
+$(BIN): $(BIN).r2
+ @echo "LD: $<"
+ $(Q) $(LDNXFLAT) $(LDNXFLATFLAGS) -o $@ $^
+
+$(ROMFS_DIR)/$(BIN): $(BIN)
+ $(Q) mkdir -p $(ROMFS_DIR)
+ $(Q) install -m 0755 $(BIN) $(ROMFS_DIR)/$(BIN)
+
+install: $(ROMFS_DIR)/$(BIN)
+
+clean:
+ $(call DELFILE, $(BIN))
+ $(call DELFILE, $(R2SRC))
+ $(call DELFILE, *.r1)
+ $(call DELFILE, *.r2)
+ $(call CLEAN)
diff --git a/examples/nxflat/tests/tlsxx/tlsxx.cxx
b/examples/nxflat/tests/tlsxx/tlsxx.cxx
new file mode 100644
index 000000000..c49fe19d8
--- /dev/null
+++ b/examples/nxflat/tests/tlsxx/tlsxx.cxx
@@ -0,0 +1,158 @@
+/****************************************************************************
+ * apps/examples/nxflat/tests/tlsxx/tlsxx.cxx
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* The constructor of a thread_local object runs in each thread, at the
+ * first use of the object in that thread.
+ */
+
+class CCounter
+{
+public:
+ CCounter(void);
+
+ const char *m_name;
+ int m_value;
+};
+
+/* The static thread_local member of a class template. The emutls control
+ * objects of the member and of its guard variable are in COMDAT groups.
+ */
+
+template <typename T> class CHolder
+{
+public:
+ CHolder(void) : m_value(42)
+ {
+ }
+
+ static thread_local CHolder<T> s_holder;
+ T m_value;
+};
+
+template <typename T> thread_local CHolder<T> CHolder<T>::s_holder;
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static int g_nctors;
+static int g_errors;
+static int *g_childaddr;
+static thread_local CCounter g_counter;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+CCounter::CCounter(void)
+{
+ m_name = "tlsxx-counter";
+ m_value = 17;
+ g_nctors++;
+}
+
+static void check(const char *who, const char *what, bool ok)
+{
+ printf("%s: %-34s %s\n", who, what, ok ? "PASS" : "FAIL");
+ if (!ok)
+ {
+ g_errors++;
+ }
+}
+
+static void check_initial(const char *who)
+{
+ printf("%s: name=%s value=%d\n", who, g_counter.m_name,
+ g_counter.m_value);
+
+ check(who, "constructor set the string",
+ strcmp(g_counter.m_name, "tlsxx-counter") == 0);
+ check(who, "constructor set the value", g_counter.m_value == 17);
+ check(who, "template member initialized",
+ CHolder<int>::s_holder.m_value == 42);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/* A function that is used through a pointer must have global scope: see
+ * the pthread test.
+ */
+
+extern "C" void *child_start_routine(void *arg)
+{
+ check_initial("CHILD");
+
+ g_childaddr = &g_counter.m_value;
+ g_counter.m_value = 2;
+ CHolder<int>::s_holder.m_value = 2;
+ return NULL;
+}
+
+extern "C" int main(int argc, char **argv)
+{
+ pthread_t thread;
+ int status;
+
+ check_initial("MAIN");
+ g_counter.m_value = 1;
+ CHolder<int>::s_holder.m_value = 1;
+
+ status = pthread_create(&thread, NULL, child_start_routine, NULL);
+ if (status != 0)
+ {
+ printf("MAIN: pthread_create() returned %d\n", status);
+ return EXIT_FAILURE;
+ }
+
+ pthread_join(thread, NULL);
+
+ check("MAIN", "value kept after the child ran", g_counter.m_value == 1);
+ check("MAIN", "template member kept",
+ CHolder<int>::s_holder.m_value == 1);
+ check("MAIN", "child has its own object",
+ g_childaddr != &g_counter.m_value);
+ check("MAIN", "one constructor call per thread", g_nctors == 2);
+
+ if (g_errors != 0)
+ {
+ printf("TLS C++ test FAILED: %d error(s)\n", g_errors);
+ return EXIT_FAILURE;
+ }
+
+ printf("TLS C++ test PASSED\n");
+ return EXIT_SUCCESS;
+}