← Back to list

Building a JavaScript Runtime from Scratch using C programming: The Jade Runtime

Experimental JavaScript runtime built with C, JavaScriptCore (JSC), and libuv.

trish · 2025-01-28 21:03 · 100 claps · 4.4 min read paywalled
#c #programming #javascript #nodejs
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development 🔬 · Science · General

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

  1. Prerequisites
  2. Project Setup
  3. Architecture Overview
  4. Runtime Header Breakdown (runtime.h)
  5. JavaScriptCore Engine Implementation (jsc_engine.c)
  6. libuv Event Loop System (uv_event_loop.c)
  7. System API Bridge (system_apis.c)
  8. Main Execution Flow (main.c)
  9. 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:

  1. JavaScript Engine (JSC): Executes JS code and manages objects
  2. Event Loop (libuv): Handles async operations and timers
  3. 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:

  1. 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:

  1. 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:

  1. 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
  1. Timeout Workflow:
uv_timer_init(...);    // Initialize timer handle
uv_timer_start(...);   // Schedule timeout
JSValueProtect(...);   // Prevent GC collection
  1. 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:

  1. String Conversion:
JSValueToStringCopy(...);      // JS → C string conversion
JSStringGetUTF8CString(...);   // Handle encoding
  1. Function Binding:
JSObjectMakeFunctionWithCallback(...); // Create JS-callable function
JSObjectSetProperty(...);              // Attach to global object
  1. 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:

  1. File Loading:
  • Read entire JS file into memory buffer
  • Null-terminate the script string
  1. Runtime Initialization:
  • Create JS context with system APIs
  • Prepare libuv event loop
  1. Script Execution:
  • Evaluate user code (schedules timers/IO)
  • Start event loop to process async operation
  1. 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

  1. Error Handling:
  • Add JSC exception reporting
  • Validate function arguments in C bindings
  1. Additional APIs:
  • File system access
  • Network sockets
  • Process management
  1. 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