Building a JavaScript Runtime from Scratch using C programming: The Jade Runtime
Experimental JavaScript runtime built with C, JavaScriptCore (JSC), and libuv.
Building a JavaScript Runtime from Scratch using C programming: The Jade Runtime

Experimental JavaScript runtime built with C, JavaScriptCore (JSC), and libuv.
Table of Contents
- Prerequisites
- Project Setup
- Architecture Overview
- Runtime Header Breakdown (
runtime.h) - JavaScriptCore Engine Implementation (
jsc_engine.c) - libuv Event Loop System (
uv_event_loop.c) - System API Bridge (
system_apis.c) - Main Execution Flow (
main.c) - Running Jade Runtime
1. Prerequisites
Linux (Debian/Ubuntu):
sudo apt update
sudo apt install \
libwebkit2gtk-4.0-dev \
libuv1-dev \
cmake \
build-essential
macOS:
brew install cmake libuv
xcode-select --install # For Xcode command line tools
2. Project Setup
Dependencies:
- JavaScriptCore (from WebKitGTK or macOS)
- libuv (for async I/O)
- CMake or Make for building
Directory Structure:
js_runtime/
├── src/
│ ├── jsc_engine.c # JSC integration
│ ├── uv_event_loop.c # libuv event loop
│ ├── system_apis.c # File I/O, HTTP bindings
│ └── main.c # CLI entry
├── include/
│ └── runtime.h # Headers
├── CMakeLists.txt # Build config
└── scripts/
└── test.js # Example JS script
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(js_runtime)
set(CMAKE_C_STANDARD 99)
find_package(PkgConfig REQUIRED)
pkg_check_modules(WEBKIT REQUIRED webkit2gtk-4.0)
pkg_check_modules(LIBUV REQUIRED libuv)
include_directories(
${WEBKIT_INCLUDE_DIRS}
${LIBUV_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/include
)
add_executable(js_runtime
src/jsc_engine.c
src/uv_event_loop.c
src/system_apis.c
src/main.c
)
target_link_libraries(js_runtime
${WEBKIT_LIBRARIES}
${LIBUV_LIBRARIES}
)
3. Architecture Overview
Core Components
/*
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ JS Engine │ ◄──►│ Event Loop │ ◄──►│ System APIs │
└─────────────┘ └─────────────┘ └─────────────┘
▲ ▲
└────── Interop ─────┘
*/
Data Flow:
- JavaScript Engine (JSC): Executes JS code and manages objects
- Event Loop (libuv): Handles async operations and timers
- System APIs: Bridge between native code and JS environment
Key Features:
- Single-threaded event loop architecture
- Automatic garbage collection (JSC)
- Native function exposure to JS
- Timer implementation with millisecond precision
4. Runtime Header (runtime.h)
Full Code:
#ifndef RUNTIME_H
#define RUNTIME_H
#include <JavaScriptCore/JavaScript.h>
#include <uv.h>
// JS Context Management
JSGlobalContextRef create_js_context();
void execute_js(JSGlobalContextRef ctx, const char* script);
// Event Loop Control
void init_event_loop();
void run_event_loop();
void set_timeout(JSContextRef ctx, JSObjectRef callback, uint64_t timeout);
// System API Exposure
void expose_system_apis(JSGlobalContextRef ctx);
#endif
Key Elements:
- Dependencies:
JavaScriptCore: Apple's JS engine (alternative to V8)libuv: Cross-platform async I/O library
2. Function Categories
- Context Lifecycle: Create/execute JS environments
- Event Loop: Manage async operations
- Native Binding: Connect JS to system functions
3. Memory Management Strategy
- JSC automatic GC for JS objects
- Manual cleanup for native handles
- Reference counting for cross-context objects
5. JavaScriptCore Engine (jsc_engine.c)
Full Code:
#include <JavaScriptCore/JavaScript.h>
#include "runtime.h"
JSGlobalContextRef create_js_context() {
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
expose_system_apis(ctx);
return ctx;
}
void execute_js(JSGlobalContextRef ctx, const char* script) {
JSStringRef js_code = JSStringCreateWithUTF8CString(script);
JSEvaluateScript(ctx, js_code, NULL, NULL, 1, NULL);
JSStringRelease(js_code);
}
Line-by-Line Explanation:
- Context Creation:
JSGlobalContextCreate(NULL); // Create isolated JS environment
expose_system_apis(ctx); // Inject native functionsc
- Creates a new JSC virtual machine
- Attaches our custom system APIs
2. Script Execution:
JSStringCreateWithUTF8CString(script); // Convert C string to JS string
JSEvaluateScript(...); // Execute in context
JSStringRelease(...); // Manual memory cleanup
- UTF-8 conversion ensures proper encoding
- Default evaluation flags for error handling
6. libuv Event Loop (uv_event_loop.c)
Full Code:
#include <uv.h>
#include <stdlib.h>
#include "runtime.h"
uv_loop_t* loop;
typedef struct {
uv_timer_t timer;
JSContextRef ctx;
JSObjectRef callback;
} TimerRequest;
static void on_timeout(uv_timer_t* handle) {
TimerRequest* tr = (TimerRequest*)handle->data;
JSValueProtect(tr->ctx, tr->callback);
JSValueRef args[] = { JSValueMakeNumber(tr->ctx, 0) };
JSObjectCallAsFunction(tr->ctx, tr->callback, NULL, 1, args, NULL);
JSValueUnprotect(tr->ctx, tr->callback);
uv_timer_stop(&tr->timer);
uv_close((uv_handle_t*)&tr->timer, NULL);
free(tr);
}
void set_timeout(JSContextRef ctx, JSObjectRef callback, uint64_t timeout) {
TimerRequest* tr = malloc(sizeof(TimerRequest));
tr->ctx = ctx;
tr->callback = callback;
uv_timer_init(loop, &tr->timer);
tr->timer.data = tr;
uv_timer_start(&tr->timer, on_timeout, timeout, 0);
JSValueProtect(ctx, callback);
}
void init_event_loop() { loop = uv_default_loop(); }
void run_event_loop() { uv_run(loop, UV_RUN_DEFAULT); }
Key Components:
- TimerRequest Structure:
typedef struct {
uv_timer_t timer; // libuv handle
JSContextRef ctx; // Execution context
JSObjectRef callback; // JS function reference
} TimerRequest;
- Bundles timer with JS callback and context
- Timeout Workflow:
uv_timer_init(...); // Initialize timer handle
uv_timer_start(...); // Schedule timeout
JSValueProtect(...); // Prevent GC collection
- Callback Execution:
JSObjectCallAsFunction(...); // Invoke JS callback
uv_close(...); // Cleanup handle
free(tr); // Release memory
7. System API Bridge (system_apis.c)
Full Code:
#include <JavaScriptCore/JavaScript.h>
#include <stdio.h>
#include <stdlib.h>
#include "runtime.h"
// Console implementation
static JSValueRef console_log(...) {
JSStringRef message = JSValueToStringCopy(ctx, args[0], exception);
char* cmsg = malloc(len);
JSStringGetUTF8CString(message, cmsg, len);
printf("LOG: %s\n", cmsg);
free(cmsg);
JSStringRelease(message);
return JSValueMakeUndefined(ctx);
}
// setTimeout binding
static JSValueRef js_set_timeout(...) {
JSObjectRef callback = JSValueToObject(ctx, args[0], exception);
uint64_t delay = JSValueToNumber(...);
set_timeout(ctx, callback, delay);
return JSValueMakeUndefined(ctx);
}
void expose_system_apis(JSGlobalContextRef ctx) {
JSObjectRef global = JSContextGetGlobalObject(ctx);
// Create console object
JSObjectRef console = JSObjectMake(ctx, NULL, NULL);
JSStringRef console_name = JSStringCreateWithUTF8CString("console");
JSObjectSetProperty(ctx, global, console_name, console, ...);
// Add console methods
JSObjectRef log_func = JSObjectMakeFunctionWithCallback(ctx, log_name, console_log);
JSObjectSetProperty(ctx, console, log_name, log_func, ...);
// Add global setTimeout
JSObjectRef setTimeout_func = JSObjectMakeFunctionWithCallback(...);
JSObjectSetProperty(ctx, global, setTimeout_name, setTimeout_func, ...);
}
Critical Implementation Details:
- String Conversion:
JSValueToStringCopy(...); // JS → C string conversion
JSStringGetUTF8CString(...); // Handle encoding
- Function Binding:
JSObjectMakeFunctionWithCallback(...); // Create JS-callable function
JSObjectSetProperty(...); // Attach to global object
- Memory Safety:
JSValueProtect(...); // Keep callback alive
free(cmsg); // Clean temporary buffers
8. Main Execution Flow (main.c)
Full Code:
#include "runtime.h"
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
if (argc < 2) { /* Error handling */ }
FILE* f = fopen(argv[1], "rb");
fseek(f, 0, SEEK_END);
long len = ftell(f);
char* script = malloc(len + 1);
fread(script, 1, len, f);
script[len] = '\0';
JSGlobalContextRef ctx = create_js_context();
init_event_loop();
execute_js(ctx, script);
run_event_loop();
JSGlobalContextRelease(ctx);
free(script);
return 0;
}
Execution Steps:
- File Loading:
- Read entire JS file into memory buffer
- Null-terminate the script string
- Runtime Initialization:
- Create JS context with system APIs
- Prepare libuv event loop
- Script Execution:
- Evaluate user code (schedules timers/IO)
- Start event loop to process async operation
- Cleanup:
- Release JS context memory
- Free script buffer
9. Running Jade Runtime
Sample Usage:
mkdir build && cd build
cmake ..
make
# Execute sample script
./jade example.js
Example Script (example.js):
console.log("Starting test...");
console.error("This is an error message!");
setTimeout(() => {
console.log("Timeout executed after 1 second!");
}, 1000);
setTimeout(() => {
console.error("Another error after 2 seconds!");
}, 2000);
Output:
LOG: Starting test...
ERROR: This is an error message!
LOG: Timeout executed after 1 second!
ERROR: Another error after 2 seconds!
Future Improvements
- Error Handling:
- Add JSC exception reporting
- Validate function arguments in C bindings
- Additional APIs:
- File system access
- Network sockets
- Process management
- Performance:
- Add handle recycling
- Implement proper event loop pooling
This complete implementation demonstrates how modern JavaScript runtimes connect high-level language features with low-level system operations. Each component plays a crucial role in creating a functional execution environment.
Jade is maintained by trish(dexter) as an educational resource for understanding low-level runtime development. Not affiliated with Node.js, Bun, or WebKit projects.
Jade GitHub Repository
Support
If you find this project helpful, consider buying me a coffee! ☕
메타데이터
- post_id
- 1d6c8e07e8d6
- slug
- building-a-javascript-runtime-from-scratch-using-c-programming-the-jade-runtime-1d6c8e07e8d6
- url
- https://medium.com/@trish07/building-a-javascript-runtime-from-scratch-using-c-programming-the-jade-runtime-1d6c8e07e8d6
- canonical_url
- https://medium.com/@trish07/building-a-javascript-runtime-from-scratch-using-c-programming-the-jade-runtime-1d6c8e07e8d6
- author_url
- https://medium.com/@trish07
- status
- ok
- fetched_at
- 2026-06-16 19:09:56