Skip to content

Instantly share code, notes, and snippets.

@kripken
Last active May 7, 2024 23:45
Show Gist options
  • Save kripken/170c8cac2660264e6563104a2fb3f63f to your computer and use it in GitHub Desktop.
Save kripken/170c8cac2660264e6563104a2fb3f63f to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <emscripten.h>
#include <emscripten/threading.h>
#include <assert.h>
#if !defined(__EMSCRIPTEN_PTHREADS__) || __EMSCRIPTEN_PTHREADS__ != 1
#error __EMSCRIPTEN_PTHREADS__ should have been defined to be equal to 1 when building with pthreads support enabled!
#endif
#define NUM_THREADS 8
volatile unsigned char globalUchar = 0;
volatile unsigned short globalUshort = 0;
volatile unsigned int globalUint = 0;
volatile float globalFloat = 0.0f;
volatile double globalDouble = 0.0;
const int N = 10;
int sharedData[N] = {};
struct Test
{
int op;
int threadId;
};
uint64_t threadCasAccumulatedWrittenData[NUM_THREADS] = {};
uint64_t threadCasAccumulatedReadData[NUM_THREADS] = {};
int rand_32()
{
return (int)(emscripten_random() * float(0x3FFFFFFF));
}
void *ThreadMain(void *arg)
{
// Do some stdio to test proxying to the main thread.
emscripten_outf("pthread %p starting\n", arg);
assert(pthread_self() != 0);
assert(globalUchar == 5);
assert(globalUshort == 5);
assert(globalUint == 5);
assert(globalFloat == 5.0f);
assert(globalDouble == 5.0);
struct Test *t = (struct Test*)arg;
emscripten_outf("Thread %d for test %d: starting computation", t->threadId, t->op);
const int ITERS = 10000;
size_t totalSize = 0;
for(int i = 0; i < ITERS; ++i)
{
// All threads do a string operation.
EM_ASM_ARGS({
console.log("arg: " + UTF8ArrayToString(HEAPU8, $0));
}, "string");
// Half the time allocate too.
if (i & 1) {
// Allocate all the way up to 1GB, from 4 threads each doing ITERS.
uint32_t GB = 1024 * 1024 * 1024;
uint32_t size = GB / (4 * ITERS * 10);
EM_ASM_ARGS({
// use JS to hide the actual use from the optimizer
console.log("alloc " + $0 + " to heap size " + HEAP8.length);
}, malloc(size));
totalSize += size;
}
}
emscripten_outf("Thread %d for test %d: finished, exit()ing %zu", t->threadId, t->op, totalSize);
pthread_exit(0);
}
struct Test t[NUM_THREADS] = {};
pthread_t thread[NUM_THREADS];
void RunTest(int test)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 4*1024);
emscripten_outf("Main thread has thread ID %d\n", (int)pthread_self());
assert(pthread_self() != 0);
switch(test)
{
case 2: memset(sharedData, 0xFF, sizeof(sharedData)); break;
case 5: memset(sharedData, 0x10, sizeof(sharedData)); break;
default: memset(sharedData, 0, sizeof(sharedData)); break;
}
emscripten_outf("Main: Starting test %d", test);
for(int i = 0; i < NUM_THREADS; ++i)
{
t[i].op = test;
t[i].threadId = i;
int rc = pthread_create(&thread[i], &attr, ThreadMain, &t[i]);
assert(rc == 0);
}
pthread_attr_destroy(&attr);
for(int i = 0; i < NUM_THREADS; ++i)
{
intptr_t status = 1;
int rc = pthread_join(thread[i], (void**)&status);
assert(rc == 0);
assert(status == 0);
}
int val = sharedData[0];
emscripten_outf("Main: Test %d finished. Result: %d", test, val);
if (test != 6)
{
for(int i = 1; i < N; ++i)
assert(sharedData[i] == sharedData[0]);
}
}
int main()
{
globalUchar = 4;
globalUshort = 4;
globalUint = 4;
globalFloat = 5.0f;
globalDouble = 5.0;
uint8_t prevUchar = emscripten_atomic_add_u8((void*)&globalUchar, 1); assert(prevUchar == 4);
uint16_t prevUshort = emscripten_atomic_add_u16((void*)&globalUshort, 1); assert(prevUshort == 4);
uint32_t prevUint = emscripten_atomic_add_u32((void*)&globalUint, 1); assert(prevUint == 4);
emscripten_atomic_fence();
__sync_synchronize();
for(int i = 0; i < 7; ++i)
RunTest(i);
printf("Main: Test successfully finished.\n");
EM_ASM({
out("Final heap size " + HEAP8.length);
});
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment