- Add build environment setup (scripts/common/env.sh, versions.sh, functions.sh) - Add dependency build scripts for Zstd, GLM, FreeType, HarfBuzz, Pixman, Cairo - Add placeholder scripts for OpenCASCADE, ngspice, protobuf - Add WASM compatibility layer (wasm/kiplatform/, wasm/libcontext/) - Add CMake modules for KiCad WASM cross-compilation - Add PCBnew test infrastructure (tests/kicad/) - Add build plan documentation Successfully tested builds: Zstd 1.5.5, GLM 0.9.9.8, FreeType 2.13.2, HarfBuzz 8.3.0, Pixman 0.42.2, Cairo 1.18.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
180 lines
4.6 KiB
C++
180 lines
4.6 KiB
C++
/*
|
|
* WASM implementation of libcontext using Emscripten Asyncify
|
|
*
|
|
* Emscripten's Asyncify allows us to implement fiber/coroutine semantics
|
|
* by saving and restoring the WebAssembly call stack.
|
|
*
|
|
* Implementation strategy:
|
|
* - Each fiber context stores an Asyncify data buffer
|
|
* - jump_fcontext suspends current execution and resumes target
|
|
* - make_fcontext creates a new context with a function entry point
|
|
*
|
|
* Note: This requires the WASM module to be compiled with:
|
|
* -sASYNCIFY=1
|
|
* -sASYNCIFY_STACK_SIZE=65536 (or larger if needed)
|
|
*
|
|
* For more information on Asyncify:
|
|
* https://emscripten.org/docs/porting/asyncify.html
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten.h>
|
|
#include <emscripten/fiber.h>
|
|
#endif
|
|
|
|
// Match the API from libcontext.h
|
|
#define LIBCONTEXT_CALL_CONVENTION
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
namespace libcontext
|
|
{
|
|
|
|
// Context structure that stores fiber state
|
|
struct fiber_context {
|
|
emscripten_fiber_t fiber;
|
|
void (*entry_func)(intptr_t);
|
|
intptr_t entry_arg;
|
|
bool initialized;
|
|
bool running;
|
|
// Stack for asyncify data
|
|
char asyncify_stack[65536];
|
|
// C stack
|
|
char* c_stack;
|
|
size_t c_stack_size;
|
|
};
|
|
|
|
// Current running context
|
|
static fiber_context* g_current_context = nullptr;
|
|
static fiber_context g_main_context;
|
|
static bool g_main_initialized = false;
|
|
|
|
// Fiber entry wrapper
|
|
static void fiber_entry_wrapper(void* arg)
|
|
{
|
|
fiber_context* ctx = (fiber_context*)arg;
|
|
if (ctx && ctx->entry_func) {
|
|
ctx->entry_func(ctx->entry_arg);
|
|
}
|
|
// If entry function returns, we need to handle it
|
|
// In original libcontext, this would call _exit
|
|
// For WASM, we'll just return to main context
|
|
}
|
|
|
|
typedef void* fcontext_t;
|
|
|
|
void LIBCONTEXT_CALL_CONVENTION release_fcontext( fcontext_t ctx )
|
|
{
|
|
#ifdef __EMSCRIPTEN__
|
|
if (ctx) {
|
|
fiber_context* fctx = (fiber_context*)ctx;
|
|
if (fctx->c_stack) {
|
|
free(fctx->c_stack);
|
|
}
|
|
free(fctx);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
intptr_t LIBCONTEXT_CALL_CONVENTION jump_fcontext( fcontext_t* ofc, fcontext_t nfc,
|
|
intptr_t vp, bool preserve_fpu )
|
|
{
|
|
#ifdef __EMSCRIPTEN__
|
|
// Initialize main context if needed
|
|
if (!g_main_initialized) {
|
|
memset(&g_main_context, 0, sizeof(g_main_context));
|
|
emscripten_fiber_init_from_current_context(
|
|
&g_main_context.fiber,
|
|
g_main_context.asyncify_stack,
|
|
sizeof(g_main_context.asyncify_stack)
|
|
);
|
|
g_main_context.initialized = true;
|
|
g_main_context.running = true;
|
|
g_current_context = &g_main_context;
|
|
g_main_initialized = true;
|
|
}
|
|
|
|
fiber_context* old_ctx = g_current_context;
|
|
fiber_context* new_ctx = (fiber_context*)nfc;
|
|
|
|
if (!new_ctx || !new_ctx->initialized) {
|
|
fprintf(stderr, "jump_fcontext: invalid target context\n");
|
|
return 0;
|
|
}
|
|
|
|
// Store the argument in the new context
|
|
new_ctx->entry_arg = vp;
|
|
|
|
// Save the old context pointer
|
|
if (ofc) {
|
|
*ofc = (fcontext_t)old_ctx;
|
|
}
|
|
|
|
// Switch contexts
|
|
g_current_context = new_ctx;
|
|
old_ctx->running = false;
|
|
new_ctx->running = true;
|
|
|
|
// Perform the fiber switch
|
|
emscripten_fiber_swap(&old_ctx->fiber, &new_ctx->fiber);
|
|
|
|
// When we return here, we've been switched back to
|
|
// Return the value passed to us
|
|
return g_current_context->entry_arg;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
fcontext_t LIBCONTEXT_CALL_CONVENTION make_fcontext( void* sp, size_t size,
|
|
void (* fn)( intptr_t ) )
|
|
{
|
|
#ifdef __EMSCRIPTEN__
|
|
// Allocate context structure
|
|
fiber_context* ctx = (fiber_context*)malloc(sizeof(fiber_context));
|
|
if (!ctx) {
|
|
return nullptr;
|
|
}
|
|
memset(ctx, 0, sizeof(fiber_context));
|
|
|
|
ctx->entry_func = fn;
|
|
ctx->entry_arg = 0;
|
|
ctx->c_stack = (char*)sp - size; // sp points to top of stack
|
|
ctx->c_stack_size = size;
|
|
|
|
// Initialize the fiber
|
|
// Note: sp is the TOP of the stack (highest address)
|
|
// The stack grows downward, so we need to pass the bottom
|
|
void* stack_bottom = (char*)sp - size;
|
|
|
|
emscripten_fiber_init(
|
|
&ctx->fiber,
|
|
fiber_entry_wrapper,
|
|
ctx, // User data for entry function
|
|
stack_bottom, // C stack (bottom)
|
|
size, // C stack size
|
|
ctx->asyncify_stack, // Asyncify stack
|
|
sizeof(ctx->asyncify_stack) // Asyncify stack size
|
|
);
|
|
|
|
ctx->initialized = true;
|
|
ctx->running = false;
|
|
|
|
return (fcontext_t)ctx;
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
}; // namespace libcontext
|
|
|
|
#ifdef __cplusplus
|
|
};
|
|
#endif
|