Skip to content

Move most of Pthread.initRuntime into native code. NFC #14775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2021

Conversation

sbc100
Copy link
Collaborator

@sbc100 sbc100 commented Jul 28, 2021

This also avoids the use of malloc both for the main pthread struct and
for its TLS vars. This in turn avoids the use of withBuiltinMalloc
since we no longer use malloc here.

@sbc100 sbc100 requested review from kripken and kleisauke July 28, 2021 05:28
Copy link
Collaborator

@kleisauke kleisauke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I added a small inline comment, which might also be resolved in a separate PR.

// XXX EMSCRIPTEN: self->tsd can be NULL in the case of the
// main thread where pthread_key_create is not called.
// See __pthread_key_create for where it get assigned.
if (!self->tsd) return NULL;
Copy link
Collaborator

@kleisauke kleisauke Jul 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this check, perhaps we could ensure that self->tsd is always initialized? This also matches musl's convention, which seems to always assign it to __pthread_tsd_main once the first thread is created:

static void *dummy_tsd[1] = { 0 };
weak_alias(dummy_tsd, __pthread_tsd_main);

self->tsd = (void **)__pthread_tsd_main;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we have at least one test that calls pthread_getspecific before any threads are created and expects it to work: posixtest.test_pthread_key_create_2_1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or at least the test expects it to return NULL and not crash

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, that test looks odd. When compiling with GCC it also complains that key is not initialized.

conformance/interfaces/pthread_key_create/2-1.c:33:14: error: ‘key’ is used uninitialized [-Werror=uninitialized]
   33 |         rc = pthread_getspecific(key);
      |              ^~~~~~~~~~~~~~~~~~~~~~~~
conformance/interfaces/pthread_key_create/2-1.c:29:23: note: ‘key’ declared here
   29 |         pthread_key_t key;
      |                       ^~~
cc1: all warnings being treated as errors

But running it locally and on Alpine (which also uses musl) can be done without any problems.

$ cd ./tests/third_party/posixtestsuite
$ docker run -v $(pwd):/app -w /app -it alpine:latest \
      apk add build-base && \
      POSIX_TARGET=conformance/interfaces/pthread_key_create make LDFLAGS='-pthread -Wno-error=uninitialized'
...
conformance/interfaces/pthread_key_create/2-1: build: PASS
conformance/interfaces/pthread_key_create/2-1: link: PASS
...
conformance/interfaces/pthread_key_create/2-1: execution: PASS

Do you think it makes sense to initialize tsd in __emscripten_pthread_data_constructor? Similar to the logic in pthread_key_create.

/* This can only happen in the main thread before
* pthread_create has been called. */
if (!self->tsd) self->tsd = __pthread_tsd_main;

Though, the current approach would also be sufficient.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, my test command for Alpine was wrong (it ran the test on my own system). This also seems to segfault on Alpine.

$ docker run -v $(pwd):/app -w /app -it alpine:latest sh -c "\
    apk add build-base && \
    POSIX_TARGET=conformance/interfaces/pthread_key_create make LDFLAGS='-pthread -Wno-error=uninitialized'"
$ cat logfile
conformance/interfaces/pthread_key_create/1-1: build: PASS
conformance/interfaces/pthread_key_create/1-1: link: PASS
conformance/interfaces/pthread_key_create/1-2: build: PASS
conformance/interfaces/pthread_key_create/1-2: link: PASS
conformance/interfaces/pthread_key_create/2-1: build: PASS
conformance/interfaces/pthread_key_create/2-1: link: PASS
conformance/interfaces/pthread_key_create/3-1: build: PASS
conformance/interfaces/pthread_key_create/3-1: link: PASS
conformance/interfaces/pthread_key_create/speculative/5-1: build: PASS
conformance/interfaces/pthread_key_create/speculative/5-1: link: PASS
conformance/interfaces/pthread_key_create/1-1: execution: PASS
conformance/interfaces/pthread_key_create/1-2: execution: PASS
conformance/interfaces/pthread_key_create/2-1: execution: INTERRUPTED: Output: 
Segmentation fault (core dumped)
conformance/interfaces/pthread_key_create/3-1: execution: PASS
conformance/interfaces/pthread_key_create/speculative/5-1: execution: FAILED: Output: 
Test FAILED: EAGAIN was returned before the key limit was exceeded

Perhaps we should report this upstream.

This also avoids the use of malloc both for the main pthread struct and
for its TLS vars.  This in turn avoids the use of `withBuiltinMalloc`
since we no longer use malloc here.
@sbc100 sbc100 force-pushed the pthead_avoid_malloc branch from 3637f1e to 09c8871 Compare July 28, 2021 16:22
@sbc100 sbc100 enabled auto-merge (squash) July 28, 2021 16:26
// The pthread struct has a field that points to itself - this is used as
// a magic ID to detect whether the pthread_t structure is 'alive'.
__main_pthread.self = &__main_pthread;
// pthread struct robust_list head should point to itself.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation looks wrong here

Suggested change
// pthread struct robust_list head should point to itself.
// pthread struct robust_list head should point to itself.

// a magic ID to detect whether the pthread_t structure is 'alive'.
__main_pthread.self = &__main_pthread;
// pthread struct robust_list head should point to itself.
__main_pthread.robust_list.head = &__main_pthread.robust_list;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__main_pthread.robust_list.head = &__main_pthread.robust_list;
__main_pthread.robust_list.head = &__main_pthread.robust_list.head;

This would avoid depending on head being the initial field, or is that bad for some reason?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I was just trying to mimic exactly what the JS code was doing. I don't really understand the purpose of this assignment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me either, but the actual address is identical in both cases - just that writing .head would still work if we reorder the struct in the future?

var tlsMemory = _malloc({{{ cDefine('PTHREAD_KEYS_MAX') * 4 }}});
for (var i = 0; i < {{{ cDefine('PTHREAD_KEYS_MAX') }}}; ++i) HEAPU32[tlsMemory/4+i] = 0;
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.tsd }}} ) >> 2, tlsMemory); // Init thread-local-storage memory array.
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.tid }}} ) >> 2, tb); // Main thread ID.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand where the code above for tb.self etc. has moved to (C), but what now handles the TLS init?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed I probably should have explained that. musl uses statically allocated tsd for the main thread.

It gets assigned on first use here:

if (!self->tsd) self->tsd = __pthread_tsd_main;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can avoid malloc completely!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, thanks... and nice to avoid malloc!

So this is for pthread_key* - what about C/C++ level TLS? Is that handled in another way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, C/C++ TLS variables are handled separately to the dynamically created TLS keys in pthread_key_create

@sbc100 sbc100 merged commit b752bd6 into main Jul 28, 2021
@sbc100 sbc100 deleted the pthead_avoid_malloc branch July 28, 2021 17:18
@kripken
Copy link
Member

kripken commented Jul 28, 2021

Oops, once again I approved a PR without noticing it had automerge, so there are still some comments from me and @kleisauke . Sorry about that, I really have to be careful about looking for automerge all the time...

@sbc100
Copy link
Collaborator Author

sbc100 commented Jul 28, 2021

Sorry @kripken I will avoid using auto-merge so much in future. And I will address all the comments here.

@kripken
Copy link
Member

kripken commented Jul 28, 2021

No, I think it's fine to use auto-merge... I just need to get used to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants