From fb9effe42274a4826d57303633af6711b2d41abd Mon Sep 17 00:00:00 2001 From: Marco Casaroli Date: Mon, 19 Oct 2026 10:02:59 +0200 Subject: [PATCH] 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 --- 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(-) create mode 100644 examples/nxflat/tests/tls/.gitignore create mode 100644 examples/nxflat/tests/tls/Makefile create mode 100644 examples/nxflat/tests/tls/tls.c create mode 100644 examples/nxflat/tests/tlsxx/.gitignore create mode 100644 examples/nxflat/tests/tlsxx/Makefile create mode 100644 examples/nxflat/tests/tlsxx/tlsxx.cxx diff --git a/examples/nxflat/tests/Makefile b/examples/nxflat/tests/Makefile index 814eb1801ad..e3fb0a3408a 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 00000000000..3d1b8c6d3cc --- /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 00000000000..f507bc21920 --- /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 00000000000..ad1625ea8ee --- /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 +#include +#include +#include + +/**************************************************************************** + * 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 00000000000..c2e1aac4421 --- /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 00000000000..c7ea7aececb --- /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 00000000000..c49fe19d873 --- /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 +#include +#include +#include + +/**************************************************************************** + * 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 class CHolder +{ +public: + CHolder(void) : m_value(42) + { + } + + static thread_local CHolder s_holder; + T m_value; +}; + +template thread_local CHolder CHolder::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::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::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::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::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; +}